| # Copyright lowRISC contributors. |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| load("@bazel_skylib//lib:shell.bzl", "shell") |
| load("@lowrisc_opentitan//rules:rv.bzl", "rv_rule") |
| |
| # https://github.com/riscv-non-isa/riscv-asm-manual/blob/master/riscv-asm.md#general-registers |
| IBEX_GPRS = [ |
| "zero", |
| "ra", |
| "sp", |
| "gp", |
| "tp", |
| "t0", |
| "t1", |
| "t2", |
| "s0", |
| "fp", # Should be an alias for s0. |
| "s1", |
| ] + ["a" + str(i) for i in range(8)] + ["s" + str(i) for i in range(2, 12)] + ["t" + str(i) for i in range(3, 7)] |
| |
| # Register access values. |
| _ACCESS_R = 0 |
| _ACCESS_RW = 1 |
| _ACCESS_WARL = 2 |
| _ACCESS_WLRL = 3 |
| |
| # https://ibex-core.readthedocs.io/en/latest/03_reference/cs_registers.html |
| _IBEX_CSRS = [ |
| ("mstatus", _ACCESS_WARL), |
| ("misa", _ACCESS_WARL), |
| ("mie", _ACCESS_WARL), |
| ("mtvec", _ACCESS_WARL), |
| ("mcountinhibit", _ACCESS_RW), |
| ] + [("mhpmevent" + str(i), _ACCESS_WARL) for i in range(3, 32)] + \ |
| [ |
| ("mscratch", _ACCESS_RW), |
| ("mepc", _ACCESS_WARL), |
| ("mcause", "WLRL"), |
| ("mtval", _ACCESS_WARL), |
| ("mip", _ACCESS_R), |
| ("pmpcfg0", _ACCESS_WARL), |
| ("pmpcfg1", _ACCESS_WARL), |
| ("pmpcfg2", _ACCESS_WARL), |
| ("pmpcfg3", _ACCESS_WARL), |
| ] + [("pmpaddr" + str(i), _ACCESS_WARL) for i in range(16)] + [ |
| ("scontext", _ACCESS_WARL), |
| ("mseccfg", _ACCESS_WARL), |
| ("mseccfgh", _ACCESS_WARL), |
| ("tselect", _ACCESS_WARL), |
| ("tdata1", _ACCESS_WARL), |
| ("tdata2", _ACCESS_WARL), |
| ("tdata3", _ACCESS_WARL), |
| ("mcontext", _ACCESS_WARL), |
| ("mscontext", _ACCESS_WARL), |
| ("dcsr", _ACCESS_WARL), |
| ("dpc", _ACCESS_RW), |
| ("dscratch0", _ACCESS_RW), |
| ("dscratch1", _ACCESS_RW), |
| ("cpuctrl", _ACCESS_WARL), |
| ("secureseed", _ACCESS_WARL), |
| ("mcycle", _ACCESS_RW), |
| ("minstret", _ACCESS_RW), |
| ] + [("mhpmcounter" + str(i), _ACCESS_WARL) for i in range(3, 32)] + [ |
| ("mcycleh", _ACCESS_RW), |
| ("minstreth", _ACCESS_RW), |
| ] + [("mhpmcounter{}h".format(i), _ACCESS_WARL) for i in range(3, 32)] + [ |
| ("mvendorid", _ACCESS_R), |
| ("marchid", _ACCESS_R), |
| ("mimpid", _ACCESS_R), |
| ("mhartid", _ACCESS_R), |
| ] |
| |
| def get_gdb_readable_csr_names(): |
| return [reg for (reg, _access) in _IBEX_CSRS] |
| |
| def get_gdb_settable_csr_names(): |
| exclude_csr_names = ["scontext", "mseccfg", "mseccfgh", "tselect", "dpc"] |
| out = [] |
| for (reg, access) in _IBEX_CSRS: |
| if access == _ACCESS_R: |
| continue |
| if reg in exclude_csr_names: |
| continue |
| if reg.startswith("pmpcfg") or reg.startswith("pmpaddr"): |
| continue |
| out.append(reg) |
| return out |
| |
| def gdb_commands_copy_registers(registers): |
| lines = ["echo :::: Copy registers.\\n"] |
| lines += [ |
| "set ${reg}_orig = ${reg}".format(reg = reg) |
| for reg in registers |
| ] |
| return "\n".join(lines) |
| |
| def gdb_commands_set_registers(val, registers): |
| lines = ["echo :::: Set registers.\\n"] |
| lines += [ |
| "set ${reg} = {val}".format(reg = reg, val = val) |
| for reg in registers |
| ] |
| return "\n".join(lines) |
| |
| def gdb_commands_restore_registers(registers): |
| lines = ["echo :::: Restore registers.\\n"] |
| lines += [ |
| "set ${reg} = ${reg}_orig".format(reg = reg) |
| for reg in registers |
| ] |
| return "\n".join(lines) |
| |
| def _opentitan_gdb_fpga_cw310_test_impl(ctx): |
| # Write the GDB script to disk and load it with GDB's `--command` argument. |
| # This enables us to separate lines with whitespace, whereas if we piped the |
| # string into GDB's stdin, each newline would cause it to repeat the |
| # previous command. |
| gdb_script_file = ctx.actions.declare_file("{}.gdb".format(ctx.label.name)) |
| |
| # This dummy script exists because test rules are a kind of executable rule, |
| # and executable rules *must* produce an output file. |
| test_script = """ |
| #!/usr/bin/env bash |
| set -ex |
| {} """.format(shell.quote(ctx.executable._coordinator.short_path)) |
| args = [ |
| ("--rom-kind", ctx.attr.rom_kind), |
| ("--openocd-path", ctx.file._openocd.short_path), |
| ("--openocd-earlgrey-config", ctx.file._openocd_earlgrey_config.path), |
| ("--openocd-jtag-adapter-config", ctx.file._openocd_jtag_adapter_config.path), |
| ("--gdb-path", ctx.file._gdb.short_path), |
| ("--gdb-script-path", gdb_script_file.short_path), |
| ("--bitstream-path", ctx.file.rom_bitstream.short_path), |
| ("--opentitantool-path", ctx.file._opentitantool.short_path), |
| ] |
| if ctx.attr.exit_success_pattern != None: |
| args.append(("--exit-success-pattern", ctx.attr.exit_success_pattern)) |
| for output in ctx.attr.gdb_expect_output_sequence: |
| args.append(("--gdb-expect-output-sequence", output)) |
| |
| arg_lines = ["{}={}".format(flag, shell.quote(value)) for flag, value in args] |
| if ctx.attr.expect_debug_disallowed: |
| arg_lines.append("--expect-debug-disallowed") |
| |
| test_script += " \\\n".join(arg_lines) |
| |
| if ctx.attr.opentitantool_cw310_uarts != "": |
| test_script += " " + ctx.attr.opentitantool_cw310_uarts |
| |
| ctx.actions.write(output = gdb_script_file, content = ctx.attr.gdb_script) |
| ctx.actions.write(output = ctx.outputs.executable, content = test_script) |
| |
| # Construct a dict that we can pass to `ctx.runfiles()`, mapping symlink |
| # names to real file paths. |
| gdb_script_symlinks_flipped = {} |
| for label in ctx.attr.gdb_script_symlinks: |
| label_files = label.files.to_list() |
| if len(label_files) != 1: |
| fail("gdb_script_symlinks labels must have exactly one file, but", label, "has these files:", label_files) |
| gdb_script_symlinks_flipped[ctx.attr.gdb_script_symlinks[label]] = label_files[0] |
| |
| gdb_script_runfiles = ctx.runfiles( |
| symlinks = gdb_script_symlinks_flipped, |
| files = gdb_script_symlinks_flipped.values(), |
| ) |
| |
| test_script_runfiles = ctx.runfiles( |
| files = [ |
| ctx.file._openocd_earlgrey_config, |
| ctx.file._openocd_jtag_adapter_config, |
| ctx.file._opentitantool, |
| ctx.file._openocd, |
| ctx.file.rom_bitstream, |
| ctx.file._gdb, |
| gdb_script_file, |
| ], |
| ).merge(ctx.attr._coordinator.data_runfiles) |
| |
| return [DefaultInfo( |
| runfiles = test_script_runfiles.merge(gdb_script_runfiles), |
| )] |
| |
| _opentitan_gdb_fpga_cw310_test = rv_rule( |
| implementation = _opentitan_gdb_fpga_cw310_test_impl, |
| attrs = { |
| "exit_success_pattern": attr.string(), |
| "gdb_script": attr.string(mandatory = True), |
| "gdb_script_symlinks": attr.label_keyed_string_dict(allow_files = True), |
| "rom_bitstream": attr.label( |
| mandatory = True, |
| allow_single_file = True, |
| ), |
| "rom_kind": attr.string(mandatory = True, values = ["Rom", "TestRom"]), |
| "gdb_expect_output_sequence": attr.string_list(), |
| "expect_debug_disallowed": attr.bool(default = False), |
| "opentitantool_cw310_uarts": attr.string(), |
| "_coordinator": attr.label( |
| default = "//rules/scripts:gdb_test_coordinator", |
| cfg = "exec", |
| executable = True, |
| ), |
| "_opentitantool": attr.label( |
| default = "//sw/host/opentitantool", |
| allow_single_file = True, |
| cfg = "exec", |
| ), |
| "_openocd_earlgrey_config": attr.label( |
| default = "//util/openocd/target:lowrisc-earlgrey.cfg", |
| allow_single_file = True, |
| ), |
| "_openocd_jtag_adapter_config": attr.label( |
| default = "//third_party/openocd:jtag_adapter_cfg", |
| allow_single_file = True, |
| ), |
| "_openocd": attr.label( |
| default = "//third_party/openocd:openocd_bin", |
| allow_single_file = True, |
| cfg = "exec", |
| ), |
| "_gdb": attr.label( |
| default = "@lowrisc_rv32imcb_files//:bin/riscv32-unknown-elf-gdb", |
| allow_single_file = True, |
| cfg = "exec", |
| ), |
| }, |
| test = True, |
| ) |
| |
| # Orchestrate opentitantool, OpenOCD, and GDB to load the given program into |
| # SRAM and execute it in-place. This macro assumes that a CW310 FPGA and an |
| # ARM-USB-TINY-H JTAG debugger are attached to the host. |
| def opentitan_gdb_fpga_cw310_test( |
| tags = [], |
| **kwargs): |
| _opentitan_gdb_fpga_cw310_test( |
| tags = tags + [ |
| "cw310", |
| "exclusive", # Prevent FPGA tests from running concurrently. |
| "jtag", |
| ], |
| opentitantool_cw310_uarts = select({ |
| "@//ci:lowrisc_fpga_cw310": "--cw310-uarts=/dev/ttyACM_CW310_1,/dev/ttyACM_CW310_0", |
| "//conditions:default": "", |
| }), |
| **kwargs |
| ) |