Skip to content

Commit 7a66bf9

Browse files
Support alternate memory system via '+is_discrete_debug_module' opt
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. Signed-off-by: Harry Callahan <hcallahan@lowrisc.org>
1 parent b2c5f8d commit 7a66bf9

File tree

9 files changed

+258
-45
lines changed

9 files changed

+258
-45
lines changed

dv/uvm/core_ibex/ibex_dv.f

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,6 @@
22
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
33
// SPDX-License-Identifier: Apache-2.0
44

5-
// The boot address is specified as a hex number and is used in SystemVerilog
6-
// code like this: 32'h `BOOT_ADDR.
7-
//
8-
// We can't put a ' character in the constant, because Riviera's TCL machinery
9-
// doesn't support them in defines. We also can't just use the decimal value
10-
// (2147483648), because an undecorated integer literal is interpreted as a
11-
// signed 32-bit integer.
12-
+define+BOOT_ADDR=8000_0000
13-
145
// Until this list is generated by FuseSoC, we have to use manually generated
156
// wrappers around the prim_* modules to instantiate the prim_generic_* ones,
167
// see https://github.com/lowRISC/ibex/issues/893.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/* Copyright lowRISC contributors.
2+
Licensed under the Apache License, Version 2.0, see LICENSE for details.
3+
SPDX-License-Identifier: Apache-2.0 */
4+
5+
/*
6+
* This linker script maps the generated debug test code into a discrete region
7+
* of the address space, representative of an external discrete debug module (ddm).
8+
* i.e. when +discrete_debug_module=1
9+
*
10+
* It also provisions space for a debug module "scratch" area that follows the debug
11+
* ROM. This allows us to test stores into the debug module without modifying
12+
* the test program.
13+
*/
14+
15+
OUTPUT_ARCH( "riscv" )
16+
ENTRY(_start)
17+
18+
MEMORY
19+
{
20+
main : ORIGIN = 0x80000000, LENGTH = 0x100000
21+
dm : ORIGIN = 0x1A110000, LENGTH = 0x1000
22+
}
23+
24+
_dm_scratch_len = 0x100;
25+
26+
SECTIONS
27+
{
28+
.text : {
29+
*(.text)
30+
. = ALIGN(0x1000);
31+
} >main
32+
.tohost : {
33+
. = ALIGN(4);
34+
*(.tohost)
35+
. = ALIGN(0x1000);
36+
} >main
37+
.page_table : {
38+
*(.page_table)
39+
} >main
40+
.data : {
41+
*(.data)
42+
} >main
43+
.user_stack : {
44+
*(.user_stack)
45+
} >main
46+
.kernel_data : {
47+
*(.kernel_data)
48+
} >main
49+
.kernel_stack : {
50+
*(.kernel_stack)
51+
} >main
52+
.bss : {
53+
*(.bss)
54+
} >main
55+
56+
_end = .;
57+
58+
.debug_module : {
59+
*(.debug_module)
60+
} >dm
61+
.dm_scratch : {
62+
. = ALIGN(4);
63+
. = . + _dm_scratch_len ;
64+
. = ALIGN(4);
65+
} >dm =0
66+
}

dv/uvm/core_ibex/scripts/compile_tb.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,25 @@ def _main() -> int:
114114
'core_ibex': md.ibex_dv_root,
115115
'tb_dir': md.dir_tb,
116116
'tb_build_log': md.tb_build_log,
117-
'cmp_opts': get_compile_opts(md.ibex_config,
118-
md.simulator),
117+
'cmp_opts':
118+
get_compile_opts(md.ibex_config, md.simulator) + \
119+
# Base address of the debug module. This is passed as a parameter
120+
# at compile time as the PMP module must always allow debug mode
121+
# accesses to it.
122+
r" -define DM_ADDR=1A11_0000 " + \
123+
r" -define DM_ADDR_MASK=0000_0FFF" + \
124+
r" -define BOOT_ADDR=8000_0000 " + \
125+
# Spike sets the following parameters via the preprocessor defines
126+
# DEBUG_ROM_ENTRY and DEBUG_ROM_TVEC.
127+
# As they cannot be moved without recompiling the ISS, treat them as
128+
# hardcoded here too, for now. These addresses are placed right at
129+
# BOOT_ADDR location, which is the location of the start of the
130+
# default vector table. (The reset vector is BOOT_ADDR/256b + 0x80)
131+
# The generated RISCV-DV assembly programs move the default vector
132+
# table (via MTVEC), and place jump instructions at these two
133+
# addresses to the generated debug test sections.
134+
r" -define DEBUG_MODE_HALT_ADDR=8000_0000 " + \
135+
r" -define DEBUG_MODE_EXCEPTION_ADDR=8000_0004 ",
119136
'dir_shared_cov': (md.dir_shared_cov if md.cov else ''),
120137
'xlm_cov_cfg_file': f"{md.ot_xcelium_cov_scripts}/cover.ccf",
121138
'dut_cov_rtl_path': md.dut_cov_rtl_path

dv/uvm/core_ibex/scripts/compile_test.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
import sys
1313
import tempfile
1414
import pathlib3x as pathlib
15+
import itertools
1516

1617
from scripts_lib import run_one, format_to_cmd, read_yaml
1718
import riscvdv_interface
18-
from test_entry import read_test_dot_seed
19+
from test_entry import read_test_dot_seed, get_test_entry
1920
from metadata import RegressionMetadata
2021
from test_run_result import TestRunResult, TestType
2122

@@ -85,6 +86,66 @@ def get_riscvdv_compile_cmds(md: RegressionMetadata, trr: TestRunResult) -> List
8586
new_cmd.append(word)
8687
new_cmds.append(new_cmd)
8788

89+
90+
if (trr.testtype == TestType.RISCVDV and trr.is_discrete_debug_module):
91+
logger.warning(f"Using discrete debug module memory layout.")
92+
93+
# Remove the objdump command for the single contiguous memory region.
94+
# (If debug module address is far from the boot_address, a single loadable
95+
# .bin file can be very large (roughly 1.6Gb for the default address params))
96+
new_cmds.pop()
97+
98+
# Replace default riscv-dv linker script with custom script
99+
for cmd in new_cmds:
100+
for index, token in enumerate(cmd):
101+
if token.startswith("-T"):
102+
cmd[index] = "-T" + str(md.ibex_riscvdv_customtarget / 'ddm_link.ld')
103+
104+
# Generate binary outputs for the different LOADABLE memory regions (main, dm).
105+
# The linker script places some sections inside the 'dm' memory region,
106+
# which may be quite far away from the 'main' memory. To avoid
107+
# generating a very large uncompressed binary file to LOAD both
108+
# segments, we probably want to use the .vmem format passed to
109+
# $readmemh to initialize the RTL-side memory model. However, as the
110+
# cosim memory model is initialized over the DPI interface, and may be
111+
# comprised of multiple 'memories' rather than a single contiguous
112+
# space, we can't simply rely on $readmemh to automatically partition a
113+
# single file across multiple arrays. Hence for it's initialization,
114+
# generate raw .bin files which will be loaded via the base address of
115+
# each section.
116+
117+
# The following sections are within the loadable 'dm' region
118+
dm_sections = [".debug_module", ".dm_scratch"]
119+
120+
# The following objcopy arguments generate outputs with only a subset of the sections
121+
# present in the object file.
122+
flatten_list = lambda unflat_list: list(itertools.chain(*unflat_list))
123+
only_sections = flatten_list([["--only-section", s] for s in dm_sections])
124+
remove_sections = flatten_list([["--remove-section", s] for s in dm_sections])
125+
126+
env = os.environ.copy()
127+
objcopy = env.get('RISCV_OBJCOPY')
128+
129+
# Generate verilog memory formatted outputs (.vmem)
130+
131+
trr.vmem_main = trr.dir_test / "test.main.vmem"
132+
trr.vmem_dm = trr.dir_test / "test.dm.vmem"
133+
vmem_dm_cmd = \
134+
[objcopy, "-O", "verilog", *only_sections, f"{trr.objectfile}", f"{trr.vmem_dm}"]
135+
vmem_main_cmd = \
136+
[objcopy, "-O", "verilog", *remove_sections, f"{trr.objectfile}", f"{trr.vmem_main}"]
137+
138+
# Generate binary-formatted outputs (.bin)
139+
140+
trr.binary_main = trr.dir_test / "test.main.bin"
141+
trr.binary_dm = trr.dir_test / "test.dm.bin"
142+
bin_dm_cmd = \
143+
[objcopy, "-O", "binary", *only_sections, f"{trr.objectfile}", f"{trr.binary_dm}"]
144+
bin_main_cmd = \
145+
[objcopy, "-O", "binary", *remove_sections, f"{trr.objectfile}", f"{trr.binary_main}"]
146+
147+
new_cmds.extend([vmem_dm_cmd, vmem_main_cmd, bin_dm_cmd, bin_main_cmd])
148+
88149
return new_cmds
89150

90151

dv/uvm/core_ibex/scripts/metadata.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import ibex_config
3030
import lib as riscvdv_lib
3131
from test_run_result import TestRunResult, TestType
32+
from test_entry import get_test_entry
3233
import directed_test_schema
3334

3435
import logging
@@ -256,7 +257,7 @@ def construct_from_metadata_dir(cls, dir_metadata: pathlib.Path):
256257
md = cls.construct_from_pickle(md_pickle)
257258
return md
258259

259-
def get_tests_and_counts(self) -> List[Tuple[str, int, TestType]]:
260+
def get_tests_and_counts(self) -> List[Tuple[Tuple[str, int, TestType], object]]:
260261
"""Get a list of tests and the number of iterations to run of each.
261262
262263
ibex_config should be the name of the Ibex configuration to be tested.
@@ -287,12 +288,12 @@ def get_tests_and_counts(self) -> List[Tuple[str, int, TestType]]:
287288
name, iterations = (test['test'], test['iterations'])
288289
assert isinstance(name, str) and isinstance(iterations, int) \
289290
and iterations > 0
290-
ret.append((name, iterations, TestType.RISCVDV))
291+
ret.append(((name, iterations, TestType.RISCVDV), test))
291292
for test in directed_filtered_list:
292293
name, iterations = (test['test'], test['iterations'])
293294
assert isinstance(name, str) and isinstance(iterations, int) \
294295
and iterations > 0
295-
ret.append((name, iterations, TestType.DIRECTED))
296+
ret.append(((name, iterations, TestType.DIRECTED), test))
296297

297298
return ret
298299

@@ -374,7 +375,8 @@ def _main():
374375
# Setup the tests/counts we are going to use, by parsing the
375376
# riscv-dv/directed-test structured data.
376377
# eg. testlist.yaml / directed_testlist.yaml
377-
md.tests_and_counts = md.get_tests_and_counts()
378+
tests_and_counts = md.get_tests_and_counts()
379+
md.tests_and_counts = [tctt for (tctt, obj) in tests_and_counts]
378380
if not md.tests_and_counts:
379381
raise RuntimeError("md.tests_and_counts is empty, cannot get TEST.SEED strings.")
380382

@@ -383,7 +385,8 @@ def _main():
383385
# constructed above, so we can easily find and import them later, and
384386
# give each test object a link back to this top-level object that
385387
# defines the wider regression.
386-
for test, count, testtype in md.tests_and_counts:
388+
for tctt, obj in tests_and_counts:
389+
test, count, testtype = tctt
387390
for testseed in range(md.seed, md.seed + count):
388391
tds_str = f"{test}.{testseed}"
389392

@@ -402,6 +405,21 @@ def _main():
402405
pickle_file=md.dir_metadata/(tds_str + ".pickle"),
403406
yaml_file=md.dir_tests/tds_str/'trr.yaml')
404407

408+
if testtype == TestType.RISCVDV:
409+
# Copy the test options into the object, to make it easier to fetch later.
410+
shlex_if_not_empty = lambda field: shlex.split(obj.get(field) or "")
411+
trr.gen_test = shlex_if_not_empty('gen_test')
412+
trr.gen_opts = shlex_if_not_empty('gen_opts')
413+
trr.rtl_test = shlex_if_not_empty('rtl_test')
414+
trr.sim_opts = shlex_if_not_empty('sim_opts')
415+
416+
# If the +discrete_debug_module=1 argument is passed via sim_opts, a alternate
417+
# memory heirarchy is enabled which seperates the debug rom memory sections
418+
# from the main binary, emulating an external debug module
419+
for plusarg, val in (trr.split('=') for trr in trr.sim_opts):
420+
if plusarg == "+discrete_debug_module":
421+
trr.is_discrete_debug_module = False if '' else bool(int(val))
422+
405423
# Get the data from the directed test yaml that we need to construct the command.
406424
if testtype == TestType.DIRECTED:
407425
trr.directed_data = (next(filter(

dv/uvm/core_ibex/scripts/run_rtl.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,20 @@ def _main() -> int:
3939
trr.rtl_test = testopts['rtl_test']
4040
trr.timeout_s = testopts.get('timeout_s') or md.run_rtl_timeout_s
4141

42-
if not os.path.exists(trr.binary):
43-
raise RuntimeError(
44-
"When computing simulation command for running "
45-
f"seed {trr.seed} of test {trr.testname}, cannot find the "
46-
f"expected binary at {trr.binary!r}.")
47-
4842
# Each test in testlist.yaml can (optionally) specify 'sim_opts'
4943
# which are to be passed to the simulator when running the test.
5044
sim_opts = ''
5145
sim_opts_raw = testopts.get('sim_opts')
5246
if sim_opts_raw:
5347
sim_opts += sim_opts_raw.replace('\n', '')
48+
# If discrete_debug_module is enabled, pass some extra sim_opts
49+
trr.ddm_sim_opts = ''
50+
if (trr.testtype == TestType.RISCVDV and trr.is_discrete_debug_module):
51+
trr.ddm_sim_opts = \
52+
f" +vmem_main={trr.vmem_main}\n" + \
53+
f" +vmem_dm={trr.vmem_dm}\n" + \
54+
f" +bin_main={trr.binary_main}\n" + \
55+
f" +bin_dm={trr.binary_dm}\n"
5456

5557
trr.rtl_log = trr.dir_test / 'rtl_sim.log'
5658
trr.rtl_trace = trr.dir_test / 'trace_core_00000000.log'
@@ -71,6 +73,7 @@ def _main() -> int:
7173
'sim_opts': (f"+signature_addr={md.signature_addr}\n" +
7274
f"+test_timeout_s={trr.timeout_s}\n" +
7375
f"{get_sim_opts(md.ibex_config, md.simulator)}\n" +
76+
trr.ddm_sim_opts +
7477
sim_opts)
7578
}
7679

dv/uvm/core_ibex/scripts/test_run_result.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,19 @@ class TestRunResult(scripts_lib.testdata_cls):
5858
rtl_simulator: Optional[str] = None # Which simulator is used
5959
iss_cosim: Optional[str] = None # Which ISS are we cosimulating with?
6060

61+
# Options for discrete debug module memory heirarchy
62+
is_discrete_debug_module: Optional[bool] = None
63+
binary_main: Optional[pathlib.Path] = None
64+
binary_dm: Optional[pathlib.Path] = None
65+
vmem_main: Optional[pathlib.Path] = None
66+
vmem_dm: Optional[pathlib.Path] = None
67+
ddm_sim_opts: Optional[List[str]] = None
68+
6169
# RISCV_DV specific test parameters
62-
gen_test: Optional[str] = None
63-
gen_opts: Optional[str] = None
64-
rtl_test: Optional[str] = None
65-
sim_opts: Optional[str] = None
70+
gen_test: Optional[List[str]] = None
71+
gen_opts: Optional[List[str]] = None
72+
rtl_test: Optional[List[str]] = None
73+
sim_opts: Optional[List[str]] = None
6674

6775
# Directed Test specific parameters
6876
directed_data: Optional[dict] = None

dv/uvm/core_ibex/tb/core_ibex_tb_top.sv

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ module core_ibex_tb_top;
5050
`define IBEX_CFG_RegFile ibex_pkg::RegFileFF
5151
`endif
5252

53+
// Ibex Parameters
5354
parameter bit PMPEnable = 1'b0;
5455
parameter int unsigned PMPGranularity = 0;
5556
parameter int unsigned PMPNumRegions = 4;
@@ -67,6 +68,12 @@ module core_ibex_tb_top;
6768
parameter bit SecureIbex = 1'b0;
6869
parameter bit ICacheScramble = 1'b0;
6970
parameter bit DbgTriggerEn = 1'b0;
71+
parameter int unsigned DmBaseAddr = 32'h`DM_ADDR;
72+
parameter int unsigned DmAddrMask = 32'h`DM_ADDR_MASK;
73+
parameter int unsigned DmHaltAddr = 32'h`DEBUG_MODE_HALT_ADDR;
74+
parameter int unsigned DmExceptionAddr = 32'h`DEBUG_MODE_EXCEPTION_ADDR;
75+
// Ibex Inputs
76+
parameter int unsigned BootAddr = 32'h`BOOT_ADDR; // ResetVec = BootAddr/256b + 0x80
7077

7178
// Scrambling interface instantiation
7279
logic [ibex_pkg::SCRAMBLE_KEY_W-1:0] scramble_key;
@@ -84,10 +91,6 @@ module core_ibex_tb_top;
8491
assign {scramble_key, scramble_nonce} = scrambling_key_if.d_data;
8592

8693
ibex_top_tracing #(
87-
.DmBaseAddr (32'h`BOOT_ADDR ),
88-
.DmAddrMask (32'h0000_0007 ),
89-
.DmHaltAddr (32'h`BOOT_ADDR + 'h0 ),
90-
.DmExceptionAddr (32'h`BOOT_ADDR + 'h4 ),
9194
.PMPEnable (PMPEnable ),
9295
.PMPGranularity (PMPGranularity ),
9396
.PMPNumRegions (PMPNumRegions ),
@@ -104,7 +107,11 @@ module core_ibex_tb_top;
104107
.SecureIbex (SecureIbex ),
105108
.ICacheScramble (ICacheScramble ),
106109
.BranchPredictor (BranchPredictor ),
107-
.DbgTriggerEn (DbgTriggerEn )
110+
.DbgTriggerEn (DbgTriggerEn ),
111+
.DmBaseAddr (DmBaseAddr ),
112+
.DmAddrMask (DmAddrMask ),
113+
.DmHaltAddr (DmHaltAddr ),
114+
.DmExceptionAddr (DmExceptionAddr )
108115

109116
) dut (
110117
.clk_i (clk ),
@@ -115,7 +122,7 @@ module core_ibex_tb_top;
115122
.ram_cfg_i ('b0 ),
116123

117124
.hart_id_i (32'b0 ),
118-
.boot_addr_i (32'h`BOOT_ADDR ), // align with spike boot address
125+
.boot_addr_i (BootAddr ),
119126

120127
.instr_req_o (instr_mem_vif.request ),
121128
.instr_gnt_i (instr_mem_vif.grant ),

0 commit comments

Comments
 (0)