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 <d.trifonov@yadro.com>
Co-authored-by: Dominik Tanous <tanous@kandou.com>
Co-authored-by: Sebastien Baillou <baillou@kandou.com>
Co-authored-by: Alex Mykyta <amykyta3@users.noreply.github.com>
This commit is contained in:
sbaillou
2025-10-26 02:22:15 +01:00
committed by GitHub
parent bb765e6ae3
commit d69af23be5
16 changed files with 477 additions and 61 deletions

View File

@@ -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,
)

View File

@@ -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)

View File

@@ -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
#------------------------

View File

@@ -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

View File

@@ -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 %}

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
//--------------------------------------------------------------------------

View File

@@ -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

View File

View File

@@ -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;
};

View File

@@ -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 %}

View File

@@ -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()