[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