From d69af23be59b80b99a396eff02716ff0de5bdb8f Mon Sep 17 00:00:00 2001 From: sbaillou Date: Sun, 26 Oct 2025 02:22:15 +0100 Subject: [PATCH] Error response for unmapped address or forbidden read/write access (#168) * feat: add ability to enable error output on the cpuif, when decoding errors occur (generate_cpuif_err in API). * fix: move signal to new place (after automatic vers) * feat: add info about new api (generate_cpuif_err) * fix: repair readback with latency * Adding generate_cpuif_err argument to peakrdl-regblock to generate cpuif error response, when the address is decoded incorrectly * add sw rd or/and wr attribure error response related and add error respone for external mem * add sw rd or/and wr error response test * add sw rd or/and wr error response for external register test and fix generation of rtl logic for external register * add sw rd or/and wr error response for external mem test * add sw rd or/and wr error response for apb3 imterfaces driver * add error response test for APB4, AXI4Lite and Avalon interfaces * rename --generate_cpuif_err to --generate-cpuif-err * style: minor typo fixes and test clean-up * refactor: move expected error check inside write/read functions * feat: add error response check to OBI testbench interface * feat: split generate-cpuif-err option into err-if-bad-addr and err-if-bad-rw options * feat: add err_if_bad_addr/rw to cfg_schema * feat: extend cpuif_err_rsp test to cover all combinations of bad_addr/bad_rw * style: lint fixes * fix: removed redundant if node.external condition to help coverage * Fix dangling hwif_in signals in testcase --------- Co-authored-by: Denis Trifonov Co-authored-by: Dominik Tanous Co-authored-by: Sebastien Baillou Co-authored-by: Alex Mykyta --- src/peakrdl_regblock/__peakrdl__.py | 18 ++ src/peakrdl_regblock/addr_decode.py | 101 +++++--- src/peakrdl_regblock/exporter.py | 11 + src/peakrdl_regblock/module_tmpl.sv | 14 ++ .../readback/templates/readback.sv | 15 ++ tests/lib/base_testcase.py | 4 + tests/lib/cpuifs/apb3/apb3_intf_driver.sv | 11 +- tests/lib/cpuifs/apb4/apb4_intf_driver.sv | 11 +- .../cpuifs/avalon/avalon_mm_intf_driver.sv | 12 +- .../cpuifs/axi4lite/axi4lite_intf_driver.sv | 12 +- tests/lib/cpuifs/obi/obi_intf_driver.sv | 10 +- .../cpuifs/passthrough/passthrough_driver.sv | 10 +- tests/test_cpuif_err_rsp/__init__.py | 0 tests/test_cpuif_err_rsp/regblock.rdl | 59 +++++ tests/test_cpuif_err_rsp/tb_template.sv | 221 ++++++++++++++++++ tests/test_cpuif_err_rsp/testcase.py | 29 +++ 16 files changed, 477 insertions(+), 61 deletions(-) create mode 100644 tests/test_cpuif_err_rsp/__init__.py create mode 100644 tests/test_cpuif_err_rsp/regblock.rdl create mode 100644 tests/test_cpuif_err_rsp/tb_template.sv create mode 100644 tests/test_cpuif_err_rsp/testcase.py diff --git a/src/peakrdl_regblock/__peakrdl__.py b/src/peakrdl_regblock/__peakrdl__.py index 21ac379..e1c2987 100644 --- a/src/peakrdl_regblock/__peakrdl__.py +++ b/src/peakrdl_regblock/__peakrdl__.py @@ -22,6 +22,8 @@ class Exporter(ExporterSubcommandPlugin): cfg_schema = { "cpuifs": {"*": schema.PythonObjectImport()}, "default_reset": schema.Choice(["rst", "rst_n", "arst", "arst_n"]), + "err_if_bad_addr": schema.Boolean(), + "err_if_bad_rw": schema.Boolean(), } @functools.lru_cache() @@ -141,6 +143,20 @@ class Exporter(ExporterSubcommandPlugin): is active-high and synchronous [rst]""" ) + arg_group.add_argument( + "--err-if-bad-addr", + action="store_true", + default=False, + help="Generate CPUIF error response, when the address is decoded incorrectly" + ) + + arg_group.add_argument( + "--err-if-bad-rw", + action="store_true", + default=False, + help="""Generate CPUIF error response, when an illegal access is + performed to a read-only or write-only register""" + ) def do_export(self, top_node: 'AddrmapNode', options: 'argparse.Namespace') -> None: cpuifs = self.get_cpuifs() @@ -203,5 +219,7 @@ class Exporter(ExporterSubcommandPlugin): generate_hwif_report=options.hwif_report, address_width=options.addr_width, default_reset_activelow=default_reset_activelow, + err_if_bad_addr=options.err_if_bad_addr or self.cfg['err_if_bad_addr'], + err_if_bad_rw=options.err_if_bad_rw or self.cfg['err_if_bad_rw'], default_reset_async=default_reset_async, ) diff --git a/src/peakrdl_regblock/addr_decode.py b/src/peakrdl_regblock/addr_decode.py index 5446b48..162ae31 100644 --- a/src/peakrdl_regblock/addr_decode.py +++ b/src/peakrdl_regblock/addr_decode.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING, Union, List, Optional -from systemrdl.node import FieldNode, RegNode +from systemrdl.node import FieldNode, RegNode, MemNode from systemrdl.walker import WalkerAction from .utils import get_indexed_path @@ -12,7 +12,7 @@ from .sv_int import SVInt if TYPE_CHECKING: from .exporter import RegblockExporter from systemrdl.node import AddrmapNode, AddressableNode - from systemrdl.node import RegfileNode, MemNode + from systemrdl.node import RegfileNode class AddressDecode: def __init__(self, exp:'RegblockExporter'): @@ -132,6 +132,33 @@ class DecodeLogicGenerator(RDLForLoopGenerator): # List of address strides for each dimension self._array_stride_stack = [] # type: List[int] + def _add_addressablenode_decoding_flags(self, node: 'AddressableNode') -> None: + addr_str = self._get_address_str(node) + addr_decoding_str = f"cpuif_req_masked & (cpuif_addr >= {addr_str}) & (cpuif_addr <= {addr_str} + {SVInt(node.size - 1, self.addr_decode.exp.ds.addr_width)})" + rhs = addr_decoding_str + rhs_valid_addr = addr_decoding_str + if isinstance(node, MemNode): + readable = node.is_sw_readable + writable = node.is_sw_writable + if readable and writable: + rhs_invalid_rw = "'0" + elif readable and not writable: + rhs = f"{addr_decoding_str} & !cpuif_req_is_wr" + rhs_invalid_rw = f"{addr_decoding_str} & cpuif_req_is_wr" + elif not readable and writable: + rhs = f"{addr_decoding_str} & cpuif_req_is_wr" + rhs_invalid_rw = f"{addr_decoding_str} & !cpuif_req_is_wr" + else: + raise RuntimeError + # Add decoding flags + self.add_content(f"{self.addr_decode.get_external_block_access_strobe(node)} = {rhs};") + self.add_content(f"is_external |= {rhs};") + if self.addr_decode.exp.ds.err_if_bad_addr: + self.add_content(f"is_valid_addr |= {rhs_valid_addr};") + if isinstance(node, MemNode): + if self.addr_decode.exp.ds.err_if_bad_rw: + self.add_content(f"is_invalid_rw |= {rhs_invalid_rw};") + def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]: super().enter_AddressableComponent(node) @@ -149,11 +176,7 @@ class DecodeLogicGenerator(RDLForLoopGenerator): if node.external and not isinstance(node, RegNode): # Is an external block - addr_str = self._get_address_str(node) - strb = self.addr_decode.get_external_block_access_strobe(node) - rhs = f"cpuif_req_masked & (cpuif_addr >= {addr_str}) & (cpuif_addr <= {addr_str} + {SVInt(node.size - 1, self.addr_decode.exp.ds.addr_width)})" - self.add_content(f"{strb} = {rhs};") - self.add_content(f"is_external |= {rhs};") + self._add_addressablenode_decoding_flags(node) return WalkerAction.SkipDescendants return WalkerAction.Continue @@ -170,44 +193,52 @@ class DecodeLogicGenerator(RDLForLoopGenerator): return a + def _add_reg_decoding_flags(self, + node: RegNode, + subword_index: Union[int, None] = None, + subword_stride: Union[int, None] = None) -> None: + if subword_index is None or subword_stride is None: + addr_decoding_str = f"cpuif_req_masked & (cpuif_addr == {self._get_address_str(node)})" + else: + addr_decoding_str = f"cpuif_req_masked & (cpuif_addr == {self._get_address_str(node, subword_offset=subword_index*subword_stride)})" + rhs_valid_addr = addr_decoding_str + readable = node.has_sw_readable + writable = node.has_sw_writable + if readable and writable: + rhs = addr_decoding_str + rhs_invalid_rw = "'0" + elif readable and not writable: + rhs = f"{addr_decoding_str} & !cpuif_req_is_wr" + rhs_invalid_rw = f"{addr_decoding_str} & cpuif_req_is_wr" + elif not readable and writable: + rhs = f"{addr_decoding_str} & cpuif_req_is_wr" + rhs_invalid_rw = f"{addr_decoding_str} & !cpuif_req_is_wr" + else: + raise RuntimeError + # Add decoding flags + if subword_index is None: + self.add_content(f"{self.addr_decode.get_access_strobe(node)} = {rhs};") + else: + self.add_content(f"{self.addr_decode.get_access_strobe(node)}[{subword_index}] = {rhs};") + if node.external: + self.add_content(f"is_external |= {rhs};") + if self.addr_decode.exp.ds.err_if_bad_addr: + self.add_content(f"is_valid_addr |= {rhs_valid_addr};") + if self.addr_decode.exp.ds.err_if_bad_rw: + self.add_content(f"is_invalid_rw |= {rhs_invalid_rw};") + def enter_Reg(self, node: RegNode) -> None: regwidth = node.get_property('regwidth') accesswidth = node.get_property('accesswidth') if regwidth == accesswidth: - rhs = f"cpuif_req_masked & (cpuif_addr == {self._get_address_str(node)})" - s = f"{self.addr_decode.get_access_strobe(node)} = {rhs};" - self.add_content(s) - if node.external: - readable = node.has_sw_readable - writable = node.has_sw_writable - if readable and writable: - self.add_content(f"is_external |= {rhs};") - elif readable and not writable: - self.add_content(f"is_external |= {rhs} & !cpuif_req_is_wr;") - elif not readable and writable: - self.add_content(f"is_external |= {rhs} & cpuif_req_is_wr;") - else: - raise RuntimeError + self._add_reg_decoding_flags(node) else: # Register is wide. Create a substrobe for each subword n_subwords = regwidth // accesswidth subword_stride = accesswidth // 8 for i in range(n_subwords): - rhs = f"cpuif_req_masked & (cpuif_addr == {self._get_address_str(node, subword_offset=i*subword_stride)})" - s = f"{self.addr_decode.get_access_strobe(node)}[{i}] = {rhs};" - self.add_content(s) - if node.external: - readable = node.has_sw_readable - writable = node.has_sw_writable - if readable and writable: - self.add_content(f"is_external |= {rhs};") - elif readable and not writable: - self.add_content(f"is_external |= {rhs} & !cpuif_req_is_wr;") - elif not readable and writable: - self.add_content(f"is_external |= {rhs} & cpuif_req_is_wr;") - else: - raise RuntimeError + self._add_reg_decoding_flags(node, i, subword_stride) def exit_AddressableComponent(self, node: 'AddressableNode') -> None: super().exit_AddressableComponent(node) diff --git a/src/peakrdl_regblock/exporter.py b/src/peakrdl_regblock/exporter.py index b2c5f29..9586cea 100644 --- a/src/peakrdl_regblock/exporter.py +++ b/src/peakrdl_regblock/exporter.py @@ -120,6 +120,13 @@ class RegblockExporter: If overriden to True, default reset is active-low instead of active-high. default_reset_async: bool If overriden to True, default reset is asynchronous instead of synchronous. + err_if_bad_addr: bool + If overriden to True: If the address is decoded incorrectly, the CPUIF response + signal shows an error. For example: APB.PSLVERR = 1'b1, AXI4LITE.*RESP = 2'b10. + err_if_bad_rw: bool + If overriden to True: If an illegal access is performed to a read-only or write-only + register, the CPUIF response signal shows an error. For example: APB.PSLVERR = 1'b1, + AXI4LITE.*RESP = 2'b10. """ # If it is the root node, skip to top addrmap if isinstance(node, RootNode): @@ -230,6 +237,10 @@ class DesignState: self.default_reset_activelow = kwargs.pop("default_reset_activelow", False) # type: bool self.default_reset_async = kwargs.pop("default_reset_async", False) # type: bool + # Generating a cpuif error + self.err_if_bad_addr = kwargs.pop("err_if_bad_addr", False) # type: bool + self.err_if_bad_rw = kwargs.pop("err_if_bad_rw", False) # type: bool + #------------------------ # Info about the design #------------------------ diff --git a/src/peakrdl_regblock/module_tmpl.sv b/src/peakrdl_regblock/module_tmpl.sv index bad1c21..10b3ab9 100644 --- a/src/peakrdl_regblock/module_tmpl.sv +++ b/src/peakrdl_regblock/module_tmpl.sv @@ -126,6 +126,7 @@ module {{ds.module_name}} //-------------------------------------------------------------------------- {{address_decode.get_strobe_struct()|indent}} decoded_reg_strb_t decoded_reg_strb; + logic decoded_err; {%- if ds.has_external_addressable %} logic decoded_strb_is_external; {% endif %} @@ -138,11 +139,20 @@ module {{ds.module_name}} logic [{{cpuif.data_width-1}}:0] decoded_wr_biten; always_comb begin + automatic logic is_valid_addr; + automatic logic is_invalid_rw; {%- if ds.has_external_addressable %} automatic logic is_external; is_external = '0; {%- endif %} + {%- if ds.err_if_bad_addr %} + is_valid_addr = '0; + {%- else %} + is_valid_addr = '1; // No error checking on valid address access + {%- endif %} + is_invalid_rw = '0; {{address_decode.get_implementation()|indent(8)}} + decoded_err = (~is_valid_addr | is_invalid_rw) & decoded_req; {%- if ds.has_external_addressable %} decoded_strb_is_external = is_external; external_req = is_external; @@ -225,7 +235,11 @@ module {{ds.module_name}} assign cpuif_wr_ack = decoded_req & decoded_req_is_wr; {%- endif %} // Writes are always granted with no error response + {%- if ds.err_if_bad_addr or ds.err_if_bad_rw %} + assign cpuif_wr_err = decoded_err; + {%- else %} assign cpuif_wr_err = '0; + {%- endif %} //-------------------------------------------------------------------------- // Readback diff --git a/src/peakrdl_regblock/readback/templates/readback.sv b/src/peakrdl_regblock/readback/templates/readback.sv index e44c9ed..08ed492 100644 --- a/src/peakrdl_regblock/readback/templates/readback.sv +++ b/src/peakrdl_regblock/readback/templates/readback.sv @@ -29,12 +29,15 @@ end logic [{{cpuif.data_width-1}}:0] readback_array_r[{{fanin_array_size}}]; logic readback_done_r; +logic readback_err_r; always_ff {{get_always_ff_event(cpuif.reset)}} begin if({{get_resetsignal(cpuif.reset)}}) begin for(int i=0; i<{{fanin_array_size}}; i++) readback_array_r[i] <= '0; readback_done_r <= '0; + readback_err_r <= '0; end else begin readback_array_r <= readback_array_c; + readback_err_r <= decoded_err; {%- if ds.has_external_addressable %} readback_done_r <= decoded_req & ~decoded_req_is_wr & ~decoded_strb_is_external; {%- else %} @@ -47,7 +50,11 @@ end always_comb begin automatic logic [{{cpuif.data_width-1}}:0] readback_data_var; readback_done = readback_done_r; + {%- if ds.err_if_bad_addr or ds.err_if_bad_rw %} + readback_err = readback_err_r; + {%- else %} readback_err = '0; + {%- endif %} readback_data_var = '0; for(int i=0; i<{{fanin_array_size}}; i++) readback_data_var |= readback_array_r[i]; readback_data = readback_data_var; @@ -63,7 +70,11 @@ always_comb begin {%- else %} readback_done = decoded_req & ~decoded_req_is_wr; {%- endif %} + {%- if ds.err_if_bad_addr or ds.err_if_bad_rw %} + readback_err = decoded_err; + {%- else %} readback_err = '0; + {%- endif %} readback_data_var = '0; for(int i=0; i<{{array_size}}; i++) readback_data_var |= readback_array[i]; readback_data = readback_data_var; @@ -75,5 +86,9 @@ end {%- else %} assign readback_done = decoded_req & ~decoded_req_is_wr; assign readback_data = '0; +{%- if ds.err_if_bad_addr or ds.err_if_bad_rw %} +assign readback_err = decoded_err; +{%- else %} assign readback_err = '0; +{%- endif %} {% endif %} diff --git a/tests/lib/base_testcase.py b/tests/lib/base_testcase.py index 6c9d4b8..1213434 100644 --- a/tests/lib/base_testcase.py +++ b/tests/lib/base_testcase.py @@ -39,6 +39,8 @@ class BaseTestCase(unittest.TestCase): retime_external = False default_reset_activelow = False default_reset_async = False + err_if_bad_addr = False + err_if_bad_rw = False #: this gets auto-loaded via the _load_request autouse fixture request = None # type: pytest.FixtureRequest @@ -118,6 +120,8 @@ class BaseTestCase(unittest.TestCase): retime_external_addrmap=self.retime_external, default_reset_activelow=self.default_reset_activelow, default_reset_async=self.default_reset_async, + err_if_bad_addr=self.err_if_bad_addr, + err_if_bad_rw=self.err_if_bad_rw, ) def delete_run_dir(self) -> None: diff --git a/tests/lib/cpuifs/apb3/apb3_intf_driver.sv b/tests/lib/cpuifs/apb3/apb3_intf_driver.sv index 5533f27..3842334 100644 --- a/tests/lib/cpuifs/apb3/apb3_intf_driver.sv +++ b/tests/lib/cpuifs/apb3/apb3_intf_driver.sv @@ -50,7 +50,7 @@ interface apb3_intf_driver #( semaphore txn_mutex = new(1); - task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data); + task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic expects_err = 1'b0); txn_mutex.get(); ##0; @@ -68,11 +68,13 @@ interface apb3_intf_driver #( // Wait for response while(cb.PREADY !== 1'b1) @(cb); + assert(!$isunknown(cb.PSLVERR)) else $error("Write to 0x%0x returned X's on PSLVERR", addr); + assert(cb.PSLVERR == expects_err) else $error("Error write response to 0x%x returned 0x%x. Expected 0x%x", addr, cb.PSLVERR, expects_err); reset(); txn_mutex.put(); endtask - task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data); + task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data, input logic expects_err = 1'b0); txn_mutex.get(); ##0; @@ -92,14 +94,15 @@ interface apb3_intf_driver #( while(cb.PREADY !== 1'b1) @(cb); assert(!$isunknown(cb.PRDATA)) else $error("Read from 0x%0x returned X's on PRDATA", addr); assert(!$isunknown(cb.PSLVERR)) else $error("Read from 0x%0x returned X's on PSLVERR", addr); + assert(cb.PSLVERR == expects_err) else $error("Error read response from 0x%x returned 0x%x. Expected 0x%x", addr, cb.PSLVERR, expects_err); data = cb.PRDATA; reset(); txn_mutex.put(); endtask - task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1); + task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, input logic [DATA_WIDTH-1:0] mask = {DATA_WIDTH{1'b1}}, input logic expects_err = 1'b0); logic [DATA_WIDTH-1:0] data; - read(addr, data); + read(addr, data, expects_err); data &= mask; assert(data == expected_data) else $error("Read from 0x%x returned 0x%x. Expected 0x%x", addr, data, expected_data); endtask diff --git a/tests/lib/cpuifs/apb4/apb4_intf_driver.sv b/tests/lib/cpuifs/apb4/apb4_intf_driver.sv index cf5258f..80f3086 100644 --- a/tests/lib/cpuifs/apb4/apb4_intf_driver.sv +++ b/tests/lib/cpuifs/apb4/apb4_intf_driver.sv @@ -58,7 +58,7 @@ interface apb4_intf_driver #( semaphore txn_mutex = new(1); - task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH/8-1:0] strb = '1); + task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH/8-1:0] strb = {DATA_WIDTH{1'b1}}, logic expects_err = 0); txn_mutex.get(); ##0; @@ -78,11 +78,13 @@ interface apb4_intf_driver #( // Wait for response while(cb.PREADY !== 1'b1) @(cb); + assert(!$isunknown(cb.PSLVERR)) else $error("Write to 0x%0x returned X's on PSLVERR", addr); + assert(cb.PSLVERR == expects_err) else $error("Error write response to 0x%x returned 0x%x. Expected 0x%x", addr, cb.PSLVERR, expects_err); reset(); txn_mutex.put(); endtask - task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data); + task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data, input logic expects_err = 0); txn_mutex.get(); ##0; @@ -104,14 +106,15 @@ interface apb4_intf_driver #( while(cb.PREADY !== 1'b1) @(cb); assert(!$isunknown(cb.PRDATA)) else $error("Read from 0x%0x returned X's on PRDATA", addr); assert(!$isunknown(cb.PSLVERR)) else $error("Read from 0x%0x returned X's on PSLVERR", addr); + assert(cb.PSLVERR == expects_err) else $error("Error read response from 0x%x returned 0x%x. Expected 0x%x", addr, cb.PSLVERR, expects_err); data = cb.PRDATA; reset(); txn_mutex.put(); endtask - task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1); + task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = {DATA_WIDTH{1'b1}}, input logic expects_err = 0); logic [DATA_WIDTH-1:0] data; - read(addr, data); + read(addr, data, expects_err); data &= mask; assert(data == expected_data) else $error("Read from 0x%x returned 0x%x. Expected 0x%x", addr, data, expected_data); endtask diff --git a/tests/lib/cpuifs/avalon/avalon_mm_intf_driver.sv b/tests/lib/cpuifs/avalon/avalon_mm_intf_driver.sv index 173be6a..72c7a1f 100644 --- a/tests/lib/cpuifs/avalon/avalon_mm_intf_driver.sv +++ b/tests/lib/cpuifs/avalon/avalon_mm_intf_driver.sv @@ -59,7 +59,7 @@ interface avalon_mm_intf_driver #( semaphore req_mutex = new(1); semaphore resp_mutex = new(1); - task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH/8-1:0] strb = '1); + task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH/8-1:0] strb = {DATA_WIDTH{1'b1}}, logic expects_err = 1'b0); fork begin req_mutex.get(); @@ -82,13 +82,14 @@ interface avalon_mm_intf_driver #( @cb; // Wait for response while(cb.av_writeresponsevalid !== 1'b1) @(cb); - assert(!$isunknown(cb.av_response)) else $error("Read from 0x%0x returned X's on av_response", addr); + assert(!$isunknown(cb.av_response)) else $error("Write to 0x%0x returned X's on av_response", addr); + assert((cb.av_response == 2'b10) == expects_err) else $error("Error write response to 0x%x returned 0x%x. Expected 0x%x", addr, (cb.av_response == 2'b10), expects_err); resp_mutex.put(); end join endtask - task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data); + task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data, input logic expects_err = 1'b0); fork begin req_mutex.get(); @@ -111,15 +112,16 @@ interface avalon_mm_intf_driver #( while(cb.av_readdatavalid !== 1'b1) @(cb); assert(!$isunknown(cb.av_readdata)) else $error("Read from 0x%0x returned X's on av_response", av_readdata); assert(!$isunknown(cb.av_response)) else $error("Read from 0x%0x returned X's on av_response", addr); + assert((cb.av_response == 2'b10) == expects_err) else $error("Error read response from 0x%x returned 0x%x. Expected 0x%x", addr, (cb.av_response == 2'b10), expects_err); data = cb.av_readdata; resp_mutex.put(); end join endtask - task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1); + task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = {DATA_WIDTH{1'b1}}, input logic expects_err = 1'b0); logic [DATA_WIDTH-1:0] data; - read(addr, data); + read(addr, data, expects_err); data &= mask; assert(data == expected_data) else $error("Read from 0x%x returned 0x%x. Expected 0x%x", addr, data, expected_data); endtask diff --git a/tests/lib/cpuifs/axi4lite/axi4lite_intf_driver.sv b/tests/lib/cpuifs/axi4lite/axi4lite_intf_driver.sv index 856048a..4251295 100644 --- a/tests/lib/cpuifs/axi4lite/axi4lite_intf_driver.sv +++ b/tests/lib/cpuifs/axi4lite/axi4lite_intf_driver.sv @@ -160,7 +160,7 @@ interface axi4lite_intf_driver #( end end - task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH/8-1:0] strb = '1); + task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, input logic [DATA_WIDTH/8-1:0] strb = {DATA_WIDTH{1'b1}}, logic expects_err = 1'b0); write_request_t req; write_response_t resp; @@ -175,7 +175,8 @@ interface axi4lite_intf_driver #( // Wait for response req.response_mbx.get(resp); - assert(!$isunknown(resp.bresp)) else $error("Read from 0x%0x returned X's on BRESP", addr); + assert(!$isunknown(resp.bresp)) else $error("Write to 0x%0x returned X's on BRESP", addr); + assert((resp.bresp==2'b10) == expects_err) else $error("Error write response to 0x%x returned 0x%x. Expected 0x%x", addr, (resp.bresp==2'b10), expects_err); endtask //-------------------------------------------------------------------------- @@ -212,7 +213,7 @@ interface axi4lite_intf_driver #( end end - task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data); + task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data, input logic expects_err = 1'b0); read_request_t req; read_response_t resp; @@ -236,12 +237,13 @@ interface axi4lite_intf_driver #( assert(!$isunknown(resp.rdata)) else $error("Read from 0x%0x returned X's on RDATA", addr); assert(!$isunknown(resp.rresp)) else $error("Read from 0x%0x returned X's on RRESP", addr); + assert((resp.rresp == 2'b10) == expects_err) else $error("Error read response from 0x%x returned 0x%x. Expected 0x%x", addr, (resp.rresp == 2'b10), expects_err); data = resp.rdata; endtask - task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1); + task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = {DATA_WIDTH{1'b1}}, input logic expects_err = 1'b0); logic [DATA_WIDTH-1:0] data; - read(addr, data); + read(addr, data, expects_err); data &= mask; assert(data == expected_data) else $error("Read from 0x%x returned 0x%x. Expected 0x%x", addr, data, expected_data); endtask diff --git a/tests/lib/cpuifs/obi/obi_intf_driver.sv b/tests/lib/cpuifs/obi/obi_intf_driver.sv index 2312ece..eb91d23 100644 --- a/tests/lib/cpuifs/obi/obi_intf_driver.sv +++ b/tests/lib/cpuifs/obi/obi_intf_driver.sv @@ -111,7 +111,7 @@ interface obi_intf_driver #( end //-------------------------------------------------------------------------- - task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data); + task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data, input logic expects_err = 1'b0); request_t req; response_t resp; logic [ID_WIDTH-1:0] id; @@ -141,18 +141,19 @@ interface obi_intf_driver #( assert(!$isunknown(resp.rdata)) else $error("Read from 0x%0x returned X's on rdata", addr); assert(!$isunknown(resp.err)) else $error("Read from 0x%0x returned X's on err", addr); assert(!$isunknown(resp.rid)) else $error("Read from 0x%0x returned X's on rid", addr); + assert(resp.err == expects_err) else $error("Error read response from 0x%x returned 0x%x. Expected 0x%x", addr, resp.err, expects_err); data = resp.rdata; endtask - task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1); + task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = {DATA_WIDTH{1'b1}}, input logic expects_err = 1'b0); logic [DATA_WIDTH-1:0] data; - read(addr, data); + read(addr, data, expects_err); data &= mask; assert(data == expected_data) else $error("Read from 0x%x returned 0x%x. Expected 0x%x", addr, data, expected_data); endtask //-------------------------------------------------------------------------- - task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH/8-1:0] strb = '1); + task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH/8-1:0] strb = {DATA_WIDTH{1'b1}}, input logic expects_err = 1'b0); request_t req; response_t resp; logic [ID_WIDTH-1:0] id; @@ -183,6 +184,7 @@ interface obi_intf_driver #( assert(!$isunknown(resp.err)) else $error("Read from 0x%0x returned X's on err", addr); assert(!$isunknown(resp.rid)) else $error("Read from 0x%0x returned X's on rid", addr); + assert(resp.err == expects_err) else $error("Error write response to 0x%x returned 0x%x. Expected 0x%x", addr, resp.err, expects_err); endtask //-------------------------------------------------------------------------- diff --git a/tests/lib/cpuifs/passthrough/passthrough_driver.sv b/tests/lib/cpuifs/passthrough/passthrough_driver.sv index 9145172..104f2dc 100644 --- a/tests/lib/cpuifs/passthrough/passthrough_driver.sv +++ b/tests/lib/cpuifs/passthrough/passthrough_driver.sv @@ -49,7 +49,7 @@ interface passthrough_driver #( semaphore txn_req_mutex = new(1); semaphore txn_resp_mutex = new(1); - task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH-1:0] biten = '1); + task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH-1:0] biten = {DATA_WIDTH{1'b1}}, logic expects_err = 1'b0); fork begin // Initiate transfer @@ -71,12 +71,13 @@ interface passthrough_driver #( txn_resp_mutex.get(); @cb; while(cb.m_cpuif_wr_ack !== 1'b1) @(cb); + assert(cb.m_cpuif_wr_err == expects_err) else $error("Error write response to 0x%x returned 0x%x. Expected 0x%x", addr, cb.m_cpuif_wr_err, expects_err); txn_resp_mutex.put(); end join endtask - task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data); + task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data, input logic expects_err = 1'b0); fork begin // Initiate transfer @@ -97,15 +98,16 @@ interface passthrough_driver #( @cb; while(cb.m_cpuif_rd_ack !== 1'b1) @(cb); assert(!$isunknown(cb.m_cpuif_rd_data)) else $error("Read from 0x%0x returned X's on m_cpuif_rd_data", addr); + assert(cb.m_cpuif_rd_err == expects_err) else $error("Error read response from 0x%x returned 0x%x. Expected 0x%x", addr, cb.m_cpuif_rd_err, expects_err); data = cb.m_cpuif_rd_data; txn_resp_mutex.put(); end join endtask - task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1); + task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = {DATA_WIDTH{1'b1}}, input logic expects_err = 1'b0); logic [DATA_WIDTH-1:0] data; - read(addr, data); + read(addr, data, expects_err); data &= mask; assert(data == expected_data) else $error("Read from 0x%x returned 0x%x. Expected 0x%x", addr, data, expected_data); endtask diff --git a/tests/test_cpuif_err_rsp/__init__.py b/tests/test_cpuif_err_rsp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_cpuif_err_rsp/regblock.rdl b/tests/test_cpuif_err_rsp/regblock.rdl new file mode 100644 index 0000000..bc684f4 --- /dev/null +++ b/tests/test_cpuif_err_rsp/regblock.rdl @@ -0,0 +1,59 @@ +addrmap top { + default regwidth = 32; + default sw=rw; + default hw=na; + + reg { + field { + sw=rw; hw=na; // Storage element + } f[31:0] = 40; + } r_rw; + + reg { + field { + sw=r; hw=na; // Wire/Bus - constant value + } f[31:0] = 80; + } r_r; + + reg { + field { + sw=w; hw=r; // Storage element + } f[31:0] = 100; + } r_w; + + external reg { + field { + sw=rw; hw=na; // Storage element + } f[31:0]; + } er_rw; + + external reg { + field { + sw=r; hw=na; // Wire/Bus - constant value + } f[31:0]; + } er_r; + + external reg { + field { + sw=w; hw=na; // Storage element + } f[31:0]; + } er_w; + + external mem { + memwidth = 32; + mementries = 2; + } mem_rw @ 0x20; + + external mem { + memwidth = 32; + mementries = 2; + sw=r; + } mem_r @ 0x28; + + external mem { + memwidth = 32; + mementries = 2; + sw=w; + } mem_w @ 0x30; + +}; diff --git a/tests/test_cpuif_err_rsp/tb_template.sv b/tests/test_cpuif_err_rsp/tb_template.sv new file mode 100644 index 0000000..462b856 --- /dev/null +++ b/tests/test_cpuif_err_rsp/tb_template.sv @@ -0,0 +1,221 @@ +{% extends "lib/tb_base.sv" %} + +{%- block dut_support %} + {% sv_line_anchor %} + + external_reg ext_reg_inst ( + .clk(clk), + .rst(rst), + + .req(hwif_out.er_rw.req), + .req_is_wr(hwif_out.er_rw.req_is_wr), + .wr_data(hwif_out.er_rw.wr_data), + .wr_biten(hwif_out.er_rw.wr_biten), + .rd_ack(hwif_in.er_rw.rd_ack), + .rd_data(hwif_in.er_rw.rd_data), + .wr_ack(hwif_in.er_rw.wr_ack) + ); + + external_reg ro_reg_inst ( + .clk(clk), + .rst(rst), + + .req(hwif_out.er_r.req), + .req_is_wr(hwif_out.er_r.req_is_wr), + .wr_data(32'b0), + .wr_biten(32'b0), + .rd_ack(hwif_in.er_r.rd_ack), + .rd_data(hwif_in.er_r.rd_data), + .wr_ack() + ); + + external_reg wo_reg_inst ( + .clk(clk), + .rst(rst), + + .req(hwif_out.er_w.req), + .req_is_wr(hwif_out.er_w.req_is_wr), + .wr_data(hwif_out.er_w.wr_data), + .wr_biten(hwif_out.er_w.wr_biten), + .rd_ack(), + .rd_data(), + .wr_ack(hwif_in.er_w.wr_ack) + ); + + external_block #( + .ADDR_WIDTH(3) + ) mem_rw_inst ( + .clk(clk), + .rst(rst), + + .req(hwif_out.mem_rw.req), + .req_is_wr(hwif_out.mem_rw.req_is_wr), + .addr(hwif_out.mem_rw.addr), + .wr_data(hwif_out.mem_rw.wr_data), + .wr_biten(hwif_out.mem_rw.wr_biten), + .rd_ack(hwif_in.mem_rw.rd_ack), + .rd_data(hwif_in.mem_rw.rd_data), + .wr_ack(hwif_in.mem_rw.wr_ack) + ); + + external_block #( + .ADDR_WIDTH(3) + ) mem_ro_inst ( + .clk(clk), + .rst(rst), + + .req(hwif_out.mem_r.req), + .req_is_wr(hwif_out.mem_r.req_is_wr), + .addr(hwif_out.mem_r.addr), + .wr_data(32'b0), + .wr_biten(32'b0), + .rd_ack(hwif_in.mem_r.rd_ack), + .rd_data(hwif_in.mem_r.rd_data), + .wr_ack(hwif_in.mem_r.wr_ack) + ); + + external_block #( + .ADDR_WIDTH(3) + ) mem_wo_inst ( + .clk(clk), + .rst(rst), + + .req(hwif_out.mem_w.req), + .req_is_wr(hwif_out.mem_w.req_is_wr), + .addr(hwif_out.mem_w.addr), + .wr_data(hwif_out.mem_w.wr_data), + .wr_biten(hwif_out.mem_w.wr_biten), + .rd_ack(), + .rd_data(), + .wr_ack(hwif_in.mem_w.wr_ack) + ); + assign hwif_in.mem_w.rd_ack = '0; + assign hwif_in.mem_w.rd_data = '0; + +{%- endblock %} + +{% block seq %} + +logic wr_err; +logic expected_wr_err; +logic expected_rd_err; +logic bad_addr_expected_err; +logic bad_rw_expected_wr_err; +logic bad_rw_expected_rd_err; +logic [5:0] addr; + + {% sv_line_anchor %} + ##1; + cb.rst <= '0; + ##1; + +{%- if testcase.err_if_bad_addr %} + bad_addr_expected_err = 1'b1; +{%- else %} + bad_addr_expected_err = 1'b0; +{%- endif %} + +{%- if testcase.err_if_bad_rw %} + bad_rw_expected_wr_err = 1'b1; + bad_rw_expected_rd_err = 1'b1; +{%- else %} + bad_rw_expected_wr_err = 1'b0; + bad_rw_expected_rd_err = 1'b0; +{%- endif %} + + // r_rw - sw=rw; hw=na; // Storage element + addr = 'h0; + expected_rd_err = 'h0; + expected_wr_err = 'h0; + cpuif.assert_read(addr, 40, .expects_err(expected_rd_err)); + cpuif.write('h0, 61, .expects_err(expected_wr_err)); + cpuif.assert_read('h0, 61, .expects_err(expected_rd_err)); + + // r_r - sw=r; hw=na; // Wire/Bus - constant value + addr = 'h4; + expected_rd_err = 'h0; + expected_wr_err = bad_rw_expected_wr_err; + cpuif.assert_read(addr, 80, .expects_err(expected_rd_err)); + cpuif.write(addr, 81, .expects_err(expected_wr_err)); + cpuif.assert_read(addr, 80, .expects_err(expected_rd_err)); + + // r_w - sw=w; hw=r; // Storage element + addr = 'h8; + expected_rd_err = bad_rw_expected_rd_err; + expected_wr_err = 'h0; + cpuif.assert_read(addr, 0, .expects_err(expected_rd_err)); + assert(cb.hwif_out.r_w.f.value == 100); + + cpuif.write(addr, 101, .expects_err(expected_wr_err)); + cpuif.assert_read(addr, 0, .expects_err(expected_rd_err)); + assert(cb.hwif_out.r_w.f.value == 101); + + // External registers + // er_rw - sw=rw; hw=na; // Storage element + addr = 'hC; + expected_rd_err = 'h0; + expected_wr_err = 'h0; + ext_reg_inst.value = 'h8C; + cpuif.assert_read(addr, 'h8C, .expects_err(expected_rd_err)); + cpuif.write(addr, 'h8D, .expects_err(expected_wr_err)); + cpuif.assert_read(addr, 'h8D, .expects_err(expected_rd_err)); + + // er_r - sw=r; hw=na; // Wire/Bus - constant value + addr = 'h10; + expected_rd_err = 'h0; + expected_wr_err = bad_rw_expected_wr_err; + ro_reg_inst.value = 'hB4; + cpuif.assert_read(addr, 'hB4, .expects_err(expected_rd_err)); + cpuif.write(addr, 'hB5, .expects_err(expected_wr_err)); + cpuif.assert_read(addr, 'hB4, .expects_err(expected_rd_err)); + + // er_w - sw=w; hw=r; // Storage element + addr = 'h14; + expected_rd_err = bad_rw_expected_rd_err; + expected_wr_err = 'h0; + wo_reg_inst.value = 'hC8; + cpuif.assert_read(addr, 0, .expects_err(expected_rd_err)); + assert(wo_reg_inst.value == 'hC8); + + cpuif.write(addr, 'hC9, .expects_err(expected_wr_err)); + cpuif.assert_read(addr, 0, .expects_err(expected_rd_err)); + assert(wo_reg_inst.value == 'hC9); + + // Reading/writing from/to non existing register + addr = 'h18; + cpuif.assert_read(addr, 0, .expects_err(bad_addr_expected_err)); + cpuif.write(addr, 'h8C, .expects_err(bad_addr_expected_err)); + + // External memories + // mem_rw - sw=rw; + addr = 'h20; + expected_rd_err = 'h0; + expected_wr_err = 'h0; + mem_rw_inst.mem[0] = 'h8C; + cpuif.assert_read(addr, 'h8C, .expects_err(expected_rd_err)); + cpuif.write(addr, 'h8D, .expects_err(expected_wr_err)); + cpuif.assert_read(addr, 'h8D, .expects_err(expected_rd_err)); + + // mem_r - sw=r; + addr = 'h28; + expected_rd_err = 'h0; + expected_wr_err = bad_rw_expected_wr_err; + mem_ro_inst.mem[0] = 'hB4; + cpuif.assert_read(addr, 'hB4, .expects_err(expected_rd_err)); + cpuif.write(addr, 'hB5, .expects_err(expected_wr_err)); + cpuif.assert_read(addr, 'hB4, .expects_err(expected_rd_err)); + + + // mem_w - sw=w; + addr = 'h30; + expected_rd_err = bad_rw_expected_rd_err; + expected_wr_err = 'h0; + mem_wo_inst.mem[0] = 'hC8; + cpuif.assert_read(addr, 0, .expects_err(expected_rd_err)); + assert(mem_wo_inst.mem[0] == 'hC8); + + cpuif.write(addr, 'hC9, .expects_err(expected_wr_err)); + cpuif.assert_read(addr, 0, .expects_err(expected_rd_err)); + assert(mem_wo_inst.mem[0] == 'hC9); + +{% endblock %} diff --git a/tests/test_cpuif_err_rsp/testcase.py b/tests/test_cpuif_err_rsp/testcase.py new file mode 100644 index 0000000..f5e7ac1 --- /dev/null +++ b/tests/test_cpuif_err_rsp/testcase.py @@ -0,0 +1,29 @@ +from parameterized import parameterized_class + +from ..lib.sim_testcase import SimTestCase +from ..lib.test_params import get_permutations +from ..lib.cpuifs import ALL_CPUIF + +@parameterized_class( + # To reduce the number of tests, cover all CPUIFs with both error injections enabled, and all + # combinations of bad_addr/bad_rw with the default CPUIF only. + get_permutations({ + "cpuif": ALL_CPUIF, + "err_if_bad_addr": [True], + "err_if_bad_rw": [True], + }) + + get_permutations({ + "err_if_bad_addr": [True, False], + "err_if_bad_rw": [True, False], + }) +) +class Test(SimTestCase): + extra_tb_files = [ + "../lib/external_reg.sv", + "../lib/external_block.sv", + ] + init_hwif_in = False + clocking_hwif_in = False + + def test_dut(self): + self.run_test()