// Copyright lowRISC contributors (OpenTitan project).
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Register Top module auto-generated by `rdl2ot`

`include "prim_assert.sv"
{%- set registers = interface.regs %}
{%- set windows = interface.windows %}
{%- set has_windows = windows|length > 0 %}
{%- set has_regs = registers|length > 0 %}
{%- set interface_name = ("_" + interface.name|lower) if interface.name %}
{%- set num_regs_digits = (interface.num_regs - 1) | string | length %}
{%- set clk_name = "aon_" %}
{%- set racl_support = (udps.bus_interface_cfg and udps.bus_interface_cfg.racl_support) %}

module {{ name|lower }}{{interface_name}}_reg_top {{"(" if not racl_support }}
{%- if racl_support %}
  {%- set if_name = interface.name|camelcase if interface.name %}
  # (
    parameter bit          EnableRacl           = 1'b0,
    parameter bit          RaclErrorRsp         = 1'b1{{"," if racl_support }}
  {%- if racl_support %}
    parameter top_racl_pkg::racl_policy_sel_t RaclPolicySelVec[{{ "{}_reg_pkg::NumRegs{}".format(name|lower, if_name) }}] =
      {{ "'{{{}_reg_pkg::NumRegs{}{{0}}}}".format(name|lower, if_name) }}
  {%- endif %}
  ) (
{%- endif %}
  input clk_i,
  input rst_ni,
{%- if interface.any_async_clk %}
  input clk_{{ clk_name }}i,
  input rst_{{ clk_name }}ni,
{%- endif %}
{%- if interface.any_shadowed_reg %}
  input rst_shadowed_ni,
{%- endif %}
  input  tlul_pkg::tl_h2d_t tl_i,
  output tlul_pkg::tl_d2h_t tl_o,

{%- if has_windows %}

  // Output port for window
  {%- set array = "  [{}]".format(interface.num_windows) if interface.num_windows > 1 %}
  output tlul_pkg::tl_h2d_t tl_win_o{{ array }},
  input  tlul_pkg::tl_d2h_t tl_win_i{{ array }},
{%- endif %}

{%- if has_regs %}
  // To HW
  {%- if interface.any_sw_writable_reg %}
  output {{ name|lower }}_reg_pkg::{{ name|lower }}{{interface_name}}_reg2hw_t reg2hw, // Write
  {%- endif %}
  {%- if interface.any_hw_writable_reg %}
  input  {{ name|lower }}_reg_pkg::{{ name|lower }}{{interface_name}}_hw2reg_t hw2reg, // Read
  {%- endif %}
{%- endif %}

{%- if interface.any_shadowed_reg %}

  output logic shadowed_storage_err_o,
  output logic shadowed_update_err_o,

{%- endif %}

{%- if racl_support %}

  // RACL interface
  input  top_racl_pkg::racl_policy_vec_t racl_policies_i,
  output top_racl_pkg::racl_error_log_t  racl_error_o,
{%- endif %}

  // Integrity check errors
  output logic intg_err_o
);

  import {{ name|lower }}_reg_pkg::* ;

{%- if interface.needs_aw %}

  localparam int AW = {{ interface.addr_width }};
{%- endif %}
{%- if interface.num_regs > 0 %}
  localparam int DW = {{ registers[0].width }};
  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 interface.num_regs == 0 and interface.num_windows == 1 and not interface.any_async_clk %}

  // 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 interface.all_async_clk %}
  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(clk_i),
    .rst_d_ni(rst_ni),
    .tl_h_i(tl_i),
    .tl_h_o(tl_o),
    .tl_d_o(tl_o),
    .tl_d_i(tl_i)
  );
{%- endif %}

{%- if interface.num_regs > 0 %}


  // incoming payload check
  logic intg_err;
  tlul_cmd_intg_chk u_chk (
    .tl_i(tl_i),
    .err_o(intg_err)
  );

  // also check for spurious write enables
  logic reg_we_err;
  logic [{{ interface.num_regs - 1 }}:0] reg_we_check;
  prim_reg_we_check #(
    .OneHotWidth({{ interface.num_regs }})
  ) u_prim_reg_we_check (
    .clk_i(clk_i),
    .rst_ni(rst_ni),
    .oh_i  (reg_we_check),
    .en_i  (reg_we && !addrmiss),
    .err_o (reg_we_err)
  );

  logic err_q;
  {%- if interface.clocks and "clk_lc_i" in interface.clocks %}

  always_ff @(posedge clk_lc_i or negedge rst_lc_ni) begin
    if (!rst_lc_ni) begin
  {%- else %}
  always_ff @(posedge clk_i or negedge rst_ni) begin
    if (!rst_ni) 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({{ (not interface.any_integrity_bypass)|int }})
  ) u_rsp_intg_gen (
    .tl_i(tl_o_pre),
    .tl_o(tl_o)
  );

{%- set num_dsp = (interface.num_regs > 0)|int + interface.num_windows %}
{%- if (num_dsp) <= 1 %}
  {%- if interface.num_windows == 0 %}

  assign tl_reg_h2d = tl_i;
  assign tl_o_pre   = tl_reg_d2h;
  {%- else %}

  assign tl_win_o = tl_i;
  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 [{{ interface.num_windows.bit_length() - 1}}:0] reg_steer;

  // socket_1n connection
  {%- if interface.num_regs > 0 %}
  assign tl_reg_h2d = tl_socket_h2d[{{ interface.num_windows }}];
  assign tl_socket_d2h[{{ interface.num_windows }}] = tl_reg_d2h;
  {%- endif %}
{{ space }}  
  {%- for win in windows %}
    {%- set win_suff = "[{}]".format(loop.index0) if interface.num_windows > 1 %}
  assign tl_win_o{{ win_suff }} = tl_socket_h2d[{{ loop.index0 }}];
  assign tl_socket_d2h[{{ loop.index0 }}] = tl_win_i{{ win_suff }};
  {%- endfor %}

  // Create Socket_1n
  tlul_socket_1n #(
    .N            ({{ num_dsp }}),
    .HReqPass     (1'b1),
    .HRspPass     (1'b1),
    .DReqPass     {{"({{{}{{1'b1}}}})".format(num_dsp) }},
    .DRspPass     {{"({{{}{{1'b1}}}})".format(num_dsp) }},
    .HReqDepth    (4'h0),
    .HRspDepth    (4'h0),
    .DReqDepth    {{"({{{}{{4'h0}}}})".format(num_dsp) }},
    .DRspDepth    {{"({{{}{{4'h0}}}})".format(num_dsp) }},
    .ExplicitErrs (1'b0)
  ) u_socket (
    .clk_i  (clk_i),
    .rst_ni (rst_ni),
    .tl_h_i (tl_i),
    .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 win in windows %}
        tl_i.a_address[AW-1:0] inside { {{- "[{}:{}]".format(win.offset, win.offset + win.size - 1 ) -}} } ? {{  interface.num_windows.bit_length() }}'d{{ loop.index0 }} :
  {%- endfor %}
        // Default set to register
        {{ interface.num_windows.bit_length() }}'d{{ num_dsp - 1 }};

    // Override this in case of an integrity error
    if (intg_err) begin
      reg_steer = {{ interface.num_windows.bit_length() }}'d{{ num_dsp - 1 }};
    end
  end
{%- endif %}

{%- if has_regs %}

  tlul_adapter_reg #(
    .RegAw(AW),
    .RegDw(DW),
    .EnableDataIntgGen({{ interface.any_integrity_bypass|int }})
  ) u_reg_if (
    .clk_i  (clk_i),
    .rst_ni (rst_ni),

    .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),
  {%- if racl_support %}
    // Translate RACL error to TLUL error if enabled
  {%- endif %}
    .error_i (reg_error{{" | (RaclErrorRsp & racl_error_o.valid)" if racl_support }})
  );

  // cdc oversampling signals

  assign reg_rdata = reg_rdata_next ;
  assign reg_error = 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
{%- endif %}
{%- for reg in registers  %}
  {%- set reindex = namespace(num=0) -%}
  {%- for offset in reg.offsets %}
    {%- set multireg_idx = loop.index0 %}
    {%- set reg_suffix = ('_' ~ multireg_idx|string) if reg.offsets|length > 1 %}
    {%- if reg.opentitan.needs_read_en %}
  logic {{ reg.name|lower ~ reg_suffix }}_re;
    {%- endif %}
    {%- if reg.opentitan.needs_write_en %}
  logic {{ reg.name|lower ~ reg_suffix }}_we;
    {%- endif %}
    {%- for field in reg.fields %}
      {%- set _field_name = ('_' ~ field.name|lower ~ reg_suffix) if reg.is_multifields %}
      {%- set field_name = (('_' ~ field.name|lower) | reindex(reindex.num)) if reg.is_multifields and reg.compacted else _field_name %}
      {%- set reindex.num = reindex.num + 1 %}
      {%- set width = "[{}:0] ".format(field.width - 1) if field.width > 1 %}
      {%- if not reg.async_clk and field.sw_readable %} 
  logic {{ width ~ reg.name|lower ~ reg_suffix ~ field_name }}_qs;
      {%- endif %}
      {%- if not reg.async_clk and field.sw_writable %} 
  logic {{ width ~ reg.name|lower ~ reg_suffix ~ field_name }}_wd;
      {%- endif %}
      {%- if reg.shadowed and not reg.external %}
  logic {{ reg.name|lower ~ reg_suffix ~ field_name }}_storage_err;
  logic {{ reg.name|lower ~ reg_suffix ~ field_name }}_update_err;
      {%- endif %}
    {%- endfor %}
    {%- if reg.async_clk %} 
  logic [{{ reg.msb }}:0] {{reg.name|lower ~ reg_suffix }}_qs;
  logic {{ reg.name|lower ~ reg_suffix }}_busy;
    {%- endif %}
  {%- endfor %}
{%- endfor %}

{%- if interface.any_async_clk %}
  // Define register CDC handling.
  // CDC handling is done on a per-reg instead of per-field boundary.
{{ space }}  
  {%- for reg in registers  %}
    {%- if reg.async_clk %} 
      {%- for offset in reg.offsets %}

        {%- set multireg_idx = loop.index0 %}
        {%- set index = ('_' ~ multireg_idx|string) if reg.offsets|length > 1 %}
        {%- set sig_name = (clk_name ~ reg.name ~ index)|lower %}

        {%- set src_we_expr = "{}_we".format(reg.name|lower ~ index) if reg.opentitan.needs_write_en else "'0" %}
        {%- set src_wd_expr = "reg_wdata[{}:0]".format(reg.msb) if reg.opentitan.needs_write_en else "'0" %}
        {%- set src_re_expr = "{}_re".format(reg.name|lower ~ index) if reg.opentitan.needs_read_en else "'0" %}
        {%- set src_regwen_expr = "{}_qs".format(reg.fields[0].write_en_signal.parent_name|lower) if reg.sw_write_en else "'0" %}
        {%- set dst_we_expr = "{}_we".format(sig_name) if reg.opentitan.needs_write_en %}
        {%- set dst_wd_expr = "{}_wdata".format(sig_name) if reg.opentitan.needs_write_en %}
        {%- set dst_re_expr = "{}_re".format(sig_name) if reg.opentitan.needs_read_en %}
        {%- set dst_qe_expr = "{}_qe".format(sig_name) if reg.hw_writable else "'0" %}
        {%- set dst_wr_req = "1" if reg.hw_writable else "0" %}
        {%- set dst_ds_expr = "{}_ds".format(sig_name) if reg.hw_writable else "'0" %}
        {%- set dst_regwen_expr = "{}_regwen".format(sig_name) if reg.sw_write_en %}
        {%- set reset_expr = "{}'h{:x}".format(reg.msb + 1, reg.reset)  %}

        {%- for field in reg.fields %}
          {%- set bit_index = "[{}:0] ".format(field.width - 1) if field.msb != field.lsb %}
          {%- set field_name = ('_' ~ field.name|lower ~ index) if reg.is_multifields %}
          {%- if reg.hw_writable %}
  logic {{ bit_index }} {{ (sig_name ~ field_name)|lower }}_ds_int;
          {%- endif %}
          {%- if field.sw_readable %}
  logic {{ bit_index }} {{ (sig_name ~ field_name)|lower }}_qs_int;
          {%- endif %}
        {%- endfor %}
        {%- if reg.hw_writable %}
  logic [{{ reg.msb }}:0] {{ sig_name }}_ds;
  logic {{ dst_qe_expr }};
        {%- endif %}
  logic [{{ reg.msb }}:0] {{ sig_name }}_qs;
        {%- if reg.opentitan.needs_write_en %}
  logic [{{ reg.msb }}:0] {{ sig_name }}_wdata;
  logic {{ sig_name }}_we;
  logic unused_{{ sig_name }}_wdata;
        {%- endif %}
        {%- if reg.opentitan.needs_read_en %}
  logic {{ dst_re_expr }};
        {%- endif %}
        {%- if reg.sw_write_en %}
  logic {{ dst_regwen_expr }};
        {%- endif %}

  always_comb begin
    {{ sig_name }}_qs = {{ reset_expr }};
        {%- if reg.hw_writable %}
    {{ dst_ds_expr }} = {{ reset_expr }};
        {%- endif %}
        {%- for field in reg.fields %}
          {%- set bits = "{}:{}".format(field.msb, field.lsb) if field.msb != field.lsb else field.msb %}
          {%- set bit_index = "[{}]".format(bits) if reg.is_multifields %}
          {%- set field_name = ('_' ~ field.name|lower ~ index) if reg.is_multifields %}

          {%- if reg.hw_writable and field.sw_readable %}
    {{ dst_ds_expr }}{{ bit_index }} = {{ sig_name ~ field_name}}_ds_int;
          {%- endif %}

          {%- if field.sw_readable %}
    {{ sig_name }}_qs{{ bit_index }} = {{ sig_name  ~ field_name}}_qs_int;
          {%- endif %}
        {%- endfor %}
  end

  prim_reg_cdc #(
    .DataWidth({{ reg.msb + 1 }}),
    .ResetVal({{ reset_expr }}),
    .BitMask({{ "{}'h{:x}".format(reg.msb + 1, reg.bitmask) }}),
    .DstWrReq({{ dst_wr_req }})
  ) u_{{ reg.name|lower ~ index }}_cdc (
    .clk_src_i    (clk_i),
    .rst_src_ni   (rst_ni),
    .clk_dst_i    (clk_{{ clk_name }}i),
    .rst_dst_ni   (rst_{{ clk_name }}ni),
    .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   ({{ reg.name|lower ~ index }}_busy),
    .src_qs_o     ({{ reg.name|lower ~ index }}_qs), // for software read back
    .dst_update_i ({{ dst_qe_expr }}),
    .dst_ds_i     ({{ dst_ds_expr }}),
    .dst_qs_i     ({{ sig_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 reg.opentitan.needs_write_en %}
  assign unused_{{ sig_name }}_wdata =
      ^{{ sig_name }}_wdata;
        {%- endif %}
{{ space -}}
      {% endfor %}
    {%- endif %}
  {%- endfor %}
{%- endif %}

{%- if has_regs %}
  // Register instances
{%- endif %}

{%- set assign = namespace(expr="") %}
{%- for reg in registers  %}
{{- space }}
  {%- set reindex = namespace(num=0) -%}
  {%- for offset in reg.offsets %}
    {%- set multireg_idx = loop.index0 if reg.is_multireg %}
    {%- set multireg_suffix = "_{}".format(multireg_idx) if reg.offsets|length > 1 %}
    {%- set regname = reg.name|lower ~ multireg_suffix %}
    {%- set clk_prefix = clk_name if reg.async_clk %}
    {%- set num_flds_wr_en = reg.opentitan.fields_write_en_bits %}
    {%- set flds_wr_en_mask = reg.opentitan.fields_write_en_mask %}
    {%- set num_flds = reg.fields|length %}
    {%- if reg.is_multireg %}
  // Subregister {{multireg_idx}} of Multireg {{ reg.name|lower }}
    {%- endif %}
{{- space }}
  // R[{{ regname }}]: V({{ reg.external }})
    {%- if reg.opentitan.needs_qe %}
  logic {{ regname }}_qe;
    {%- endif %}
    {%- if reg.opentitan.needs_int_qe %}
  logic {{ '[{}:0] {}'.format(reg.fields|length - 1, regname) }}_flds_we;
    {%- endif %}
    {%- if reg.opentitan.needs_qe  %}
      {%- if reg.external %}
        {%- if num_flds_wr_en < num_flds and num_flds_wr_en > 0 %}
  // This ignores QEs that are set to constant 0 due to read-only fields.
  logic unused_{{ reg.name|lower }}_flds_we;
  assign unused_{{ reg.name|lower }}_flds_we = {{ "^({}_flds_we & {}'h{:x})".format(reg.name|lower, num_flds, flds_wr_en_mask ) }};
      {%- endif %}
      {%- if reg.external and num_flds_wr_en == num_flds %}
  // In case all fields are read-only the aggregated register QE will be zero as well.
      {%- endif %}
      {%- set right_expr = "{}_flds_we".format(regname) %}
      {%- set right_expr = "({} | {}'h{:x})".format(right_expr, num_flds, flds_wr_en_mask ) if reg.external and num_flds_wr_en < num_flds and num_flds_wr_en > 0 else right_expr %}
  assign {{regname }}_qe = &{{ "{}".format(right_expr) if num_flds_wr_en > 0 else "{}".format(right_expr) }};
      {%- else %}
  prim_flop #(
    .Width(1),
    .ResetValue(0)
  ) u_{{ reg.name|lower ~ loop.index0}}_qe (
    .clk_i({{reg.async_clk|lower if reg.async_clk else "clk_i"}}),
    .rst_ni({{reg.async_rst|lower if reg.async_rst else "rst_ni"}}),
      {%- set right_expr = " | {}'h{:x})".format(num_flds, num_flds_wr_en) if num_flds_wr_en %}
    .d_i({{"&{}{}_flds_we{}".format("(" if num_flds_wr_en, regname, right_expr) }}),
    .q_o({{ regname }}_qe)
  );
      {%- endif %}
    {%- endif %}
    {%- if reg.async_clk and reg.hw_writable %}
  assign {{clk_prefix ~ regname }}_qe = |{{"{}".format(regname) }}_flds_we;
    {%- endif %}
    {%- if reg.sw_write_en and reg.opentitan.needs_write_en %}
  // Create REGWEN-gated WE signal
  logic {{clk_prefix ~ regname }}_gated_we;
      {%- set assign.expr = (clk_prefix ~ regname ~ '_we')|lower %}
      {%- if reg.sw_write_en %}
        {%- set wr_en_sig_name = reg.fields[0].write_en_signal.name|lower %}
        {%- set wr_en_reg_name = reg.fields[0].write_en_signal.parent_name|lower %}
        {%- set assign.expr = (clk_prefix ~ regname|lower ~ '_regwen') if reg.async_clk else "{}_qs".format(wr_en_reg_name) %}
        {%- if "MultiBitBool" in reg.fields[0].write_en_signal.encode %}
          {%- set width = reg.fields[0].write_en_signal.width %}
          {%- set assign.expr = "prim_mubi_pkg::mubi{}_test_true_strict(prim_mubi_pkg::mubi{}_t'({}_qs))".format(width, width, wr_en_sig_name)|lower %}
        {%- endif %}
      {%- endif %}
      {%- set assignment =  "  assign {}{}_gated_we = {}{}_we & {};".format(clk_prefix, regname, clk_prefix, regname, assign.expr) %}
{{assignment if assignment|length < 100 else assignment|replace("= ", "=\n    ")}}
    {%- endif %}
    {%- for field in reg.fields  %}
      {%- set _field_name = "_{}{}{}".format(field.name, multireg_suffix, reg_suffix)|lower if reg.is_multifields and not reg.compacted %}
      {%- set field_name = (("_" ~ field.name) | reindex(reindex.num) | lower) if reg.is_multifields and reg.compacted else _field_name %}
      {%- set property = ".{}".format(field.name)|lower if reg.is_multifields and not reg.opentitan.is_homogeneous %}
      {%- set bit_index = "[{}:{}]".format(field.msb, field.lsb) if field.msb != field.lsb else "[{}]".format(field.msb) %}
      {%- if reg.is_multifields %}
  //   F{{ '[{}]: {}:{}'.format(field_name|trim("_"), field.msb, field.lsb)|lower }}
      {%- endif %}
  prim_subreg{{ '_ext' if reg.external else ('_shadow' if reg.shadowed) }} #(
      {%- set align_width = 4 if reg.external else 6 %}
    .DW{{ "{space:>{width}}({num})".format(space=" ", num=field.width, width=align_width) }}
      {%- if not reg.external -%}
    ,
    .SwAccess(prim_subreg_pkg::SwAccess{{ field.opentitan.reggen_sw_access }}),
    .RESVAL  ({{ "{}'h{:x}".format(field.width, (field.reset if field.reset else 0)) }}),
    .Mubi    (1'b{{ ("MultiBitBool" in field.encode)|int }})
      {%- endif %}
  ) u_{{ regname ~ field_name}} (
      {%- if not reg.external %}
    .clk_i   (clk_{{ clk_prefix if reg.async_clk }}i),
    .rst_ni  (rst_{{ clk_prefix if reg.async_clk }}ni),
        {%- if reg.shadowed %}
    .rst_shadowed_ni (rst_shadowed_ni),
        {%- endif %}
      {%- endif %}
{{- space }}
      {%- set _idx = loop.index0 if reg.opentitan.is_homogeneous and reg.is_multifields else multireg_idx %}
      {%- set idx = reindex.num if reg.compacted else _idx %}
      {%- set reindex.num = reindex.num + 1 %}
      {%- set sig_name = (reg.name ~ ("[{}]".format(idx) if reg.is_multireg or (reg.opentitan.is_homogeneous and reg.is_multifields)) ~ property)|lower -%}
      {%- set suffix = "_int" if reg.async_clk %}
      {%- if not reg.external %}

    // from register interface
      {%- endif %}
      {%- if reg.external or reg.shadowed %}
    .re     ({{ "{}{}{}_re".format(clk_prefix, reg.name, multireg_suffix)|lower if field.sw_readable or reg.shadowed else "1'b0" }}),
      {%- endif %}
    .we     ({{ "{}{}_{}".format(clk_prefix ~ regname, ("_gated" if field.sw_write_en), "re" if field.clear_onread  else "we") if field.sw_writable else "1'b0"  }}),
    .wd     ({{ "{}{}{}_wd{}{}".format(clk_prefix, regname, field_name if not reg.async_clk, "ata" if reg.async_clk, bit_index if reg.async_clk) if field.sw_writable else "'0"  }}),
      {%- if not reg.external %}

    // from internal hardware
      {%- endif %}
      {%- if not reg.external %}
    .de     ({{ "hw2reg.{}.de".format(sig_name ) if field.hw_writable else "1'b0" }}),
      {%- endif %}
    .d      ({{ "hw2reg.{}.d".format(sig_name ) if field.hw_writable else "'0" }}),
      {%- if not reg.external %}

    // to internal hardware
      {%- endif %}
      {%- if reg.external %}
    .qre    ({{  "reg2hw.{}.re".format(sig_name) if reg.hwre or reg.shadowed }}),
      {%- endif %}
    .qe     ({{ "{}_flds_we[{}]".format(regname, loop.index0) if reg.opentitan.needs_int_qe  }}),
    .q      ({{ "reg2hw.{}.q".format(sig_name) if field.hw_readable }}),
    .ds     ({{ "{}{}{}_ds{}".format(clk_prefix, regname, field_name, suffix) if reg.async_clk and reg.hw_writable }}),
      {%- if not reg.external %}

    // to register interface (read)
      {%- endif %}
    .qs     ({{ "{}{}_qs{}".format(clk_prefix, regname ~ field_name, suffix) if field.sw_readable }})
      {%- if not reg.external and reg.shadowed -%}
      ,

    // Shadow register phase. Relevant for hwext only.
    .phase  (),

    // Shadow register error conditions
        {%- if reg.async_clk %}
    .err_update  (async_{{ regname ~ field_name }}_err_update),
    .err_storage (async_{{ regname ~ field_name }}_err_storage)
        {%- else %}
    .err_update  ({{ regname ~ field_name }}_update_err),
    .err_storage ({{ regname ~ field_name }}_storage_err)
        {%- endif %}
      {%- endif %}
  );
    {%- if field.hw_readable and field.swmod %}
  assign reg2hw.{{ sig_name }}.qe = {{ regname }}_qe;
    {%- endif %}
{{- space }}
{{ space }}
    {%- endfor %}
{{ space }}
  {%- endfor %}
{%- endfor %}

{%- if has_regs %}

  logic [{{interface.num_regs - 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 [{{interface.num_regs - 1 }}:0] racl_addr_hit_read;
  logic [{{interface.num_regs - 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(top_racl_pkg::racl_role_vec_t) )
    ) u_racl_role_encode (
      .in_i ( racl_role     ),
      .en_i ( 1'b1          ),
      .out_o( racl_role_vec )
    );
    {%- if udps.static_racl_support %}
    // For the static RACL assignment for racl_ctrl only one role (ROT_PRIVATE) is used,
    // leaving others unread. Intentionally read them to avoid linting errors.
    logic unused_role_vec;
    assign unused_role_vec = ^racl_role_vec;
    {%- endif %}
  end else begin : gen_no_racl_role_logic
    assign racl_role     = '0;
    assign racl_role_vec = '0;
  end
  {%- endif %}
  always_comb begin
  {%- if racl_support %}
    racl_addr_hit_read  = '0;
    racl_addr_hit_write = '0;
  {%- endif %}
  {%- set ns = namespace(counter=0) %}
  {%- for reg in registers %}
    {%- for offset in reg.offsets %}
      {%- set index = "{num:>{width}}".format(num=ns.counter, width=num_regs_digits) %}
      {%- set ns.counter = ns.counter + 1 %}
    addr_hit[{{ index }}] = (reg_addr == {{ (name ~ '_' ~ reg.name)|upper }}{% if reg.offsets|length > 1 %}_{{ loop.index0 }}{% endif %}_OFFSET);
    {%- endfor %}
  {%- endfor %}
  {%- if racl_support %}

    if (EnableRacl) begin : gen_racl_hit
      for (int unsigned slice_idx = 0; slice_idx < {{interface.num_regs}}; slice_idx++) begin
    {%- if not udps.static_racl_support %}
        racl_addr_hit_read[slice_idx] =
            addr_hit[slice_idx] & (|(racl_policies_i[RaclPolicySelVec[slice_idx]].read_perm
                                      & racl_role_vec));
        racl_addr_hit_write[slice_idx] =
            addr_hit[slice_idx] & (|(racl_policies_i[RaclPolicySelVec[slice_idx]].write_perm
                                      & racl_role_vec));
    {%- else %}
        // Static RACL protection with ROT_PRIVATE policy
        racl_addr_hit_read[slice_idx] =
          addr_hit[slice_idx] & (|(top_racl_pkg::RACL_POLICY_ROT_PRIVATE_RD & racl_role_vec));
        racl_addr_hit_write[slice_idx] =
          addr_hit[slice_idx] & (|(top_racl_pkg::RACL_POLICY_ROT_PRIVATE_WR & racl_role_vec));
    {%- endif %}
      end
    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 %}
  // A valid address hit, access, but failed the RACL check
  assign racl_error_o.valid = |addr_hit & ((reg_re & ~|racl_addr_hit_read) |
                                           (reg_we & ~|racl_addr_hit_write));
  assign racl_error_o.request_address = top_pkg::TL_AW'(reg_addr);
  assign racl_error_o.racl_role       = racl_role;
  assign racl_error_o.overflow        = 1'b0;

  if (EnableRacl) begin : gen_racl_log
    assign racl_error_o.ctn_uid     = top_racl_pkg::tlul_extract_ctn_uid_bits(tl_i.a_user.rsvd);
    assign racl_error_o.read_access = tl_i.a_opcode == tlul_pkg::Get;
  end else begin : gen_no_racl_log
    assign racl_error_o.ctn_uid     = '0;
    assign racl_error_o.read_access = 1'b0;
  end
  {%- endif %}

  // Check sub-word write is permitted
  always_comb begin
    wr_err = (reg_we &
  {%- set ns = namespace(counter=0) %}
  {%- set interface_name = ("_" + interface.name|lower) if interface.name -%}
  {%- set wr_addr_hit = "racl_addr_hit_write" if racl_support else "addr_hit" %}
  {%- set rd_addr_hit = "racl_addr_hit_read" if racl_support else "addr_hit" %}
  {%- for reg in registers %}
    {%- set outer_loop = loop -%}
    {%- for offset in reg.offsets %}
      {%- set index = "{num:>{width}}".format(num=ns.counter, width=num_regs_digits) %}
      {%- set ns.counter = ns.counter + 1 %}
              {{"(" if loop.first and outer_loop.first else " " -}}
              {{"({}[{}] & (|({}_PERMIT[{}] & ~reg_be)))".format(wr_addr_hit , index,(name ~ interface_name)|upper, index ) }}
      {%- if loop.last and outer_loop.last %}));{% else %} |{% endif %}
    {%- endfor %}
  {%- endfor %}
  end

  // Generate write-enables
  {%- set ns = namespace(re_index=0) -%}
  {%- for reg in registers  %}
    {%- set reindex = namespace(num=0) -%}
    {%- for offset in reg.offsets %}
      {%- set reg_suffix = ('_' ~ loop.index0|string) if reg.offsets|length > 1 %}
      {%- set regname = "{}{}".format(reg.name, reg_suffix)|lower %}
      {%- if reg.opentitan.needs_read_en %}
  assign {{ regname }}_re = {{rd_addr_hit}}[{{ ns.re_index }}] & reg_re & !reg_error;
      {%- endif %}
      {%- if reg.opentitan.needs_write_en %}
  assign {{ regname }}_we = {{wr_addr_hit}}[{{ ns.re_index }}] & reg_we & !reg_error;
{{space}}
      {%- endif %}
      {%- set ns.re_index = ns.re_index + 1 %}
      {%- for field in reg.fields %}
        {%- if field.sw_writable and not reg.async_clk %}
          {%- set _field_name = ('_' ~ field.name|lower  ~ reg_suffix) if reg.is_multifields %}
          {%- set field_name = (('_' ~ field.name|lower) | reindex(reindex.num)) if reg.is_multifields and reg.compacted else _field_name%}
          {%- set reindex.num = reindex.num + 1 %}
          {%- set left_expr = "{}{}{}".format(reg.name, reg_suffix, field_name)|lower %}
          {%- set bit_index = "{}:{}".format(field.msb, field.lsb) if field.width > 1 else field.msb %}
          {%- set right_expr = "'1" if field.clear_onread else "reg_wdata[{}]".format(bit_index) %}
  assign {{ left_expr }}_wd = {{ right_expr }};
{{space}}
        {%- endif %}
      {%- endfor %}
    {%- endfor %}
  {%- endfor %}
{{space}}
  // Assign write-enables to checker logic vector.
  always_comb begin
  {%- set ns = namespace(counter=0) %}
  {%- for reg in registers %}
    {%- for offset in reg.offsets %}
      {%- set reg_suffix = ('_' ~ loop.index0|string) if reg.offsets|length > 1 %}
      {%- set expr = "{}{}{}_we".format(reg.name, reg_suffix, "_gated" if not reg.async_clk and reg.sw_write_en)|lower if reg.opentitan.needs_write_en else "1'b0" %}
    reg_we_check[{{ ns.counter }}] = {{ expr }};
      {%- set ns.counter = ns.counter + 1 %}
    {%- endfor %}
  {%- endfor %}
  end

  // Read data return
  always_comb begin
    reg_rdata_next = '0;
    unique case (1'b1)
  {%- set ns = namespace(counter=0) %}
  {%- for reg in registers %}
    {%- set reindex = namespace(num=0) -%}
    {%- for offset in reg.offsets %}
      {%- set reg_suffix = ('_' ~ loop.index0|string) if reg.offsets|length > 1  %}
      {{rd_addr_hit}}[{{ ns.counter }}]: begin
      {%- set ns.counter = ns.counter + 1 %}
      {%- if reg.async_clk %}
        reg_rdata_next = DW'({{ "{}{}_qs".format(reg.name, reg_suffix)|lower }});
      {%- else %}
        {%- for field in reg.fields %}
            {%- set _field_name = ('_' ~ field.name|lower ~ reg_suffix) if reg.is_multifields %}
            {%- set field_name = (('_' ~ field.name) | reindex(reindex.num)) if reg.is_multifields and reg.compacted else _field_name %}
            {%- set reindex.num = reindex.num + 1 %}
            {%- set index = "{}:{}".format(field.msb, field.lsb) if field.width > 1 else field.msb %}
            {%- set expr = "{}{}{}_qs".format(reg.name, reg_suffix, field_name)|lower if field.sw_readable else "'0" %}
        reg_rdata_next{{ "[{}] = {}".format(index, expr)|lower }};
        {%- endfor %}
      {%- endif %}
      end
      {%- if not reg.async_clk %}
{{ space }}
      {%- endif %}
    {%- endfor -%}
  {%- endfor %}
      default: begin
        reg_rdata_next = '1;
      end
    endcase
  end

  // shadow busy
  logic shadow_busy;
  {%- if interface.any_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 interface.any_shadowed_reg %}

  // Collect up storage and update errors
  assign shadowed_storage_err_o = |{
    {%- set ns = namespace(is_first = true) %}
    {%- for reg in registers  %}
      {%- if reg.shadowed and not reg.external %}
        {{- "," if not ns.is_first }}
        {%- set ns.is_first = false %}
        {%- for field in reg.fields  %}
          {%- set field_name = "_{}".format(field.name)|lower if reg.is_multifields %}
    {{ "{}{}_storage_err".format(reg.name, field_name)|lower }}
    {{- "," if not loop.last }}
        {%- endfor %}
      {%- endif %}
    {%- endfor %}
  };
  assign shadowed_update_err_o = |{
    {%- set ns = namespace(is_first = true) %}
    {%- for reg in registers  %}
      {%- if reg.shadowed and not reg.external %}
        {{- "," if not ns.is_first }}
        {%- set ns.is_first = false %}
        {%- for field in reg.fields  %}
          {%- set field_name = "_{}".format(field.name)|lower if reg.is_multifields %}
    {{ "{}{}_update_err".format(reg.name, field_name)|lower }}
    {{- "," if not loop.last }}
        {%- endfor %}
      {%- endif %}
    {%- endfor %}
  };

  {%- endif %}

  // register busy

  {%- if not interface.any_async_clk %}
  assign reg_busy = shadow_busy;
  {%- else %}
  logic reg_busy_sel;
  assign reg_busy = (reg_busy_sel | shadow_busy) & tl_i.a_valid;
  always_comb begin
    reg_busy_sel = '0;
    unique case (1'b1)
    {%- for index, busy_signal in interface.async_registers %}
      addr_hit[{{ index }}]: begin
        reg_busy_sel = {{ busy_signal|lower}}_busy;
      end
    {%- endfor %}
      default: begin
        reg_busy_sel  = '0;
      end
    endcase
  end
  {%- endif %}

  // Unused signal tieoff

  // 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;
{%- if racl_support %}
  logic unused_policy_sel;
  assign unused_policy_sel = ^racl_policies_i;
{%- endif %}

  // Assertions for Register Interface
  `ASSERT_PULSE(wePulse, reg_we, clk_i, !rst_ni)
  `ASSERT_PULSE(rePulse, reg_re, clk_i, !rst_ni)

  `ASSERT(reAfterRv, $rose(reg_re || reg_we) |=> tl_o_pre.d_valid, clk_i, !rst_ni)

  `ASSERT(en2addrHit, (reg_we || reg_re) |-> $onehot0(addr_hit), clk_i, !rst_ni)

  // 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

