| // 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 |
| |
| 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 |
| %> |
| `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}_reg_pkg::${reg2hw_t} reg2hw, // Write |
| % endif |
| % if rb.get_n_bits(["d","de"]): |
| input ${lblock}_reg_pkg::${hw2reg_t} hw2reg, // Read |
| % endif |
| |
| // Integrity check errors |
| output logic intg_err_o, |
| |
| // Config |
| input devmode_i // If 1, explicit error return for unmapped register access |
| ); |
| |
| import ${lblock}_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 |
| |
| % 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) |
| ); |
| |
| logic intg_err_q; |
| always_ff @(posedge ${reg_clk_expr} or negedge ${reg_rst_expr}) begin |
| if (!${reg_rst_expr}) begin |
| intg_err_q <= '0; |
| end else if (intg_err) begin |
| intg_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 = intg_err_q | intg_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 |
| unique case (${f'{tl_h2d_expr}.a_address[AW-1:0]'}) inside |
| % for i,w in enumerate(rb.windows): |
| <% |
| 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}]" |
| %>\ |
| ${addr_test}: begin |
| reg_steer = ${i}; |
| end |
| % endfor |
| default: begin |
| // Default set to register |
| reg_steer = ${num_dsp-1}; |
| end |
| endcase |
| |
| // Override this in case of an integrity error |
| if (intg_err) begin |
| reg_steer = ${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), |
| |
| .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) |
| ); |
| |
| % if not rb.async_if: |
| // cdc oversampling signals |
| % for clock in rb.clocks.values(): |
| <% |
| clk_name = clock.clock_base_name |
| tgl_expr = clk_name + "_tgl" |
| cname = clock.clock |
| rname = clock.reset |
| %>\ |
| logic sync_${clk_name}_update; |
| prim_sync_reqack u_${tgl_expr} ( |
| .clk_src_i(${cname}), |
| .rst_src_ni(${rname}), |
| .clk_dst_i(${reg_clk_expr}), |
| .rst_dst_ni(${reg_rst_expr}), |
| .req_chk_i(1'b1), |
| .src_req_i(1'b1), |
| .src_ack_o(), |
| .dst_req_o(sync_${clk_name}_update), |
| .dst_ack_i(sync_${clk_name}_update) |
| ); |
| % endfor |
| % endif |
| |
| % 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() |
| 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"{base_name}_{r_name}_we" if r.needs_we() else "" |
| dst_wd_expr = f"{base_name}_{r_name}_wdata" if r.needs_we() else "" |
| dst_re_expr = f"{base_name}_{r_name}_re" if r.needs_re() else "" |
| dst_regwen_expr = f"{base_name}_{r_name}_regwen" if r.regwen else "" |
| %> |
| % if len(r.fields) > 1: |
| % for f in r.fields: |
| % if f.swaccess.allows_read(): |
| logic ${str_arr_sv(f.bits)} ${base_name}_${r_name}_${f.name.lower()}_qs_int; |
| % endif |
| % endfor |
| % else: |
| % if r.fields[0].swaccess.allows_read(): |
| logic ${str_arr_sv(r.fields[0].bits)} ${base_name}_${r_name}_qs_int; |
| % endif |
| % endif |
| logic [${r.get_width()-1}:0] ${base_name}_${r_name}_d; |
| % if r.needs_we(): |
| logic [${r.get_width()-1}:0] ${base_name}_${r_name}_wdata; |
| logic ${base_name}_${r_name}_we; |
| logic unused_${base_name}_${r_name}_wdata; |
| % endif |
| % if r.needs_re(): |
| logic ${base_name}_${r_name}_re; |
| % endif |
| % if r.regwen: |
| logic ${base_name}_${r_name}_regwen; |
| % endif |
| |
| always_comb begin |
| ${base_name}_${r_name}_d = '0; |
| % if len(r.fields) > 1: |
| % for f in r.fields: |
| % if f.swaccess.allows_read(): |
| ${base_name}_${r_name}_d[${str_bits_sv(f.bits)}] = ${base_name}_${r_name}_${f.name.lower()}_qs_int; |
| % endif |
| % endfor |
| % else: |
| % if f.swaccess.allows_read(): |
| ${base_name}_${r_name}_d = ${base_name}_${r_name}_qs_int; |
| % endif |
| % endif |
| end |
| |
| prim_reg_cdc #( |
| .DataWidth(${r.get_width()}), |
| .ResetVal(${r.get_width()}'h${format(r.resval, "x")}), |
| .BitMask(${r.get_width()}'h${r.bitmask()}) |
| ) 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_update_i (sync_${r.async_clk.clock_base_name}_update), |
| .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_d_i (${base_name}_${r_name}_d), |
| .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_${base_name}_${r_name}_wdata = |
| ^${base_name}_${r_name}_wdata; |
| % endif |
| % endif |
| % endfor |
| |
| // Register instances |
| % for r in rb.all_regs: |
| <% |
| if isinstance(r, MultiRegister): |
| r0 = r.reg |
| srs = r.regs |
| else: |
| r0 = r |
| srs = [r] |
| |
| reg_name = r0.name.lower() |
| fld_count = 0 |
| %>\ |
| % 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})') |
| %>\ |
| ${reg_hdr} |
| % for field in 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) |
| %>\ |
| % if len(sr.fields) > 1: |
| // F[${fld_name}]: ${field.bits.msb}:${field.bits.lsb} |
| % endif |
| ${finst_gen(sr, field, finst_name, fsig_name)} |
| % 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\ |
| |
| % 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 |
| |
| // 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 |
| |
| // register busy |
| % if rb.async_if: |
| 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, r in enumerate(regs_flat): |
| % if r.async_clk: |
| addr_hit[${i}]: begin |
| reg_busy_sel = ${r.name.lower() + "_busy"}; |
| end |
| % endif |
| % 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 |
| </%def>\ |
| <%def name="finst_gen(reg, field, finst_name, fsig_name)">\ |
| <% |
| |
| 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'{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' |
| we_signal = f'{clk_base_name}{reg_name}_{we_suffix}' |
| |
| if reg.async_clk and reg.regwen: |
| we_expr = f'{we_signal} & {clk_base_name}{reg_name}_regwen' |
| elif reg.regwen: |
| we_expr = f'{we_signal} & {reg.regwen.lower()}_qs' |
| else: |
| we_expr = we_signal |
| |
| # 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 field.hwaccess.allows_read(): |
| qe_expr = f'reg2hw.{fsig_name}.qe' if reg.hwqe else '' |
| q_expr = f'reg2hw.{fsig_name}.q' |
| else: |
| qe_expr = '' |
| 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 '' |
| |
| %>\ |
| <% |
| |
| if reg.async_clk: |
| update_expr = "sync_" + reg.async_clk.clock.strip("_iclk_")+ "_update" |
| %>\ |
| % 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}), |
| .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: |
| ${subreg_block} #( |
| .DW (${field.bits.width()}), |
| .SwAccess(prim_subreg_pkg::SwAccess${field.swaccess.value[1].name.upper()}), |
| .RESVAL (${resval_expr}) |
| ) u_${finst_name} ( |
| .clk_i (${clk_expr}), |
| .rst_ni (${rst_expr}), |
| % 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}), |
| |
| // 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 |
| .err_update (reg2hw.${fsig_name}.err_update), |
| .err_storage (reg2hw.${fsig_name}.err_storage) |
| % endif |
| ); |
| % endif ## end non-constant prim_subreg |
| % 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_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="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>\ |