[dv/otp_ctrl] initial commit for OTP sanity sequence This PR supports OTP sanity sequence. This PR covers all the sequence for DAI interface. There will be a following PR that supports exercising all interfaces within OTP. Signed-off-by: Cindy Chen <chencindy@google.com>
diff --git a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env.sv b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env.sv index 4277f77..7cb1f80 100644 --- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env.sv +++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env.sv
@@ -17,6 +17,10 @@ if (!uvm_config_db#(pwr_otp_vif)::get(this, "", "pwr_otp_vif", cfg.pwr_otp_vif)) begin `uvm_fatal(get_full_name(), "failed to get pwr_otp_vif from uvm_config_db") end + if (!uvm_config_db#(lc_provision_en_vif)::get(this, "", "lc_provision_en_vif", + cfg.lc_provision_en_vif)) begin + `uvm_fatal(get_full_name(), "failed to get lc_provision_en_vif from uvm_config_db") + end if (!uvm_config_db#(mem_bkdr_vif)::get(this, "", "mem_bkdr_vif", cfg.mem_bkdr_vif)) begin `uvm_fatal(`gfn, "failed to get mem_bkdr_vif from uvm_config_db") end
diff --git a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_cfg.sv b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_cfg.sv index 04502fd..e237d4d 100644 --- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_cfg.sv +++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_cfg.sv
@@ -5,9 +5,9 @@ class otp_ctrl_env_cfg extends cip_base_env_cfg #(.RAL_T(otp_ctrl_reg_block)); // ext component cfgs - //pwr_vif pwr_vif; - pwr_otp_vif pwr_otp_vif; - mem_bkdr_vif mem_bkdr_vif; + pwr_otp_vif pwr_otp_vif; + lc_provision_en_vif lc_provision_en_vif; + mem_bkdr_vif mem_bkdr_vif; `uvm_object_utils_begin(otp_ctrl_env_cfg) `uvm_object_utils_end
diff --git a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_pkg.sv b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_pkg.sv index e8245ef..b1fa13d 100644 --- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_pkg.sv +++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_pkg.sv
@@ -12,23 +12,72 @@ import cip_base_pkg::*; import csr_utils_pkg::*; import otp_ctrl_ral_pkg::*; + import otp_ctrl_pkg::*; + import lc_ctrl_pkg::*; // macro includes `include "uvm_macros.svh" `include "dv_macros.svh" // parameters + parameter uint DIGEST_SIZE = 8; + parameter uint CREATOR_WINDOW_BASE_ADDR = 'h1000; + parameter uint OWNER_WINDOW_BASE_ADDR = 'h1300; + parameter uint TEST_ACCESS_BASE_ADDR = 'h2000; + parameter uint WINDOW_SIZE = 512 * 4; + + // lc does not have digest + parameter bit[10:0] DIGESTS_ADDR [NumPart-1] = { + PartInfo[CreatorSwCfgIdx].offset + PartInfo[CreatorSwCfgIdx].size - DIGEST_SIZE, + PartInfo[OwnerSwCfgIdx].offset + PartInfo[OwnerSwCfgIdx].size - DIGEST_SIZE, + PartInfo[HwCfgIdx].offset + PartInfo[HwCfgIdx].size - DIGEST_SIZE, + PartInfo[Secret0Idx].offset + PartInfo[Secret0Idx].size - DIGEST_SIZE, + PartInfo[Secret1Idx].offset + PartInfo[Secret1Idx].size - DIGEST_SIZE, + PartInfo[Secret2Idx].offset + PartInfo[Secret2Idx].size - DIGEST_SIZE}; // types typedef virtual pins_if #(3) pwr_otp_vif; + typedef virtual pins_if #(4) lc_provision_en_vif; typedef virtual mem_bkdr_if mem_bkdr_vif; - typedef enum { + typedef enum bit [1:0] { OtpOperationDone, - OtpErr + OtpErr, + NumOtpCtrlIntr } otp_intr_e; + // TODO: needs update once design finalize + typedef enum bit [6:0] { + OtpCheckPending = 7'b0_000_001, + OtpIdle = 7'b0_000_010, + OtpKeyFsmErr = 7'b0_000_100, + OtpScrmblFsmErr = 7'b0_001_000, + OtpLfsrFsmErr = 7'b0_010_000, + OtpChkTimeout = 7'b0_100_000, + OptPartErrs = 7'b1_000_000 + } otp_status_e; + // functions + function automatic int get_part_index(bit [TL_DW-1:0] addr); + int index; + for (index = 0; index < otp_ctrl_pkg::NumPart; index++) begin + if (PartInfo[index].offset > addr) begin + index--; + break; + end + end + return index; + endfunction + + function automatic bit is_secret(bit [TL_DW-1:0] addr); + int part_index = get_part_index(addr); + if (part_index inside {[Secret0Idx:Secret2Idx]}) return 0; + else return 1; + endfunction + + function automatic bit [TL_AW-1:0] get_sw_window_addr(bit [TL_AW-1:0] dai_addr); + get_sw_window_addr = dai_addr + CREATOR_WINDOW_BASE_ADDR; + endfunction // package sources `include "otp_ctrl_env_cfg.sv"
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 344d64d..7f635b0 100644 --- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv +++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv
@@ -33,10 +33,11 @@ virtual task process_tl_access(tl_seq_item item, tl_channels_e channel = DataChannel); uvm_reg csr; - bit do_read_check = 1'b1; - bit write = item.is_write(); - uvm_reg_addr_t csr_addr = get_normalized_addr(item.a_addr); - + bit do_read_check = 1'b0; + bit write = item.is_write(); + uvm_reg_addr_t csr_addr = get_normalized_addr(item.a_addr); + bit [TL_AW-1:0] addr_mask = cfg.csr_addr_map_size - 1; + bit addr_phase_read = (!write && channel == AddrChannel); bit addr_phase_write = (write && channel == AddrChannel); bit data_phase_read = (!write && channel == DataChannel); @@ -46,8 +47,13 @@ if (csr_addr inside {cfg.csr_addrs}) begin csr = ral.default_map.get_reg_by_offset(csr_addr); `DV_CHECK_NE_FATAL(csr, null) - end - else begin + // memories + // TODO: memory read check, change hardcoded to parameters once design finalized + end else if ((csr_addr & addr_mask) inside + {[CREATOR_WINDOW_BASE_ADDR : CREATOR_WINDOW_BASE_ADDR + WINDOW_SIZE], + [TEST_ACCESS_BASE_ADDR : TEST_ACCESS_BASE_ADDR + WINDOW_SIZE]}) begin + return; + end else begin `uvm_fatal(`gfn, $sformatf("Access unexpected addr 0x%0h", csr_addr)) end @@ -72,7 +78,7 @@ // FIXME end default: begin - `uvm_fatal(`gfn, $sformatf("invalid csr: %0s", csr.get_full_name())) + //`uvm_fatal(`gfn, $sformatf("invalid csr: %0s", csr.get_full_name())) end endcase
diff --git a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_base_vseq.sv b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_base_vseq.sv index d543184..bee784a 100644 --- a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_base_vseq.sv +++ b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_base_vseq.sv
@@ -13,10 +13,15 @@ // various knobs to enable certain routines bit do_otp_ctrl_init = 1'b1; + rand bit [NumOtpCtrlIntr-1:0] en_intr; + `uvm_object_new virtual task dut_init(string reset_kind = "HARD"); - super.dut_init(); + super.dut_init(reset_kind); + cfg.pwr_otp_vif.drive_pin(0, 0); + cfg.lc_provision_en_vif.drive(lc_ctrl_pkg::Off); + // reset power init pin and lc_provision_en pin if (do_otp_ctrl_init) otp_ctrl_init(); endtask @@ -27,9 +32,51 @@ // setup basic otp_ctrl features virtual task otp_ctrl_init(); - cfg.pwr_otp_vif.drive_pin(0, 0); // reset memory to avoid readout X cfg.mem_bkdr_vif.clear_mem(); + csr_wr(ral.intr_enable, en_intr); + endtask + + // this task triggers an OTP write sequence via the DAI interface + virtual task dai_wr(bit [TL_DW-1:0] addr, + bit [TL_DW-1:0] wdata0, + bit [TL_DW-1:0] wdata1 = 0); + csr_wr(ral.direct_access_address, addr); + csr_wr(ral.direct_access_wdata_0, wdata0); + if (!is_secret(addr)) csr_wr(ral.direct_access_wdata_1, wdata1); + csr_wr(ral.direct_access_cmd, int'(otp_ctrl_pkg::DaiWrite)); + csr_spinwait(ral.intr_state, 1 << OtpOperationDone); + csr_wr(ral.intr_state, 1'b1 << OtpOperationDone); + endtask : dai_wr + + // this task triggers an OTP readout sequence via the DAI interface + virtual task dai_rd(bit [TL_DW-1:0] addr, + output bit [TL_DW-1:0] rdata0, + output bit [TL_DW-1:0] rdata1); + csr_wr(ral.direct_access_address, addr); + csr_wr(ral.direct_access_cmd, int'(otp_ctrl_pkg::DaiRead)); + csr_spinwait(ral.intr_state, 1 << OtpOperationDone); + + csr_rd(ral.direct_access_rdata_0, rdata0); + if (!is_secret(addr)) csr_rd(ral.direct_access_rdata_1, rdata1); + csr_wr(ral.intr_state, 1'b1 << OtpOperationDone); + endtask : dai_rd + + // this task exercises an OTP digest calculation via the DAI interface + virtual task cal_digest(int part_idx); + csr_wr(ral.direct_access_address, DIGESTS_ADDR[part_idx]); + csr_wr(ral.direct_access_cmd, otp_ctrl_pkg::DaiDigest); + csr_spinwait(ral.intr_state, 1 << OtpOperationDone); + csr_wr(ral.intr_state, 1 << OtpOperationDone); + endtask + + // this task provisions all HW partitions + // SW partitions could not be provisioned via DAI interface + // LC partitions cannot be locked + virtual task cal_hw_digests(); + for (int i = int'(HwCfgIdx); i < int'(LifeCycleIdx); i++) begin + cal_digest(i); + end endtask endclass : otp_ctrl_base_vseq
diff --git a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_sanity_vseq.sv b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_sanity_vseq.sv index 1e5f84a..3392e9d 100644 --- a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_sanity_vseq.sv +++ b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_sanity_vseq.sv
@@ -3,17 +3,101 @@ // SPDX-License-Identifier: Apache-2.0 // basic sanity test vseq +`define PART_ADDR_RANGE(i) \ + {[PartInfo[``i``].offset : (PartInfo[``i``].offset + PartInfo[``i``].size - DIGEST_SIZE - 1)]} + class otp_ctrl_sanity_vseq extends otp_ctrl_base_vseq; `uvm_object_utils(otp_ctrl_sanity_vseq) `uvm_object_new + randc bit [TL_AW-1:0] dai_addr; + rand bit [TL_DW-1:0] wdata0, wdata1; + rand int num_dai_wr; + rand otp_ctrl_pkg::part_idx_e part_idx; + + // TODO: temp -> no life-cycle partition involved + constraint partition_index_c { + part_idx inside {[CreatorSwCfgIdx:Secret2Idx]}; + } + + constraint dai_addr_c { + if (part_idx == CreatorSwCfgIdx) dai_addr inside `PART_ADDR_RANGE(CreatorSwCfgIdx); + if (part_idx == OwnerSwCfgIdx) dai_addr inside `PART_ADDR_RANGE(OwnerSwCfgIdx); + if (part_idx == HwCfgIdx) dai_addr inside `PART_ADDR_RANGE(HwCfgIdx); + if (part_idx == Secret0Idx) dai_addr inside `PART_ADDR_RANGE(Secret0Idx); + if (part_idx == Secret1Idx) dai_addr inside `PART_ADDR_RANGE(Secret1Idx); + if (part_idx == Secret2Idx) dai_addr inside `PART_ADDR_RANGE(Secret2Idx); + if (part_idx == LifeCycleIdx) dai_addr inside `PART_ADDR_RANGE(LifeCycleIdx); + + dai_addr % 4 == 0; + if (part_idx inside {[Secret0Idx:Secret2Idx]}) dai_addr % 8 == 0; + } + + constraint num_dai_wr_c {num_dai_wr inside {[1:20]};} + + virtual task dut_init(string reset_kind = "HARD"); + super.dut_init(reset_kind); + // drive pwr_otp_req pin + cfg.pwr_otp_vif.drive_pin(0, 1); + wait(cfg.pwr_otp_vif.pins[2] == 1); + cfg.lc_provision_en_vif.drive(lc_ctrl_pkg::On); + endtask + virtual task pre_start(); super.pre_start(); + num_dai_wr.rand_mode(0); endtask task body(); + for (int i = 1; i <= num_trans; i++) begin + bit [TL_DW-1:0] rdata, tlul_rdata; + `uvm_info(`gfn, $sformatf("starting seq %0d/%0d", i, num_trans), UVM_MEDIUM) + do_otp_ctrl_init = 1; + dut_init(); + do_otp_ctrl_init = 0; + + // after otp-init done, check status + cfg.clk_rst_vif.wait_clks(1); + csr_rd_check(.ptr(ral.status), .compare_value(OtpIdle)); + + for (int i = 0; i < num_dai_wr; i++) begin + bit [TL_DW-1:0] rdata0, rdata1; + `DV_CHECK_RANDOMIZE_FATAL(this) + + // OTP write via DAI + dai_wr(dai_addr, wdata0, wdata1); + + // OTP read via DAI + dai_rd(dai_addr, rdata0, rdata1); + + // check read data + `DV_CHECK_EQ(wdata0, rdata0, $sformatf("read data0 mismatch at addr %0h", dai_addr)) + if (!is_secret(dai_addr)) begin + `DV_CHECK_EQ(wdata1, rdata1, $sformatf("read data1 mismatch at addr %0h", dai_addr)) + end + + // if write sw partitions, check tlul window + if (part_idx inside {CreatorSwCfgIdx, OwnerSwCfgIdx}) begin + bit [TL_AW-1:0] tlul_addr = get_sw_window_addr(dai_addr); + + // random issue reset, OTP content should not be cleared + if ($urandom_range(0, 1)) dut_init(); + tl_access(.addr(tlul_addr), .write(0), .data(tlul_rdata), .blocking(1)); + `DV_CHECK_EQ(tlul_rdata, rdata0, $sformatf("mem read out mismatch at addr %0h", tlul_addr)) + end + end + + // check no error + csr_rd_check(.ptr(ral.status), .compare_value(OtpIdle)); + + // lock HW digests + `uvm_info(`gfn, "Trigger HW digest calculation", UVM_HIGH) + cal_hw_digests(); + csr_rd_check(.ptr(ral.status), .compare_value(OtpIdle)); + end endtask : body endclass : otp_ctrl_sanity_vseq +`undef PART_ADDR_RANGE
diff --git a/hw/ip/otp_ctrl/dv/tb.sv b/hw/ip/otp_ctrl/dv/tb.sv index ab971ec..f22803e 100644 --- a/hw/ip/otp_ctrl/dv/tb.sv +++ b/hw/ip/otp_ctrl/dv/tb.sv
@@ -15,6 +15,7 @@ wire clk, rst_n; wire devmode; + wire [3:0] lc_provision_en; // TODO: use standard req/rsp agent wire [2:0] pwr_otp; wire [NUM_MAX_INTERRUPTS-1:0] interrupts; @@ -26,6 +27,7 @@ pins_if #(1) devmode_if(devmode); // TODO: use standard req/rsp agent pins_if #(3) pwr_otp_if(pwr_otp); + pins_if #(4) lc_provision_en_if(lc_provision_en); tl_if tl_if(.clk(clk), .rst_n(rst_n)); // dut @@ -38,19 +40,40 @@ // interrupt .intr_otp_operation_done_o (intr_otp_operation_done), .intr_otp_error_o (intr_otp_error), - // TODO: add remaining IOs and hook them + // alert .alert_rx_i ('0), - .otp_edn_rsp_i ('0), - .pwr_otp_req_i (pwr_otp[0]), - .pwr_otp_rsp_o (pwr_otp[2:1]), - .lc_otp_program_req_i ('0), - .lc_otp_token_req_i ('0), - .lc_escalate_en_i ('0), - .lc_provision_en_i ('0), + .alert_tx_o ( ), + // ast + .otp_ast_pwr_seq_o ( ), + .otp_ast_pwr_seq_h_i ('0), + // edn + .otp_edn_o ( ), + .otp_edn_i ('0), + // pwrmgr + .pwr_otp_i (pwr_otp[0]), + .pwr_otp_o (pwr_otp[2:1]), + // lc + .lc_otp_program_i ('0), + .lc_otp_program_o ( ), + .lc_otp_token_i ('0), + .lc_otp_token_o ( ), + .lc_escalate_en_i (lc_ctrl_pkg::Off), + .lc_provision_en_i (lc_provision_en), .lc_dft_en_i ('0), - .flash_otp_key_req_i ('0), - .sram_otp_key_req_i ('0), - .otbn_otp_key_req_i ('0) + .otp_lc_data_o ( ), + // keymgr + .otp_keymgr_key_o ( ), + // flash + .flash_otp_key_i ('0), + .flash_otp_key_o ( ), + // sram + .sram_otp_key_i ('0), + .sram_otp_key_o ( ), + // otbn + .otbn_otp_key_i ('0), + .otbn_otp_key_o ( ), + // hw cfg + .hw_cfg_o ( ) ); // bind mem_bkdr_if @@ -69,8 +92,11 @@ uvm_config_db#(intr_vif)::set(null, "*.env", "intr_vif", intr_if); uvm_config_db#(pwr_otp_vif)::set(null, "*.env", "pwr_otp_vif", pwr_otp_if); uvm_config_db#(devmode_vif)::set(null, "*.env", "devmode_vif", devmode_if); + uvm_config_db#(lc_provision_en_vif)::set(null, "*.env", "lc_provision_en_vif", + lc_provision_en_if); uvm_config_db#(virtual tl_if)::set(null, "*.env.m_tl_agent*", "vif", tl_if); - uvm_config_db#(mem_bkdr_vif)::set(null, "*.env", "mem_bkdr_vif", `OTP_CTRL_MEM_HIER.mem_bkdr_if); + uvm_config_db#(mem_bkdr_vif)::set(null, "*.env", "mem_bkdr_vif", + `OTP_CTRL_MEM_HIER.mem_bkdr_if); $timeformat(-12, 0, " ps", 12); run_test(); end