| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // Register Top module auto-generated by `reggen` |
| <% |
| from reggen import gen_rtl |
| from reggen.access import HwAccess, SwRdAccess, SwWrAccess |
| from reggen.lib import get_basename |
| from reggen.register import Register |
| from reggen.multi_register import MultiRegister |
| from reggen.bits import Bits |
| |
| alias_impl = "_" + block.alias_impl if block.alias_impl else "" |
| |
| num_wins = len(rb.windows) |
| num_reg_dsp = 1 if rb.all_regs else 0 |
| num_dsp = num_wins + num_reg_dsp |
| regs_flat = rb.flat_regs |
| max_regs_char = len("{}".format(len(regs_flat) - 1)) |
| addr_width = rb.get_addr_width() |
| |
| # Used for the dev_select_i signal on a tlul_socket_1n with N = |
| # num_wins + 1. This needs to be able to represent any value up to |
| # N-1. |
| steer_msb = ((num_wins).bit_length()) - 1 |
| |
| lblock = block.name.lower() |
| ublock = lblock.upper() |
| |
| u_mod_base = mod_base.upper() |
| |
| reg2hw_t = gen_rtl.get_iface_tx_type(block, if_name, False) |
| hw2reg_t = gen_rtl.get_iface_tx_type(block, if_name, True) |
| |
| 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 |
| # are any registers (obviously). We also use it if there are any windows that |
| # don't start at zero and end at 1 << addr_width (see the "addr_checks" |
| # calculation below for where that comes from). |
| needs_aw = (bool(regs_flat) or |
| num_wins > 1 or |
| rb.windows and ( |
| rb.windows[0].offset != 0 or |
| rb.windows[0].size_in_bytes != (1 << addr_width))) |
| |
| |
| common_data_intg_gen = 0 if rb.has_data_intg_passthru else 1 |
| adapt_data_intg_gen = 1 if rb.has_data_intg_passthru else 0 |
| assert common_data_intg_gen != adapt_data_intg_gen |
| |
| # declare a fully asynchronous interface |
| reg_clk_expr = "clk_i" |
| reg_rst_expr = "rst_ni" |
| tl_h2d_expr = "tl_i" |
| tl_d2h_expr = "tl_o" |
| if rb.async_if: |
| tl_h2d_expr = "tl_async_h2d" |
| tl_d2h_expr = "tl_async_d2h" |
| for clock in rb.clocks.values(): |
| reg_clk_expr = clock.clock |
| reg_rst_expr = clock.reset |
| |
| # A map from "register" (which might be a multiregister) to a pair (r0, srs) |
| # where r0 is the prototype register and srs is a list of single registers |
| # corresponding to the original register. |
| r0_srs = {} |
| |
| # A big map from field to "finst names". These names are a pair (fld_pfx, |
| # name), where fld_pfx is the name used to index into hw2reg / reg2hw |
| # structures (something like "my_reg.my_field") and name is the name that |
| # gets prefixed onto local signals (something like "my_reg_my_field"). |
| finst_names = {} |
| |
| for r in rb.all_regs: |
| if isinstance(r, MultiRegister): |
| r0 = r.reg |
| srs = r.regs |
| else: |
| r0 = r |
| srs = [r] |
| |
| r0_srs[r] = (r0, srs) |
| |
| reg_name = r0.name.lower() |
| fld_count = 0 |
| for sr_idx, sr in enumerate(srs): |
| sr_name = sr.name.lower() |
| for fidx, field in enumerate(sr.fields): |
| if isinstance(r, MultiRegister): |
| sig_idx = fld_count if r.is_homogeneous() else sr_idx |
| fsig_pfx = '{}[{}]'.format(reg_name, sig_idx) |
| else: |
| fsig_pfx = reg_name |
| |
| fld_count += 1 |
| |
| fld_name = field.name.lower() |
| if len(sr.fields) == 1: |
| finst_name = sr_name |
| fsig_name = fsig_pfx |
| else: |
| finst_name = sr_name + '_' + fld_name |
| if isinstance(r, MultiRegister): |
| if r.is_homogeneous(): |
| fsig_name = fsig_pfx |
| else: |
| fsig_name = '{}.{}'.format(fsig_pfx, get_basename(fld_name)) |
| else: |
| fsig_name = '{}.{}'.format(fsig_pfx, fld_name) |
| |
| finst_names[field] = (fsig_name, finst_name) |
| |
| %> |
| `include "prim_assert.sv" |
| |
| module ${mod_name} ( |
| input clk_i, |
| input rst_ni, |
| % if rb.has_internal_shadowed_reg(): |
| input rst_shadowed_ni, |
| % endif |
| % for clock in rb.clocks.values(): |
| input ${clock.clock}, |
| input ${clock.reset}, |
| % endfor |
| input tlul_pkg::tl_h2d_t tl_i, |
| output tlul_pkg::tl_d2h_t tl_o, |
| % if num_wins != 0: |
| |
| // Output port for window |
| output tlul_pkg::tl_h2d_t tl_win_o${win_array_decl}, |
| input tlul_pkg::tl_d2h_t tl_win_i${win_array_decl}, |
| |
| % endif |
| // To HW |
| % if rb.get_n_bits(["q","qe","re"]): |
| output ${lblock}${alias_impl}_reg_pkg::${reg2hw_t} reg2hw, // Write |
| % endif |
| % if rb.get_n_bits(["d","de"]): |
| input ${lblock}${alias_impl}_reg_pkg::${hw2reg_t} hw2reg, // Read |
| % endif |
| |
| % if rb.has_internal_shadowed_reg(): |
| output logic shadowed_storage_err_o, |
| output logic shadowed_update_err_o, |
| |
| %endif |
| // Integrity check errors |
| output logic intg_err_o, |
| |
| // Config |
| input devmode_i // If 1, explicit error return for unmapped register access |
| ); |
| |
| import ${lblock}${alias_impl}_reg_pkg::* ; |
| |
| % if needs_aw: |
| localparam int AW = ${addr_width}; |
| % endif |
| % if rb.all_regs: |
| localparam int DW = ${block.regwidth}; |
| localparam int DBW = DW/8; // Byte Width |
| |
| // register signals |
| logic reg_we; |
| logic reg_re; |
| logic [AW-1:0] reg_addr; |
| logic [DW-1:0] reg_wdata; |
| logic [DBW-1:0] reg_be; |
| logic [DW-1:0] reg_rdata; |
| logic reg_error; |
| |
| logic addrmiss, wr_err; |
| |
| logic [DW-1:0] reg_rdata_next; |
| logic reg_busy; |
| |
| tlul_pkg::tl_h2d_t tl_reg_h2d; |
| tlul_pkg::tl_d2h_t tl_reg_d2h; |
| % endif |
| |
| ## The clock and reset inputs aren't used if this device interface has no |
| ## registers, only one window and isn't marked asynchronous. In that case, add |
| ## an unused_ signal to avoid lint warnings. |
| % if not rb.all_regs and num_wins == 1 and not rb.async_if: |
| // Add an unloaded flop to make use of clock / reset |
| // This is done to specifically address lint complaints of unused clocks/resets |
| // Since the flop is unloaded it will be removed during synthesis |
| logic unused_reg; |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| unused_reg <= '0; |
| end else begin |
| unused_reg <= tl_i.a_valid; |
| end |
| end |
| |
| |
| % endif |
| % if rb.async_if: |
| tlul_pkg::tl_h2d_t tl_async_h2d; |
| tlul_pkg::tl_d2h_t tl_async_d2h; |
| tlul_fifo_async #( |
| .ReqDepth(2), |
| .RspDepth(2) |
| ) u_if_sync ( |
| .clk_h_i(clk_i), |
| .rst_h_ni(rst_ni), |
| .clk_d_i(${reg_clk_expr}), |
| .rst_d_ni(${reg_rst_expr}), |
| .tl_h_i(tl_i), |
| .tl_h_o(tl_o), |
| .tl_d_o(${tl_h2d_expr}), |
| .tl_d_i(${tl_d2h_expr}) |
| ); |
| % endif |
| |
| % if rb.all_regs: |
| // incoming payload check |
| logic intg_err; |
| tlul_cmd_intg_chk u_chk ( |
| .tl_i(${tl_h2d_expr}), |
| .err_o(intg_err) |
| ); |
| |
| // also check for spurious write enables |
| logic reg_we_err; |
| ## Note that the write-enables are per register. |
| ## Hence, we reduce the byte address to a word address here. |
| logic [${len(regs_flat)-1}:0] reg_we_check; |
| prim_reg_we_check #( |
| .OneHotWidth(${len(regs_flat)}) |
| ) u_prim_reg_we_check ( |
| .clk_i(${reg_clk_expr}), |
| .rst_ni(${reg_rst_expr}), |
| .oh_i (reg_we_check), |
| .en_i (reg_we && !addrmiss), |
| .err_o (reg_we_err) |
| ); |
| |
| logic err_q; |
| <% |
| clk_lc_found = False; |
| for clock in rb.clocks.values(): |
| if clock.clock == "clk_lc_i": |
| clk_lc_found = True; |
| endif |
| endfor |
| %>\ |
| % if clk_lc_found: |
| always_ff @(posedge clk_lc_i or negedge rst_lc_ni) begin |
| if (!rst_lc_ni) begin |
| % else: |
| always_ff @(posedge ${reg_clk_expr} or negedge ${reg_rst_expr}) begin |
| if (!${reg_rst_expr}) begin |
| % endif |
| err_q <= '0; |
| end else if (intg_err || reg_we_err) begin |
| err_q <= 1'b1; |
| end |
| end |
| |
| // integrity error output is permanent and should be used for alert generation |
| // register errors are transactional |
| assign intg_err_o = err_q | intg_err | reg_we_err; |
| % else: |
| // Since there are no registers in this block, commands are routed through to windows which |
| // can report their own integrity errors. |
| assign intg_err_o = 1'b0; |
| % endif |
| |
| // outgoing integrity generation |
| tlul_pkg::tl_d2h_t tl_o_pre; |
| tlul_rsp_intg_gen #( |
| .EnableRspIntgGen(1), |
| .EnableDataIntgGen(${common_data_intg_gen}) |
| ) u_rsp_intg_gen ( |
| .tl_i(tl_o_pre), |
| .tl_o(${tl_d2h_expr}) |
| ); |
| |
| % if num_dsp <= 1: |
| ## Either no windows (and just registers) or no registers and only |
| ## one window. |
| % if num_wins == 0: |
| assign tl_reg_h2d = ${tl_h2d_expr}; |
| assign tl_o_pre = tl_reg_d2h; |
| % else: |
| assign tl_win_o = ${tl_h2d_expr}; |
| assign tl_o_pre = tl_win_i; |
| % endif |
| % else: |
| tlul_pkg::tl_h2d_t tl_socket_h2d [${num_dsp}]; |
| tlul_pkg::tl_d2h_t tl_socket_d2h [${num_dsp}]; |
| |
| logic [${steer_msb}:0] reg_steer; |
| |
| // socket_1n connection |
| % if rb.all_regs: |
| assign tl_reg_h2d = tl_socket_h2d[${num_wins}]; |
| assign tl_socket_d2h[${num_wins}] = tl_reg_d2h; |
| |
| % endif |
| % for i,t in enumerate(rb.windows): |
| <% |
| win_suff = f'[{i}]' if num_wins > 1 else '' |
| %>\ |
| assign tl_win_o${win_suff} = tl_socket_h2d[${i}]; |
| % if common_data_intg_gen == 0 and rb.windows[i].data_intg_passthru == False: |
| ## If there are multiple windows, and not every window has data integrity |
| ## passthrough, we must generate data integrity for it here. |
| tlul_rsp_intg_gen #( |
| .EnableRspIntgGen(0), |
| .EnableDataIntgGen(1) |
| ) u_win${i}_data_intg_gen ( |
| .tl_i(tl_win_i${win_suff}), |
| .tl_o(tl_socket_d2h[${i}]) |
| ); |
| % else: |
| assign tl_socket_d2h[${i}] = tl_win_i${win_suff}; |
| % endif |
| % endfor |
| |
| // Create Socket_1n |
| tlul_socket_1n #( |
| .N (${num_dsp}), |
| .HReqPass (1'b1), |
| .HRspPass (1'b1), |
| .DReqPass ({${num_dsp}{1'b1}}), |
| .DRspPass ({${num_dsp}{1'b1}}), |
| .HReqDepth (4'h0), |
| .HRspDepth (4'h0), |
| .DReqDepth ({${num_dsp}{4'h0}}), |
| .DRspDepth ({${num_dsp}{4'h0}}), |
| .ExplicitErrs (1'b0) |
| ) u_socket ( |
| .clk_i (${reg_clk_expr}), |
| .rst_ni (${reg_rst_expr}), |
| .tl_h_i (${tl_h2d_expr}), |
| .tl_h_o (tl_o_pre), |
| .tl_d_o (tl_socket_h2d), |
| .tl_d_i (tl_socket_d2h), |
| .dev_select_i (reg_steer) |
| ); |
| |
| // Create steering logic |
| always_comb begin |
| reg_steer = |
| % for i,w in enumerate(rb.windows): |
| <% |
| steer_width = steer_msb + 1 |
| base_addr = w.offset |
| limit_addr = w.offset + w.size_in_bytes |
| assert (limit_addr-1 >= base_addr) |
| addr_test = f"[{base_addr}:{limit_addr-1}]" |
| %>\ |
| ${f'{tl_h2d_expr}.a_address[AW-1:0]'} inside {${addr_test}} ? ${steer_width}'d${i} : |
| % endfor |
| // Default set to register |
| ${steer_width}'d${num_dsp-1}; |
| |
| // Override this in case of an integrity error |
| if (intg_err) begin |
| reg_steer = ${steer_width}'d${num_dsp-1}; |
| end |
| end |
| % endif |
| % if rb.all_regs: |
| |
| tlul_adapter_reg #( |
| .RegAw(AW), |
| .RegDw(DW), |
| .EnableDataIntgGen(${adapt_data_intg_gen}) |
| ) u_reg_if ( |
| .clk_i (${reg_clk_expr}), |
| .rst_ni (${reg_rst_expr}), |
| |
| .tl_i (tl_reg_h2d), |
| .tl_o (tl_reg_d2h), |
| |
| .en_ifetch_i(prim_mubi_pkg::MuBi4False), |
| .intg_error_o(), |
| |
| .we_o (reg_we), |
| .re_o (reg_re), |
| .addr_o (reg_addr), |
| .wdata_o (reg_wdata), |
| .be_o (reg_be), |
| .busy_i (reg_busy), |
| .rdata_i (reg_rdata), |
| .error_i (reg_error) |
| ); |
| |
| // cdc oversampling signals |
| |
| % if block.expose_reg_if: |
| assign reg2hw.reg_if.reg_we = reg_we; |
| assign reg2hw.reg_if.reg_re = reg_re; |
| assign reg2hw.reg_if.reg_addr = reg_addr; |
| assign reg2hw.reg_if.reg_wdata = reg_wdata; |
| assign reg2hw.reg_if.reg_be = reg_be; |
| |
| % endif |
| assign reg_rdata = reg_rdata_next ; |
| assign reg_error = (devmode_i & addrmiss) | wr_err | intg_err; |
| |
| // Define SW related signals |
| // Format: <reg>_<field>_{wd|we|qs} |
| // or <reg>_{wd|we|qs} if field == 1 or 0 |
| % for r in regs_flat: |
| ${reg_sig_decl(r)}\ |
| % for f in r.fields: |
| <% |
| fld_suff = '_' + f.name.lower() if len(r.fields) > 1 else '' |
| sig_name = r.name.lower() + fld_suff |
| %>\ |
| ${field_sig_decl(f, sig_name, r.hwext, r.shadowed, r.async_clk)}\ |
| % endfor |
| % endfor |
| % if len(rb.clocks.values()) > 0: |
| // Define register CDC handling. |
| // CDC handling is done on a per-reg instead of per-field boundary. |
| % endif |
| % for r in regs_flat: |
| % if r.async_clk: |
| <% |
| base_name = r.async_clk.clock_base_name |
| r_name = r.name.lower() |
| comb_name = f"{base_name}_{r_name}" |
| src_we_expr = f"{r_name}_we" if r.needs_we() else "'0" |
| src_wd_expr = f"reg_wdata[{r.get_width()-1}:0]" if r.needs_we() else "'0" |
| src_re_expr = f"{r_name}_re" if r.needs_re() else "'0" |
| src_regwen_expr = f"{r.regwen.lower()}_qs" if r.regwen else "'0" |
| dst_we_expr = f"{comb_name}_we" if r.needs_we() else "" |
| dst_wd_expr = f"{comb_name}_wdata" if r.needs_we() else "" |
| dst_re_expr = f"{comb_name}_re" if r.needs_re() else "" |
| dst_regwen_expr = f"{comb_name}_regwen" if r.regwen else "" |
| dst_qe_expr = f"{comb_name}_qe" if r.is_hw_writable() else "'0" |
| dst_wr_req = "1" if r.is_hw_writable() else "0" |
| dst_ds_expr = f"{comb_name}_ds" if r.is_hw_writable() else "'0" |
| reset_val = format(r.resval, "x") |
| reset_val_expr = f"{r.get_width()}'h{reset_val}" |
| %> |
| % if len(r.fields) > 1: |
| % for f in r.fields: |
| % if r.is_hw_writable(): |
| logic ${str_arr_sv(f.bits)} ${comb_name}_${f.name.lower()}_ds_int; |
| % endif |
| % if f.swaccess.allows_read(): |
| logic ${str_arr_sv(f.bits)} ${comb_name}_${f.name.lower()}_qs_int; |
| % endif |
| % endfor |
| % else: |
| % if r.is_hw_writable(): |
| logic ${str_arr_sv(r.fields[0].bits)} ${comb_name}_ds_int; |
| % endif |
| % if r.fields[0].swaccess.allows_read(): |
| logic ${str_arr_sv(r.fields[0].bits)} ${comb_name}_qs_int; |
| % endif |
| % endif |
| % if r.is_hw_writable(): |
| logic [${r.get_width()-1}:0] ${comb_name}_ds; |
| logic ${dst_qe_expr}; |
| % endif |
| logic [${r.get_width()-1}:0] ${comb_name}_qs; |
| % if r.needs_we(): |
| logic [${r.get_width()-1}:0] ${comb_name}_wdata; |
| logic ${dst_we_expr}; |
| logic unused_${comb_name}_wdata; |
| % endif |
| % if r.needs_re(): |
| logic ${dst_re_expr}; |
| % endif |
| % if r.regwen: |
| logic ${dst_regwen_expr}; |
| % endif |
| |
| ## Since prim_reg_cdc operates at the level of the register, the registers |
| ## hw writability is used to determine whether the ds is needed. |
| always_comb begin |
| ${comb_name}_qs = ${reset_val_expr}; |
| % if r.is_hw_writable(): |
| ${comb_name}_ds = ${reset_val_expr}; |
| % endif |
| % if len(r.fields) > 1: |
| % for f in r.fields: |
| % if r.is_hw_writable() and f.swaccess.allows_read(): |
| ${comb_name}_ds[${str_bits_sv(f.bits)}] = ${comb_name}_${f.name.lower()}_ds_int; |
| % endif |
| % if f.swaccess.allows_read(): |
| ${comb_name}_qs[${str_bits_sv(f.bits)}] = ${comb_name}_${f.name.lower()}_qs_int; |
| % endif |
| % endfor |
| % else: |
| % if r.is_hw_writable() and r.fields[0].swaccess.allows_read(): |
| ${comb_name}_ds = ${comb_name}_ds_int; |
| % endif |
| % if r.fields[0].swaccess.allows_read(): |
| ${comb_name}_qs = ${comb_name}_qs_int; |
| % endif |
| % endif |
| end |
| |
| prim_reg_cdc #( |
| .DataWidth(${r.get_width()}), |
| .ResetVal(${reset_val_expr}), |
| .BitMask(${r.get_width()}'h${r.bitmask()}), |
| .DstWrReq(${dst_wr_req}) |
| ) u_${r_name}_cdc ( |
| .clk_src_i (${reg_clk_expr}), |
| .rst_src_ni (${reg_rst_expr}), |
| .clk_dst_i (${r.async_clk.clock}), |
| .rst_dst_ni (${r.async_clk.reset}), |
| .src_regwen_i (${src_regwen_expr}), |
| .src_we_i (${src_we_expr}), |
| .src_re_i (${src_re_expr}), |
| .src_wd_i (${src_wd_expr}), |
| .src_busy_o (${r_name}_busy), |
| .src_qs_o (${r_name}_qs), // for software read back |
| .dst_update_i (${dst_qe_expr}), |
| .dst_ds_i (${dst_ds_expr}), |
| .dst_qs_i (${comb_name}_qs), |
| .dst_we_o (${dst_we_expr}), |
| .dst_re_o (${dst_re_expr}), |
| .dst_regwen_o (${dst_regwen_expr}), |
| .dst_wd_o (${dst_wd_expr}) |
| ); |
| % if r.needs_we(): |
| assign unused_${comb_name}_wdata = |
| ^${comb_name}_wdata; |
| % endif |
| % endif |
| % endfor |
| |
| // Register instances |
| % for r in rb.all_regs: |
| <% |
| r0, srs = r0_srs[r] |
| reg_name = r0.name.lower() |
| %>\ |
| % for sr_idx, sr in enumerate(srs): |
| <% |
| sr_name = sr.name.lower() |
| |
| if isinstance(r, MultiRegister): |
| reg_hdr = (f' // Subregister {sr_idx} of Multireg {reg_name}\n' + |
| f' // R[{sr_name}]: V({sr.hwext})') |
| else: |
| reg_hdr = (f' // R[{sr_name}]: V({sr.hwext})') |
| clk_expr = sr.async_clk.clock if sr.async_clk else reg_clk_expr |
| rst_expr = sr.async_clk.reset if sr.async_clk else reg_rst_expr |
| %>\ |
| ${reg_hdr} |
| % if sr.needs_qe(): |
| logic ${sr_name}_qe; |
| % endif |
| % if sr.needs_int_qe(): |
| logic [${len(sr.fields)-1}:0] ${sr_name}_flds_we; |
| % endif |
| % if sr.needs_qe() and sr.hwext: |
| assign ${sr_name}_qe = &${sr_name}_flds_we; |
| % elif sr.needs_qe(): |
| prim_flop #( |
| .Width(1), |
| .ResetValue(0) |
| ) u_${reg_name}${sr_idx}_qe ( |
| .clk_i(${clk_expr}), |
| .rst_ni(${rst_expr}), |
| .d_i(&${sr_name}_flds_we), |
| .q_o(${sr_name}_qe) |
| ); |
| % endif |
| <% |
| # We usually use the REG_we signal, but use REG_re for RC fields |
| # (which get updated on a read, not a write) |
| clk_base_name = f"{sr.async_clk.clock_base_name}_" if sr.async_clk else "" |
| we_suffix = 're' if field.swaccess.swrd() == SwRdAccess.RC else 'we' |
| we_signal = f'{clk_base_name}{sr_name}_{we_suffix}' |
| |
| if sr.async_clk and sr.regwen: |
| we_expr = f'{we_signal} & {clk_base_name}{sr_name}_regwen' |
| elif sr.regwen: |
| we_expr = f'{we_signal} & {sr.regwen.lower()}_qs' |
| else: |
| we_expr = we_signal |
| |
| we_expr_regwen_gated = f'{clk_base_name}{sr_name}_gated_{we_suffix}' |
| %>\ |
| % if sr.async_clk and sr.is_hw_writable(): |
| assign ${clk_base_name}${sr_name}_qe = |${sr_name}_flds_we; |
| % endif |
| ## Only create this helper signal if there actually is a REGWEN gate. |
| ## Otherwise the WE signal is connected directly to the register. |
| % if sr.regwen and sr.needs_we(): |
| // Create REGWEN-gated WE signal |
| logic ${we_expr_regwen_gated}; |
| <% |
| # Wrap the assignment if the statement is too long |
| assignment = f'assign {we_expr_regwen_gated} = {we_expr};' |
| if len(assignment) > 100-2: |
| assignment = f'assign {we_expr_regwen_gated} =\n {we_expr};' |
| %>\ |
| ${assignment} |
| % endif |
| % for fidx, field in enumerate(sr.fields): |
| <% |
| fld_name = field.name.lower() |
| fsig_name, finst_name = finst_names[field] |
| %>\ |
| % if len(sr.fields) > 1: |
| // F[${fld_name}]: ${field.bits.msb}:${field.bits.lsb} |
| % endif |
| ${finst_gen(sr, field, finst_name, fsig_name, fidx)} |
| % endfor |
| |
| % endfor |
| % endfor |
| |
| logic [${len(regs_flat)-1}:0] addr_hit; |
| always_comb begin |
| addr_hit = '0; |
| % for i,r in enumerate(regs_flat): |
| addr_hit[${"{}".format(i).rjust(max_regs_char)}] = (reg_addr == ${ublock}_${r.name.upper()}_OFFSET); |
| % endfor |
| end |
| |
| assign addrmiss = (reg_re || reg_we) ? ~|addr_hit : 1'b0 ; |
| |
| % if regs_flat: |
| <% |
| # We want to signal wr_err if reg_be (the byte enable signal) is true for |
| # 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)))' |
| .format(idx=str(i).rjust(max_regs_char), |
| mod=u_mod_base) |
| for i in range(len(regs_flat))] |
| wr_err_expr = (' |\n' + (' ' * 15)).join(wr_err_terms) |
| %>\ |
| // Check sub-word write is permitted |
| always_comb begin |
| wr_err = (reg_we & |
| (${wr_err_expr})); |
| end |
| % else: |
| assign wr_error = 1'b0; |
| % endif\ |
| |
| |
| // Generate write-enables |
| % for i, r in enumerate(regs_flat): |
| ${reg_enable_gen(r, i)}\ |
| % if len(r.fields) == 1: |
| ${field_wd_gen(r.fields[0], r.name.lower(), r.hwext, r.shadowed, r.async_clk, r.name, i)}\ |
| % else: |
| % for f in r.fields: |
| ${field_wd_gen(f, r.name.lower() + "_" + f.name.lower(), r.hwext, r.shadowed, r.async_clk, r.name, i)}\ |
| % endfor |
| % endif |
| % endfor |
| |
| // Assign write-enables to checker logic vector. |
| always_comb begin |
| reg_we_check = '0; |
| % for i, r in enumerate(regs_flat): |
| <% |
| # The WE checking logic does NOT protect RC fields. |
| if r.needs_we(): |
| # In case this is an asynchronous register, the WE signal is taken from |
| # the CDC primitive input. This could be enhanced in the future to provide |
| # more protection for asynchronous registers. |
| if r.async_clk or not r.regwen: |
| we_expr = f'{r.name.lower()}_we' |
| else: |
| we_expr = f'{r.name.lower()}_gated_we' |
| else: |
| we_expr = "1'b0" |
| |
| assignment = f'reg_we_check[{i}] = {we_expr};' |
| |
| # Wrap the assignment if the statement is too long |
| if len(assignment) > 100-4: |
| assignment = f'reg_we_check[{i}] =\n {we_expr};' |
| %>\ |
| ${assignment} |
| % endfor |
| end |
| |
| // Read data return |
| always_comb begin |
| reg_rdata_next = '0; |
| unique case (1'b1) |
| % for i, r in enumerate(regs_flat): |
| % if r.async_clk: |
| addr_hit[${i}]: begin |
| reg_rdata_next = DW'(${r.name.lower()}_qs); |
| end |
| % elif len(r.fields) == 1: |
| addr_hit[${i}]: begin |
| ${rdata_gen(r.fields[0], r.name.lower())}\ |
| end |
| |
| % else: |
| addr_hit[${i}]: begin |
| % for f in r.fields: |
| ${rdata_gen(f, r.name.lower() + "_" + f.name.lower())}\ |
| % endfor |
| end |
| |
| % endif |
| % endfor |
| default: begin |
| reg_rdata_next = '1; |
| end |
| endcase |
| end |
| |
| // shadow busy |
| logic shadow_busy; |
| % if rb.has_internal_shadowed_reg(): |
| logic rst_done; |
| logic shadow_rst_done; |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| rst_done <= '0; |
| end else begin |
| rst_done <= 1'b1; |
| end |
| end |
| |
| always_ff @(posedge clk_i or negedge rst_shadowed_ni) begin |
| if (!rst_shadowed_ni) begin |
| shadow_rst_done <= '0; |
| end else begin |
| shadow_rst_done <= 1'b1; |
| end |
| end |
| |
| // both shadow and normal resets have been released |
| assign shadow_busy = ~(rst_done & shadow_rst_done); |
| % else: |
| assign shadow_busy = 1'b0; |
| % endif |
| |
| % if rb.has_internal_shadowed_reg(): |
| // Collect up storage and update errors |
| <% |
| shadowed_field_pfxs = [] |
| for r in rb.all_regs: |
| r0, srs = r0_srs[r] |
| |
| if not (r0.shadowed and not r0.hwext): |
| continue |
| |
| for sr in srs: |
| for field in sr.fields: |
| _, pfx = finst_names[field] |
| shadowed_field_pfxs.append(pfx) |
| %>\ |
| assign shadowed_storage_err_o = |{ |
| % for pfx in shadowed_field_pfxs: |
| ${pfx}_storage_err${"" if loop.last else ","} |
| % endfor |
| }; |
| assign shadowed_update_err_o = |{ |
| % for pfx in shadowed_field_pfxs: |
| ${pfx}_update_err${"" if loop.last else ","} |
| % endfor |
| }; |
| |
| % endif |
| // register busy |
| <% |
| async_busy_signals = {} |
| for i, r in enumerate(regs_flat): |
| if r.async_clk: |
| async_busy_signals[i] = r.name.lower() + "_busy" |
| %>\ |
| % if rb.async_if or not async_busy_signals: |
| assign reg_busy = shadow_busy; |
| % else: |
| logic reg_busy_sel; |
| assign reg_busy = reg_busy_sel | shadow_busy; |
| always_comb begin |
| reg_busy_sel = '0; |
| unique case (1'b1) |
| % for i, busy_signal in async_busy_signals.items(): |
| addr_hit[${i}]: begin |
| reg_busy_sel = ${busy_signal}; |
| end |
| % endfor |
| default: begin |
| reg_busy_sel = '0; |
| end |
| endcase |
| end |
| |
| % endif |
| % endif |
| |
| // Unused signal tieoff |
| % if rb.all_regs: |
| |
| // wdata / byte enable are not always fully used |
| // add a blanket unused statement to handle lint waivers |
| logic unused_wdata; |
| logic unused_be; |
| assign unused_wdata = ^reg_wdata; |
| assign unused_be = ^reg_be; |
| % else: |
| // devmode_i is not used if there are no registers |
| logic unused_devmode; |
| assign unused_devmode = ^devmode_i; |
| % endif |
| % if rb.all_regs: |
| |
| // Assertions for Register Interface |
| `ASSERT_PULSE(wePulse, reg_we, ${reg_clk_expr}, !${reg_rst_expr}) |
| `ASSERT_PULSE(rePulse, reg_re, ${reg_clk_expr}, !${reg_rst_expr}) |
| |
| `ASSERT(reAfterRv, $rose(reg_re || reg_we) |=> tl_o_pre.d_valid, ${reg_clk_expr}, !${reg_rst_expr}) |
| |
| `ASSERT(en2addrHit, (reg_we || reg_re) |-> $onehot0(addr_hit), ${reg_clk_expr}, !${reg_rst_expr}) |
| |
| // this is formulated as an assumption such that the FPV testbenches do disprove this |
| // property by mistake |
| //`ASSUME(reqParity, tl_reg_h2d.a_valid |-> tl_reg_h2d.a_user.chk_en == tlul_pkg::CheckDis) |
| |
| % endif |
| endmodule |
| <%def name="str_bits_sv(bits)">\ |
| % if bits.msb != bits.lsb: |
| ${bits.msb}:${bits.lsb}\ |
| % else: |
| ${bits.msb}\ |
| % endif |
| </%def>\ |
| <%def name="str_arr_sv(bits)">\ |
| % if bits.msb != bits.lsb: |
| [${bits.msb-bits.lsb}:0] \ |
| % endif |
| </%def>\ |
| <%def name="reg_sig_decl(reg)">\ |
| % if reg.needs_re(): |
| logic ${reg.name.lower()}_re; |
| % endif |
| % if reg.needs_we(): |
| logic ${reg.name.lower()}_we; |
| % endif |
| % if reg.async_clk: |
| logic [${reg.get_width()-1}:0] ${reg.name.lower()}_qs; |
| logic ${reg.name.lower()}_busy; |
| % endif |
| </%def>\ |
| <%def name="field_sig_decl(field, sig_name, hwext, shadowed, async_clk)">\ |
| % if not async_clk and field.swaccess.allows_read(): |
| logic ${str_arr_sv(field.bits)}${sig_name}_qs; |
| % endif |
| % if not async_clk and field.swaccess.allows_write(): |
| logic ${str_arr_sv(field.bits)}${sig_name}_wd; |
| % endif |
| % if shadowed and not hwext: |
| logic ${sig_name}_storage_err; |
| logic ${sig_name}_update_err; |
| % endif |
| </%def>\ |
| <%def name="finst_gen(reg, field, finst_name, fsig_name, fidx)">\ |
| <% |
| |
| clk_base_name = f"{reg.async_clk.clock_base_name}_" if reg.async_clk else "" |
| reg_name = reg.name.lower() |
| clk_expr = reg.async_clk.clock if reg.async_clk else reg_clk_expr |
| rst_expr = reg.async_clk.reset if reg.async_clk else reg_rst_expr |
| re_expr = f'{clk_base_name}{reg_name}_re' if field.swaccess.allows_read() or reg.shadowed else "1'b0" |
| |
| # software inputs to field instance, write enable, read enable, write data |
| if field.swaccess.allows_write(): |
| # We usually use the REG_we signal, but use REG_re for RC fields |
| # (which get updated on a read, not a write) |
| we_suffix = 're' if field.swaccess.swrd() == SwRdAccess.RC else 'we' |
| # If this is a REGWEN gated field, need to use the gated WE signal. |
| gated_suffix = '_gated' if reg.regwen else '' |
| we_expr = f'{clk_base_name}{reg_name}{gated_suffix}_{we_suffix}' |
| |
| # when async, pick from the cdc handled data |
| wd_expr = f'{finst_name}_wd' |
| if reg.async_clk: |
| if field.bits.msb == field.bits.lsb: |
| bit_sel = f'{field.bits.msb}' |
| else: |
| bit_sel = f'{field.bits.msb}:{field.bits.lsb}' |
| wd_expr = f'{clk_base_name}{reg_name}_wdata[{bit_sel}]' |
| |
| else: |
| we_expr = "1'b0" |
| wd_expr = "'0" |
| |
| # hardware inputs to field instance |
| if field.hwaccess.allows_write(): |
| de_expr = f'hw2reg.{fsig_name}.de' |
| d_expr = f'hw2reg.{fsig_name}.d' |
| else: |
| de_expr = "1'b0" |
| d_expr = "'0" |
| |
| # field instance outputs |
| qre_expr = f'reg2hw.{fsig_name}.re' if reg.hwre or reg.shadowed else "" |
| |
| if reg.needs_int_qe() or field.hwaccess.allows_read(): |
| qe_expr = f'{reg_name}_flds_we[{fidx}]' if reg.needs_int_qe() else '' |
| else: |
| qe_expr = '' |
| |
| if field.hwaccess.allows_read(): |
| qe_reg_expr = f'reg2hw.{fsig_name}.qe' |
| q_expr = f'reg2hw.{fsig_name}.q' |
| else: |
| q_expr = '' |
| |
| # 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 '' |
| ds_expr = f'{clk_base_name}{finst_name}_ds{async_suffix}' if reg.async_clk and reg.is_hw_writable() else '' |
| |
| %>\ |
| % if reg.hwext: ## if hwext, instantiate prim_subreg_ext |
| <% |
| subreg_block = "prim_subreg_ext" |
| %>\ |
| ${subreg_block} #( |
| .DW (${field.bits.width()}) |
| ) u_${finst_name} ( |
| .re (${re_expr}), |
| .we (${we_expr}), |
| .wd (${wd_expr}), |
| .d (${d_expr}), |
| .qre (${qre_expr}), |
| .qe (${qe_expr}), |
| .q (${q_expr}), |
| .ds (${ds_expr}), |
| .qs (${qs_expr}) |
| ); |
| % else: |
| <% |
| # This isn't a field in a hwext register. Instantiate prim_subreg, |
| # prim_subreg_shadow or constant assign. |
| |
| resval_expr = f"{field.bits.width()}'h{field.resval or 0:x}" |
| is_const_reg = not (field.hwaccess.allows_read() or |
| field.hwaccess.allows_write() or |
| field.swaccess.allows_write() or |
| field.swaccess.swrd() != SwRdAccess.RD) |
| |
| subreg_block = 'prim_subreg' + ('_shadow' if reg.shadowed else '') |
| %>\ |
| % if is_const_reg: |
| // constant-only read |
| assign ${finst_name}_qs = ${resval_expr}; |
| % else: |
| % if reg.async_clk and reg.shadowed: |
| logic async_${finst_name}_err_update; |
| logic async_${finst_name}_err_storage; |
| |
| // storage error is persistent and can be sampled at any time |
| prim_flop_2sync #( |
| .Width(1), |
| .ResetValue('0) |
| ) u_${finst_name}_err_storage_sync ( |
| .clk_i, |
| .rst_ni, |
| .d_i(async_${finst_name}_err_storage), |
| .q_o(${finst_name}_storage_err) |
| ); |
| |
| // update error is transient and must be immediately captured |
| prim_pulse_sync u_${finst_name}_err_update_sync ( |
| .clk_src_i(${reg.async_clk.clock}), |
| .rst_src_ni(${reg.async_clk.reset}), |
| .src_pulse_i(async_${finst_name}_err_update), |
| .clk_dst_i(clk_i), |
| .rst_dst_ni(rst_ni), |
| .dst_pulse_o(${finst_name}_update_err) |
| ); |
| % endif |
| ${subreg_block} #( |
| .DW (${field.bits.width()}), |
| .SwAccess(prim_subreg_pkg::SwAccess${field.swaccess.value[1].name.upper()}), |
| .RESVAL (${resval_expr}) |
| ) u_${finst_name} ( |
| % if reg.sync_clk: |
| // sync clock and reset required for this register |
| .clk_i (${reg.sync_clk.clock}), |
| .rst_ni (${reg.sync_clk.reset}), |
| % else: |
| .clk_i (${clk_expr}), |
| .rst_ni (${rst_expr}), |
| % endif |
| % if reg.shadowed and not reg.hwext: |
| .rst_shadowed_ni (rst_shadowed_ni), |
| % endif |
| |
| // from register interface |
| % if reg.shadowed: |
| .re (${re_expr}), |
| % endif |
| .we (${we_expr}), |
| .wd (${wd_expr}), |
| |
| // from internal hardware |
| .de (${de_expr}), |
| .d (${d_expr}), |
| |
| // to internal hardware |
| .qe (${qe_expr}), |
| .q (${q_expr}), |
| .ds (${ds_expr}), |
| |
| // to register interface (read) |
| % if not reg.shadowed: |
| .qs (${qs_expr}) |
| % else: |
| .qs (${qs_expr}), |
| |
| // Shadow register phase. Relevant for hwext only. |
| .phase (), |
| |
| // Shadow register error conditions |
| % if reg.async_clk: |
| .err_update (async_${finst_name}_err_update), |
| .err_storage (async_${finst_name}_err_storage) |
| % else: |
| .err_update (${finst_name}_update_err), |
| .err_storage (${finst_name}_storage_err) |
| % endif |
| % endif |
| ); |
| % endif ## end non-constant prim_subreg |
| % endif |
| % if field.hwaccess.allows_read() and field.hwqe: |
| assign ${qe_reg_expr} = ${reg_name}_qe; |
| % endif |
| </%def>\ |
| <%def name="reg_enable_gen(reg, idx)">\ |
| % if reg.needs_re(): |
| assign ${reg.name.lower()}_re = addr_hit[${idx}] & reg_re & !reg_error; |
| % endif |
| % if reg.needs_we(): |
| assign ${reg.name.lower()}_we = addr_hit[${idx}] & reg_we & !reg_error; |
| % endif |
| </%def>\ |
| <%def name="field_wd_gen(field, sig_name, hwext, shadowed, async_clk, reg_name, idx)">\ |
| <% |
| needs_wd = field.swaccess.allows_write() |
| space = '\n' if needs_wd or needs_re else '' |
| %>\ |
| ${space}\ |
| % if needs_wd and not async_clk: |
| % if field.swaccess.swrd() == SwRdAccess.RC: |
| assign ${sig_name}_wd = '1; |
| % else: |
| assign ${sig_name}_wd = reg_wdata[${str_bits_sv(field.bits)}]; |
| % endif |
| % endif |
| </%def>\ |
| <%def name="rdata_gen(field, sig_name, rd_name='reg_rdata_next')">\ |
| % if field.swaccess.allows_read(): |
| ${rd_name}[${str_bits_sv(field.bits)}] = ${sig_name}_qs; |
| % else: |
| ${rd_name}[${str_bits_sv(field.bits)}] = '0; |
| % endif |
| </%def>\ |
| <%def name="reg_cdc_gen(field, sig_name, hwext, shadowed, idx)">\ |
| <% |
| needs_wd = field.swaccess.allows_write() |
| space = '\n' if needs_wd or needs_re else '' |
| %>\ |
| ${space}\ |
| % if needs_wd: |
| % if field.swaccess.swrd() == SwRdAccess.RC: |
| assign ${sig_name}_wd = '1; |
| % else: |
| assign ${sig_name}_wd = reg_wdata[${str_bits_sv(field.bits)}]; |
| % endif |
| % endif |
| </%def>\ |