[otp_ctrl] Step up parity to ECC checks in buffer regs
Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/hw/ip/otp_ctrl/data/otp_ctrl.hjson b/hw/ip/otp_ctrl/data/otp_ctrl.hjson
index 08b7de6..830f001 100644
--- a/hw/ip/otp_ctrl/data/otp_ctrl.hjson
+++ b/hw/ip/otp_ctrl/data/otp_ctrl.hjson
@@ -28,7 +28,7 @@
alert_list: [
{ name: "otp_macro_failure",
- desc: "This alert triggers if hardware detects a parity bit or digest error in the buffered partitions.",
+ desc: "This alert triggers if hardware detects an ECC or digest error in the buffered partitions.",
}
{ name: "otp_check_failure",
desc: "This alert triggers if the digest over the buffered registers does not match with the digest stored in OTP.",
@@ -718,7 +718,7 @@
{ value: "6",
name: "CHECK_FAIL_ERROR",
desc: '''
- A parity, integrity or consistency mismatch has been detected in the buffer registers.
+ An ECC, integrity or consistency mismatch has been detected in the buffer registers.
This error should never occur during normal operation and is not recoverable.
This error triggers an otp_check_failure alert.
'''
diff --git a/hw/ip/otp_ctrl/data/otp_ctrl.hjson.tpl b/hw/ip/otp_ctrl/data/otp_ctrl.hjson.tpl
index c3acc81..3025a89 100644
--- a/hw/ip/otp_ctrl/data/otp_ctrl.hjson.tpl
+++ b/hw/ip/otp_ctrl/data/otp_ctrl.hjson.tpl
@@ -41,7 +41,7 @@
alert_list: [
{ name: "otp_macro_failure",
- desc: "This alert triggers if hardware detects a parity bit or digest error in the buffered partitions.",
+ desc: "This alert triggers if hardware detects an ECC or digest error in the buffered partitions.",
}
{ name: "otp_check_failure",
desc: "This alert triggers if the digest over the buffered registers does not match with the digest stored in OTP.",
@@ -407,7 +407,7 @@
{ value: "6",
name: "CHECK_FAIL_ERROR",
desc: '''
- A parity, integrity or consistency mismatch has been detected in the buffer registers.
+ An ECC, integrity or consistency mismatch has been detected in the buffer registers.
This error should never occur during normal operation and is not recoverable.
This error triggers an otp_check_failure alert.
'''
diff --git a/hw/ip/otp_ctrl/doc/_index.md b/hw/ip/otp_ctrl/doc/_index.md
index f9ffe5a..606d07c 100644
--- a/hw/ip/otp_ctrl/doc/_index.md
+++ b/hw/ip/otp_ctrl/doc/_index.md
@@ -190,7 +190,7 @@
Once the appropriate partitions have been locked, the hardware integrity checker employs two integrity checks to verify the content of the volatile buffer registers:
-1. All buffered partitions have additional byte parity protection that is concurrently monitored.
+1. All buffered partitions have additional ECC protection (8bit ECC for each 64bit block) that is concurrently monitored.
2. The digest of the partition is recomputed at semi-random intervals and compared to the digest stored alongside the partition.
The purpose of this check is NOT to check between the storage flops and the OTP, but whether the buffer register contents remain consistent with the calculated digest.
@@ -545,7 +545,7 @@
Also, read access through the DAI and the CSR window can be locked at runtime via a CSR.
Read transactions through the CSR window will error out if they are out of bounds, or if read access is locked.
-Note that unrecoverable [OTP errors]({{< relref "#generalized-open-source-interface" >}}) or parity failures in the digest register will move the partition controller into a terminal error state.
+Note that unrecoverable [OTP errors]({{< relref "#generalized-open-source-interface" >}}) or ECC failures in the digest register will move the partition controller into a terminal error state.
#### Buffered Partition
@@ -568,7 +568,7 @@
Otherwise, if no digest is available, the controller will read out the whole partition from OTP, and compare it to the contents stored in the buffer registers.
In case of a mismatch, the buffered values are gated to their default, and an alert is triggered through the error handling logic.
-Note that in case of unrecoverable OTP errors or parity failures in the buffer registers, the partition controller FSM is moved into a terminal error state, which locks down all access through DAI and clamps the values that are broadcast in hardware to their defaults.
+Note that in case of unrecoverable OTP errors or ECC failures in the buffer registers, the partition controller FSM is moved into a terminal error state, which locks down all access through DAI and clamps the values that are broadcast in hardware to their defaults.
### Direct Access Interface Control
@@ -883,7 +883,7 @@
0x3 | MacroEccUncorrError | no | x | - | x | x | An uncorrectable ECC error has occurred during a read operation in the OTP macro.
0x4 | MacroWriteBlankError | yes | x | x | x | x | This error is returned if a write operation attempted to overwrite an already programmed location.
0x5 | AccessError | yes | x | - | x | - | An access error has occurred (e.g. write to write-locked region, or read to a read-locked region).
-0x6 | CheckFailError | no | - | - | x | x | An unrecoverable parity, integrity or consistency error has been detected.
+0x6 | CheckFailError | no | - | - | x | x | An unrecoverable ECC, integrity or consistency error has been detected.
0x7 | FsmStateError | no | x | x | x | x | The FSM has been glitched into an invalid state, or escalation has been triggered and the FSM has been moved into a terminal error state.
All non-zero error codes listed above trigger an `otp_error` interrupt.
diff --git a/hw/ip/otp_ctrl/otp_ctrl.core b/hw/ip/otp_ctrl/otp_ctrl.core
index 629e82e..9bb0deb 100644
--- a/hw/ip/otp_ctrl/otp_ctrl.core
+++ b/hw/ip/otp_ctrl/otp_ctrl.core
@@ -14,10 +14,11 @@
- lowrisc:prim:ram_1p
- lowrisc:prim:otp
- lowrisc:prim:lfsr
+ - lowrisc:prim:secded
- lowrisc:ip:pwrmgr_pkg
files:
- rtl/otp_ctrl_reg_top.sv
- - rtl/otp_ctrl_parity_reg.sv
+ - rtl/otp_ctrl_ecc_reg.sv
- rtl/otp_ctrl_scrmbl.sv
- rtl/otp_ctrl_lfsr_timer.sv
- rtl/otp_ctrl_part_unbuf.sv
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_ecc_reg.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_ecc_reg.sv
new file mode 100644
index 0000000..2595fe8
--- /dev/null
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_ecc_reg.sv
@@ -0,0 +1,97 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Register file for buffered OTP partitions. ECC is used to detect up
+// to two simultaneous errors within each 64bit word.
+//
+// TODO: discuss whether reset is allowed here. We may also need a secure wiping feature.
+
+`include "prim_assert.sv"
+
+module otp_ctrl_ecc_reg #(
+ parameter int Width = 64, // bit
+ parameter int Depth = 128,
+ localparam int Aw = prim_util_pkg::vbits(Depth) // derived parameter
+) (
+ input logic clk_i,
+ input logic rst_ni,
+
+ input logic wren_i,
+ input logic [Aw-1:0] addr_i,
+ input logic [Width-1:0] wdata_i,
+
+ // Concurrent output of the register state.
+ output logic [Depth-1:0][Width-1:0] data_o,
+ // Concurrent ECC check error is flagged via this signal.
+ output logic ecc_err_o
+);
+
+ // Integration checks for parameters.
+ `ASSERT_INIT(WidthMustBe64bit_A, Width == 64)
+
+ localparam int EccWidth = 8;
+
+ logic [Depth-1:0][Width-1:0] data_d, data_q;
+ logic [Depth-1:0][EccWidth-1:0] ecc_d, ecc_q;
+ logic [Width+EccWidth-1:0] ecc_enc;
+
+ // Only one encoder is needed.
+ prim_secded_72_64_enc u_prim_secded_72_64_enc (
+ .in(wdata_i),
+ .out(ecc_enc)
+ );
+
+ if (Depth == 1) begin : gen_one_word_only
+ always_comb begin : p_write
+ data_o = data_q;
+ data_d = data_q;
+ ecc_d = ecc_q;
+
+ if (wren_i && 32'(addr_i) < Depth) begin
+ {ecc_d[0], data_d[0]} = ecc_enc;
+ end
+ end
+ end else begin : gen_multiple_words
+ always_comb begin : p_write
+ data_o = data_q;
+ data_d = data_q;
+ ecc_d = ecc_q;
+
+ if (wren_i && 32'(addr_i) < Depth) begin
+ {ecc_d[addr_i], data_d[addr_i]} = ecc_enc;
+ end
+ end
+ end
+
+ // Concurrent ECC checks.
+ logic [Depth-1:0][1:0] err;
+ for (genvar k = 0; k < Depth; k++) begin : gen_ecc_dec
+ prim_secded_72_64_dec u_prim_secded_72_64_dec (
+ .in({ecc_q[k], data_q[k]}),
+ // We only rely on the error detection mechanism,
+ // and not on error correction.
+ .d_o(),
+ .syndrome_o(),
+ .err_o(err[k])
+ );
+ end
+
+ assign ecc_err_o = |err;
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
+ if (!rst_ni) begin
+ ecc_q <= '0;
+ data_q <= '0;
+ end else begin
+ ecc_q <= ecc_d;
+ data_q <= data_d;
+ end
+ end
+
+ `ASSERT_KNOWN(EccKnown_A, ecc_q)
+ `ASSERT_KNOWN(DataKnown_A, data_q)
+ `ASSERT_KNOWN(DataOutKnown_A, data_o)
+ `ASSERT_KNOWN(EccErrKnown_A, ecc_err_o)
+
+endmodule : otp_ctrl_ecc_reg
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_parity_reg.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_parity_reg.sv
deleted file mode 100644
index 34bd250..0000000
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_parity_reg.sv
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-//
-// Parity-protected register file for buffered OTP partitions.
-//
-// TODO: discuss whether reset is allowed here. We may also need a secure wiping feature.
-
-`include "prim_assert.sv"
-
-module otp_ctrl_parity_reg #(
- parameter int Width = 32, // bit
- parameter int Depth = 128,
- localparam int Aw = prim_util_pkg::vbits(Depth) // derived parameter
-) (
- input logic clk_i,
- input logic rst_ni,
-
- input logic wren_i,
- input logic [Aw-1:0] addr_i,
- input logic [Width-1:0] wdata_i,
-
- // Concurrent output of the register state.
- output logic [Depth-1:0][Width-1:0] data_o,
- // Concurrent parity check error is flagged via this signal.
- output logic parity_err_o
-);
-
- // Integration checks for parameters.
- `ASSERT_INIT(WidthMustBeByteAligned_A, Width % 8 == 0)
-
- logic [Depth-1:0][Width-1:0] data_d, data_q;
- logic [Depth-1:0][Width/8-1:0] parity_d, parity_q;
-
- if (Depth == 1) begin : gen_one_word_only
- always_comb begin : p_write
- data_o = data_q;
- data_d = data_q;
- parity_d = parity_q;
-
- if (wren_i && 32'(addr_i) < Depth) begin
- data_d[0] = wdata_i;
- // Calculate EVEN parity bit for each Byte written.
- for (int k = 0; k < Width/8; k ++) begin
- parity_d[0][k] = ^wdata_i[k*8 +: 8];
- end
- end
- end
- end else begin : gen_multiple_words
- always_comb begin : p_write
- data_o = data_q;
- data_d = data_q;
- parity_d = parity_q;
-
- if (wren_i && 32'(addr_i) < Depth) begin
- data_d[addr_i] = wdata_i;
- // Calculate EVEN parity bit for each Byte written.
- for (int k = 0; k < Width/8; k ++) begin
- parity_d[addr_i][k] = ^wdata_i[k*8 +: 8];
- end
- end
- end
- end
-
- always_comb begin : p_parity
- // Concurrent parity check.
- parity_err_o = 1'b0;
- for (int j = 0; j < Depth; j ++) begin
- for (int k = 0; k < Width/8; k ++) begin
- parity_err_o |= ^{data_q[j][k*8 +: 8], parity_q[j][k]};
- end
- end
- end
-
- always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
- if (!rst_ni) begin
- parity_q <= '0;
- data_q <= '0;
- end else begin
- parity_q <= parity_d;
- data_q <= data_d;
- end
- end
-
- `ASSERT_KNOWN(ParityKnown_A, parity_q)
- `ASSERT_KNOWN(DataKnown_A, data_q)
-
-endmodule : otp_ctrl_parity_reg
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_part_buf.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_buf.sv
index e84fc35..4833d16 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_part_buf.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_buf.sv
@@ -141,7 +141,7 @@
logic [StateWidth-1:0] state_q;
logic [CntWidth-1:0] cnt_d, cnt_q;
logic cnt_en, cnt_clr;
- logic parity_err;
+ logic ecc_err;
logic buffer_reg_en;
logic [ScrmblBlockWidth-1:0] data_mux;
@@ -213,7 +213,7 @@
InitWaitSt: begin
if (otp_rvalid_i) begin
buffer_reg_en = 1'b1;
- // The only error we tolerate is an ECC soft error. However,
+ // The pnly error we tolerate is an ECC soft error. However,
// we still signal that error via the error state output.
if (!(otp_err_i inside {NoError, MacroEccCorrError})) begin
state_d = ErrorSt;
@@ -508,8 +508,8 @@
// Unconditionally jump into the terminal error state in case of
- // a parity error or escalation, and lock access to the partition down.
- if (parity_err) begin
+ // an ECC error or escalation, and lock access to the partition down.
+ if (ecc_err) begin
state_d = ErrorSt;
if (state_q != ErrorSt) begin
error_d = CheckFailError;
@@ -552,19 +552,18 @@
// Buffer Regs //
/////////////////
- // TODO: need to add secure erase feature here.
logic [Info.size*8-1:0] data;
- otp_ctrl_parity_reg #(
+ otp_ctrl_ecc_reg #(
.Width ( ScrmblBlockWidth ),
.Depth ( NumScrmblBlocks )
- ) u_otp_ctrl_parity_reg (
+ ) u_otp_ctrl_ecc_reg (
.clk_i,
.rst_ni,
- .wren_i ( buffer_reg_en ),
- .addr_i ( cnt_q ),
- .wdata_i ( data_mux ),
- .data_o ( data ),
- .parity_err_o ( parity_err )
+ .wren_i ( buffer_reg_en ),
+ .addr_i ( cnt_q ),
+ .wdata_i ( data_mux ),
+ .data_o ( data ),
+ .ecc_err_o ( ecc_err )
);
// Hardware output gating.
@@ -673,15 +672,15 @@
access_i.read_lock != Unlocked
|->
access_o.read_lock == Locked)
- // Parity error
- `ASSERT(ParityErrorState_A,
- parity_err
+ // ECC error in buffer regs
+ `ASSERT(EccErrorState_A,
+ ecc_err
|=>
state_q == ErrorSt)
// OTP error response
`ASSERT(OtpErrorState_A,
state_q inside {InitWaitSt, CnstyReadWaitSt} && otp_rvalid_i &&
- !(otp_err_i inside {NoError, MacroEccCorrError}) && !parity_err
+ !(otp_err_i inside {NoError, MacroEccCorrError}) && !ecc_err
|=>
state_q == ErrorSt && error_o == $past(otp_err_i))
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_part_unbuf.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_unbuf.sv
index b5e2896..6077e6c 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_part_unbuf.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_unbuf.sv
@@ -107,7 +107,7 @@
otp_err_e error_d, error_q;
logic digest_reg_en;
- logic parity_err;
+ logic ecc_err;
logic [SwWindowAddrWidth-1:0] tlul_addr_d, tlul_addr_q;
logic [StateWidth-1:0] state_q;
@@ -259,8 +259,8 @@
endcase // state_q
// Unconditionally jump into the terminal error state in case of
- // a parity error or escalation, and lock access to the partition down.
- if (parity_err) begin
+ // an ECC error or escalation, and lock access to the partition down.
+ if (ecc_err) begin
state_d = ErrorSt;
if (state_q != ErrorSt) begin
error_d = CheckFailError;
@@ -294,17 +294,17 @@
// Digest Reg //
////////////////
- otp_ctrl_parity_reg #(
+ otp_ctrl_ecc_reg #(
.Width ( ScrmblBlockWidth ),
.Depth ( 1 )
- ) u_otp_ctrl_parity_reg (
+ ) u_otp_ctrl_ecc_reg (
.clk_i,
.rst_ni,
- .wren_i ( digest_reg_en ),
- .addr_i ( '0 ),
- .wdata_i ( otp_rdata_i ),
- .data_o ( digest_o ),
- .parity_err_o ( parity_err )
+ .wren_i ( digest_reg_en ),
+ .addr_i ( '0 ),
+ .wdata_i ( otp_rdata_i ),
+ .data_o ( digest_o ),
+ .ecc_err_o ( ecc_err )
);
////////////////////////
@@ -407,15 +407,15 @@
tlul_req_i && tlul_gnt_o ##1 access_o.read_lock != Unlocked
|=>
tlul_rerror_o > '0 && tlul_rvalid_o)
- // Parity error
- `ASSERT(ParityErrorState_A,
- parity_err
+ // ECC error in buffer regs.
+ `ASSERT(EccErrorState_A,
+ ecc_err
|=>
state_q == ErrorSt)
// OTP error response
`ASSERT(OtpErrorState_A,
state_q inside {InitWaitSt, ReadWaitSt} && otp_rvalid_i &&
- !(otp_err_i inside {NoError, MacroEccCorrError}) && !parity_err
+ !(otp_err_i inside {NoError, MacroEccCorrError}) && !ecc_err
|=>
state_q == ErrorSt && error_o == $past(otp_err_i))