[otp_ctrl] Update LC types within OTP
Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/hw/ip/otp_ctrl/doc/_index.md b/hw/ip/otp_ctrl/doc/_index.md
index 82d8eb9..cafcefa 100644
--- a/hw/ip/otp_ctrl/doc/_index.md
+++ b/hw/ip/otp_ctrl/doc/_index.md
@@ -594,8 +594,8 @@

Upon reset release the LCI FSM waits until the OTP controller has initialized and the LCI gets enabled.
-Once it is in the idle state, incremental life cycle state updates can be initiated via the life cycle interface as [described here]({{< relref "#state-transitions" >}}).
-The LCI controller takes the life cycle state delta to be programmed and writes all non-blank 16bit words to OTP.
+Once it is in the idle state, life cycle state updates can be initiated via the life cycle interface as [described here]({{< relref "#state-transitions" >}}).
+The LCI controller takes the life cycle state to be programmed and writes all 16bit words to OTP.
In case of unrecoverable OTP errors, the FSM signals an error to the life cycle controller and moves into a terminal error state.
### Key Derivation Interface
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
index 8d9a348..ee73824 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
@@ -758,8 +758,8 @@
.error_o ( part_error[LciIdx] ),
.lci_idle_o ( lci_idle ),
.lc_req_i ( lc_otp_program_i.req ),
- .lc_state_delta_i ( lc_otp_program_i.state_delta ),
- .lc_count_delta_i ( lc_otp_program_i.count_delta ),
+ .lc_state_i ( lc_otp_program_i.state ),
+ .lc_count_i ( lc_otp_program_i.count ),
.lc_ack_o ( lc_otp_program_o.ack ),
.lc_err_o ( lc_otp_program_o.err ),
.otp_req_o ( part_otp_arb_req[LciIdx] ),
@@ -940,7 +940,7 @@
otp_ctrl_part_buf #(
.Info(PartInfo[k]),
// By default all counter words are set and the life cycle state is scrapped.
- .DataDefault({{lc_ctrl_pkg::NumLcCountValues{lc_ctrl_pkg::Set}}, lc_ctrl_pkg::LcStScrap})
+ .DataDefault({lc_ctrl_pkg::LcCnt16, lc_ctrl_pkg::LcStScrap})
) u_part_buf (
.clk_i,
.rst_ni,
@@ -1024,13 +1024,14 @@
RmaTokenSize];
// The device is personalized if the root key has been provisioned and locked
- assign otp_lc_data_o.id_state = (part_digest[Secret2Idx] != '0) ? lc_ctrl_pkg::Set :
- lc_ctrl_pkg::Blk;
+ assign otp_lc_data_o.id_state = (part_digest[Secret2Idx] != '0) ?
+ lc_ctrl_pkg::LcIdPersonalized :
+ lc_ctrl_pkg::LcIdBlank;
// Lifecycle state
assign otp_lc_data_o.state = lc_ctrl_pkg::lc_state_e'(part_buf_data[LcStateOffset +:
LcStateSize]);
- assign otp_lc_data_o.count = lc_ctrl_pkg::lc_cnt_t'(part_buf_data[LcTransitionCntOffset +:
+ assign otp_lc_data_o.count = lc_ctrl_pkg::lc_cnt_e'(part_buf_data[LcTransitionCntOffset +:
LcTransitionCntSize]);
// Assert life cycle state valid signal only when all partitions have initialized.
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv
index b7ca7cf..aa01105 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv
@@ -14,21 +14,22 @@
// Lifecycle partition information
parameter part_info_t Info = part_info_t'(0)
) (
- input clk_i,
- input rst_ni,
- input lci_en_i,
+ input clk_i,
+ input rst_ni,
+ input lci_en_i,
// Escalation input. This moves the FSM into a terminal state and locks down
// the partition.
- input lc_ctrl_pkg::lc_tx_t escalate_en_i,
+ input lc_ctrl_pkg::lc_tx_t escalate_en_i,
// Life cycle transition request. In order to perform a state transition,
- // the LC controller signals the incremental value with respect to the
- // current state. The OTP controller then only programs the non-zero
- // state delta.
- input lc_req_i,
- input lc_ctrl_pkg::lc_state_e lc_state_delta_i,
- input lc_ctrl_pkg::lc_cnt_t lc_count_delta_i,
- output logic lc_ack_o,
- output logic lc_err_o,
+ // the LC controller signals the new count and state. The OTP wrapper then
+ // only programs bits that have not been programmed before.
+ // Note that a transition request will fail if the request attempts to
+ // clear already programmed bits within OTP.
+ input lc_req_i,
+ input lc_ctrl_pkg::lc_state_e lc_state_i,
+ input lc_ctrl_pkg::lc_cnt_e lc_count_i,
+ output logic lc_ack_o,
+ output logic lc_err_o,
// Output error state of partition, to be consumed by OTP error/alert logic.
// Note that most errors are not recoverable and move the partition FSM into
// a terminal error state.
@@ -91,7 +92,6 @@
logic cnt_clr, cnt_en;
logic [CntWidth-1:0] cnt_d, cnt_q;
otp_err_e error_d, error_q;
- logic delta_data_is_set;
state_e state_d, state_q;
// Output LCI errors
@@ -135,23 +135,15 @@
end
end
///////////////////////////////////////////////////////////////////
- // Loop through the lifecycle sate and burn in the words that are set.
+ // Loop through the lifecycle sate and burn in all words.
+ // If the write data contains a 0 bit in a position where a bit has already been
+ // programmed to 1 before, the OTP errors out.
WriteSt: begin
// Check whether the OTP word is nonzero.
- if (delta_data_is_set) begin
- otp_req_o = 1'b1;
- otp_cmd_o = OtpWrite;
- if (otp_gnt_i) begin
- state_d = WriteWaitSt;
- end
- // Check whether we examined all OTP words.
- // If yes, we are done and can go back to idle.
- end else if (cnt_q == NumLcOtpWords-1) begin
- state_d = IdleSt;
- lc_ack_o = 1'b1;
- // Otherwise we increase the OTP word counter.
- end else begin
- cnt_en = 1'b1;
+ otp_req_o = 1'b1;
+ otp_cmd_o = OtpWrite;
+ if (otp_gnt_i) begin
+ state_d = WriteWaitSt;
end
end
///////////////////////////////////////////////////////////////////
@@ -160,17 +152,13 @@
// terminal error state.
WriteWaitSt: begin
if (otp_rvalid_i) begin
- // Check OTP return code. We only tolerate a MacroWriteBlankError error here -
- // all other errors should not occur and are therefore terminal.
+ // Check OTP return code.
+ // No errors are tolerated here.
if (otp_err_i != NoError) begin
error_d = otp_err_i;
lc_ack_o = 1'b1;
lc_err_o = 1'b1;
- if (otp_err_i != MacroWriteBlankError) begin
- state_d = ErrorSt;
- end else begin
- state_d = IdleSt;
- end
+ state_d = ErrorSt;
end else begin
// Check whether we examined all OTP words.
// If yes, we are done and can go back to idle.
@@ -228,10 +216,9 @@
// Always transfer 16bit blocks.
assign otp_size_o = '0;
- logic [NumLcOtpWords-1:0][OtpWidth-1:0] delta_data;
- assign delta_data = {lc_count_delta_i, lc_state_delta_i};
- assign otp_wdata_o = OtpIfWidth'(delta_data[cnt_q]);
- assign delta_data_is_set = (delta_data[cnt_q] != lc_ctrl_pkg::Blk);
+ logic [NumLcOtpWords-1:0][OtpWidth-1:0] data;
+ assign data = {lc_count_i, lc_state_i};
+ assign otp_wdata_o = OtpIfWidth'(data[cnt_q]);
logic unused_rdata;
assign unused_rdata = ^otp_rdata_i;
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
index bffde32..47a7d12 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
@@ -202,19 +202,19 @@
///////////////////////////////
typedef struct packed {
- logic valid;
- lc_ctrl_pkg::lc_state_e state;
- lc_ctrl_pkg::lc_cnt_t count;
- logic [lc_ctrl_pkg::LcTokenWidth-1:0] test_unlock_token;
- logic [lc_ctrl_pkg::LcTokenWidth-1:0] test_exit_token;
- logic [lc_ctrl_pkg::LcTokenWidth-1:0] rma_token;
- lc_ctrl_pkg::lc_value_e id_state;
+ logic valid;
+ lc_ctrl_pkg::lc_state_e state;
+ lc_ctrl_pkg::lc_cnt_e count;
+ lc_ctrl_pkg::lc_token_t test_unlock_token;
+ lc_ctrl_pkg::lc_token_t test_exit_token;
+ lc_ctrl_pkg::lc_token_t rma_token;
+ lc_ctrl_pkg::lc_id_state_e id_state;
} otp_lc_data_t;
typedef struct packed {
logic req;
- lc_ctrl_pkg::lc_state_e state_delta;
- lc_ctrl_pkg::lc_cnt_t count_delta;
+ lc_ctrl_pkg::lc_state_e state;
+ lc_ctrl_pkg::lc_cnt_e count;
} lc_otp_program_req_t;
typedef struct packed {
@@ -225,12 +225,12 @@
// RAW unlock token hashing request.
typedef struct packed {
logic req;
- logic [lc_ctrl_pkg::LcTokenWidth-1:0] token_input;
+ lc_ctrl_pkg::lc_token_t token_input;
} lc_otp_token_req_t;
typedef struct packed {
logic ack;
- logic [lc_ctrl_pkg::LcTokenWidth-1:0] hashed_token;
+ lc_ctrl_pkg::lc_token_t hashed_token;
} lc_otp_token_rsp_t;
////////////////////////////////
diff --git a/hw/ip/prim_generic/rtl/prim_generic_otp.sv b/hw/ip/prim_generic/rtl/prim_generic_otp.sv
index 107bd52..f22b00a 100644
--- a/hw/ip/prim_generic/rtl/prim_generic_otp.sv
+++ b/hw/ip/prim_generic/rtl/prim_generic_otp.sv
@@ -10,7 +10,6 @@
// This determines the maximum number of native words that
// can be transferred accross the interface in one cycle.
parameter int SizeWidth = otp_ctrl_pkg::OtpSizeWidth,
- parameter int ErrWidth = otp_ctrl_pkg::OtpErrWidth,
// Width of the power sequencing signal.
parameter int PwrSeqWidth = otp_ctrl_pkg::OtpPwrSeqWidth,
// Number of Test TL-UL words
@@ -231,12 +230,13 @@
req = 1'b1;
end
// Wait for readout to complete first.
- // If the word is not blank, or if we got an uncorrectable ECC error,
- // the check has failed and we abort the write at this point.
+ // If the write data would clear an already programmed bit, or if we got an uncorrectable
+ // ECC error, the check has failed and we abort the write at this point.
WriteWaitSt: begin
if (rvalid) begin
cnt_en = 1'b1;
- if (rerror[1] || rdata_d != '0) begin
+ // TODO: this blank check needs to be extended to account for the ECC bits as well.
+ if (rerror[1] || (rdata_d & wdata_q[cnt_q]) != rdata_d) begin
state_d = IdleSt;
valid_d = 1'b1;
err_d = otp_ctrl_pkg::MacroWriteBlankError;
@@ -250,7 +250,7 @@
end
end
end
- // Now that we are sure that the memory is blank, we can write all native words in one go.
+ // Now that the write check was successful, we can write all native words in one go.
WriteSt: begin
req = 1'b1;
wren = 1'b1;