[prim] Add shadow register primitive

This commit adds a shadow register primitive according to @tjaychen's
Shadow Register RFC:
- If the content of the staged register and the value written to the
  committed register do not match, this causes an update error.
- If the contents of committed and shadow registers differ, this causes a
  storage error.

This is related to lowRISC/OpenTitan#150.

Signed-off-by: Pirmin Vogel <vogelpi@lowrisc.org>
diff --git a/hw/ip/prim/prim.core b/hw/ip/prim/prim.core
index 1e39fec..f9c7779 100644
--- a/hw/ip/prim/prim.core
+++ b/hw/ip/prim/prim.core
@@ -42,8 +42,10 @@
       - rtl/prim_pulse_sync.sv
       - rtl/prim_filter.sv
       - rtl/prim_filter_ctr.sv
+      - rtl/prim_subreg_arb.sv
       - rtl/prim_subreg.sv
       - rtl/prim_subreg_ext.sv
+      - rtl/prim_subreg_shadow.sv
       - rtl/prim_intr_hw.sv
     file_type: systemVerilogSource
 
diff --git a/hw/ip/prim/rtl/prim_subreg.sv b/hw/ip/prim/rtl/prim_subreg.sv
index ce2b55b..d1fab64 100644
--- a/hw/ip/prim/rtl/prim_subreg.sv
+++ b/hw/ip/prim/rtl/prim_subreg.sv
@@ -27,50 +27,38 @@
   output logic [DW-1:0] qs
 );
 
-  logic          wr_en ;
+  logic          wr_en;
   logic [DW-1:0] wr_data;
 
-  if ((SWACCESS == "RW") || (SWACCESS == "WO")) begin : gen_w
-    assign wr_en   = we | de ;
-    assign wr_data = (we == 1'b1) ? wd : d ; // SW higher priority
-  end else if (SWACCESS == "RO") begin : gen_ro
-    // Unused we, wd
-    assign wr_en   = de ;
-    assign wr_data = d  ;
-  end else if (SWACCESS == "W1S") begin : gen_w1s
-    // If SWACCESS is W1S, then assume hw tries to clear.
-    // So, give a chance HW to clear when SW tries to set.
-    // If both try to set/clr at the same bit pos, SW wins.
-    assign wr_en   = we | de ;
-    assign wr_data = (de ? d : q) | (we ? wd : '0);
-  end else if (SWACCESS == "W1C") begin : gen_w1c
-    // If SWACCESS is W1C, then assume hw tries to set.
-    // So, give a chance HW to set when SW tries to clear.
-    // If both try to set/clr at the same bit pos, SW wins.
-    assign wr_en   = we | de ;
-    assign wr_data = (de ? d : q) & (we ? ~wd : '1);
-  end else if (SWACCESS == "W0C") begin : gen_w0c
-    assign wr_en   = we | de ;
-    assign wr_data = (de ? d : q) & (we ? wd : '1);
-  end else if (SWACCESS == "RC") begin : gen_rc
-    // This swtype is not recommended but exists for compatibility.
-    // WARN: we signal is actually read signal not write enable.
-    assign wr_en  = we | de ;
-    assign wr_data = (de ? d : q) & (we ? '0 : '1);
-  end else begin : gen_hw
-    assign wr_en   = de ;
-    assign wr_data = d  ;
+  prim_subreg_arb #(
+    .DW       ( DW       ),
+    .SWACCESS ( SWACCESS )
+  ) wr_en_data_arb (
+    .we,
+    .wd,
+    .de,
+    .d,
+    .q,
+    .wr_en,
+    .wr_data
+  );
+
+  always_ff @(posedge clk_i or negedge rst_ni) begin
+    if (!rst_ni) begin
+      qe <= 1'b0;
+    end else begin
+      qe <= we;
+    end
   end
 
   always_ff @(posedge clk_i or negedge rst_ni) begin
-    if (!rst_ni) qe <= 1'b0;
-    else        qe <= we  ;
+    if (!rst_ni) begin
+      q <= RESVAL;
+    end else if (wr_en) begin
+      q <= wr_data;
+    end
   end
 
-  always_ff @(posedge clk_i or negedge rst_ni) begin
-    if (!rst_ni)     q <= RESVAL ;
-    else if (wr_en) q <= wr_data;
-  end
   assign qs = q;
 
 endmodule
diff --git a/hw/ip/prim/rtl/prim_subreg_arb.sv b/hw/ip/prim/rtl/prim_subreg_arb.sv
new file mode 100644
index 0000000..410cf2d
--- /dev/null
+++ b/hw/ip/prim/rtl/prim_subreg_arb.sv
@@ -0,0 +1,79 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Write enable and data arbitration logic for register slice conforming to Comportibility guide.
+
+module prim_subreg_arb #(
+  parameter int DW       = 32  ,
+  parameter     SWACCESS = "RW"  // {RW, RO, WO, W1C, W1S, W0C, RC}
+) (
+  // From SW: valid for RW, WO, W1C, W1S, W0C, RC.
+  // In case of RC, top connects read pulse to we.
+  input          we,
+  input [DW-1:0] wd,
+
+  // From HW: valid for HRW, HWO.
+  input          de,
+  input [DW-1:0] d,
+
+  // From register: actual reg value.
+  input [DW-1:0] q,
+
+  // To register: actual write enable and write data.
+  output logic          wr_en,
+  output logic [DW-1:0] wr_data
+);
+
+  if ((SWACCESS == "RW") || (SWACCESS == "WO")) begin : gen_w
+    assign wr_en   = we | de;
+    assign wr_data = (we == 1'b1) ? wd : d; // SW higher priority
+    // Unused q - Prevent lint errors.
+    logic [DW-1:0] unused_q;
+    assign unused_q = q;
+  end else if (SWACCESS == "RO") begin : gen_ro
+    assign wr_en   = de;
+    assign wr_data = d;
+    // Unused we, wd, q - Prevent lint errors.
+    logic          unused_we;
+    logic [DW-1:0] unused_wd;
+    logic [DW-1:0] unused_q;
+    assign unused_we = we;
+    assign unused_wd = wd;
+    assign unused_q  = q;
+  end else if (SWACCESS == "W1S") begin : gen_w1s
+    // If SWACCESS is W1S, then assume hw tries to clear.
+    // So, give a chance HW to clear when SW tries to set.
+    // If both try to set/clr at the same bit pos, SW wins.
+    assign wr_en   = we | de;
+    assign wr_data = (de ? d : q) | (we ? wd : '0);
+  end else if (SWACCESS == "W1C") begin : gen_w1c
+    // If SWACCESS is W1C, then assume hw tries to set.
+    // So, give a chance HW to set when SW tries to clear.
+    // If both try to set/clr at the same bit pos, SW wins.
+    assign wr_en   = we | de;
+    assign wr_data = (de ? d : q) & (we ? ~wd : '1);
+  end else if (SWACCESS == "W0C") begin : gen_w0c
+    assign wr_en   = we | de;
+    assign wr_data = (de ? d : q) & (we ? wd : '1);
+  end else if (SWACCESS == "RC") begin : gen_rc
+    // This swtype is not recommended but exists for compatibility.
+    // WARN: we signal is actually read signal not write enable.
+    assign wr_en  = we | de;
+    assign wr_data = (de ? d : q) & (we ? '0 : '1);
+    // Unused wd - Prevent lint errors.
+    logic [DW-1:0] unused_wd;
+    assign unused_wd = wd;
+  end else begin : gen_hw
+    assign wr_en   = de;
+    assign wr_data = d;
+    // Unused we, wd, q - Prevent lint errors.
+    logic          unused_we;
+    logic [DW-1:0] unused_wd;
+    logic [DW-1:0] unused_q;
+    assign unused_we = we;
+    assign unused_wd = wd;
+    assign unused_q  = q;
+  end
+
+endmodule
diff --git a/hw/ip/prim/rtl/prim_subreg_shadow.sv b/hw/ip/prim/rtl/prim_subreg_shadow.sv
new file mode 100644
index 0000000..347e57f
--- /dev/null
+++ b/hw/ip/prim/rtl/prim_subreg_shadow.sv
@@ -0,0 +1,157 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Shadowed register slice conforming to Comportibility guide.
+
+module prim_subreg_shadow #(
+  parameter int            DW       = 32  ,
+  parameter                SWACCESS = "RW", // {RW, RO, WO, W1C, W1S, W0C, RC}
+  parameter logic [DW-1:0] RESVAL   = '0    // reset value
+) (
+  input clk_i,
+  input rst_ni,
+
+  // From SW: valid for RW, WO, W1C, W1S, W0C, RC.
+  // SW reads clear phase unless SWACCESS is RO.
+  input          re,
+  // In case of RC, top connects read pulse to we.
+  input          we,
+  input [DW-1:0] wd,
+
+  // From HW: valid for HRW, HWO.
+  input          de,
+  input [DW-1:0] d,
+
+  // Output to HW and Reg Read
+  output logic          qe,
+  output logic [DW-1:0] q,
+  output logic [DW-1:0] qs,
+
+  // Error conditions
+  output logic err_update,
+  output logic err_storage
+);
+
+  // Subreg control signals
+  logic          phase_clear;
+  logic          phase_q;
+  logic          staged_we, shadow_we, committed_we;
+  logic          staged_de, shadow_de, committed_de;
+
+  // Subreg status and data signals
+  logic          staged_qe, shadow_qe, committed_qe;
+  logic [DW-1:0] staged_q,  shadow_q,  committed_q;
+  logic [DW-1:0] committed_qs;
+
+  // Effective write enable and write data signals.
+  // These depend on we, de and wd, d, q as well as SWACCESS.
+  logic          wr_en;
+  logic [DW-1:0] wr_data;
+
+  prim_subreg_arb #(
+    .DW       ( DW       ),
+    .SWACCESS ( SWACCESS )
+  ) wr_en_data_arb (
+    .we      ( we      ),
+    .wd      ( wd      ),
+    .de      ( de      ),
+    .d       ( d       ),
+    .q       ( q       ),
+    .wr_en   ( wr_en   ),
+    .wr_data ( wr_data )
+  );
+
+  // Phase clearing:
+  // - SW reads clear phase unless SWACCESS is RO.
+  // - In case of RO, SW should not interfere with update process.
+  assign phase_clear = (SWACCESS == "RO") ? 1'b0 : re;
+
+  // Phase tracker:
+  // - Reads from SW clear the phase back to 0.
+  // - Writes have priority (can come from SW or HW).
+  always_ff @(posedge clk_i or negedge rst_ni) begin : phase_reg
+    if (!rst_ni) begin
+      phase_q <= 1'b0;
+    end else if (wr_en) begin
+      phase_q <= ~phase_q;
+    end else if (phase_clear) begin
+      phase_q <= 1'b0;
+    end
+  end
+
+  // The staged register:
+  // - Holds the 1's complement value.
+  // - Written in Phase 0.
+  assign staged_we = we & ~phase_q;
+  assign staged_de = de & ~phase_q;
+  prim_subreg #(
+    .DW       ( DW       ),
+    .SWACCESS ( SWACCESS ),
+    .RESVAL   ( ~RESVAL  )
+  ) staged_reg (
+    .clk_i    ( clk_i     ),
+    .rst_ni   ( rst_ni    ),
+    .we       ( staged_we ),
+    .wd       ( ~wd       ),
+    .de       ( staged_de ),
+    .d        ( ~d        ),
+    .qe       ( staged_qe ),
+    .q        ( staged_q  ),
+    .qs       (           )
+  );
+
+  // The shadow register:
+  // - Holds the 1's complement value.
+  // - Written in Phase 1.
+  // - Writes are ignored in case of update errors.
+  // - Gets the value from the staged register.
+  assign shadow_we = we & phase_q & ~err_update;
+  assign shadow_de = de & phase_q & ~err_update;
+  prim_subreg #(
+    .DW       ( DW       ),
+    .SWACCESS ( SWACCESS ),
+    .RESVAL   ( ~RESVAL  )
+  ) shadow_reg (
+    .clk_i    ( clk_i     ),
+    .rst_ni   ( rst_ni    ),
+    .we       ( shadow_we ),
+    .wd       ( staged_q  ),
+    .de       ( shadow_de ),
+    .d        ( staged_q  ),
+    .qe       ( shadow_qe ),
+    .q        ( shadow_q  ),
+    .qs       (           )
+  );
+
+  // The committed register:
+  // - Written in Phase 1.
+  // - Writes are ignored in case of update errors.
+  assign committed_we = shadow_we;
+  assign committed_de = shadow_de;
+  prim_subreg #(
+    .DW       ( DW       ),
+    .SWACCESS ( SWACCESS ),
+    .RESVAL   ( RESVAL   )
+  ) committed_reg (
+    .clk_i    ( clk_i        ),
+    .rst_ni   ( rst_ni       ),
+    .we       ( committed_we ),
+    .wd       ( wd           ),
+    .de       ( committed_de ),
+    .d        ( d            ),
+    .qe       ( committed_qe ),
+    .q        ( committed_q  ),
+    .qs       ( committed_qs )
+  );
+
+  // Error detection - all bits must match.
+  assign err_update  = (~staged_q != wr_data) ? phase_q & wr_en : 1'b0;
+  assign err_storage = (~shadow_q != committed_q);
+
+  // Remaining output assignments
+  assign qe = staged_qe | shadow_qe | committed_qe;
+  assign q  = committed_q;
+  assign qs = committed_qs;
+
+endmodule