[dv/otp_ctrl] Fix regression lc output mismatch
This PR fixes three lc_esc related mismatch:
1). when lc_esc is On, the otp_lc_o should return default value.
2). when lc_esc is issued during otp write, it will wait until OTP write
complete, then backdoor align
3). when reset/lc_esc_on is issued during digest calculate, will use
backdoor to recover digest value.
This PR also adds lc_esc_en sequence to stress_all test.
Signed-off-by: Cindy Chen <chencindy@google.com>
diff --git a/hw/ip/otp_ctrl/dv/env/otp_ctrl_if.sv b/hw/ip/otp_ctrl/dv/env/otp_ctrl_if.sv
index 1d3dcf4..fd517c8 100644
--- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_if.sv
+++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_if.sv
@@ -43,6 +43,7 @@
lc_esc_dly1 <= lc_ctrl_pkg::Off;
lc_esc_dly2 <= lc_ctrl_pkg::Off;
lc_check_byp_en_i <= lc_ctrl_pkg::Off;
+ lc_esc_on <= 0;
end else begin
lc_prog_err_dly1 <= lc_prog_err;
lc_esc_dly1 <= lc_escalate_en_i;
@@ -50,13 +51,14 @@
if (lc_prog_req && lc_check_byp_en_i == lc_ctrl_pkg::Off && lc_check_byp_en) begin
lc_check_byp_en_i <= lc_ctrl_pkg::On;
end
+ if (lc_esc_dly2 == lc_ctrl_pkg::On && !lc_esc_on) begin
+ lc_esc_on <= 1;
+ end
end
end
assign lc_prog_no_sta_check = lc_prog_err | lc_prog_err_dly1 | lc_prog_req | lc_esc_on;
- assign lc_esc_on = lc_esc_dly2 != lc_ctrl_pkg::Off;
-
// TODO: for lc_tx, except esc_en signal, all value different from On is treated as Off,
// technically we can randomize values here once scb supports
task automatic init();
diff --git a/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv b/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv
index 9b4dc7d..3705788 100644
--- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv
+++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv
@@ -18,6 +18,7 @@
// This flag is used when reset is issued during otp dai write access.
bit dai_wr_ip;
+ int dai_digest_ip = LifeCycleIdx; // Default to LC as it does not have digest.
// This bit is used for DAI interface to mark if the read access is valid.
bit dai_read_valid;
@@ -111,6 +112,12 @@
otp_ctrl_pkg::otp_keymgr_key_t exp_keymgr_data;
bit [otp_ctrl_pkg::KeyMgrKeyWidth-1:0] exp_keymgr_key0, exp_keymgr_key1;
+ if (dai_digest_ip != LifeCycleIdx) begin
+ bit [TL_DW-1:0] otp_addr = PART_OTP_DIGEST_ADDRS[dai_digest_ip];
+ otp_a[otp_addr] = cfg.mem_bkdr_vif.read32(otp_addr << 2);
+ otp_a[otp_addr+1] = cfg.mem_bkdr_vif.read32((otp_addr << 2) + 4);
+ dai_digest_ip = LifeCycleIdx;
+ end
predict_digest_csrs();
if (cfg.otp_ctrl_vif.lc_esc_on == 0) begin
@@ -127,8 +134,11 @@
otp_hw_cfg_data_t'({<<32 {otp_a[HwCfgOffset/4 +: HwCfgSize/4]}});
`DV_CHECK_EQ(cfg.otp_ctrl_vif.otp_hw_cfg_o.data, exp_hwcfg_data)
- `DV_CHECK_EQ(cfg.otp_ctrl_vif.lc_data_o.count, otp_lc_data[0 +: LcCountWidth])
- `DV_CHECK_EQ(cfg.otp_ctrl_vif.lc_data_o.state, otp_lc_data[LcCountWidth +: LcStateWidth])
+ if (!cfg.otp_ctrl_vif.lc_esc_on) begin
+ `DV_CHECK_EQ(cfg.otp_ctrl_vif.lc_data_o.count, otp_lc_data[0 +: LcCountWidth])
+ `DV_CHECK_EQ(cfg.otp_ctrl_vif.lc_data_o.state,
+ otp_lc_data[LcCountWidth +: LcStateWidth])
+ end
// Otp_keymgr outputs creator root key shares from the secret2 partition.
// Depends on lc_seed_hw_rd_en_i, it will output the real keys or a constant
@@ -166,8 +176,6 @@
// LC_escalate_en will trigger fatal check alert.
set_exp_alert("fatal_check_error", 1, 5);
- recover_interrupted_op();
-
// Update status bits.
exp_status = '0;
for (int i = 0; i < OtpTimeoutErrIdx; i++) begin
@@ -179,8 +187,11 @@
// Update digest values and direct_access_regwen.
predict_rdata(1, 0, 0);
void'(ral.direct_access_regwen.predict(0));
- void'(ral.creator_sw_cfg_read_lock.predict(0));
- void'(ral.owner_sw_cfg_read_lock.predict(0));
+
+ // Unlike reset, OTP write won't exit immediately when lc_escalate_en is On.
+ // So here wait until otp program done, then backdoor read.
+ cfg.clk_rst_vif.wait_clks(12);
+ recover_interrupted_op();
wait(cfg.otp_ctrl_vif.lc_esc_on == 0);
end
@@ -513,24 +524,22 @@
sw_read_lock = `gmv(ral.owner_sw_cfg_read_lock) == 0;
end
- // SW partitions write read_lock_csr can lock read access
- // Secret partitions cal digest can also lock read access
- // However, digest is always readable except SW partitions (Issue #5752)
- if (sw_read_lock || (is_secret(dai_addr) && get_digest_reg_val(part_idx) != 0 &&
- !is_digest(dai_addr))) begin
+ // SW partitions write read_lock_csr can lock read access.
+ if (sw_read_lock ||
+ // Secret partitions cal digest can also lock read access.
+ // However, digest is always readable except SW partitions (Issue #5752).
+ (is_secret(dai_addr) && get_digest_reg_val(part_idx) != 0 &&
+ !is_digest(dai_addr)) ||
+ // If the partition is secret2 and lc_creator_seed_sw_rw is disable, then
+ // return access error.
+ (part_idx == Secret2Idx && !is_digest(dai_addr) &&
+ cfg.otp_ctrl_vif.lc_creator_seed_sw_rw_en_i == lc_ctrl_pkg::Off)) begin
predict_err(OtpDaiErrIdx, OtpAccessError);
predict_rdata(is_secret(dai_addr) || is_digest(dai_addr), 0, 0);
// DAI interface access error, even though injected ECC error, it won't be read
// out and detected. (TODO: can remove this once ECC is adopted in mem_bkdr_if)
cfg.ecc_err = OtpNoEccErr;
- // If the partition is secret2 and lc_creator_seed_sw_rw is disable, then cannot
- // return access error.
- end else if (part_idx == Secret2Idx &&
- cfg.otp_ctrl_vif.lc_creator_seed_sw_rw_en_i == lc_ctrl_pkg::Off &&
- !is_digest(dai_addr)) begin
- predict_err(OtpDaiErrIdx, OtpAccessError);
- predict_rdata(is_secret(dai_addr) || is_digest(dai_addr), 0, 0);
end else begin
bit [TL_AW-1:0] otp_addr = get_scb_otp_addr();
if (cfg.ecc_err == OtpNoEccErr) begin
@@ -559,11 +568,9 @@
DaiWrite: begin
bit[TL_AW-1:0] otp_addr = get_scb_otp_addr();
// check if write locked
- if (get_digest_reg_val(part_idx) != 0) begin
- predict_err(OtpDaiErrIdx, OtpAccessError);
- end else if (part_idx == Secret2Idx &&
- cfg.otp_ctrl_vif.lc_creator_seed_sw_rw_en_i == lc_ctrl_pkg::Off &&
- !is_digest(dai_addr)) begin
+ if (get_digest_reg_val(part_idx) != 0 ||
+ (part_idx == Secret2Idx && !is_digest(dai_addr) &&
+ cfg.otp_ctrl_vif.lc_creator_seed_sw_rw_en_i == lc_ctrl_pkg::Off)) begin
predict_err(OtpDaiErrIdx, OtpAccessError);
end else begin
predict_no_err(OtpDaiErrIdx);
@@ -645,6 +652,7 @@
if (item.d_data[OtpDaiIdleIdx]) begin
check_otp_idle(1);
dai_wr_ip = 0;
+ dai_digest_ip = LifeCycleIdx;
end
// STATUS register check with mask
@@ -815,25 +823,25 @@
void'(ral.owner_sw_cfg_digest_1.predict(
.value(otp_a[PART_OTP_DIGEST_ADDRS[OwnerSwCfgIdx] + 1]), .kind(UVM_PREDICT_DIRECT)));
- void'(ral.hw_cfg_digest_0.predict(
- .value(otp_a[PART_OTP_DIGEST_ADDRS[HwCfgIdx]]), .kind(UVM_PREDICT_DIRECT)));
- void'(ral.hw_cfg_digest_1.predict(
- .value(otp_a[PART_OTP_DIGEST_ADDRS[HwCfgIdx] + 1]), .kind(UVM_PREDICT_DIRECT)));
+ void'(ral.hw_cfg_digest_0.predict(.value((cfg.otp_ctrl_vif.lc_esc_on) ? 0 :
+ otp_a[PART_OTP_DIGEST_ADDRS[HwCfgIdx]]), .kind(UVM_PREDICT_DIRECT)));
+ void'(ral.hw_cfg_digest_1.predict(.value((cfg.otp_ctrl_vif.lc_esc_on) ? 0 :
+ otp_a[PART_OTP_DIGEST_ADDRS[HwCfgIdx] + 1]), .kind(UVM_PREDICT_DIRECT)));
- void'(ral.secret0_digest_0.predict(
- .value(otp_a[PART_OTP_DIGEST_ADDRS[Secret0Idx]]), .kind(UVM_PREDICT_DIRECT)));
- void'(ral.secret0_digest_1.predict(
- .value(otp_a[PART_OTP_DIGEST_ADDRS[Secret0Idx] + 1]), .kind(UVM_PREDICT_DIRECT)));
+ void'(ral.secret0_digest_0.predict(.value((cfg.otp_ctrl_vif.lc_esc_on) ? 0 :
+ otp_a[PART_OTP_DIGEST_ADDRS[Secret0Idx]]), .kind(UVM_PREDICT_DIRECT)));
+ void'(ral.secret0_digest_1.predict(.value((cfg.otp_ctrl_vif.lc_esc_on) ? 0 :
+ otp_a[PART_OTP_DIGEST_ADDRS[Secret0Idx] + 1]), .kind(UVM_PREDICT_DIRECT)));
- void'(ral.secret1_digest_0.predict(
- .value(otp_a[PART_OTP_DIGEST_ADDRS[Secret1Idx]]), .kind(UVM_PREDICT_DIRECT)));
- void'(ral.secret1_digest_1.predict(
- .value(otp_a[PART_OTP_DIGEST_ADDRS[Secret1Idx] + 1]), .kind(UVM_PREDICT_DIRECT)));
+ void'(ral.secret1_digest_0.predict(.value((cfg.otp_ctrl_vif.lc_esc_on) ? 0 :
+ otp_a[PART_OTP_DIGEST_ADDRS[Secret1Idx]]), .kind(UVM_PREDICT_DIRECT)));
+ void'(ral.secret1_digest_1.predict(.value((cfg.otp_ctrl_vif.lc_esc_on) ? 0 :
+ otp_a[PART_OTP_DIGEST_ADDRS[Secret1Idx] + 1]), .kind(UVM_PREDICT_DIRECT)));
- void'(ral.secret2_digest_0.predict(
- .value(otp_a[PART_OTP_DIGEST_ADDRS[Secret2Idx]]), .kind(UVM_PREDICT_DIRECT)));
- void'(ral.secret2_digest_1.predict(
- .value(otp_a[PART_OTP_DIGEST_ADDRS[Secret2Idx] + 1]), .kind(UVM_PREDICT_DIRECT)));
+ void'(ral.secret2_digest_0.predict(.value((cfg.otp_ctrl_vif.lc_esc_on) ? 0 :
+ otp_a[PART_OTP_DIGEST_ADDRS[Secret2Idx]]), .kind(UVM_PREDICT_DIRECT)));
+ void'(ral.secret2_digest_1.predict(.value((cfg.otp_ctrl_vif.lc_esc_on) ? 0 :
+ otp_a[PART_OTP_DIGEST_ADDRS[Secret2Idx] + 1]), .kind(UVM_PREDICT_DIRECT)));
endfunction
function void update_digest_to_otp(int part_idx, bit [TL_DW*2-1:0] digest);
@@ -871,6 +879,7 @@
return;
end else begin
predict_no_err(OtpDaiErrIdx);
+ dai_digest_ip = part_idx;
end
case (part_idx)
HwCfgIdx: mem_q = otp_a[HW_CFG_START_ADDR:HW_CFG_END_ADDR];
@@ -1075,16 +1084,16 @@
virtual function bit is_tl_mem_access_allowed(tl_seq_item item, string ral_name);
// If sw partition is read locked, then access policy changes from RO to no access
uvm_reg_addr_t addr = ral.get_word_aligned_addr(item.a_addr);
- if (`gmv(ral.creator_sw_cfg_read_lock) == 0) begin
+ if (`gmv(ral.creator_sw_cfg_read_lock) == 0 || cfg.otp_ctrl_vif.lc_esc_on == 1) begin
if (addr inside {[cfg.mem_ranges[ral_name][0].start_addr :
- cfg.mem_ranges[ral_name][0].start_addr + CreatorSwCfgSize - 1]}) begin
+ cfg.mem_ranges[ral_name][0].start_addr + CreatorSwCfgSize - 1]}) begin
predict_err(OtpCreatorSwCfgErrIdx, OtpAccessError);
`DV_CHECK_EQ(item.d_data, 0,
$sformatf("locked mem read mismatch at TLUL addr %0h in CreatorSwCfg", addr))
return 0;
end
end
- if (`gmv(ral.owner_sw_cfg_read_lock) == 0) begin
+ if (`gmv(ral.owner_sw_cfg_read_lock) == 0 || cfg.otp_ctrl_vif.lc_esc_on == 1) begin
if (addr inside {[cfg.mem_ranges[ral_name][0].start_addr + OwnerSwCfgOffset :
cfg.mem_ranges[ral_name][0].start_addr + OwnerSwCfgSize + OwnerSwCfgOffset - 1]}) begin
predict_err(OtpOwnerSwCfgErrIdx, OtpAccessError);
diff --git a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_parallel_lc_esc_vseq.sv b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_parallel_lc_esc_vseq.sv
index 4a85c59..a9d59e9 100644
--- a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_parallel_lc_esc_vseq.sv
+++ b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_parallel_lc_esc_vseq.sv
@@ -47,18 +47,12 @@
// TODO: in alert_esc_monitor, makes it auto-response like push-pull agent
if (en_auto_alerts_response && cfg.list_of_alerts.size()) run_alert_rsp_seq_nonblocking();
- // Turn off reset because if issuing lc_escalation_en during otp program, scb cannot
- // predict if the OTP memory is programmed or not.
- // TODO: temp disable, support it in stress_all_with_rand_reset case
- do_reset_in_seq = 0;
-
// Wait 5 clock cycles until async lc_escalate_en propogate to each state machine.
cfg.clk_rst_vif.wait_clks(5);
// After LC_escalate is On, we trigger the dai_errs_vseq to check interfaces will return
// default values and the design won't hang.
otp_ctrl_dai_errs_vseq::body();
- do_reset_in_seq = 1;
endtask
virtual task post_start();
diff --git a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_stress_all_vseq.sv b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_stress_all_vseq.sv
index e0ee9d2..135d66b 100644
--- a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_stress_all_vseq.sv
+++ b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_stress_all_vseq.sv
@@ -18,6 +18,7 @@
// Drive dft_en pins to access the test_access memory, this is used for tl_error sequence in
// stress_all_with_rand_reset test
cfg.otp_ctrl_vif.drive_lc_dft_en(lc_ctrl_pkg::On);
+ if ($urandom_range(0, 1)) cfg.otp_ctrl_vif.drive_lc_escalate_en(lc_ctrl_pkg::Off);
// Once turn on lc_dft_en regiser, will need some time to update the state register
// Two clock cycles for lc_async mode, one clock cycle for driving dft_en
@@ -32,6 +33,7 @@
"otp_ctrl_test_access_vseq",
// TODO: support this seq:
// "otp_ctrl_parallel_lc_req_vseq",
+ "otp_ctrl_parallel_lc_esc_vseq",
"otp_ctrl_parallel_key_req_vseq"};
for (int i = 1; i <= num_trans; i++) begin
@@ -76,6 +78,7 @@
endtask : body
virtual task read_and_check_all_csrs_after_reset();
+ cfg.otp_ctrl_vif.drive_lc_escalate_en(lc_ctrl_pkg::Off);
otp_pwr_init();
super.read_and_check_all_csrs_after_reset();
endtask