Skip to content

Commit d69af23

Browse files
sbaillouDenis TrifonovDominik TanousSebastien Baillouamykyta3
authored
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>
1 parent bb765e6 commit d69af23

File tree

16 files changed

+477
-61
lines changed

16 files changed

+477
-61
lines changed

src/peakrdl_regblock/__peakrdl__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class Exporter(ExporterSubcommandPlugin):
2222
cfg_schema = {
2323
"cpuifs": {"*": schema.PythonObjectImport()},
2424
"default_reset": schema.Choice(["rst", "rst_n", "arst", "arst_n"]),
25+
"err_if_bad_addr": schema.Boolean(),
26+
"err_if_bad_rw": schema.Boolean(),
2527
}
2628

2729
@functools.lru_cache()
@@ -141,6 +143,20 @@ def add_exporter_arguments(self, arg_group: 'argparse._ActionsContainer') -> Non
141143
is active-high and synchronous [rst]"""
142144
)
143145

146+
arg_group.add_argument(
147+
"--err-if-bad-addr",
148+
action="store_true",
149+
default=False,
150+
help="Generate CPUIF error response, when the address is decoded incorrectly"
151+
)
152+
153+
arg_group.add_argument(
154+
"--err-if-bad-rw",
155+
action="store_true",
156+
default=False,
157+
help="""Generate CPUIF error response, when an illegal access is
158+
performed to a read-only or write-only register"""
159+
)
144160

145161
def do_export(self, top_node: 'AddrmapNode', options: 'argparse.Namespace') -> None:
146162
cpuifs = self.get_cpuifs()
@@ -203,5 +219,7 @@ def do_export(self, top_node: 'AddrmapNode', options: 'argparse.Namespace') -> N
203219
generate_hwif_report=options.hwif_report,
204220
address_width=options.addr_width,
205221
default_reset_activelow=default_reset_activelow,
222+
err_if_bad_addr=options.err_if_bad_addr or self.cfg['err_if_bad_addr'],
223+
err_if_bad_rw=options.err_if_bad_rw or self.cfg['err_if_bad_rw'],
206224
default_reset_async=default_reset_async,
207225
)

src/peakrdl_regblock/addr_decode.py

Lines changed: 66 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import TYPE_CHECKING, Union, List, Optional
22

3-
from systemrdl.node import FieldNode, RegNode
3+
from systemrdl.node import FieldNode, RegNode, MemNode
44
from systemrdl.walker import WalkerAction
55

66
from .utils import get_indexed_path
@@ -12,7 +12,7 @@
1212
if TYPE_CHECKING:
1313
from .exporter import RegblockExporter
1414
from systemrdl.node import AddrmapNode, AddressableNode
15-
from systemrdl.node import RegfileNode, MemNode
15+
from systemrdl.node import RegfileNode
1616

1717
class AddressDecode:
1818
def __init__(self, exp:'RegblockExporter'):
@@ -132,6 +132,33 @@ def __init__(self, addr_decode: AddressDecode) -> None:
132132
# List of address strides for each dimension
133133
self._array_stride_stack = [] # type: List[int]
134134

135+
def _add_addressablenode_decoding_flags(self, node: 'AddressableNode') -> None:
136+
addr_str = self._get_address_str(node)
137+
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)})"
138+
rhs = addr_decoding_str
139+
rhs_valid_addr = addr_decoding_str
140+
if isinstance(node, MemNode):
141+
readable = node.is_sw_readable
142+
writable = node.is_sw_writable
143+
if readable and writable:
144+
rhs_invalid_rw = "'0"
145+
elif readable and not writable:
146+
rhs = f"{addr_decoding_str} & !cpuif_req_is_wr"
147+
rhs_invalid_rw = f"{addr_decoding_str} & cpuif_req_is_wr"
148+
elif not readable and writable:
149+
rhs = f"{addr_decoding_str} & cpuif_req_is_wr"
150+
rhs_invalid_rw = f"{addr_decoding_str} & !cpuif_req_is_wr"
151+
else:
152+
raise RuntimeError
153+
# Add decoding flags
154+
self.add_content(f"{self.addr_decode.get_external_block_access_strobe(node)} = {rhs};")
155+
self.add_content(f"is_external |= {rhs};")
156+
if self.addr_decode.exp.ds.err_if_bad_addr:
157+
self.add_content(f"is_valid_addr |= {rhs_valid_addr};")
158+
if isinstance(node, MemNode):
159+
if self.addr_decode.exp.ds.err_if_bad_rw:
160+
self.add_content(f"is_invalid_rw |= {rhs_invalid_rw};")
161+
135162

136163
def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]:
137164
super().enter_AddressableComponent(node)
@@ -149,11 +176,7 @@ def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[Walker
149176

150177
if node.external and not isinstance(node, RegNode):
151178
# Is an external block
152-
addr_str = self._get_address_str(node)
153-
strb = self.addr_decode.get_external_block_access_strobe(node)
154-
rhs = f"cpuif_req_masked & (cpuif_addr >= {addr_str}) & (cpuif_addr <= {addr_str} + {SVInt(node.size - 1, self.addr_decode.exp.ds.addr_width)})"
155-
self.add_content(f"{strb} = {rhs};")
156-
self.add_content(f"is_external |= {rhs};")
179+
self._add_addressablenode_decoding_flags(node)
157180
return WalkerAction.SkipDescendants
158181

159182
return WalkerAction.Continue
@@ -170,44 +193,52 @@ def _get_address_str(self, node: 'AddressableNode', subword_offset: int=0) -> st
170193
return a
171194

172195

196+
def _add_reg_decoding_flags(self,
197+
node: RegNode,
198+
subword_index: Union[int, None] = None,
199+
subword_stride: Union[int, None] = None) -> None:
200+
if subword_index is None or subword_stride is None:
201+
addr_decoding_str = f"cpuif_req_masked & (cpuif_addr == {self._get_address_str(node)})"
202+
else:
203+
addr_decoding_str = f"cpuif_req_masked & (cpuif_addr == {self._get_address_str(node, subword_offset=subword_index*subword_stride)})"
204+
rhs_valid_addr = addr_decoding_str
205+
readable = node.has_sw_readable
206+
writable = node.has_sw_writable
207+
if readable and writable:
208+
rhs = addr_decoding_str
209+
rhs_invalid_rw = "'0"
210+
elif readable and not writable:
211+
rhs = f"{addr_decoding_str} & !cpuif_req_is_wr"
212+
rhs_invalid_rw = f"{addr_decoding_str} & cpuif_req_is_wr"
213+
elif not readable and writable:
214+
rhs = f"{addr_decoding_str} & cpuif_req_is_wr"
215+
rhs_invalid_rw = f"{addr_decoding_str} & !cpuif_req_is_wr"
216+
else:
217+
raise RuntimeError
218+
# Add decoding flags
219+
if subword_index is None:
220+
self.add_content(f"{self.addr_decode.get_access_strobe(node)} = {rhs};")
221+
else:
222+
self.add_content(f"{self.addr_decode.get_access_strobe(node)}[{subword_index}] = {rhs};")
223+
if node.external:
224+
self.add_content(f"is_external |= {rhs};")
225+
if self.addr_decode.exp.ds.err_if_bad_addr:
226+
self.add_content(f"is_valid_addr |= {rhs_valid_addr};")
227+
if self.addr_decode.exp.ds.err_if_bad_rw:
228+
self.add_content(f"is_invalid_rw |= {rhs_invalid_rw};")
229+
173230
def enter_Reg(self, node: RegNode) -> None:
174231
regwidth = node.get_property('regwidth')
175232
accesswidth = node.get_property('accesswidth')
176233

177234
if regwidth == accesswidth:
178-
rhs = f"cpuif_req_masked & (cpuif_addr == {self._get_address_str(node)})"
179-
s = f"{self.addr_decode.get_access_strobe(node)} = {rhs};"
180-
self.add_content(s)
181-
if node.external:
182-
readable = node.has_sw_readable
183-
writable = node.has_sw_writable
184-
if readable and writable:
185-
self.add_content(f"is_external |= {rhs};")
186-
elif readable and not writable:
187-
self.add_content(f"is_external |= {rhs} & !cpuif_req_is_wr;")
188-
elif not readable and writable:
189-
self.add_content(f"is_external |= {rhs} & cpuif_req_is_wr;")
190-
else:
191-
raise RuntimeError
235+
self._add_reg_decoding_flags(node)
192236
else:
193237
# Register is wide. Create a substrobe for each subword
194238
n_subwords = regwidth // accesswidth
195239
subword_stride = accesswidth // 8
196240
for i in range(n_subwords):
197-
rhs = f"cpuif_req_masked & (cpuif_addr == {self._get_address_str(node, subword_offset=i*subword_stride)})"
198-
s = f"{self.addr_decode.get_access_strobe(node)}[{i}] = {rhs};"
199-
self.add_content(s)
200-
if node.external:
201-
readable = node.has_sw_readable
202-
writable = node.has_sw_writable
203-
if readable and writable:
204-
self.add_content(f"is_external |= {rhs};")
205-
elif readable and not writable:
206-
self.add_content(f"is_external |= {rhs} & !cpuif_req_is_wr;")
207-
elif not readable and writable:
208-
self.add_content(f"is_external |= {rhs} & cpuif_req_is_wr;")
209-
else:
210-
raise RuntimeError
241+
self._add_reg_decoding_flags(node, i, subword_stride)
211242

212243
def exit_AddressableComponent(self, node: 'AddressableNode') -> None:
213244
super().exit_AddressableComponent(node)

src/peakrdl_regblock/exporter.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ def export(self, node: Union[RootNode, AddrmapNode], output_dir:str, **kwargs: A
120120
If overriden to True, default reset is active-low instead of active-high.
121121
default_reset_async: bool
122122
If overriden to True, default reset is asynchronous instead of synchronous.
123+
err_if_bad_addr: bool
124+
If overriden to True: If the address is decoded incorrectly, the CPUIF response
125+
signal shows an error. For example: APB.PSLVERR = 1'b1, AXI4LITE.*RESP = 2'b10.
126+
err_if_bad_rw: bool
127+
If overriden to True: If an illegal access is performed to a read-only or write-only
128+
register, the CPUIF response signal shows an error. For example: APB.PSLVERR = 1'b1,
129+
AXI4LITE.*RESP = 2'b10.
123130
"""
124131
# If it is the root node, skip to top addrmap
125132
if isinstance(node, RootNode):
@@ -230,6 +237,10 @@ def __init__(self, top_node: AddrmapNode, kwargs: Any) -> None:
230237
self.default_reset_activelow = kwargs.pop("default_reset_activelow", False) # type: bool
231238
self.default_reset_async = kwargs.pop("default_reset_async", False) # type: bool
232239

240+
# Generating a cpuif error
241+
self.err_if_bad_addr = kwargs.pop("err_if_bad_addr", False) # type: bool
242+
self.err_if_bad_rw = kwargs.pop("err_if_bad_rw", False) # type: bool
243+
233244
#------------------------
234245
# Info about the design
235246
#------------------------

src/peakrdl_regblock/module_tmpl.sv

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ module {{ds.module_name}}
126126
//--------------------------------------------------------------------------
127127
{{address_decode.get_strobe_struct()|indent}}
128128
decoded_reg_strb_t decoded_reg_strb;
129+
logic decoded_err;
129130
{%- if ds.has_external_addressable %}
130131
logic decoded_strb_is_external;
131132
{% endif %}
@@ -138,11 +139,20 @@ module {{ds.module_name}}
138139
logic [{{cpuif.data_width-1}}:0] decoded_wr_biten;
139140

140141
always_comb begin
142+
automatic logic is_valid_addr;
143+
automatic logic is_invalid_rw;
141144
{%- if ds.has_external_addressable %}
142145
automatic logic is_external;
143146
is_external = '0;
144147
{%- endif %}
148+
{%- if ds.err_if_bad_addr %}
149+
is_valid_addr = '0;
150+
{%- else %}
151+
is_valid_addr = '1; // No error checking on valid address access
152+
{%- endif %}
153+
is_invalid_rw = '0;
145154
{{address_decode.get_implementation()|indent(8)}}
155+
decoded_err = (~is_valid_addr | is_invalid_rw) & decoded_req;
146156
{%- if ds.has_external_addressable %}
147157
decoded_strb_is_external = is_external;
148158
external_req = is_external;
@@ -225,7 +235,11 @@ module {{ds.module_name}}
225235
assign cpuif_wr_ack = decoded_req & decoded_req_is_wr;
226236
{%- endif %}
227237
// Writes are always granted with no error response
238+
{%- if ds.err_if_bad_addr or ds.err_if_bad_rw %}
239+
assign cpuif_wr_err = decoded_err;
240+
{%- else %}
228241
assign cpuif_wr_err = '0;
242+
{%- endif %}
229243

230244
//--------------------------------------------------------------------------
231245
// Readback

src/peakrdl_regblock/readback/templates/readback.sv

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,15 @@ end
2929

3030
logic [{{cpuif.data_width-1}}:0] readback_array_r[{{fanin_array_size}}];
3131
logic readback_done_r;
32+
logic readback_err_r;
3233
always_ff {{get_always_ff_event(cpuif.reset)}} begin
3334
if({{get_resetsignal(cpuif.reset)}}) begin
3435
for(int i=0; i<{{fanin_array_size}}; i++) readback_array_r[i] <= '0;
3536
readback_done_r <= '0;
37+
readback_err_r <= '0;
3638
end else begin
3739
readback_array_r <= readback_array_c;
40+
readback_err_r <= decoded_err;
3841
{%- if ds.has_external_addressable %}
3942
readback_done_r <= decoded_req & ~decoded_req_is_wr & ~decoded_strb_is_external;
4043
{%- else %}
@@ -47,7 +50,11 @@ end
4750
always_comb begin
4851
automatic logic [{{cpuif.data_width-1}}:0] readback_data_var;
4952
readback_done = readback_done_r;
53+
{%- if ds.err_if_bad_addr or ds.err_if_bad_rw %}
54+
readback_err = readback_err_r;
55+
{%- else %}
5056
readback_err = '0;
57+
{%- endif %}
5158
readback_data_var = '0;
5259
for(int i=0; i<{{fanin_array_size}}; i++) readback_data_var |= readback_array_r[i];
5360
readback_data = readback_data_var;
@@ -63,7 +70,11 @@ always_comb begin
6370
{%- else %}
6471
readback_done = decoded_req & ~decoded_req_is_wr;
6572
{%- endif %}
73+
{%- if ds.err_if_bad_addr or ds.err_if_bad_rw %}
74+
readback_err = decoded_err;
75+
{%- else %}
6676
readback_err = '0;
77+
{%- endif %}
6778
readback_data_var = '0;
6879
for(int i=0; i<{{array_size}}; i++) readback_data_var |= readback_array[i];
6980
readback_data = readback_data_var;
@@ -75,5 +86,9 @@ end
7586
{%- else %}
7687
assign readback_done = decoded_req & ~decoded_req_is_wr;
7788
assign readback_data = '0;
89+
{%- if ds.err_if_bad_addr or ds.err_if_bad_rw %}
90+
assign readback_err = decoded_err;
91+
{%- else %}
7892
assign readback_err = '0;
93+
{%- endif %}
7994
{% endif %}

tests/lib/base_testcase.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class BaseTestCase(unittest.TestCase):
3939
retime_external = False
4040
default_reset_activelow = False
4141
default_reset_async = False
42+
err_if_bad_addr = False
43+
err_if_bad_rw = False
4244

4345
#: this gets auto-loaded via the _load_request autouse fixture
4446
request = None # type: pytest.FixtureRequest
@@ -118,6 +120,8 @@ def export_regblock(self):
118120
retime_external_addrmap=self.retime_external,
119121
default_reset_activelow=self.default_reset_activelow,
120122
default_reset_async=self.default_reset_async,
123+
err_if_bad_addr=self.err_if_bad_addr,
124+
err_if_bad_rw=self.err_if_bad_rw,
121125
)
122126

123127
def delete_run_dir(self) -> None:

tests/lib/cpuifs/apb3/apb3_intf_driver.sv

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ interface apb3_intf_driver #(
5050

5151
semaphore txn_mutex = new(1);
5252

53-
task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data);
53+
task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic expects_err = 1'b0);
5454
txn_mutex.get();
5555
##0;
5656

@@ -68,11 +68,13 @@ interface apb3_intf_driver #(
6868

6969
// Wait for response
7070
while(cb.PREADY !== 1'b1) @(cb);
71+
assert(!$isunknown(cb.PSLVERR)) else $error("Write to 0x%0x returned X's on PSLVERR", addr);
72+
assert(cb.PSLVERR == expects_err) else $error("Error write response to 0x%x returned 0x%x. Expected 0x%x", addr, cb.PSLVERR, expects_err);
7173
reset();
7274
txn_mutex.put();
7375
endtask
7476

75-
task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data);
77+
task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data, input logic expects_err = 1'b0);
7678
txn_mutex.get();
7779
##0;
7880

@@ -92,14 +94,15 @@ interface apb3_intf_driver #(
9294
while(cb.PREADY !== 1'b1) @(cb);
9395
assert(!$isunknown(cb.PRDATA)) else $error("Read from 0x%0x returned X's on PRDATA", addr);
9496
assert(!$isunknown(cb.PSLVERR)) else $error("Read from 0x%0x returned X's on PSLVERR", addr);
97+
assert(cb.PSLVERR == expects_err) else $error("Error read response from 0x%x returned 0x%x. Expected 0x%x", addr, cb.PSLVERR, expects_err);
9598
data = cb.PRDATA;
9699
reset();
97100
txn_mutex.put();
98101
endtask
99102

100-
task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1);
103+
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);
101104
logic [DATA_WIDTH-1:0] data;
102-
read(addr, data);
105+
read(addr, data, expects_err);
103106
data &= mask;
104107
assert(data == expected_data) else $error("Read from 0x%x returned 0x%x. Expected 0x%x", addr, data, expected_data);
105108
endtask

0 commit comments

Comments
 (0)