-
Notifications
You must be signed in to change notification settings - Fork 613
Add a discrete debug module simulation mode #2274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add a discrete debug module simulation mode #2274
Conversation
These assertions were a little too strong. While in practice the halt and exception vector addresses would likely be within the range of the debug_module, this is not mandated by the specification. We actually do this is the current testbench configuration. The halt and exception vectors are parameterized to `BOOT_ADDR and `BOOT_ADDR + 0x4 respectively, and the riscv-dv generated test binaries insert jump handlers at those locations that target the .debug_rom and .debug_exception sections somewhere else in the monolithic test binary. The assertions only worked because the current parameterizations for Debug Module addresses put the debug module right at the BOOT_ADDR parameter, which doesn't really make much sense. .DmBaseAddr (32'h`BOOT_ADDR) .DmAddrMask (32'h0000_0007 ) MTVEC is reset to `BOOT_ADDR, so this memory region would normally be used for the mmode trap handler vector table.
In the future we probably want use Fusesoc to generate the filelist, which can be dropped in place of the ibex_dv.f file now that it only contains file includes.
In particular, this makes use of the new method processor_t::set_debug_module_range(reg_t start_debug_val, reg_t end_debug_val)
When passing +is_discrete_debug_module to 'sim_opts' in the riscv-dv testlist (dv/uvm/core_ibex/riscv_dv_extension/testlist.yaml), this now results in the test being compiled and loaded into the simulation memory models in a way that mimics a discrete debug module mapped away from the main processor memory. Up to this point, the riscv-dv binaries generate debug rom sections which are simply placed within a single monolithic code section. This requires a bit of machinery to achieve: - A new linker script to compile our test software for this layout (riscv_dv_extension/link.ld - Test binaries output into multiple binaries which are loaded seperately to initialize the sparse memory space. Two regions are supported, 'main' and 'dm'. - Initialize more testbench parameters via CLI args. This allows their values to be chosen dynamically in the future and to be test-dependent. For now, the various address / mask parameters are still set to fixed values for all tests. One slightly unclean implementation detail is that the cosimulation model's memory is loaded via binary .bin files, while the simulator's memory model is loaded via verilog .vmem memory files. Ideally we would use .vmem files for both, but the interface to the cosimulation model via DPI only implements byte-writes, so loading a raw binary file is more convenient for that interface.
This also matches an equivalent change in spike, which still configures this address via a preprocessor macro.
Draft while I sort out CI breakage, but I don't anticipate huge structural changes from here. |
dv/uvm/core_ibex/ibex_dv.f
Outdated
@@ -10,9 +10,6 @@ | |||
// (2147483648), because an undecorated integer literal is interpreted as a | |||
// signed 32-bit integer. | |||
+define+BOOT_ADDR=8000_0000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this commit looks reasonable, but isn't the BOOT_ADDR define "left over"? Should that go in ibex_dv_defines.f
too?
... Ah! I've just noticed that it gets dropped in two commits' time. Maybe just add a note to the commit log here that says it will happen?
@@ -72,7 +72,8 @@ class ibex_cosim_scoreboard extends uvm_scoreboard; | |||
|
|||
// TODO: Ensure log file on reset gets append rather than overwrite? | |||
cosim_handle = spike_cosim_init(cfg.isa_string, cfg.start_pc, cfg.start_mtvec, cfg.log_file, | |||
cfg.pmp_num_regions, cfg.pmp_granularity, cfg.mhpm_counter_num, cfg.secure_ibex, cfg.icache); | |||
cfg.pmp_num_regions, cfg.pmp_granularity, cfg.mhpm_counter_num, cfg.secure_ibex, cfg.icache, | |||
cfg.dm_start_addr, cfg.dm_end_addr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to add a check here that the two addresses aren't both zero (because someone forgot to wire them up) ?
@@ -40,6 +40,11 @@ class SpikeCosim : public simif_t, public Cosim { | |||
std::vector<std::string> errors; | |||
bool nmi_mode; | |||
|
|||
// Start/End address of the debug module. The PMP module requires these | |||
// addresses to implement a always-allow policy for accesses in debug mode. | |||
uint32_t dm_start_addr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do these values get set? I think e.g. the dm_start_addr
argument to the constructor only gets passed to the processor object?
Is this the remnants of another design?
|
||
_dm_scratch_len = 0x100; | ||
|
||
/* PHDRS */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented code. Drop it?
if token.startswith("-T"): | ||
cmd[index] = "-T" + str(md.ibex_riscvdv_customtarget / 'link.ld') | ||
|
||
# Generate binary outputs for the different LOADABLE memory regions (main, dm) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Missing "." at the end of this line? The rest of this comment looks like it could do with reflowing too: M-q?
|
||
trr.vmem_main = trr.dir_test / "test.main.vmem" | ||
trr.vmem_dm = trr.dir_test / "test.dm.vmem" | ||
vmem_dm_cmd = [env.get('RISCV_OBJCOPY'), "-O", "verilog", *only_sections, f"{trr.objectfile}", f"{trr.vmem_dm}"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good. I'd probably be inclined to define a "objcopy" function to avoid having to repeat the env.get()
stuff.
# If the +discrete_debug_module=1 argument is passed via sim_opts, a alternate | ||
# memory heirarchy is enabled which seperates the debug rom memory sections | ||
# from the main binary, emulating an external debug module | ||
for plusarg, val in (trr.split('=') for trr in trr.sim_opts): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow: it's rather clever that this nesting is possible! It's not super easy to parse though: maybe define a variable for the generator / list that you get from splitting sim_opts
?
# memory heirarchy is enabled which seperates the debug rom memory sections | ||
# from the main binary, emulating an external debug module | ||
for plusarg, val in (trr.split('=') for trr in trr.sim_opts): | ||
if (plusarg == "+discrete_debug_module"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Stray ()
# from the main binary, emulating an external debug module | ||
for plusarg, val in (trr.split('=') for trr in trr.sim_opts): | ||
if (plusarg == "+discrete_debug_module"): | ||
trr.is_discrete_debug_module = val # Value should be truthy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm slightly surprised by this. Isn't val
just a string? A value of zero ends up looking like this:
>>> print('yes' if '0' else 'no')
yes
>>>
@@ -39,18 +39,20 @@ def _main() -> int: | |||
trr.rtl_test = testopts['rtl_test'] | |||
trr.timeout_s = testopts.get('timeout_s') or md.run_rtl_timeout_s | |||
|
|||
if not os.path.exists(trr.binary): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we want to keep some (slightly generalised) version of this check?
Note. do not merge without coordination. Due to the way we handle the external spike dependency, lowRISC/riscv-isa-sim#26 will need to be merged in coordination with this PR to avoid CI breakage.
This PR adds a mechanism for testing ibex in a memory system that provisions code in a way to mimic the presence of a discrete external debug module.
This PR just adds the machinery to implement this. Follow up merges will add new tests that make use of it.
Desc.
When passing +is_discrete_debug_module to 'sim_opts' in the riscv-dv testlist
(dv/uvm/core_ibex/riscv_dv_extension/testlist.yaml), this now results in the test
being compiled and loaded into the simulation memory models in a way that mimics
a discrete debug module mapped away from the main processor memory. Up to this
point, the riscv-dv binaries generate debug rom sections which are simply placed
within a single monolithic code section.
This requires a bit of machinery to achieve:
(
riscv_dv_extension/link.ld
)initialize the sparse memory space. Two regions are supported, 'main' and 'dm'.
to be chosen dynamically in the future and to be test-dependent. For now, the
various address / mask parameters are still set to fixed values for all tests.
One slightly unclean implementation detail is that the cosimulation model's
memory is loaded via binary .bin files, while the simulator's memory model is
loaded via verilog .vmem memory files. Ideally we would use .vmem files for
both, but the interface to the cosimulation model via DPI only implements
byte-writes, so loading a raw binary file is more convenient for that interface.
TODO
This could probably do with some standalone documentation, as there are still some
assumptions that glue the different steps together (.e.g .section names in the linker script).
However, this is broadly true for the entire DV architecture.