From a0ea68ee7bf16b9a8d611ccbea589078e8bc7a6b Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Sun, 1 Dec 2024 15:44:47 +0100 Subject: [PATCH] [racl] Implement RACL checks in reggen Signed-off-by: Robert Schilling --- util/reggen/bus_interfaces.py | 16 ++++-- util/reggen/lib.py | 5 ++ util/reggen/reg_top.sv.tpl | 100 ++++++++++++++++++++++++++++++---- 3 files changed, 107 insertions(+), 14 deletions(-) diff --git a/util/reggen/bus_interfaces.py b/util/reggen/bus_interfaces.py index 54821109a32b3..b3dc380b61bd1 100644 --- a/util/reggen/bus_interfaces.py +++ b/util/reggen/bus_interfaces.py @@ -7,7 +7,7 @@ from typing import Dict, List, Optional, Tuple from reggen.inter_signal import InterSignal -from reggen.lib import check_list, check_keys, check_str, check_optional_str +from reggen.lib import check_list, check_keys, check_str, check_optional_bool, check_optional_str class BusInterfaces: @@ -18,7 +18,8 @@ def __init__(self, has_unnamed_device: bool, named_devices: List[str], device_async: Dict[Optional[str], str], - device_hier_paths: Dict[Optional[str], str]): + device_hier_paths: Dict[Optional[str], str], + racl_support: Dict[Optional[str], bool]): assert has_unnamed_device or named_devices assert len(named_hosts) == len(set(named_hosts)) assert len(named_devices) == len(set(named_devices)) @@ -30,6 +31,7 @@ def __init__(self, self.named_devices = named_devices self.device_async = device_async self.device_hier_paths = device_hier_paths + self.racl_support = racl_support @staticmethod def from_raw(raw: object, where: str) -> 'BusInterfaces': @@ -41,12 +43,13 @@ def from_raw(raw: object, where: str) -> 'BusInterfaces': named_devices = [] device_async = {} device_hier_paths = {} + racl_support_map = {} for idx, raw_entry in enumerate(check_list(raw, where)): entry_what = 'entry {} of {}'.format(idx + 1, where) ed = check_keys(raw_entry, entry_what, ['protocol', 'direction'], - ['name', 'async', 'hier_path']) + ['name', 'async', 'hier_path', 'racl_support']) protocol = check_str(ed['protocol'], 'protocol field of ' + entry_what) @@ -69,6 +72,9 @@ def from_raw(raw: object, where: str) -> 'BusInterfaces': hier_path = check_optional_str(ed.get('hier_path'), 'hier_path field of ' + entry_what) + racl_support = check_optional_bool(ed.get('racl_support'), + 'racl_support field of ' + entry_what) + if direction == 'host': if name is None: if has_unnamed_host: @@ -111,12 +117,14 @@ def from_raw(raw: object, where: str) -> 'BusInterfaces': else: device_hier_paths[name] = 'u_reg' + racl_support_map[name] = bool(racl_support) + if not (has_unnamed_device or named_devices): raise ValueError('No device interface at ' + where) return BusInterfaces(has_unnamed_host, named_hosts, host_async, has_unnamed_device, named_devices, - device_async, device_hier_paths) + device_async, device_hier_paths, racl_support_map) def has_host(self) -> bool: return bool(self.has_unnamed_host or self.named_hosts) diff --git a/util/reggen/lib.py b/util/reggen/lib.py index 8839a1569b258..c6bdeae8cadae 100644 --- a/util/reggen/lib.py +++ b/util/reggen/lib.py @@ -239,6 +239,11 @@ def check_optional_str(obj: object, what: str) -> Optional[str]: return None if obj is None else check_str(obj, what) +def check_optional_bool(obj: object, what: str) -> Optional[bool]: + '''Check that obj is a bool or None''' + return None if obj is None else check_bool(obj, what) + + def check_optional_name(obj: object, what: str) -> Optional[str]: '''Check that obj is a valid name or None''' return None if obj is None else check_name(obj, what) diff --git a/util/reggen/reg_top.sv.tpl b/util/reggen/reg_top.sv.tpl index 06b281eb63acb..38a2f2663690f 100644 --- a/util/reggen/reg_top.sv.tpl +++ b/util/reggen/reg_top.sv.tpl @@ -33,6 +33,8 @@ reg2hw_t = gen_rtl.get_iface_tx_type(block, if_name, False) hw2reg_t = gen_rtl.get_iface_tx_type(block, if_name, True) + racl_support = block.bus_interfaces.racl_support[if_name] + win_array_decl = f' [{num_wins}]' if num_wins > 1 else '' # Calculate whether we're going to need an AW parameter. We use it if there @@ -115,7 +117,13 @@ %> `include "prim_assert.sv" -module ${mod_name} ( +module ${mod_name}${' (' if not racl_support else ''} +% if racl_support: + # ( + parameter bit EnableRacl = 1'b0, + parameter bit RaclErrorRsp = 1'b1 + ) ( +% endif input clk_i, input rst_ni, % if rb.has_internal_shadowed_reg(): @@ -147,6 +155,14 @@ module ${mod_name} ( output logic shadowed_update_err_o, %endif +% if racl_support: + // RACL interface + input top_racl_pkg::racl_policy_vec_t racl_policies_i, + input integer racl_policy_sel_vec_i[${len(rb.flat_regs)}], + output logic racl_error_o, + output top_racl_pkg::racl_error_log_t racl_error_log_o, + +% endif // Integrity check errors output logic intg_err_o ); @@ -387,7 +403,12 @@ module ${mod_name} ( .be_o (reg_be), .busy_i (reg_busy), .rdata_i (reg_rdata), + % if racl_support: + // Translate RACL error to TLUL error if enabled + .error_i (reg_error | (RaclErrorRsp & racl_error_o)) + % else: .error_i (reg_error) + % endif ); // cdc oversampling signals @@ -641,14 +662,69 @@ ${finst_gen(sr, field, finst_name, fsig_name, fidx)} % endfor logic [${len(regs_flat)-1}:0] addr_hit; +% if racl_support: + top_racl_pkg::racl_role_vec_t racl_role_vec; + top_racl_pkg::racl_role_t racl_role; + + logic [${len(regs_flat)-1}:0] racl_addr_hit_read; + logic [${len(regs_flat)-1}:0] racl_addr_hit_write; + + if (EnableRacl) begin : gen_racl_role_logic + // Retrieve RACL role from user bits and one-hot encode that for the comparison bitmap + assign racl_role = top_racl_pkg::tlul_extract_racl_role_bits(tl_i.a_user.rsvd); + + prim_onehot_enc #( + .OneHotWidth( $bits(prim_onehot_enc) ) + ) u_racl_role_encode ( + .in_i ( racl_role ), + .en_i ( 1'b1 ), + .out_o( racl_role_vec ) + ); + end else begin : gen_no_racl_role_logic + assign racl_role = '0; + assign racl_role_vec = '0; + end + +% endif always_comb begin addr_hit = '0; + % if racl_support: + racl_addr_hit_read = '0; + racl_addr_hit_write = '0; + % endif % for i,r in enumerate(regs_flat): - addr_hit[${"{}".format(i).rjust(max_regs_char)}] = (reg_addr == ${ublock}_${r.name.upper()}_OFFSET); +<% slice = '{}'.format(i).rjust(max_regs_char) %>\ + addr_hit[${slice}] = (reg_addr == ${ublock}_${r.name.upper()}_OFFSET); % endfor + % if racl_support: + + if (EnableRacl) begin : gen_racl_hit + % for i,r in enumerate(regs_flat): +<% slice = '{}'.format(i).rjust(max_regs_char) %>\ + racl_addr_hit_read [${slice}] = addr_hit[${slice}] & (|(racl_policies_i[racl_policy_sel_vec_i[${slice}]].read_perm & racl_role_vec)); + racl_addr_hit_write[${slice}] = addr_hit[${slice}] & (|(racl_policies_i[racl_policy_sel_vec_i[${slice}]].write_perm & racl_role_vec)); + % endfor + end else begin : gen_no_racl + racl_addr_hit_read = addr_hit; + racl_addr_hit_write = addr_hit; + end + % endif end assign addrmiss = (reg_re || reg_we) ? ~|addr_hit : 1'b0 ; +% if racl_support: + // Address hit but failed the RACL check + assign racl_error_o = (|addr_hit) & ~(|(addr_hit & (racl_addr_hit_read | racl_addr_hit_write))); + assign racl_error_log_o.racl_role = racl_role; + + if (EnableRacl) begin : gen_racl_log + assign racl_error_log_o.ctn_uid = top_racl_pkg::tlul_extract_ctn_uid_bits(tl_i.a_user.rsvd); + assign racl_error_log_o.read_not_write = tl_i.a_opcode == tlul_pkg::Get; + end else begin : gen_no_racl_log + assign racl_error_log_o.ctn_uid = '0; + assign racl_error_log_o.read_not_write = 1'b0; + end +% endif % if regs_flat: <% @@ -656,9 +732,11 @@ ${finst_gen(sr, field, finst_name, fsig_name, fidx)} # any bytes that aren't supported by a register. That's true if a # addr_hit[i] and a bit is set in reg_be but not in *_PERMIT[i]. - wr_err_terms = ['(addr_hit[{idx}] & (|({mod}_PERMIT[{idx}] & ~reg_be)))' + wr_addr_hit = 'racl_addr_hit_write' if racl_support else 'addr_hit' + wr_err_terms = ['({wr_addr_hit}[{idx}] & (|({mod}_PERMIT[{idx}] & ~reg_be)))' .format(idx=str(i).rjust(max_regs_char), - mod=u_mod_base) + mod=u_mod_base, + wr_addr_hit=wr_addr_hit) for i in range(len(regs_flat))] wr_err_expr = (' |\n' + (' ' * 15)).join(wr_err_terms) %>\ @@ -715,18 +793,19 @@ ${field_wd_gen(f, r.name.lower() + "_" + f.name.lower(), r.hwext, r.shadowed, r. always_comb begin reg_rdata_next = '0; unique case (1'b1) +<% read_addr_hit = 'racl_addr_hit_read' if racl_support else 'addr_hit' %>\ % for i, r in enumerate(regs_flat): % if r.async_clk: - addr_hit[${i}]: begin + ${read_addr_hit}[${i}]: begin reg_rdata_next = DW'(${r.name.lower()}_qs); end % elif len(r.fields) == 1: - addr_hit[${i}]: begin + ${read_addr_hit}[${i}]: begin ${rdata_gen(r.fields[0], r.name.lower())}\ end % else: - addr_hit[${i}]: begin + ${read_addr_hit}[${i}]: begin % for f in r.fields: ${rdata_gen(f, r.name.lower() + "_" + f.name.lower())}\ % endfor @@ -942,7 +1021,7 @@ ${bits.msb}\ mubi_expr = "1'b1" else: mubi_expr = "1'b0" - + # when async, the outputs are aggregated first by the cdc module async_suffix = '_int' if reg.async_clk else '' qs_expr = f'{clk_base_name}{finst_name}_qs{async_suffix}' if field.swaccess.allows_read() else '' @@ -1068,11 +1147,12 @@ ${bits.msb}\ % endif \ <%def name="reg_enable_gen(reg, idx)">\ +<% wr_addr_hit = 'racl_addr_hit_write' if racl_support else 'addr_hit'%>\ % if reg.needs_re(): - assign ${reg.name.lower()}_re = addr_hit[${idx}] & reg_re & !reg_error; + assign ${reg.name.lower()}_re = ${wr_addr_hit}[${idx}] & reg_re & !reg_error; % endif % if reg.needs_we(): - assign ${reg.name.lower()}_we = addr_hit[${idx}] & reg_we & !reg_error; + assign ${reg.name.lower()}_we = ${wr_addr_hit}[${idx}] & reg_we & !reg_error; % endif \ <%def name="field_wd_gen(field, sig_name, hwext, shadowed, async_clk, reg_name, idx)">\