blob: 95db83b4f37ea8d6fe09b9ff60675febea5b1ebb [file] [log] [blame]
// 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>\