diff --git a/hw/ip/rom_ctrl/dv/env/rom_ctrl_env.sv b/hw/ip/rom_ctrl/dv/env/rom_ctrl_env.sv index 122aa0ee0c073..888dab7f2166f 100644 --- a/hw/ip/rom_ctrl/dv/env/rom_ctrl_env.sv +++ b/hw/ip/rom_ctrl/dv/env/rom_ctrl_env.sv @@ -15,34 +15,37 @@ class rom_ctrl_env extends cip_base_env #( // KMAC interface agent kmac_app_agent m_kmac_agent; - function void build_phase(uvm_phase phase); - super.build_phase(phase); + extern function void build_phase(uvm_phase phase); + extern function void connect_phase(uvm_phase phase); - // Get the mem_bkdr interface - if (!uvm_config_db#(mem_bkdr_util)::get(this, "", "mem_bkdr_util", cfg.mem_bkdr_util_h)) begin - `uvm_fatal(`gfn, "failed to get mem_bkdr_util from uvm_config_db") - end - // Get the rom_ctrl interface - if (!uvm_config_db#(rom_ctrl_vif)::get(this, "", "rom_ctrl_vif", cfg.rom_ctrl_vif)) begin - `uvm_fatal(`gfn, "failed to get rom_ctrl_vif from uvm_config_db") - end +endclass - // Build the KMAC agent - m_kmac_agent = kmac_app_agent::type_id::create("m_kmac_agent", this); - uvm_config_db#(kmac_app_agent_cfg)::set(this, "m_kmac_agent", "cfg", cfg.m_kmac_agent_cfg); +function void rom_ctrl_env::build_phase(uvm_phase phase); + super.build_phase(phase); - cfg.scoreboard = scoreboard; + // Get the mem_bkdr interface + if (!uvm_config_db#(mem_bkdr_util)::get(this, "", "mem_bkdr_util", cfg.mem_bkdr_util_h)) begin + `uvm_fatal(`gfn, "failed to get mem_bkdr_util from uvm_config_db") + end + // Get the rom_ctrl interface + if (!uvm_config_db#(rom_ctrl_vif)::get(this, "", "rom_ctrl_vif", cfg.rom_ctrl_vif)) begin + `uvm_fatal(`gfn, "failed to get rom_ctrl_vif from uvm_config_db") + end - endfunction + // Build the KMAC agent + m_kmac_agent = kmac_app_agent::type_id::create("m_kmac_agent", this); + uvm_config_db#(kmac_app_agent_cfg)::set(this, "m_kmac_agent", "cfg", cfg.m_kmac_agent_cfg); - function void connect_phase(uvm_phase phase); - super.connect_phase(phase); + cfg.scoreboard = scoreboard; - m_kmac_agent.monitor.analysis_port.connect(scoreboard.kmac_rsp_fifo.analysis_export); - m_kmac_agent.monitor.req_analysis_port.connect(scoreboard.kmac_req_fifo.analysis_export); +endfunction - virtual_sequencer.kmac_sequencer_h = m_kmac_agent.sequencer; +function void rom_ctrl_env::connect_phase(uvm_phase phase); + super.connect_phase(phase); - endfunction + m_kmac_agent.monitor.analysis_port.connect(scoreboard.kmac_rsp_fifo.analysis_export); + m_kmac_agent.monitor.req_analysis_port.connect(scoreboard.kmac_req_fifo.analysis_export); -endclass + virtual_sequencer.kmac_sequencer_h = m_kmac_agent.sequencer; + +endfunction diff --git a/hw/ip/rom_ctrl/dv/env/rom_ctrl_env_cfg.sv b/hw/ip/rom_ctrl/dv/env/rom_ctrl_env_cfg.sv index 7982c435e4a52..0aa94f1aa2a2a 100644 --- a/hw/ip/rom_ctrl/dv/env/rom_ctrl_env_cfg.sv +++ b/hw/ip/rom_ctrl/dv/env/rom_ctrl_env_cfg.sv @@ -23,72 +23,77 @@ class rom_ctrl_env_cfg extends cip_base_env_cfg #(.RAL_T(rom_ctrl_regs_reg_block // A handle to the scoreboard, used to flag expected errors. rom_ctrl_scoreboard scoreboard; - // Control the device-side delay for the kmac app agent that talks to the dut. If it is large, - // rom_ctrl will spend all its time waiting for kmac to accept words that rom_ctrl is trying to - // send to kmac. Randomise this to be small with high probability and occasionally make it 10 (to - // check that the interface from rom_ctrl to kmac can be stalled properly). - constraint kmac_accept_delay_max_c { - if (!m_kmac_agent_cfg.m_data_push_agent_cfg.zero_delays) { - m_kmac_agent_cfg.m_data_push_agent_cfg.device_delay_max dist { 1 :/ 10, 10 :/ 1 }; - } - } + extern constraint kmac_accept_delay_max_c; + extern function new (string name=""); + extern virtual function void initialize(bit [31:0] csr_base_addr = '1); + extern virtual function dv_base_reg_block create_ral_by_name(string name); `uvm_object_utils_begin(rom_ctrl_env_cfg) `uvm_object_utils_end - function new (string name=""); - super.new(name); - - can_reset_with_csr_accesses = 1'b1; - - list_of_alerts = rom_ctrl_env_pkg::LIST_OF_ALERTS; - tl_intg_alert_name = "fatal"; - ral_model_names.push_back("rom_ctrl_prim_reg_block"); - - num_interrupts = 0; - - m_kmac_agent_cfg = kmac_app_agent_cfg::type_id::create("m_kmac_agent_cfg"); - m_kmac_agent_cfg.if_mode = dv_utils_pkg::Device; - m_kmac_agent_cfg.start_default_device_seq = 1'b1; - m_kmac_agent_cfg.constant_share_means_error = 1'b0; - // The checker reads the upper 8 words of ROM which takes 9 cycles. The rsp_delay_max has been - // rounded off by 9*2=18 cycles along with adding 2 just to give an extra precision. - m_kmac_agent_cfg.rsp_delay_min = 'd0; - m_kmac_agent_cfg.rsp_delay_max = 'd20; - - sec_cm_alert_name = "fatal"; - endfunction - - virtual function void initialize(bit [31:0] csr_base_addr = '1); - super.initialize(csr_base_addr); - - // default TLUL supports 1 outstanding item, the rom TLUL supports 2 outstanding items. - m_tl_agent_cfgs[RAL_T::type_name].max_outstanding_req = 1; - m_tl_agent_cfgs[rom_ral_name].max_outstanding_req = 2; - - // Tell the CIP base code what bit gets set if we see a TL fault. - tl_intg_alert_fields[ral.fatal_alert_cause.integrity_error] = 1; - endfunction - - // Override the default implementation in dv_base_env_cfg. - // - // This is required for the ROM environment for reuse at the chip level as 2 different - // parameterizations of the design and testbench exist, as a result the custom RAL model for the - // ROM memory primitive must also be explicitly parameterized. - // - // We cannot instantiate parameterized UVM objects/components using the standard factory - // mechanisms, so a custom instantiation method is required here. - // - // Note that the ROM only has 2 RAL models, one is the "default" CSR model, - // and the other is the custom model to represent the memory primitive. - virtual function dv_base_reg_block create_ral_by_name(string name); - if (name == RAL_T::type_name) begin - return super.create_ral_by_name(name); - end else if (name == rom_ral_name) begin - return rom_ctrl_prim_reg_block#(ROM_WORD_ADDR_WIDTH)::type_id::create(rom_ral_name); - end else begin - `uvm_error(`gfn, $sformatf("%0s is an illegal RAL model name", name)) - end - endfunction - endclass + +// Control the device-side delay for the kmac app agent that talks to the dut. If it is large, +// rom_ctrl will spend all its time waiting for kmac to accept words that rom_ctrl is trying to +// send to kmac. Randomise this to be small with high probability and occasionally make it 10 (to +// check that the interface from rom_ctrl to kmac can be stalled properly). +constraint rom_ctrl_env_cfg::kmac_accept_delay_max_c { + if (!m_kmac_agent_cfg.m_data_push_agent_cfg.zero_delays) { + m_kmac_agent_cfg.m_data_push_agent_cfg.device_delay_max dist { 1 :/ 10, 10 :/ 1 }; + } +} + +function rom_ctrl_env_cfg::new (string name=""); + super.new(name); + + can_reset_with_csr_accesses = 1'b1; + + list_of_alerts = rom_ctrl_env_pkg::LIST_OF_ALERTS; + tl_intg_alert_name = "fatal"; + ral_model_names.push_back("rom_ctrl_prim_reg_block"); + + num_interrupts = 0; + + m_kmac_agent_cfg = kmac_app_agent_cfg::type_id::create("m_kmac_agent_cfg"); + m_kmac_agent_cfg.if_mode = dv_utils_pkg::Device; + m_kmac_agent_cfg.start_default_device_seq = 1'b1; + m_kmac_agent_cfg.constant_share_means_error = 1'b0; + // The checker reads the upper 8 words of ROM which takes 9 cycles. The rsp_delay_max has been + // rounded off by 9*2=18 cycles along with adding 2 just to give an extra precision. + m_kmac_agent_cfg.rsp_delay_min = 'd0; + m_kmac_agent_cfg.rsp_delay_max = 'd20; + + sec_cm_alert_name = "fatal"; +endfunction + +function void rom_ctrl_env_cfg::initialize(bit [31:0] csr_base_addr = '1); + super.initialize(csr_base_addr); + + // default TLUL supports 1 outstanding item, the rom TLUL supports 2 outstanding items. + m_tl_agent_cfgs[RAL_T::type_name].max_outstanding_req = 1; + m_tl_agent_cfgs[rom_ral_name].max_outstanding_req = 2; + + // Tell the CIP base code what bit gets set if we see a TL fault. + tl_intg_alert_fields[ral.fatal_alert_cause.integrity_error] = 1; +endfunction + +// Override the default implementation in dv_base_env_cfg. +// +// This is required for the ROM environment for reuse at the chip level as 2 different +// parameterizations of the design and testbench exist, as a result the custom RAL model for the +// ROM memory primitive must also be explicitly parameterized. +// +// We cannot instantiate parameterized UVM objects/components using the standard factory +// mechanisms, so a custom instantiation method is required here. +// +// Note that the ROM only has 2 RAL models, one is the "default" CSR model, +// and the other is the custom model to represent the memory primitive. +function dv_base_reg_block rom_ctrl_env_cfg::create_ral_by_name(string name); + if (name == RAL_T::type_name) begin + return super.create_ral_by_name(name); + end else if (name == rom_ral_name) begin + return rom_ctrl_prim_reg_block#(ROM_WORD_ADDR_WIDTH)::type_id::create(rom_ral_name); + end else begin + `uvm_error(`gfn, $sformatf("%0s is an illegal RAL model name", name)) + end +endfunction diff --git a/hw/ip/rom_ctrl/dv/env/rom_ctrl_env_cov.sv b/hw/ip/rom_ctrl/dv/env/rom_ctrl_env_cov.sv index 57478f09e8f71..e9d512676be44 100644 --- a/hw/ip/rom_ctrl/dv/env/rom_ctrl_env_cov.sv +++ b/hw/ip/rom_ctrl/dv/env/rom_ctrl_env_cov.sv @@ -17,16 +17,19 @@ class rom_ctrl_env_cov extends cip_base_env_cov #(.CFG_T(rom_ctrl_env_cfg)); // covergroups // [add covergroups here] - function new(string name, uvm_component parent); - super.new(name, parent); - // [instantiate covergroups here] - endfunction : new - - virtual function void build_phase(uvm_phase phase); - super.build_phase(phase); - // [or instantiate covergroups here] - // Please instantiate sticky_intr_cov array of objects for all interrupts that are sticky - // See cip_base_env_cov for details - endfunction + extern function new(string name, uvm_component parent); + extern virtual function void build_phase(uvm_phase phase); endclass + +function rom_ctrl_env_cov::new(string name, uvm_component parent); + super.new(name, parent); + // [instantiate covergroups here] +endfunction : new + +function void rom_ctrl_env_cov::build_phase(uvm_phase phase); + super.build_phase(phase); + // [or instantiate covergroups here] + // Please instantiate sticky_intr_cov array of objects for all interrupts that are sticky + // See cip_base_env_cov for details +endfunction diff --git a/hw/ip/rom_ctrl/dv/env/rom_ctrl_prim_ral_pkg.sv b/hw/ip/rom_ctrl/dv/env/rom_ctrl_prim_ral_pkg.sv index 17b564c13a6ff..093a299db64a5 100644 --- a/hw/ip/rom_ctrl/dv/env/rom_ctrl_prim_ral_pkg.sv +++ b/hw/ip/rom_ctrl/dv/env/rom_ctrl_prim_ral_pkg.sv @@ -16,18 +16,24 @@ package rom_ctrl_prim_ral_pkg; `uvm_object_param_utils(rom_ctrl_prim_mem_rom_mem#(MemDepth)) - function new(string name = "rom_ctrl_prim_mem_rom_mem", - longint unsigned size = MemDepth, - int unsigned n_bits = 32, - string access = "RO", - int has_coverage = UVM_NO_COVERAGE); - super.new(name, size, n_bits, access, has_coverage); - set_mem_partial_write_support(1); - set_data_intg_passthru(1); - endfunction : new + extern function new(string name = "rom_ctrl_prim_mem_rom_mem", + longint unsigned size = MemDepth, + int unsigned n_bits = 32, + string access = "RO", + int has_coverage = UVM_NO_COVERAGE); endclass : rom_ctrl_prim_mem_rom_mem + function rom_ctrl_prim_mem_rom_mem::new(string name = "rom_ctrl_prim_mem_rom_mem", + longint unsigned size = MemDepth, + int unsigned n_bits = 32, + string access = "RO", + int has_coverage = UVM_NO_COVERAGE); + super.new(name, size, n_bits, access, has_coverage); + set_mem_partial_write_support(1); + set_data_intg_passthru(1); + endfunction : new + class rom_ctrl_prim_reg_block #(parameter int AddrWidth = 10) extends dv_base_reg_block; // memories @@ -35,31 +41,37 @@ package rom_ctrl_prim_ral_pkg; `uvm_object_param_utils(rom_ctrl_prim_reg_block#(AddrWidth)) - function new(string name = "rom_ctrl_prim_reg_block", - int has_coverage = UVM_NO_COVERAGE); - super.new(name, has_coverage); - endfunction : new - - virtual function void build(uvm_reg_addr_t base_addr, - csr_excl_item csr_excl = null); - // create default map - this.default_map = create_map(.name("default_map"), - .base_addr(base_addr), - .n_bytes(4), - .endian(UVM_LITTLE_ENDIAN)); - if (csr_excl == null) begin - csr_excl = csr_excl_item::type_id::create("csr_excl"); - this.csr_excl = csr_excl; - end - - // create memories - rom_mem = rom_ctrl_prim_mem_rom_mem#(2 ** AddrWidth)::type_id::create("rom_mem"); - rom_mem.configure(.parent(this)); - default_map.add_mem(.mem(rom_mem), - .offset(32'h0), - .rights("RO")); - - endfunction : build + extern function new(string name = "rom_ctrl_prim_reg_block", + int has_coverage = UVM_NO_COVERAGE); + + extern virtual function void build(uvm_reg_addr_t base_addr, csr_excl_item csr_excl = null); + endclass : rom_ctrl_prim_reg_block + function rom_ctrl_prim_reg_block::new(string name = "rom_ctrl_prim_reg_block", + int has_coverage = UVM_NO_COVERAGE); + super.new(name, has_coverage); + endfunction : new + + function void rom_ctrl_prim_reg_block::build(uvm_reg_addr_t base_addr, + csr_excl_item csr_excl = null); + // create default map + this.default_map = create_map(.name("default_map"), + .base_addr(base_addr), + .n_bytes(4), + .endian(UVM_LITTLE_ENDIAN)); + if (csr_excl == null) begin + csr_excl = csr_excl_item::type_id::create("csr_excl"); + this.csr_excl = csr_excl; + end + + // create memories + rom_mem = rom_ctrl_prim_mem_rom_mem#(2 ** AddrWidth)::type_id::create("rom_mem"); + rom_mem.configure(.parent(this)); + default_map.add_mem(.mem(rom_mem), + .offset(32'h0), + .rights("RO")); + + endfunction : build + endpackage diff --git a/hw/ip/rom_ctrl/dv/env/rom_ctrl_scoreboard.sv b/hw/ip/rom_ctrl/dv/env/rom_ctrl_scoreboard.sv index 887fbb23a8923..fef289c91cec9 100644 --- a/hw/ip/rom_ctrl/dv/env/rom_ctrl_scoreboard.sv +++ b/hw/ip/rom_ctrl/dv/env/rom_ctrl_scoreboard.sv @@ -24,270 +24,286 @@ class rom_ctrl_scoreboard extends cip_base_scoreboard #( `uvm_component_new - function void build_phase(uvm_phase phase); - super.build_phase(phase); - kmac_req_fifo = new("kmac_req_fifo", this); - kmac_rsp_fifo = new("kmac_rsp_fifo", this); - endfunction - - task run_phase(uvm_phase phase); - super.run_phase(phase); - fork - process_kmac_req_fifo(); - process_kmac_rsp_fifo(); - monitor_rom_ctrl_if(); - join - endtask - - virtual task process_kmac_req_fifo(); - kmac_app_item kmac_req; - forever begin - kmac_req_fifo.get(kmac_req); - if (!cfg.en_scb) continue; - - `uvm_info(`gfn, $sformatf("Detected a KMAC req:\n%0s", kmac_req.sprint()), UVM_HIGH) - // We shouldn't see any further requests one the check has completed - `DV_CHECK_EQ(rom_check_complete, 1'b0, "Spurious ROM request received") - // Check the data is valid - check_kmac_data(kmac_req.byte_data_q); - end - endtask - - // Read data sent to the kmac block and check it against memory data - virtual function void check_kmac_data(const ref byte byte_data_q[$]); - int word = 0; - int addr = 0; - // Check that we received the expected amount of data - `DV_CHECK_EQ(byte_data_q.size(), KMAC_DATA_SIZE, "Unexpected kmac data size") - // Read out the data 5 bytes at a time (one word is 39bit packed into 5 byte) - while (word < byte_data_q.size()) begin - bit [KMAC_DATA_WORD_SIZE*8-1:0] exp, act; - bit [ROM_MEM_W-1:0] mem_data; - mem_data = cfg.mem_bkdr_util_h.rom_encrypt_read32( - addr, RND_CNST_SCR_KEY, RND_CNST_SCR_NONCE, 1'b0); - exp = {{KMAC_DATA_WORD_SIZE*8-ROM_MEM_W{1'b0}}, mem_data}; - for (int i = 0; i < KMAC_DATA_WORD_SIZE; i++) begin - act[i*8+:8] = byte_data_q[word+i]; - end - // Check the data matches - `DV_CHECK_EQ(act, exp, $sformatf("Unexpected data at addr: %0x", addr)) - // Check the address is within range - `DV_CHECK_LT(addr, MAX_CHECK_ADDR, - $sformatf("Check address out of range: %0x", addr)) - addr += (TL_DW / 8); - word += KMAC_DATA_WORD_SIZE; - end - endfunction - - virtual task process_kmac_rsp_fifo(); - kmac_app_item kmac_rsp; - forever begin - kmac_rsp_fifo.get(kmac_rsp); - if (!cfg.en_scb) continue; - - `uvm_info(`gfn, $sformatf("Detected a KMAC response:\n%0s", kmac_rsp.sprint()), UVM_HIGH) - // We shouldn't see any further responses one the check has completed - `DV_CHECK_EQ(rom_check_complete, 1'b0, "Spurious ROM response received") - kmac_digest = kmac_rsp.rsp_digest_share0 ^ kmac_rsp.rsp_digest_share1; - get_expected_digest(); - update_ral_digests(); - digest_good = prim_mubi_pkg::mubi4_bool_to_mubi( - kmac_digest[DIGEST_SIZE-1:0] == expected_digest); - rom_check_complete = 1'b1; - end - endtask - - // Pull the expected digest value from the top of rom - virtual function void get_expected_digest(); - bit [DIGEST_SIZE-1:0] digest; - bit [`ROM_BYTE_ADDR_WIDTH-1:0] dig_addr; - // Get the digest from rom - // The digest is the top 8 words in memory (unscrambled) - dig_addr = MAX_CHECK_ADDR; - for (int i = 0; i < DIGEST_SIZE / TL_DW; i++) begin - bit [ROM_MEM_W-1:0] mem_data = cfg.mem_bkdr_util_h.rom_encrypt_read32( - dig_addr, RND_CNST_SCR_KEY, RND_CNST_SCR_NONCE, 1'b0); - digest[i*TL_DW+:TL_DW] = mem_data[TL_DW-1:0]; - dig_addr += (TL_DW / 8); - end - expected_digest = digest; - endfunction - - // Update the RAL model with expected values for the digest registers - // - // This works by using the predict function with UVM_PREDICT_READ. This tells the register model - // that we've just read the given value from the registers. We do this rather than using - // UVM_PREDICT_DIRECT (the default) because it avoids UVM thinking that there might be a race - // against CSR operations that are already in flight. - virtual function void update_ral_digests(); - for (int i = 0; i < DIGEST_SIZE / TL_DW; i++) begin - `DV_CHECK(ral.digest[i].predict(.value(kmac_digest[i*TL_DW+:TL_DW]), - .kind(UVM_PREDICT_READ))) - `DV_CHECK(ral.exp_digest[i].predict(.value(expected_digest[i*TL_DW+:TL_DW]), - .kind(UVM_PREDICT_READ))) - end - endfunction - - // Montitor and check outputs sent to pwrmgr and keymgr - virtual task monitor_rom_ctrl_if(); - if (!cfg.en_scb) return; - forever begin - @(cfg.rom_ctrl_vif.pwrmgr_data or cfg.rom_ctrl_vif.keymgr_data or cfg.under_reset); - if (cfg.under_reset) continue; - // Check data sent to pwrmgr - if (prim_mubi_pkg::mubi4_test_true_strict(cfg.rom_ctrl_vif.pwrmgr_data.done)) begin - `DV_CHECK_EQ(pwrmgr_complete, 1'b0, "Spurious pwrmgr signal") - `DV_CHECK_EQ(cfg.rom_ctrl_vif.pwrmgr_data.good, digest_good, "Incorrect pwrmgr result") - pwrmgr_complete = 1'b1; - end - // Check data sent to keymgr - if (cfg.rom_ctrl_vif.keymgr_data.valid) begin - `DV_CHECK_EQ(keymgr_complete, 1'b0, "Spurious keymgr signal") - `DV_CHECK_EQ(cfg.rom_ctrl_vif.keymgr_data.data, kmac_digest[DIGEST_SIZE-1:0], "Incorrect keymgr digest") - keymgr_complete = 1'b1; - end - end - endtask + extern function void build_phase(uvm_phase phase); + extern task run_phase(uvm_phase phase); + extern virtual task process_kmac_req_fifo(); + extern virtual function void check_kmac_data(const ref byte byte_data_q[$]); + extern virtual task process_kmac_rsp_fifo(); + extern virtual function void get_expected_digest(); + extern virtual function void update_ral_digests(); + extern virtual task monitor_rom_ctrl_if(); + extern virtual function void check_rom_access(tl_seq_item item); + extern virtual task process_tl_access(tl_seq_item item, tl_channels_e channel, string ral_name); + extern virtual function void reset(string kind = "HARD"); + extern function void check_phase(uvm_phase phase); + extern virtual function void phase_ready_to_end(uvm_phase phase); - virtual function void check_rom_access(tl_seq_item item); - bit [ROM_MEM_W-1:0] exp_data; +endclass - if (item.is_write()) begin - `DV_CHECK_EQ(item.d_error, 1'b1, "Attempted write did not return error") +function void rom_ctrl_scoreboard::build_phase(uvm_phase phase); + super.build_phase(phase); + kmac_req_fifo = new("kmac_req_fifo", this); + kmac_rsp_fifo = new("kmac_rsp_fifo", this); +endfunction + +task rom_ctrl_scoreboard::run_phase(uvm_phase phase); + super.run_phase(phase); + fork + process_kmac_req_fifo(); + process_kmac_rsp_fifo(); + monitor_rom_ctrl_if(); + join +endtask + +task rom_ctrl_scoreboard::process_kmac_req_fifo(); + kmac_app_item kmac_req; + forever begin + kmac_req_fifo.get(kmac_req); + if (!cfg.en_scb) continue; + + `uvm_info(`gfn, $sformatf("Detected a KMAC req:\n%0s", kmac_req.sprint()), UVM_HIGH) + // We shouldn't see any further requests one the check has completed + `DV_CHECK_EQ(rom_check_complete, 1'b0, "Spurious ROM request received") + // Check the data is valid + check_kmac_data(kmac_req.byte_data_q); + end +endtask + +// Read data sent to the kmac block and check it against memory data +function void rom_ctrl_scoreboard::check_kmac_data(const ref byte byte_data_q[$]); + int word = 0; + int addr = 0; + // Check that we received the expected amount of data + `DV_CHECK_EQ(byte_data_q.size(), KMAC_DATA_SIZE, "Unexpected kmac data size") + // Read out the data 5 bytes at a time (one word is 39bit packed into 5 byte) + while (word < byte_data_q.size()) begin + bit [KMAC_DATA_WORD_SIZE*8-1:0] exp, act; + bit [ROM_MEM_W-1:0] mem_data; + mem_data = cfg.mem_bkdr_util_h.rom_encrypt_read32( + addr, RND_CNST_SCR_KEY, RND_CNST_SCR_NONCE, 1'b0); + exp = {{KMAC_DATA_WORD_SIZE*8-ROM_MEM_W{1'b0}}, mem_data}; + for (int i = 0; i < KMAC_DATA_WORD_SIZE; i++) begin + act[i*8+:8] = byte_data_q[word+i]; end - `DV_CHECK_EQ(item.d_error, item.get_exp_d_error(), "TLUL ROM read error incorrect") - - exp_data = cfg.mem_bkdr_util_h.rom_encrypt_read32( - item.a_addr, RND_CNST_SCR_KEY, RND_CNST_SCR_NONCE, 1'b1); - - for (int i = 0; i < TL_DW / 8; i++) begin - if (item.a_mask[i]) begin - `DV_CHECK_EQ(item.d_data[i*8+:8], exp_data[i*8+:8], "TLUL ROM read data incorrect") - end + // Check the data matches + `DV_CHECK_EQ(act, exp, $sformatf("Unexpected data at addr: %0x", addr)) + // Check the address is within range + `DV_CHECK_LT(addr, MAX_CHECK_ADDR, + $sformatf("Check address out of range: %0x", addr)) + addr += (TL_DW / 8); + word += KMAC_DATA_WORD_SIZE; + end +endfunction + +task rom_ctrl_scoreboard::process_kmac_rsp_fifo(); + kmac_app_item kmac_rsp; + forever begin + kmac_rsp_fifo.get(kmac_rsp); + if (!cfg.en_scb) continue; + + `uvm_info(`gfn, $sformatf("Detected a KMAC response:\n%0s", kmac_rsp.sprint()), UVM_HIGH) + // We shouldn't see any further responses one the check has completed + `DV_CHECK_EQ(rom_check_complete, 1'b0, "Spurious ROM response received") + kmac_digest = kmac_rsp.rsp_digest_share0 ^ kmac_rsp.rsp_digest_share1; + get_expected_digest(); + update_ral_digests(); + digest_good = prim_mubi_pkg::mubi4_bool_to_mubi( + kmac_digest[DIGEST_SIZE-1:0] == expected_digest); + rom_check_complete = 1'b1; + end +endtask + +// Pull the expected digest value from the top of rom +function void rom_ctrl_scoreboard::get_expected_digest(); + bit [DIGEST_SIZE-1:0] digest; + bit [`ROM_BYTE_ADDR_WIDTH-1:0] dig_addr; + // Get the digest from rom + // The digest is the top 8 words in memory (unscrambled) + dig_addr = MAX_CHECK_ADDR; + for (int i = 0; i < DIGEST_SIZE / TL_DW; i++) begin + bit [ROM_MEM_W-1:0] mem_data = cfg.mem_bkdr_util_h.rom_encrypt_read32( + dig_addr, RND_CNST_SCR_KEY, RND_CNST_SCR_NONCE, 1'b0); + digest[i*TL_DW+:TL_DW] = mem_data[TL_DW-1:0]; + dig_addr += (TL_DW / 8); + end + expected_digest = digest; +endfunction + +// Update the RAL model with expected values for the digest registers +// +// This works by using the predict function with UVM_PREDICT_READ. This tells the register model +// that we've just read the given value from the registers. We do this rather than using +// UVM_PREDICT_DIRECT (the default) because it avoids UVM thinking that there might be a race +// against CSR operations that are already in flight. +function void rom_ctrl_scoreboard::update_ral_digests(); + for (int i = 0; i < DIGEST_SIZE / TL_DW; i++) begin + `DV_CHECK(ral.digest[i].predict(.value(kmac_digest[i*TL_DW+:TL_DW]), + .kind(UVM_PREDICT_READ))) + `DV_CHECK(ral.exp_digest[i].predict(.value(expected_digest[i*TL_DW+:TL_DW]), + .kind(UVM_PREDICT_READ))) + end +endfunction + +// Montitor and check outputs sent to pwrmgr and keymgr +task rom_ctrl_scoreboard::monitor_rom_ctrl_if(); + if (!cfg.en_scb) return; + forever begin + @(cfg.rom_ctrl_vif.pwrmgr_data or cfg.rom_ctrl_vif.keymgr_data or cfg.under_reset); + if (cfg.under_reset) continue; + // Check data sent to pwrmgr + if (prim_mubi_pkg::mubi4_test_true_strict(cfg.rom_ctrl_vif.pwrmgr_data.done)) begin + `DV_CHECK_EQ(pwrmgr_complete, 1'b0, "Spurious pwrmgr signal") + `DV_CHECK_EQ(cfg.rom_ctrl_vif.pwrmgr_data.good, digest_good, "Incorrect pwrmgr result") + pwrmgr_complete = 1'b1; end - endfunction + // Check data sent to keymgr + if (cfg.rom_ctrl_vif.keymgr_data.valid) begin + `DV_CHECK_EQ(keymgr_complete, 1'b0, "Spurious keymgr signal") + `DV_CHECK_EQ(cfg.rom_ctrl_vif.keymgr_data.data, kmac_digest[DIGEST_SIZE-1:0], "Incorrect keymgr digest") + keymgr_complete = 1'b1; + end + end +endtask - virtual task process_tl_access(tl_seq_item item, tl_channels_e channel, string ral_name); - dv_base_reg_block ral_model = cfg.ral_models[ral_name]; - uvm_reg_addr_t csr_addr = ral_model.get_word_aligned_addr(item.a_addr); - uvm_reg csr = ral_model.default_map.get_reg_by_offset(csr_addr); +function void rom_ctrl_scoreboard::check_rom_access(tl_seq_item item); + bit [ROM_MEM_W-1:0] exp_data; - bit do_read_check = 1'b1; - bit write = item.is_write(); + if (item.is_write()) begin + `DV_CHECK_EQ(item.d_error, 1'b1, "Attempted write did not return error") + end + `DV_CHECK_EQ(item.d_error, item.get_exp_d_error(), "TLUL ROM read error incorrect") - bit addr_phase_read = (!write && channel == AddrChannel); - bit addr_phase_write = (write && channel == AddrChannel); - bit data_phase_read = (!write && channel == DataChannel); - bit data_phase_write = (write && channel == DataChannel); + exp_data = cfg.mem_bkdr_util_h.rom_encrypt_read32( + item.a_addr, RND_CNST_SCR_KEY, RND_CNST_SCR_NONCE, 1'b1); - if (ral_name == "rom_ctrl_prim_reg_block") begin - if (channel == DataChannel && !disable_rom_acc_chk) begin - check_rom_access(item); - end - return; + for (int i = 0; i < TL_DW / 8; i++) begin + if (item.a_mask[i]) begin + `DV_CHECK_EQ(item.d_data[i*8+:8], exp_data[i*8+:8], "TLUL ROM read data incorrect") end - - // if access was to a valid csr, get the csr handle - if (csr_addr inside {cfg.ral_models[ral_name].csr_addrs}) begin - csr = cfg.ral_models[ral_name].default_map.get_reg_by_offset(csr_addr); - `DV_CHECK_NE_FATAL(csr, null) - end else begin - `uvm_fatal(`gfn, $sformatf("Access unexpected addr 0x%0h", csr_addr)) + end +endfunction + +task rom_ctrl_scoreboard::process_tl_access(tl_seq_item item, + tl_channels_e channel, + string ral_name); + dv_base_reg_block ral_model = cfg.ral_models[ral_name]; + uvm_reg_addr_t csr_addr = ral_model.get_word_aligned_addr(item.a_addr); + uvm_reg csr = ral_model.default_map.get_reg_by_offset(csr_addr); + + bit do_read_check = 1'b1; + bit write = item.is_write(); + + bit addr_phase_read = (!write && channel == AddrChannel); + bit addr_phase_write = (write && channel == AddrChannel); + bit data_phase_read = (!write && channel == DataChannel); + bit data_phase_write = (write && channel == DataChannel); + + if (ral_name == "rom_ctrl_prim_reg_block") begin + if (channel == DataChannel && !disable_rom_acc_chk) begin + check_rom_access(item); end - - // If we get here, then the access was on the register channel. If it was to an invalid CSR, - // there's nothing more to do. The base classes should already predict an error response. - if (csr == null) - return; - - // if incoming access is a write to a valid csr, then make updates right away - if (addr_phase_write) begin - void'(csr.predict(.value(item.a_data), .kind(UVM_PREDICT_WRITE), .be(item.a_mask))); + return; + end + + // if access was to a valid csr, get the csr handle + if (csr_addr inside {cfg.ral_models[ral_name].csr_addrs}) begin + csr = cfg.ral_models[ral_name].default_map.get_reg_by_offset(csr_addr); + `DV_CHECK_NE_FATAL(csr, null) + end else begin + `uvm_fatal(`gfn, $sformatf("Access unexpected addr 0x%0h", csr_addr)) + end + + // If we get here, then the access was on the register channel. If it was to an invalid CSR, + // there's nothing more to do. The base classes should already predict an error response. + if (csr == null) + return; + + // if incoming access is a write to a valid csr, then make updates right away + if (addr_phase_write) begin + void'(csr.predict(.value(item.a_data), .kind(UVM_PREDICT_WRITE), .be(item.a_mask))); + end + + // process the csr req + // for write, update local variable and fifo at address phase + // for read, update predication at address phase and compare at data phase + case (csr.get_name()) + // add individual case item for each csr + "alert_test": begin + if (addr_phase_write && item.a_data[0]) set_exp_alert("fatal", .is_fatal(0)); end - - // process the csr req - // for write, update local variable and fifo at address phase - // for read, update predication at address phase and compare at data phase - case (csr.get_name()) - // add individual case item for each csr - "alert_test": begin - if (addr_phase_write && item.a_data[0]) set_exp_alert("fatal", .is_fatal(0)); - end - "fatal_alert_cause": begin - // do_nothing - end - "digest_0", "digest_1", "digest_2", "digest_3", "digest_4", "digest_5", "digest_6", - "digest_7", "exp_digest_0", "exp_digest_1", "exp_digest_2", "exp_digest_3", - "exp_digest_4", "exp_digest_5", "exp_digest_6", "exp_digest_7": begin - if (!rom_check_complete) begin - do_read_check = 1'b0; - end - end - default: begin - `uvm_fatal(`gfn, $sformatf("invalid csr: %0s", csr.get_full_name())) + "fatal_alert_cause": begin + // do_nothing + end + "digest_0", "digest_1", "digest_2", "digest_3", "digest_4", "digest_5", "digest_6", + "digest_7", "exp_digest_0", "exp_digest_1", "exp_digest_2", "exp_digest_3", + "exp_digest_4", "exp_digest_5", "exp_digest_6", "exp_digest_7": begin + if (!rom_check_complete) begin + do_read_check = 1'b0; end - endcase + end + default: begin + `uvm_fatal(`gfn, $sformatf("invalid csr: %0s", csr.get_full_name())) + end + endcase - // On reads, if do_read_check, is set, then check mirrored_value against item.d_data - if (data_phase_read) begin - if (do_read_check) begin - `DV_CHECK_EQ(csr.get_mirrored_value(), item.d_data, - $sformatf("reg name: %0s", csr.get_full_name())) - end - void'(csr.predict(.value(item.d_data), .kind(UVM_PREDICT_READ))); + // On reads, if do_read_check, is set, then check mirrored_value against item.d_data + if (data_phase_read) begin + if (do_read_check) begin + `DV_CHECK_EQ(csr.get_mirrored_value(), item.d_data, + $sformatf("reg name: %0s", csr.get_full_name())) end - endtask - - virtual function void reset(string kind = "HARD"); - super.reset(kind); - // reset local fifos queues and variables - rom_check_complete = 1'b0; - pwrmgr_complete = 1'b0; - keymgr_complete = 1'b0; - endfunction - - function void check_phase(uvm_phase phase); - super.check_phase(phase); - // post test checks - ensure that all local fifos and queues are empty - if (cfg.en_scb) begin - `DV_CHECK_EQ(rom_check_complete, 1'b1, "rom check didn't finish") - `DV_CHECK_EQ(pwrmgr_complete, 1'b1, "pwrmgr signals never checked") - `DV_CHECK_EQ(keymgr_complete, 1'b1, "keymgr signals never checked") - - // We normally expect pwrmgr_complete to be true, which means that we have sent MuBi4True to - // the power manager to say that the rom check has finished. It won't be true if we saw a - // reset before the ROM check finished. In that case, the vseq should have finished before we - // came out of reset, so we will be in reset now. - // - // The other thing that can happen is an injected error that puts the FSM in rom_ctrl_fsm into - // the Invalid state. This is a terminal state, so we won't have told the power manager that - // the check has finished. - if (!pwrmgr_complete) begin - if (cfg.clk_rst_vif.rst_n) begin - string fsm_state_path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.state_q"; - logic [9:0] fsm_state; - - `DV_CHECK(uvm_hdl_read(fsm_state_path, fsm_state)); - `DV_CHECK_EQ(fsm_state, rom_ctrl_pkg::Invalid) - end + void'(csr.predict(.value(item.d_data), .kind(UVM_PREDICT_READ))); + end +endtask + +function void rom_ctrl_scoreboard::reset(string kind = "HARD"); + super.reset(kind); + // reset local fifos queues and variables + rom_check_complete = 1'b0; + pwrmgr_complete = 1'b0; + keymgr_complete = 1'b0; +endfunction + +function void rom_ctrl_scoreboard::check_phase(uvm_phase phase); + super.check_phase(phase); + // post test checks - ensure that all local fifos and queues are empty + if (cfg.en_scb) begin + `DV_CHECK_EQ(rom_check_complete, 1'b1, "rom check didn't finish") + `DV_CHECK_EQ(pwrmgr_complete, 1'b1, "pwrmgr signals never checked") + `DV_CHECK_EQ(keymgr_complete, 1'b1, "keymgr signals never checked") + + // We normally expect pwrmgr_complete to be true, which means that we have sent MuBi4True to + // the power manager to say that the rom check has finished. It won't be true if we saw a + // reset before the ROM check finished. In that case, the vseq should have finished before we + // came out of reset, so we will be in reset now. + // + // The other thing that can happen is an injected error that puts the FSM in rom_ctrl_fsm into + // the Invalid state. This is a terminal state, so we won't have told the power manager that + // the check has finished. + if (!pwrmgr_complete) begin + if (cfg.clk_rst_vif.rst_n) begin + string fsm_state_path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.state_q"; + logic [9:0] fsm_state; + + `DV_CHECK(uvm_hdl_read(fsm_state_path, fsm_state)); + `DV_CHECK_EQ(fsm_state, rom_ctrl_pkg::Invalid) end end - endfunction - - virtual function void phase_ready_to_end(uvm_phase phase); - if (phase.get_name() != "run") return; - - // Raising an objection and waiting for the pwrmgr_complete to set. This will add an extra - // delay after the test finishes and rom_ctrl_fsm would be in a done state which will set - // pwrmgr_data_o.done. - phase.raise_objection(this, {`gfn, " objection raised"}); - fork - begin - if (cfg.en_scb) - wait (pwrmgr_complete); - phase.drop_objection(this, {`gfn, " objection dropped"}); - end - join_none - endfunction + end +endfunction -endclass +function void rom_ctrl_scoreboard::phase_ready_to_end(uvm_phase phase); + if (phase.get_name() != "run") return; + + // Raising an objection and waiting for the pwrmgr_complete to set. This will add an extra + // delay after the test finishes and rom_ctrl_fsm would be in a done state which will set + // pwrmgr_data_o.done. + phase.raise_objection(this, {`gfn, " objection raised"}); + fork + begin + if (cfg.en_scb) + wait (pwrmgr_complete); + phase.drop_objection(this, {`gfn, " objection dropped"}); + end + join_none +endfunction diff --git a/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_base_vseq.sv b/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_base_vseq.sv index e1e90e690433f..60bfcec748482 100644 --- a/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_base_vseq.sv +++ b/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_base_vseq.sv @@ -15,232 +15,280 @@ class rom_ctrl_base_vseq extends cip_base_vseq #( `uvm_object_new - virtual task dut_init(string reset_kind = "HARD"); - super.dut_init(reset_kind); - // Disable intr test since no interrupts - do_clear_all_interrupts = 1'b0; - endtask - - virtual task apply_reset(string kind = "HARD"); - // Initialize memory at the beginning of reset since the DUT can come out of reset before this - // task completes (due to the second RAL clk_rst_if) - rom_ctrl_mem_init(); - super.apply_reset(kind); - endtask - - // Task to build a random rom in memory - virtual task rom_ctrl_mem_init(); - bit [31:0] rnd_data; - - // Randomize the memory contents. - // - // We can't just use the mem_bkdr_util randomize_mem function because that doesn't obey the - // scrambling key. This wouldn't be a problem (the memory is supposed to be random!), except - // that we also need to pick ECC values that match. - for (int i = 0; i < ROM_SIZE_WORDS; i++) begin - `DV_CHECK_STD_RANDOMIZE_FATAL(rnd_data) - cfg.mem_bkdr_util_h.rom_encrypt_write32_integ(i * 4, - rnd_data, - RND_CNST_SCR_KEY, - RND_CNST_SCR_NONCE, - 1'b1); - end - endtask - - // Task to perform `num_ops` fully randomized memory transactions. - virtual task do_rand_ops(int num_ops, bit read_only = 0); - addr_range_t loc_mem_range[$] = cfg.ral_models["rom_ctrl_prim_reg_block"].mem_ranges; - - bit [TL_DW-1:0] data; - bit [TL_AW-1:0] addr; - bit write; - int mem_idx; - - repeat (num_ops) begin - bit completed, saw_err; - - int mem_idx = $urandom_range(0, loc_mem_range.size - 1); - `DV_CHECK_STD_RANDOMIZE_FATAL(data) - `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(addr, - addr inside {[loc_mem_range[mem_idx].start_addr : - loc_mem_range[mem_idx].end_addr]};) - `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(write, !do_rom_error_req -> write == 1'b0; - read_only -> write == 1'b0;) - - tl_access_w_abort(.addr(addr), - .data(data), - .completed(completed), - .saw_err(saw_err), - .mask(get_rand_contiguous_mask('1)), - .write(write), - .blocking(1'b0), - .check_rsp(1'b0), - .tl_sequencer_h(p_sequencer.tl_sequencer_hs["rom_ctrl_prim_reg_block"]), - .req_abort_pct(do_rom_error_req ? 50 : 0), - .tl_access_timeout_ns(cfg.tl_access_timeout_ns)); - end - csr_utils_pkg::wait_no_outstanding_access(); - endtask + extern virtual task dut_init(string reset_kind = "HARD"); + extern virtual task apply_reset(string kind = "HARD"); + extern virtual task rom_ctrl_mem_init(); + extern virtual task do_rand_ops(int num_ops, bit read_only = 0); + extern virtual task read_digest_regs(); + extern virtual function bit [DIGEST_SIZE-1:0] get_expected_digest(); + extern virtual task tl_access( + input bit [TL_AW-1:0] addr, + input bit write, + inout bit [TL_DW-1:0] data, + input uint tl_access_timeout_ns = cfg.tl_access_timeout_ns, + input bit [TL_DBW-1:0] mask = '1, + input bit check_rsp = 1'b1, + input bit exp_err_rsp = 1'b0, + input bit [TL_DW-1:0] exp_data = 0, + input bit [TL_DW-1:0] compare_mask = '1, + input bit check_exp_data = 1'b0, + input bit blocking = csr_utils_pkg::default_csr_blocking, + input mubi4_t instr_type = MuBi4False, + tl_sequencer tl_sequencer_h = p_sequencer.tl_sequencer_h, + input tl_intg_err_e tl_intg_err_type = TlIntgErrNone); - // Read the digest[i] registers (computed by kmac) and the exp_digest[i] registers (which rom_ctrl - // read from the top of the ROM). Check these registers all have their expected values. - // - // Exits early on a system reset. - virtual task read_digest_regs(); - uvm_status_e status; - for (int i = 0; i < DIGEST_SIZE / TL_DW; i++) begin - ral.digest[i].mirror(.status(status), .check(UVM_CHECK)); - if (!cfg.clk_rst_vif.rst_n) return; - `DV_CHECK_EQ(status, UVM_IS_OK) - end - for (int i = 0; i < DIGEST_SIZE / TL_DW; i++) begin - ral.exp_digest[i].mirror(.status(status), .check(UVM_CHECK)); - if (!cfg.clk_rst_vif.rst_n) return; - `DV_CHECK_EQ(status, UVM_IS_OK) - end - endtask - - // Pull the expected digest value from the top of rom - virtual function bit [DIGEST_SIZE-1:0] get_expected_digest(); - bit [DIGEST_SIZE-1:0] digest; - bit [`ROM_BYTE_ADDR_WIDTH-1:0] dig_addr; - // Get the digest from rom - // The digest is the top 8 words in memory (unscrambled) - dig_addr = MAX_CHECK_ADDR; - for (int i = 0; i < DIGEST_SIZE / TL_DW; i++) begin - bit [ROM_MEM_W-1:0] mem_data = cfg.mem_bkdr_util_h.rom_encrypt_read32( - dig_addr, RND_CNST_SCR_KEY, RND_CNST_SCR_NONCE, 1'b0); - digest[i*TL_DW+:TL_DW] = mem_data[TL_DW-1:0]; - dig_addr += (TL_DW / 8); - end - return digest; - endfunction - - // Overrides tl_access in cip_base_vseq to add custom timeout. Timeout overriden to - // cfg.tl_access_timeout_ns (40ms) - // The ROM takes a while to be read and otherwise some tests may timeout when using - // default timeout. - virtual task tl_access(input bit [TL_AW-1:0] addr, - input bit write, - inout bit [TL_DW-1:0] data, - input uint tl_access_timeout_ns = cfg.tl_access_timeout_ns, - input bit [TL_DBW-1:0] mask = '1, - input bit check_rsp = 1'b1, - input bit exp_err_rsp = 1'b0, - input bit [TL_DW-1:0] exp_data = 0, - input bit [TL_DW-1:0] compare_mask = '1, - input bit check_exp_data = 1'b0, - input bit blocking = csr_utils_pkg::default_csr_blocking, - input mubi4_t instr_type = MuBi4False, - tl_sequencer tl_sequencer_h = p_sequencer.tl_sequencer_h, - input tl_intg_err_e tl_intg_err_type = TlIntgErrNone); - - super.tl_access(.addr(addr), .write(write), .data(data), - .tl_access_timeout_ns(tl_access_timeout_ns), - .mask(mask), .check_rsp(check_rsp), .exp_err_rsp(exp_err_rsp), - .compare_mask(compare_mask), .check_exp_data(check_exp_data), - .blocking(blocking), .instr_type(instr_type), .tl_sequencer_h(tl_sequencer_h), - .tl_intg_err_type(tl_intg_err_type)); - endtask - - // Overrides tl_access_w_abort in cip_base_vseq to add custom timeout. Timeout overriden to - // cfg.tl_access_timeout_ns (40ms) - // The ROM takes a while to be read and otherwise some tests may timeout when using - // default timeout. - virtual task tl_access_w_abort( - input bit [TL_AW-1:0] addr, - input bit write, - inout bit [TL_DW-1:0] data, - output bit completed, - output bit saw_err, - input uint tl_access_timeout_ns = cfg.tl_access_timeout_ns, - input bit [TL_DBW-1:0] mask = '1, - input bit check_rsp = 1'b1, - input bit exp_err_rsp = 1'b0, - input bit [TL_DW-1:0] exp_data = 0, - input bit [TL_DW-1:0] compare_mask = '1, - input bit check_exp_data = 1'b0, - input bit blocking = csr_utils_pkg::default_csr_blocking, - input mubi4_t instr_type = MuBi4False, - tl_sequencer tl_sequencer_h = p_sequencer.tl_sequencer_h, - input tl_intg_err_e tl_intg_err_type = TlIntgErrNone, - input int req_abort_pct = 0); - - - super.tl_access_w_abort(.addr(addr), .write(write), .data(data), .completed(completed), - .saw_err(saw_err), .tl_access_timeout_ns(tl_access_timeout_ns), - .mask(mask), .check_rsp(check_rsp), .exp_err_rsp(exp_err_rsp), - .exp_data(exp_data), .compare_mask(compare_mask), - .check_exp_data(check_exp_data), .blocking(blocking), - .instr_type(instr_type), .tl_sequencer_h(tl_sequencer_h), - .tl_intg_err_type(tl_intg_err_type), .req_abort_pct(req_abort_pct)); - endtask - - // Configure the KMAC agent to respond with a digest matching the given value. This is sent in two - // shares, which are chosen randomly. - function void set_kmac_digest(bit [DIGEST_SIZE-1:0] value); - bit [kmac_pkg::AppDigestW-1:0] share0; - kmac_pkg::rsp_digest_t rsp_digest_h; - - `DV_CHECK_STD_RANDOMIZE_FATAL(share0) - rsp_digest_h.digest_share0 = share0; - rsp_digest_h.digest_share1 = rsp_digest_h.digest_share0 ^ value; - cfg.m_kmac_agent_cfg.add_user_digest_share(rsp_digest_h); - endfunction - - // Configure the KMAC agent to respond with the ROM's expected digest if correct is as_expected - // and the wrong digest otherwise. - function void configure_kmac_digest(bit as_expected); - bit [DIGEST_SIZE-1:0] digest; - - // Read the expected digest from the ROM. - digest = get_expected_digest(); - - if (!as_expected) begin - // We want to choose a digest that doesn't match. To do so, start with the expected digest and - // then xor with a nonzero value. If single_bit is set, the digest will only be wrong at a - // single index, which means that most of the words in the digest will match the expected - // digest (but not all of them). - bit [kmac_pkg::AppDigestW-1:0] mask; - bit single_bit = $urandom_range(0, 1); - `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(mask, mask != 0; single_bit -> $countones(mask) == 1;) - digest ^= mask; - end + extern virtual task tl_access_w_abort( + input bit [TL_AW-1:0] addr, + input bit write, + inout bit [TL_DW-1:0] data, + output bit completed, + output bit saw_err, + input uint tl_access_timeout_ns = cfg.tl_access_timeout_ns, + input bit [TL_DBW-1:0] mask = '1, + input bit check_rsp = 1'b1, + input bit exp_err_rsp = 1'b0, + input bit [TL_DW-1:0] exp_data = 0, + input bit [TL_DW-1:0] compare_mask = '1, + input bit check_exp_data = 1'b0, + input bit blocking = csr_utils_pkg::default_csr_blocking, + input mubi4_t instr_type = MuBi4False, + tl_sequencer tl_sequencer_h = p_sequencer.tl_sequencer_h, + input tl_intg_err_e tl_intg_err_type = TlIntgErrNone, + input int req_abort_pct = 0); - set_kmac_digest(digest); - endfunction + extern function void set_kmac_digest(bit [DIGEST_SIZE-1:0] value); + extern function void configure_kmac_digest(bit as_expected); + extern task wait_for_fatal_alert(bit check_fsm_state = 1'b1, + int max_delay = 10000, + int max_wait_cycle = 1000); + +endclass : rom_ctrl_base_vseq - // Wait for a fatal alert to be raised +task rom_ctrl_base_vseq::dut_init(string reset_kind = "HARD"); + super.dut_init(reset_kind); + // Disable intr test since no interrupts + do_clear_all_interrupts = 1'b0; +endtask + +task rom_ctrl_base_vseq::apply_reset(string kind = "HARD"); + // Initialize memory at the beginning of reset since the DUT can come out of reset before this + // task completes (due to the second RAL clk_rst_if) + rom_ctrl_mem_init(); + super.apply_reset(kind); +endtask + +// Task to build a random rom in memory +task rom_ctrl_base_vseq::rom_ctrl_mem_init(); + bit [31:0] rnd_data; + + // Randomize the memory contents. // - // We expect the FATAL_ALERT_CAUSE register to contain 32'd1, which means that the checker_error - // field is true, but the integrity_error field is false. - task wait_for_fatal_alert(bit check_fsm_state = 1'b1, - int max_delay = 10000, - int max_wait_cycle = 1000); - uvm_reg_data_t act_val; - cfg.scoreboard.set_exp_alert("fatal", .is_fatal(1'b1), .max_delay(max_delay)); - - fork - begin - void'(ral.fatal_alert_cause.predict(.value(32'd1), .kind(UVM_PREDICT_READ), .be(4'hF))); - wait_alert_trigger ("fatal", .max_wait_cycle(1000), .wait_complete(1)); - csr_utils_pkg::csr_rd(.ptr(ral.fatal_alert_cause), .value(act_val), .check(UVM_CHECK)); - end - begin - repeat(3) wait_alert_trigger ("fatal", .max_wait_cycle(max_wait_cycle), .wait_complete(1)); - end - join - - if (check_fsm_state) begin - string alert_o_path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.alert_o"; - string state_q_path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.state_q"; - bit rdata_alert; - bit [$bits(rom_ctrl_pkg::fsm_state_e)-1:0] rdata_state; - `DV_CHECK(uvm_hdl_read(alert_o_path, rdata_alert)) - `DV_CHECK_EQ(rdata_alert, 1) - `DV_CHECK(uvm_hdl_read(state_q_path, rdata_state)) - `DV_CHECK_EQ(rdata_state, rom_ctrl_pkg::Invalid) + // We can't just use the mem_bkdr_util randomize_mem function because that doesn't obey the + // scrambling key. This wouldn't be a problem (the memory is supposed to be random!), except + // that we also need to pick ECC values that match. + for (int i = 0; i < ROM_SIZE_WORDS; i++) begin + `DV_CHECK_STD_RANDOMIZE_FATAL(rnd_data) + cfg.mem_bkdr_util_h.rom_encrypt_write32_integ(i * 4, + rnd_data, + RND_CNST_SCR_KEY, + RND_CNST_SCR_NONCE, + 1'b1); + end +endtask + +// Task to perform `num_ops` fully randomized memory transactions. +task rom_ctrl_base_vseq::do_rand_ops(int num_ops, bit read_only = 0); + addr_range_t loc_mem_range[$] = cfg.ral_models["rom_ctrl_prim_reg_block"].mem_ranges; + + bit [TL_DW-1:0] data; + bit [TL_AW-1:0] addr; + bit write; + int mem_idx; + + repeat (num_ops) begin + bit completed, saw_err; + + int mem_idx = $urandom_range(0, loc_mem_range.size - 1); + `DV_CHECK_STD_RANDOMIZE_FATAL(data) + `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(addr, + addr inside {[loc_mem_range[mem_idx].start_addr : + loc_mem_range[mem_idx].end_addr]};) + `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(write, !do_rom_error_req -> write == 1'b0; + read_only -> write == 1'b0;) + + tl_access_w_abort(.addr(addr), + .data(data), + .completed(completed), + .saw_err(saw_err), + .mask(get_rand_contiguous_mask('1)), + .write(write), + .blocking(1'b0), + .check_rsp(1'b0), + .tl_sequencer_h(p_sequencer.tl_sequencer_hs["rom_ctrl_prim_reg_block"]), + .req_abort_pct(do_rom_error_req ? 50 : 0), + .tl_access_timeout_ns(cfg.tl_access_timeout_ns)); + end + csr_utils_pkg::wait_no_outstanding_access(); +endtask + +// Read the digest[i] registers (computed by kmac) and the exp_digest[i] registers (which rom_ctrl +// read from the top of the ROM). Check these registers all have their expected values. +// +// Exits early on a system reset. +task rom_ctrl_base_vseq::read_digest_regs(); + uvm_status_e status; + for (int i = 0; i < DIGEST_SIZE / TL_DW; i++) begin + ral.digest[i].mirror(.status(status), .check(UVM_CHECK)); + if (!cfg.clk_rst_vif.rst_n) return; + `DV_CHECK_EQ(status, UVM_IS_OK) + end + for (int i = 0; i < DIGEST_SIZE / TL_DW; i++) begin + ral.exp_digest[i].mirror(.status(status), .check(UVM_CHECK)); + if (!cfg.clk_rst_vif.rst_n) return; + `DV_CHECK_EQ(status, UVM_IS_OK) + end +endtask + +// Pull the expected digest value from the top of rom +function bit [DIGEST_SIZE-1:0] rom_ctrl_base_vseq::get_expected_digest(); + bit [DIGEST_SIZE-1:0] digest; + bit [`ROM_BYTE_ADDR_WIDTH-1:0] dig_addr; + // Get the digest from rom + // The digest is the top 8 words in memory (unscrambled) + dig_addr = MAX_CHECK_ADDR; + for (int i = 0; i < DIGEST_SIZE / TL_DW; i++) begin + bit [ROM_MEM_W-1:0] mem_data = cfg.mem_bkdr_util_h.rom_encrypt_read32( + dig_addr, RND_CNST_SCR_KEY, RND_CNST_SCR_NONCE, 1'b0); + digest[i*TL_DW+:TL_DW] = mem_data[TL_DW-1:0]; + dig_addr += (TL_DW / 8); + end + return digest; +endfunction + +// Overrides tl_access in cip_base_vseq to add custom timeout. Timeout overriden to +// cfg.tl_access_timeout_ns (40ms) +// The ROM takes a while to be read and otherwise some tests may timeout when using +// default timeout. +task rom_ctrl_base_vseq::tl_access( + input bit [TL_AW-1:0] addr, + input bit write, + inout bit [TL_DW-1:0] data, + input uint tl_access_timeout_ns, + input bit [TL_DBW-1:0] mask = '1, + input bit check_rsp = 1'b1, + input bit exp_err_rsp = 1'b0, + input bit [TL_DW-1:0] exp_data = 0, + input bit [TL_DW-1:0] compare_mask = '1, + input bit check_exp_data = 1'b0, + input bit blocking, + input mubi4_t instr_type = MuBi4False, + tl_sequencer tl_sequencer_h = p_sequencer.tl_sequencer_h, + input tl_intg_err_e tl_intg_err_type = TlIntgErrNone); + + super.tl_access(.addr(addr), .write(write), .data(data), + .tl_access_timeout_ns(tl_access_timeout_ns), + .mask(mask), .check_rsp(check_rsp), .exp_err_rsp(exp_err_rsp), + .compare_mask(compare_mask), .check_exp_data(check_exp_data), + .blocking(blocking), .instr_type(instr_type), .tl_sequencer_h(tl_sequencer_h), + .tl_intg_err_type(tl_intg_err_type)); +endtask + +// Overrides tl_access_w_abort in cip_base_vseq to add custom timeout. Timeout overriden to +// cfg.tl_access_timeout_ns (40ms) +// The ROM takes a while to be read and otherwise some tests may timeout when using +// default timeout. +task rom_ctrl_base_vseq::tl_access_w_abort( + input bit [TL_AW-1:0] addr, + input bit write, + inout bit [TL_DW-1:0] data, + output bit completed, + output bit saw_err, + input uint tl_access_timeout_ns, + input bit [TL_DBW-1:0] mask = '1, + input bit check_rsp = 1'b1, + input bit exp_err_rsp = 1'b0, + input bit [TL_DW-1:0] exp_data = 0, + input bit [TL_DW-1:0] compare_mask = '1, + input bit check_exp_data = 1'b0, + input bit blocking, + input mubi4_t instr_type = MuBi4False, + tl_sequencer tl_sequencer_h = p_sequencer.tl_sequencer_h, + input tl_intg_err_e tl_intg_err_type = TlIntgErrNone, + input int req_abort_pct = 0); + + + super.tl_access_w_abort(.addr(addr), .write(write), .data(data), .completed(completed), + .saw_err(saw_err), .tl_access_timeout_ns(tl_access_timeout_ns), + .mask(mask), .check_rsp(check_rsp), .exp_err_rsp(exp_err_rsp), + .exp_data(exp_data), .compare_mask(compare_mask), + .check_exp_data(check_exp_data), .blocking(blocking), + .instr_type(instr_type), .tl_sequencer_h(tl_sequencer_h), + .tl_intg_err_type(tl_intg_err_type), .req_abort_pct(req_abort_pct)); +endtask + +// Configure the KMAC agent to respond with a digest matching the given value. This is sent in two +// shares, which are chosen randomly. +function void rom_ctrl_base_vseq::set_kmac_digest(bit [DIGEST_SIZE-1:0] value); + bit [kmac_pkg::AppDigestW-1:0] share0; + kmac_pkg::rsp_digest_t rsp_digest_h; + + `DV_CHECK_STD_RANDOMIZE_FATAL(share0) + rsp_digest_h.digest_share0 = share0; + rsp_digest_h.digest_share1 = rsp_digest_h.digest_share0 ^ value; + cfg.m_kmac_agent_cfg.add_user_digest_share(rsp_digest_h); +endfunction + +// Configure the KMAC agent to respond with the ROM's expected digest if correct is as_expected +// and the wrong digest otherwise. +function void rom_ctrl_base_vseq::configure_kmac_digest(bit as_expected); + bit [DIGEST_SIZE-1:0] digest; + + // Read the expected digest from the ROM. + digest = get_expected_digest(); + + if (!as_expected) begin + // We want to choose a digest that doesn't match. To do so, start with the expected digest and + // then xor with a nonzero value. If single_bit is set, the digest will only be wrong at a + // single index, which means that most of the words in the digest will match the expected + // digest (but not all of them). + bit [kmac_pkg::AppDigestW-1:0] mask; + bit single_bit = $urandom_range(0, 1); + `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(mask, mask != 0; single_bit -> $countones(mask) == 1;) + digest ^= mask; + end + + set_kmac_digest(digest); +endfunction + +// Wait for a fatal alert to be raised +// +// We expect the FATAL_ALERT_CAUSE register to contain 32'd1, which means that the checker_error +// field is true, but the integrity_error field is false. +task rom_ctrl_base_vseq::wait_for_fatal_alert(bit check_fsm_state = 1'b1, + int max_delay = 10000, + int max_wait_cycle = 1000); + uvm_reg_data_t act_val; + cfg.scoreboard.set_exp_alert("fatal", .is_fatal(1'b1), .max_delay(max_delay)); + + fork + begin + void'(ral.fatal_alert_cause.predict(.value(32'd1), .kind(UVM_PREDICT_READ), .be(4'hF))); + wait_alert_trigger ("fatal", .max_wait_cycle(1000), .wait_complete(1)); + csr_utils_pkg::csr_rd(.ptr(ral.fatal_alert_cause), .value(act_val), .check(UVM_CHECK)); end - endtask: wait_for_fatal_alert + begin + repeat(3) wait_alert_trigger ("fatal", .max_wait_cycle(max_wait_cycle), .wait_complete(1)); + end + join -endclass : rom_ctrl_base_vseq + if (check_fsm_state) begin + string alert_o_path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.alert_o"; + string state_q_path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.state_q"; + bit rdata_alert; + bit [$bits(rom_ctrl_pkg::fsm_state_e)-1:0] rdata_state; + `DV_CHECK(uvm_hdl_read(alert_o_path, rdata_alert)) + `DV_CHECK_EQ(rdata_alert, 1) + `DV_CHECK(uvm_hdl_read(state_q_path, rdata_state)) + `DV_CHECK_EQ(rdata_state, rom_ctrl_pkg::Invalid) + end +endtask: wait_for_fatal_alert diff --git a/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_common_vseq.sv b/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_common_vseq.sv index ef6d3ed75817c..68d5836fca31f 100644 --- a/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_common_vseq.sv +++ b/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_common_vseq.sv @@ -5,176 +5,193 @@ class rom_ctrl_common_vseq extends rom_ctrl_base_vseq; `uvm_object_utils(rom_ctrl_common_vseq) - constraint num_trans_c { - num_trans inside {[1:2]}; - } `uvm_object_new // If this flag is set, we will wait a short time after coming out of reset to allow the DUT to // finish its start-up sequence and become available for TL accesses. bit pause_after_dut_init = 1'b0; - // Write rubbish to the storage backing memory for a prim_fifo_sync - function void splat_fifo_storage(string fifo_path, int unsigned depth); - for (int unsigned i = 0; i < depth; i++) begin - string storage = $sformatf("%0s.gen_normal_fifo.storage[%0d]", fifo_path, i); - bit [31:0] value; - randcase - 1: value = '0; - 1: value = '1; - 1: value = $urandom; - endcase - `DV_CHECK_FATAL(uvm_hdl_deposit(storage, value)) - end - endfunction + extern constraint num_trans_c; + extern virtual task body(); + extern function void splat_fifo_storage(string fifo_path, int unsigned depth); + extern virtual function void inject_intg_fault_in_passthru_mem( + dv_base_mem mem, + bit [bus_params_pkg::BUS_AW-1:0] addr); + extern function bit is_ptr_in_prim_counts_fifo(string path, string fifo_path); + extern function bit is_ptr_in_adapters_fifo(string path, output bit in_req_fifo); + extern virtual function void sec_cm_fi_ctrl_svas(sec_cm_base_if_proxy if_proxy, bit enable); + extern virtual task check_sec_cm_fi_resp(sec_cm_base_if_proxy if_proxy); + extern virtual task wait_while_reading_low(); + extern virtual task dut_init(string reset_kind = "HARD"); + extern virtual task run_passthru_mem_tl_intg_err_vseq(int num_times = 1); - virtual task body(); - run_common_vseq_wrapper(num_trans); - endtask : body +endclass - virtual function void inject_intg_fault_in_passthru_mem(dv_base_mem mem, - bit [bus_params_pkg::BUS_AW-1:0] addr); - bit[tlul_pkg::DataIntgWidth+bus_params_pkg::BUS_DW-1:0] rdata; - bit[tlul_pkg::DataIntgWidth+bus_params_pkg::BUS_DW-1:0] flip_bits; +constraint rom_ctrl_common_vseq::num_trans_c { + num_trans inside {[1:2]}; +} + +// Write rubbish to the storage backing memory for a prim_fifo_sync +function void rom_ctrl_common_vseq::splat_fifo_storage(string fifo_path, int unsigned depth); + for (int unsigned i = 0; i < depth; i++) begin + string storage = $sformatf("%0s.gen_normal_fifo.storage[%0d]", fifo_path, i); + bit [31:0] value; + randcase + 1: value = '0; + 1: value = '1; + 1: value = $urandom; + endcase + `DV_CHECK_FATAL(uvm_hdl_deposit(storage, value)) + end +endfunction - rdata = cfg.mem_bkdr_util_h.rom_encrypt_read32(addr, RND_CNST_SCR_KEY, - RND_CNST_SCR_NONCE, 1'b1); +task rom_ctrl_common_vseq::body(); + run_common_vseq_wrapper(num_trans); +endtask : body - `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(flip_bits, - $countones(flip_bits) inside {[1:cip_base_pkg::MAX_TL_ECC_ERRORS]};) +function void rom_ctrl_common_vseq::inject_intg_fault_in_passthru_mem( + dv_base_mem mem, + bit [bus_params_pkg::BUS_AW-1:0] addr); + bit[tlul_pkg::DataIntgWidth+bus_params_pkg::BUS_DW-1:0] rdata; + bit[tlul_pkg::DataIntgWidth+bus_params_pkg::BUS_DW-1:0] flip_bits; - `uvm_info(`gfn, $sformatf("Backdoor change mem (addr 0x%0h) value 0x%0h by flipping bits %0h", - addr, rdata, flip_bits), UVM_LOW) + rdata = cfg.mem_bkdr_util_h.rom_encrypt_read32(addr, RND_CNST_SCR_KEY, + RND_CNST_SCR_NONCE, 1'b1); - cfg.mem_bkdr_util_h.rom_encrypt_write32_integ(addr, rdata, RND_CNST_SCR_KEY, RND_CNST_SCR_NONCE, - 1'b1, flip_bits); - endfunction + `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(flip_bits, + $countones(flip_bits) inside {[1:cip_base_pkg::MAX_TL_ECC_ERRORS]};) - // Return 1 if path is a pointer in the prim_count associated with the fifo at fifo_path - function bit is_ptr_in_prim_counts_fifo(string path, string fifo_path); - string cnt_path = {fifo_path, ".gen_normal_fifo.u_fifo_cnt"}; - string ptr_rel_paths[] = '{"gen_secure_ptrs.u_rptr", "gen_secure_ptrs.u_wptr"}; + `uvm_info(`gfn, $sformatf("Backdoor change mem (addr 0x%0h) value 0x%0h by flipping bits %0h", + addr, rdata, flip_bits), UVM_LOW) - foreach (ptr_rel_paths[i]) begin - if (path == {cnt_path, ".", ptr_rel_paths[i]}) begin - return 1'b1; - end - end - return 1'b0; - endfunction - - // Return 1 if path is a pointer in a prim_count for a fifo in with the adapter at adapter_path. - // If returning 1, this also writes to in_req_fifo output argument, setting the bit if this is a - // request fifo. - function bit is_ptr_in_adapters_fifo(string path, output bit in_req_fifo); - string adapter_path = {"tb.dut.u_tl_adapter_rom"}; - string fifo_paths[] = '{{adapter_path, ".u_reqfifo"}, - {adapter_path, ".u_sramreqfifo"}, - {adapter_path, ".u_rspfifo"}}; - - foreach (fifo_paths[i]) begin - if (is_ptr_in_prim_counts_fifo(path, fifo_paths[i])) begin - in_req_fifo = (i == 0 || i == 1); - return 1'b1; - end - end - return 1'b0; - endfunction + cfg.mem_bkdr_util_h.rom_encrypt_write32_integ(addr, rdata, RND_CNST_SCR_KEY, RND_CNST_SCR_NONCE, + 1'b1, flip_bits); +endfunction - virtual function void sec_cm_fi_ctrl_svas(sec_cm_base_if_proxy if_proxy, bit enable); - if (if_proxy.sec_cm_type == SecCmPrimCount) begin - // If we are injecting an error into a prim_count inside a prim_fifo_sync, we need to disable - // the DataKnown_A assertion inside the fifo. The problem is that we're telling the FIFO that - // it contains some elements that it doesn't really contain, so the backing memory is probably - // 'X, which fails an !$isunknown() check. The touching_fifo bit is computed to figure out - // whether this is happening. +// Return 1 if path is a pointer in the prim_count associated with the fifo at fifo_path +function bit rom_ctrl_common_vseq::is_ptr_in_prim_counts_fifo(string path, string fifo_path); + string cnt_path = {fifo_path, ".gen_normal_fifo.u_fifo_cnt"}; + string ptr_rel_paths[] = '{"gen_secure_ptrs.u_rptr", "gen_secure_ptrs.u_wptr"}; - bit touching_fifo = 1'b0, touching_req_fifo = 1'b0; + foreach (ptr_rel_paths[i]) begin + if (path == {cnt_path, ".", ptr_rel_paths[i]}) begin + return 1'b1; + end + end + return 1'b0; +endfunction + +// Return 1 if path is a pointer in a prim_count for a fifo in with the adapter at adapter_path. +// If returning 1, this also writes to in_req_fifo output argument, setting the bit if this is a +// request fifo. +function bit rom_ctrl_common_vseq::is_ptr_in_adapters_fifo(string path, output bit in_req_fifo); + string adapter_path = {"tb.dut.u_tl_adapter_rom"}; + string fifo_paths[] = '{{adapter_path, ".u_reqfifo"}, + {adapter_path, ".u_sramreqfifo"}, + {adapter_path, ".u_rspfifo"}}; + + foreach (fifo_paths[i]) begin + if (is_ptr_in_prim_counts_fifo(path, fifo_paths[i])) begin + in_req_fifo = (i == 0 || i == 1); + return 1'b1; + end + end + return 1'b0; +endfunction + +function void rom_ctrl_common_vseq::sec_cm_fi_ctrl_svas(sec_cm_base_if_proxy if_proxy, + bit enable); + if (if_proxy.sec_cm_type == SecCmPrimCount) begin + // If we are injecting an error into a prim_count inside a prim_fifo_sync, we need to disable + // the DataKnown_A assertion inside the fifo. The problem is that we're telling the FIFO that + // it contains some elements that it doesn't really contain, so the backing memory is probably + // 'X, which fails an !$isunknown() check. The touching_fifo bit is computed to figure out + // whether this is happening. + + bit touching_fifo = 1'b0, touching_req_fifo = 1'b0; + + if (is_ptr_in_adapters_fifo(if_proxy.path, touching_req_fifo)) begin + if (!enable) begin + `uvm_info(`gfn, "Doing FI on a prim_fifo_sync. Disabling related assertions", UVM_HIGH) + $assertoff(0, "tb.dut.u_tl_adapter_rom.u_reqfifo"); + $assertoff(0, "tb.dut.u_tl_adapter_rom.u_sramreqfifo"); + $assertoff(0, "tb.dut.u_tl_adapter_rom.u_rspfifo"); + end else begin + $asserton(0, "tb.dut.u_tl_adapter_rom.u_reqfifo"); + $asserton(0, "tb.dut.u_tl_adapter_rom.u_sramreqfifo"); + $asserton(0, "tb.dut.u_tl_adapter_rom.u_rspfifo"); + end - if (is_ptr_in_adapters_fifo(if_proxy.path, touching_req_fifo)) begin + // Disable assertions that we expect to fail if we corrupt a request FIFO. This causes us to + // generate spurious TL transactions. + if (touching_req_fifo) begin if (!enable) begin - `uvm_info(`gfn, "Doing FI on a prim_fifo_sync. Disabling related assertions", UVM_HIGH) - $assertoff(0, "tb.dut.u_tl_adapter_rom.u_reqfifo"); - $assertoff(0, "tb.dut.u_tl_adapter_rom.u_sramreqfifo"); - $assertoff(0, "tb.dut.u_tl_adapter_rom.u_rspfifo"); + `uvm_info(`gfn, "Doing FI on a request fifo. Disabling related assertions", UVM_HIGH) + $assertoff(0, "tb.dut"); end else begin - $asserton(0, "tb.dut.u_tl_adapter_rom.u_reqfifo"); - $asserton(0, "tb.dut.u_tl_adapter_rom.u_sramreqfifo"); - $asserton(0, "tb.dut.u_tl_adapter_rom.u_rspfifo"); + $asserton(0, "tb.dut"); end - - // Disable assertions that we expect to fail if we corrupt a request FIFO. This causes us to - // generate spurious TL transactions. - if (touching_req_fifo) begin - if (!enable) begin - `uvm_info(`gfn, "Doing FI on a request fifo. Disabling related assertions", UVM_HIGH) - $assertoff(0, "tb.dut"); - end else begin - $asserton(0, "tb.dut"); - end - end - end - end - - endfunction: sec_cm_fi_ctrl_svas - - virtual task check_sec_cm_fi_resp(sec_cm_base_if_proxy if_proxy); - uvm_reg_field fatal_cause; - super.check_sec_cm_fi_resp(if_proxy); - case (if_proxy.sec_cm_type) - SecCmPrimCount : begin - if (!uvm_re_match("*.u_tl_adapter_rom*", if_proxy.path)) - fatal_cause = ral.fatal_alert_cause.integrity_error; - else - fatal_cause = ral.fatal_alert_cause.checker_error; - csr_utils_pkg::csr_rd_check(.ptr(fatal_cause), .compare_value(1)); end - default : - `DV_CHECK_EQ(cfg.rom_ctrl_vif.checker_fsm_state, rom_ctrl_pkg::Invalid) - endcase - endtask : check_sec_cm_fi_resp - - // Wait while the dut's checker FSM is in the "ReadingLow" state. This waits the bulk of the time - // after reset and will finish when the dut is almost ready to start handling TL transactions. - virtual task wait_while_reading_low(); - while (cfg.rom_ctrl_vif.checker_fsm_state == rom_ctrl_pkg::ReadingLow) begin - #1000ns; end - endtask - - // A slightly tweaked version of the base dut_init which obeys pause_after_dut_init - virtual task dut_init(string reset_kind = "HARD"); - super.dut_init(reset_kind); - - if (pause_after_dut_init) begin - wait_while_reading_low(); + end + +endfunction: sec_cm_fi_ctrl_svas + +task rom_ctrl_common_vseq::check_sec_cm_fi_resp(sec_cm_base_if_proxy if_proxy); + uvm_reg_field fatal_cause; + super.check_sec_cm_fi_resp(if_proxy); + case (if_proxy.sec_cm_type) + SecCmPrimCount : begin + if (!uvm_re_match("*.u_tl_adapter_rom*", if_proxy.path)) + fatal_cause = ral.fatal_alert_cause.integrity_error; + else + fatal_cause = ral.fatal_alert_cause.checker_error; + csr_utils_pkg::csr_rd_check(.ptr(fatal_cause), .compare_value(1)); end - - // If do_apply_reset is true then super.dut_init just applied a reset and the rom_ctrl sram - // request fifos will be empty. We might be about to do fault injection on those fifos' counters - // which will generate spurious requests. But we don't want the request data to be X (because it - // will cause the fifos' error signals to get stuck at X). Write some arbitrary rubbish to the - // contents. - if (do_apply_reset) begin - splat_fifo_storage("tb.dut.u_tl_adapter_rom.u_reqfifo", 2); - splat_fifo_storage("tb.dut.u_tl_adapter_rom.u_sramreqfifo", 2); - end - endtask - - // This task is defined in cip_base_vseq. It tries to run some TL accesses and injects integrity - // errors in parallel. To make it work for rom_ctrl, we need to wait a bit for the DUT to be ready - // for TL accesses. - virtual task run_passthru_mem_tl_intg_err_vseq(int num_times = 1); + default : + `DV_CHECK_EQ(cfg.rom_ctrl_vif.checker_fsm_state, rom_ctrl_pkg::Invalid) + endcase +endtask : check_sec_cm_fi_resp + +// Wait while the dut's checker FSM is in the "ReadingLow" state. This waits the bulk of the time +// after reset and will finish when the dut is almost ready to start handling TL transactions. +task rom_ctrl_common_vseq::wait_while_reading_low(); + while (cfg.rom_ctrl_vif.checker_fsm_state == rom_ctrl_pkg::ReadingLow) begin + #1000ns; + end +endtask + +// A slightly tweaked version of the base dut_init which obeys pause_after_dut_init +task rom_ctrl_common_vseq::dut_init(string reset_kind = "HARD"); + super.dut_init(reset_kind); + + if (pause_after_dut_init) begin wait_while_reading_low(); - pause_after_dut_init = 1'b1; - - // Waiting like this takes quite a while, so running with a large value of num_times will cause - // the test to fail with a UVM phase timeout. Rather than overriding the test_timeout_ns - // argument in dv_base_test.sv, we have a simple bodge to divide the count down to something - // that takes a similar time to the other blocks. - num_times = (num_times + 3) / 4; - - super.run_passthru_mem_tl_intg_err_vseq(num_times); - endtask - -endclass + end + + // If do_apply_reset is true then super.dut_init just applied a reset and the rom_ctrl sram + // request fifos will be empty. We might be about to do fault injection on those fifos' counters + // which will generate spurious requests. But we don't want the request data to be X (because it + // will cause the fifos' error signals to get stuck at X). Write some arbitrary rubbish to the + // contents. + if (do_apply_reset) begin + splat_fifo_storage("tb.dut.u_tl_adapter_rom.u_reqfifo", 2); + splat_fifo_storage("tb.dut.u_tl_adapter_rom.u_sramreqfifo", 2); + end +endtask + +// This task is defined in cip_base_vseq. It tries to run some TL accesses and injects integrity +// errors in parallel. To make it work for rom_ctrl, we need to wait a bit for the DUT to be ready +// for TL accesses. +task rom_ctrl_common_vseq::run_passthru_mem_tl_intg_err_vseq(int num_times = 1); + wait_while_reading_low(); + pause_after_dut_init = 1'b1; + + // Waiting like this takes quite a while, so running with a large value of num_times will cause + // the test to fail with a UVM phase timeout. Rather than overriding the test_timeout_ns + // argument in dv_base_test.sv, we have a simple bodge to divide the count down to something + // that takes a similar time to the other blocks. + num_times = (num_times + 3) / 4; + + super.run_passthru_mem_tl_intg_err_vseq(num_times); +endtask diff --git a/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_corrupt_sig_fatal_chk_vseq.sv b/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_corrupt_sig_fatal_chk_vseq.sv index 034afb4cdac2f..14049fa8d6869 100644 --- a/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_corrupt_sig_fatal_chk_vseq.sv +++ b/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_corrupt_sig_fatal_chk_vseq.sv @@ -24,278 +24,286 @@ class rom_ctrl_corrupt_sig_fatal_chk_vseq extends rom_ctrl_base_vseq; rand countermeasure_id_e cm_id; + extern task body(); + extern task wait_with_bound(int max_clks); + extern task force_sig(string path, int value); + extern task chk_fsm_state(); + extern task wait_for_fsm_state_inside(ref rom_ctrl_pkg::fsm_state_e states_to_visit[$]); + extern task pick_err_inj_point(); + extern function prim_mubi_pkg::mubi4_t get_invalid_mubi4(); - task body(); - int num_reps; - `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(num_reps, (num_reps >= 10 && num_reps <= 20);) - for (int i = 0; i < num_reps; i++) begin - `DV_CHECK_STD_RANDOMIZE_FATAL(cm_id) - `uvm_info(`gfn, $sformatf("iteration %0d/%0d, cmd_id = %s", i, num_reps, cm_id.name()), - UVM_LOW) - case (cm_id) - // This test tries to cover all possible FSM transitions to the invalid state by triggering - // an alert inside the comparison module. - LocalEscalation: begin - rom_ctrl_pkg::fsm_state_e s; - rom_ctrl_pkg::fsm_state_e states_to_visit[$]; - // This FSM assumes a linear progression through the FSM states. - // Make sure the last state is the Invalid state. - `DV_CHECK_EQ(s.last(), rom_ctrl_pkg::Invalid) - s = s.first(); - while (s != s.last()) begin - states_to_visit.push_back(s); - s = s.next(); - end - while (states_to_visit.size() > 0) begin - wait_for_fsm_state_inside(states_to_visit); - // This is a sparsely encoded FSM, where all-zero is always an invalid state, hence we - // can use all-zero to trigger an alert. - force_sig("tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.u_compare.state_d", '0); - wait_for_fatal_alert(); - dut_init(); - end - end - // Once rom_ctrl has handed control of the mux to the bus, the internal FSM counter should - // point at the top of ROM. The unexpected_counter_change signal in rom_ctrl_fsm goes high - // and generates a fatal alert if that counter is perturbed in any way. To test this, - // addr_q in the counter is corrupted with any value other than the ROM's top address. - CheckerCtrConsistency: begin - bit [12:0] invalid_addr; - wait (mubi4_test_true_strict(cfg.rom_ctrl_vif.pwrmgr_data.done)); - wait_with_bound(10000); - // Pick the index of a ROM word other than the top one - `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(invalid_addr, (invalid_addr < (RomSizeWords-1));); - force_sig("tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.u_counter.addr_q", invalid_addr); - wait_for_fatal_alert(); - end - // The main checker FSM steps on internal 'done' signals, coming from its address counter, - // the KMAC response and its comparison counter. If any of these are asserted at times we - // don't expect, the FSM jumps to an invalid state. This triggers an alert and will not set - // the external 'done' signal for pwrmgr to continue boot. - CheckerCtrlFlowConsistency: begin - wait_with_bound(100); - force_sig("tb.dut.kmac_data_i.done", 1); - wait_for_fatal_alert(); - dut_init(); - wait_with_bound(100); - force_sig("tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.checker_done", 1); - wait_for_fatal_alert(); - dut_init(); - wait_with_bound(100); - force_sig("tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.counter_done", 1); - wait_for_fatal_alert(); - end - // The main checker FSM steps on internal 'done' signals, coming from its address counter, - // the KMAC response and its comparison counter. If any of these are asserted at times - // we don't expect, the FSM jumps to an invalid state. This triggers an alert and will not - // set the external 'done' signal for pwrmgr to continue boot. To test this start_checker_q - // signal from rom_ctrl_fsm is asserted randomly. - CompareCtrlFlowConsistency: begin - string start_chk_path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.start_checker_q"; - bit rdata; - pick_err_inj_point(); - do begin - `DV_CHECK(uvm_hdl_read(start_chk_path, rdata)) - @(posedge cfg.clk_rst_vif.clk); - end while (rdata != 0); - force_sig(start_chk_path, 1'b1); - wait_for_fatal_alert(); - end - // The hash comparison module has an internal count. If this glitches to a nonzero value - // before the comparison starts (state=Waiting) or to a value other than the last index - // after the comparison ends (state=Done) then a fatal alert is generated. - // Each of these cases are covered in cases "CompareCtrConsistencyWaiting" and - // "CompareCtrConsistencyDone" - CompareCtrConsistencyWaiting: begin - bit [2:0] invalid_addr; - bit [4:0] fsm_state_q; - string path; - wait_with_bound(2000); - path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.u_compare.state_q"; - `DV_CHECK(uvm_hdl_read(path,fsm_state_q)); - if(fsm_state_q != 5'b00100) begin - `uvm_fatal(`gfn, {"Case:'CompareCtrConsistencyWaiting' hit when 'rom_ctrl_compare' ", - "state!=Done hence sequence-case exits"}) - end - // State == Waiting -> It's ok to poke the addr_q to be non-zero - `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(invalid_addr, invalid_addr > 0;); - force_sig("tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.u_compare.addr_q", - invalid_addr); - wait_for_fatal_alert(); - dut_init(); - end - CompareCtrConsistencyDone: begin - bit [2:0] invalid_addr; +endclass : rom_ctrl_corrupt_sig_fatal_chk_vseq - wait (cfg.rom_ctrl_vif.pwrmgr_data.done == MuBi4True); - // After cfg.rom_ctrl_vif.pwrmgr_data.done = True we're in Done state - wait_with_bound(10); - // LastAddr has been set to 7 - `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(invalid_addr, invalid_addr < 7;); - force_sig("tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.u_compare.addr_q", invalid_addr); - wait_for_fatal_alert(); +task rom_ctrl_corrupt_sig_fatal_chk_vseq::body(); + int num_reps; + `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(num_reps, (num_reps >= 10 && num_reps <= 20);) + for (int i = 0; i < num_reps; i++) begin + `DV_CHECK_STD_RANDOMIZE_FATAL(cm_id) + `uvm_info(`gfn, $sformatf("iteration %0d/%0d, cmd_id = %s", i, num_reps, cm_id.name()), + UVM_LOW) + case (cm_id) + // This test tries to cover all possible FSM transitions to the invalid state by triggering + // an alert inside the comparison module. + LocalEscalation: begin + rom_ctrl_pkg::fsm_state_e s; + rom_ctrl_pkg::fsm_state_e states_to_visit[$]; + // This FSM assumes a linear progression through the FSM states. + // Make sure the last state is the Invalid state. + `DV_CHECK_EQ(s.last(), rom_ctrl_pkg::Invalid) + s = s.first(); + while (s != s.last()) begin + states_to_visit.push_back(s); + s = s.next(); end - // The mux that arbitrates between the checker and the bus is multi-bit encoded. - // An invalid value generates a fatal alert with the sel_invalid signal in the rom_ctrl_mux - // module. To test this rom_select_bus_o is forced with any value other than MuBi4True and - // MuBi4False. - MuxMubi: begin - pick_err_inj_point(); - force_sig("tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.rom_select_bus_o", - get_invalid_mubi4()); - wait_for_fatal_alert(.check_fsm_state(1'b0)); + while (states_to_visit.size() > 0) begin + wait_for_fsm_state_inside(states_to_visit); + // This is a sparsely encoded FSM, where all-zero is always an invalid state, hence we + // can use all-zero to trigger an alert. + force_sig("tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.u_compare.state_d", '0); + wait_for_fatal_alert(); dut_init(); - pick_err_inj_point(); - force_sig("tb.dut.u_mux.sel_bus_q", get_invalid_mubi4()); - wait_for_fatal_alert(.check_fsm_state(1'b0)); end - // The mux that arbitrates between the checker and the bus gives access to the checker at - // the start of time and then switches to the bus, never going back. If a glitch does cause - // it to switch back, a fatal alert is generated with the sel_reverted or sel_q_reverted - // signals in the rom_ctrl_mux module. To test this rom_select_bus_o is forced to - // MuBi4False after rom check is completed. - MuxConsistency: begin - wait (cfg.rom_ctrl_vif.pwrmgr_data.done == MuBi4True); - wait_with_bound(10); - force_sig("tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.rom_select_bus_o", MuBi4False); - wait_for_fatal_alert(.check_fsm_state(1'b0)); + end + // Once rom_ctrl has handed control of the mux to the bus, the internal FSM counter should + // point at the top of ROM. The unexpected_counter_change signal in rom_ctrl_fsm goes high + // and generates a fatal alert if that counter is perturbed in any way. To test this, + // addr_q in the counter is corrupted with any value other than the ROM's top address. + CheckerCtrConsistency: begin + bit [12:0] invalid_addr; + wait (mubi4_test_true_strict(cfg.rom_ctrl_vif.pwrmgr_data.done)); + wait_with_bound(10000); + // Pick the index of a ROM word other than the top one + `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(invalid_addr, (invalid_addr < (RomSizeWords-1));); + force_sig("tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.u_counter.addr_q", invalid_addr); + wait_for_fatal_alert(); + end + // The main checker FSM steps on internal 'done' signals, coming from its address counter, + // the KMAC response and its comparison counter. If any of these are asserted at times we + // don't expect, the FSM jumps to an invalid state. This triggers an alert and will not set + // the external 'done' signal for pwrmgr to continue boot. + CheckerCtrlFlowConsistency: begin + wait_with_bound(100); + force_sig("tb.dut.kmac_data_i.done", 1); + wait_for_fatal_alert(); + dut_init(); + wait_with_bound(100); + force_sig("tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.checker_done", 1); + wait_for_fatal_alert(); + dut_init(); + wait_with_bound(100); + force_sig("tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.counter_done", 1); + wait_for_fatal_alert(); + end + // The main checker FSM steps on internal 'done' signals, coming from its address counter, + // the KMAC response and its comparison counter. If any of these are asserted at times + // we don't expect, the FSM jumps to an invalid state. This triggers an alert and will not + // set the external 'done' signal for pwrmgr to continue boot. To test this start_checker_q + // signal from rom_ctrl_fsm is asserted randomly. + CompareCtrlFlowConsistency: begin + string start_chk_path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.start_checker_q"; + bit rdata; + pick_err_inj_point(); + do begin + `DV_CHECK(uvm_hdl_read(start_chk_path, rdata)) + @(posedge cfg.clk_rst_vif.clk); + end while (rdata != 0); + force_sig(start_chk_path, 1'b1); + wait_for_fatal_alert(); + end + // The hash comparison module has an internal count. If this glitches to a nonzero value + // before the comparison starts (state=Waiting) or to a value other than the last index + // after the comparison ends (state=Done) then a fatal alert is generated. + // Each of these cases are covered in cases "CompareCtrConsistencyWaiting" and + // "CompareCtrConsistencyDone" + CompareCtrConsistencyWaiting: begin + bit [2:0] invalid_addr; + bit [4:0] fsm_state_q; + string path; + wait_with_bound(2000); + path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.u_compare.state_q"; + `DV_CHECK(uvm_hdl_read(path,fsm_state_q)); + if(fsm_state_q != 5'b00100) begin + `uvm_fatal(`gfn, {"Case:'CompareCtrConsistencyWaiting' hit when 'rom_ctrl_compare' ", + "state!=Done hence sequence-case exits"}) end - // Inject errors into bus_rom_rom_index (which is how an attacker would get a different - // memory word) and then check that the data that gets read doesn't match the data stored - // at the glitched address. - CtrlRedun: begin - addr_range_t loc_mem_range[$] = cfg.ral_models["rom_ctrl_prim_reg_block"].mem_ranges; - bit [TL_DW-1:0] rdata, rdata_tgt, corr_data; - bit [TL_AW-1:0] addr; - int mem_idx = $urandom_range(0, loc_mem_range.size - 1); - bit [12:0] bus_rom_rom_index_val; - bit [12:0] corr_bus_rom_rom_index_val; - bit [TL_AW-1:0] tgt_addr; - cip_tl_seq_item tl_access_rsp; - bit completed, saw_err; - string path; + // State == Waiting -> It's ok to poke the addr_q to be non-zero + `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(invalid_addr, invalid_addr > 0;); + force_sig("tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.u_compare.addr_q", + invalid_addr); + wait_for_fatal_alert(); + dut_init(); + end + CompareCtrConsistencyDone: begin + bit [2:0] invalid_addr; - wait (cfg.rom_ctrl_vif.pwrmgr_data.done == MuBi4True); - wait_with_bound(10); - `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(addr, - addr inside {[loc_mem_range[mem_idx].start_addr : - loc_mem_range[mem_idx].end_addr]};) - bus_rom_rom_index_val = addr[2 +: RomIndexWidth]; - `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(tgt_addr, - tgt_addr inside {[loc_mem_range[mem_idx].start_addr : - loc_mem_range[mem_idx].end_addr]}; - (tgt_addr != addr);) - corr_bus_rom_rom_index_val = tgt_addr[2 +: RomIndexWidth]; - tl_access_sub(.addr(addr), .write(0), .data(rdata), .completed(completed), - .saw_err(saw_err), .check_rsp(1), .rsp(tl_access_rsp), - .tl_sequencer_h(p_sequencer.tl_sequencer_hs["rom_ctrl_prim_reg_block"])); - void'(tl_access_rsp.is_d_chan_intg_ok(.en_rsp_intg_chk(1), - .en_data_intg_chk(1), - .throw_error(1))); - tl_access_sub(.addr(tgt_addr), .write(0), .data(rdata_tgt), .completed(completed), - .saw_err(saw_err), .check_rsp(1), .rsp(tl_access_rsp), - .tl_sequencer_h(p_sequencer.tl_sequencer_hs["rom_ctrl_prim_reg_block"])); - void'(tl_access_rsp.is_d_chan_intg_ok(.en_rsp_intg_chk(1), - .en_data_intg_chk(1), - .throw_error(1))); - fork - begin - cfg.en_scb_tl_err_chk = 0; - cfg.scoreboard.disable_rom_acc_chk = 1; - tl_access_sub(.addr(addr), .write(0), .data(corr_data), .completed(completed), - .saw_err(saw_err), .check_rsp(1), .rsp(tl_access_rsp), - .tl_sequencer_h(p_sequencer.tl_sequencer_hs["rom_ctrl_prim_reg_block"]) - ); - `DV_CHECK_EQ(completed, 1) - `DV_CHECK_EQ(saw_err, 0) - if ((corr_data == rdata) || (corr_data == rdata_tgt)) begin - `uvm_error(`gfn, "corr_data matching data in rom") - end - cfg.en_scb_tl_err_chk = 1; - cfg.scoreboard.disable_rom_acc_chk = 0; - end - begin - string rom_idx_path = "tb.dut.bus_rom_rom_index"; + wait (cfg.rom_ctrl_vif.pwrmgr_data.done == MuBi4True); + // After cfg.rom_ctrl_vif.pwrmgr_data.done = True we're in Done state + wait_with_bound(10); + // LastAddr has been set to 7 + `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(invalid_addr, invalid_addr < 7;); + force_sig("tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.u_compare.addr_q", invalid_addr); + wait_for_fatal_alert(); + end + // The mux that arbitrates between the checker and the bus is multi-bit encoded. + // An invalid value generates a fatal alert with the sel_invalid signal in the rom_ctrl_mux + // module. To test this rom_select_bus_o is forced with any value other than MuBi4True and + // MuBi4False. + MuxMubi: begin + pick_err_inj_point(); + force_sig("tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.rom_select_bus_o", + get_invalid_mubi4()); + wait_for_fatal_alert(.check_fsm_state(1'b0)); + dut_init(); + pick_err_inj_point(); + force_sig("tb.dut.u_mux.sel_bus_q", get_invalid_mubi4()); + wait_for_fatal_alert(.check_fsm_state(1'b0)); + end + // The mux that arbitrates between the checker and the bus gives access to the checker at + // the start of time and then switches to the bus, never going back. If a glitch does cause + // it to switch back, a fatal alert is generated with the sel_reverted or sel_q_reverted + // signals in the rom_ctrl_mux module. To test this rom_select_bus_o is forced to + // MuBi4False after rom check is completed. + MuxConsistency: begin + wait (cfg.rom_ctrl_vif.pwrmgr_data.done == MuBi4True); + wait_with_bound(10); + force_sig("tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.rom_select_bus_o", MuBi4False); + wait_for_fatal_alert(.check_fsm_state(1'b0)); + end + // Inject errors into bus_rom_rom_index (which is how an attacker would get a different + // memory word) and then check that the data that gets read doesn't match the data stored + // at the glitched address. + CtrlRedun: begin + addr_range_t loc_mem_range[$] = cfg.ral_models["rom_ctrl_prim_reg_block"].mem_ranges; + bit [TL_DW-1:0] rdata, rdata_tgt, corr_data; + bit [TL_AW-1:0] addr; + int mem_idx = $urandom_range(0, loc_mem_range.size - 1); + bit [12:0] bus_rom_rom_index_val; + bit [12:0] corr_bus_rom_rom_index_val; + bit [TL_AW-1:0] tgt_addr; + cip_tl_seq_item tl_access_rsp; + bit completed, saw_err; + string path; - wait (cfg.m_tl_agent_cfgs["rom_ctrl_prim_reg_block"].vif.h2d.a_valid); - $assertoff(0, "tb.dut.BusRomIndicesMatch_A"); - `DV_CHECK(uvm_hdl_force(rom_idx_path, corr_bus_rom_rom_index_val)); - wait (!cfg.m_tl_agent_cfgs["rom_ctrl_prim_reg_block"].vif.h2d.a_valid); - `DV_CHECK(uvm_hdl_release(rom_idx_path)); + wait (cfg.rom_ctrl_vif.pwrmgr_data.done == MuBi4True); + wait_with_bound(10); + `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(addr, + addr inside {[loc_mem_range[mem_idx].start_addr : + loc_mem_range[mem_idx].end_addr]};) + bus_rom_rom_index_val = addr[2 +: RomIndexWidth]; + `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(tgt_addr, + tgt_addr inside {[loc_mem_range[mem_idx].start_addr : + loc_mem_range[mem_idx].end_addr]}; + (tgt_addr != addr);) + corr_bus_rom_rom_index_val = tgt_addr[2 +: RomIndexWidth]; + tl_access_sub(.addr(addr), .write(0), .data(rdata), .completed(completed), + .saw_err(saw_err), .check_rsp(1), .rsp(tl_access_rsp), + .tl_sequencer_h(p_sequencer.tl_sequencer_hs["rom_ctrl_prim_reg_block"])); + void'(tl_access_rsp.is_d_chan_intg_ok(.en_rsp_intg_chk(1), + .en_data_intg_chk(1), + .throw_error(1))); + tl_access_sub(.addr(tgt_addr), .write(0), .data(rdata_tgt), .completed(completed), + .saw_err(saw_err), .check_rsp(1), .rsp(tl_access_rsp), + .tl_sequencer_h(p_sequencer.tl_sequencer_hs["rom_ctrl_prim_reg_block"])); + void'(tl_access_rsp.is_d_chan_intg_ok(.en_rsp_intg_chk(1), + .en_data_intg_chk(1), + .throw_error(1))); + fork + begin + cfg.en_scb_tl_err_chk = 0; + cfg.scoreboard.disable_rom_acc_chk = 1; + tl_access_sub(.addr(addr), .write(0), .data(corr_data), .completed(completed), + .saw_err(saw_err), .check_rsp(1), .rsp(tl_access_rsp), + .tl_sequencer_h(p_sequencer.tl_sequencer_hs["rom_ctrl_prim_reg_block"]) + ); + `DV_CHECK_EQ(completed, 1) + `DV_CHECK_EQ(saw_err, 0) + if ((corr_data == rdata) || (corr_data == rdata_tgt)) begin + `uvm_error(`gfn, "corr_data matching data in rom") end - join - end - default: begin - // do nothing - end - endcase - wait_with_bound(10); - dut_init(); - $asserton(0, "tb.dut.BusRomIndicesMatch_A"); - end - endtask : body + cfg.en_scb_tl_err_chk = 1; + cfg.scoreboard.disable_rom_acc_chk = 0; + end + begin + string rom_idx_path = "tb.dut.bus_rom_rom_index"; - task wait_with_bound(int max_clks); - // Lower bound is chosen to be 2 to allow for at least 1 clock cycle after ROM check is done. - repeat($urandom_range(2,max_clks)) - @(negedge cfg.clk_rst_vif.clk); - endtask: wait_with_bound + wait (cfg.m_tl_agent_cfgs["rom_ctrl_prim_reg_block"].vif.h2d.a_valid); + $assertoff(0, "tb.dut.BusRomIndicesMatch_A"); + `DV_CHECK(uvm_hdl_force(rom_idx_path, corr_bus_rom_rom_index_val)); + wait (!cfg.m_tl_agent_cfgs["rom_ctrl_prim_reg_block"].vif.h2d.a_valid); + `DV_CHECK(uvm_hdl_release(rom_idx_path)); + end + join + end + default: begin + // do nothing + end + endcase + wait_with_bound(10); + dut_init(); + $asserton(0, "tb.dut.BusRomIndicesMatch_A"); + end +endtask : body - task force_sig(string path, int value); - `DV_CHECK(uvm_hdl_force(path, value)); - `uvm_info(`gfn, $sformatf("Setting path: %s to value=0x%0x",path,value), UVM_DEBUG) +task rom_ctrl_corrupt_sig_fatal_chk_vseq::wait_with_bound(int max_clks); + // Lower bound is chosen to be 2 to allow for at least 1 clock cycle after ROM check is done. + repeat($urandom_range(2,max_clks)) @(negedge cfg.clk_rst_vif.clk); - `DV_CHECK(uvm_hdl_release(path)); - endtask: force_sig +endtask: wait_with_bound - task chk_fsm_state(); - string alert_o_path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.alert_o"; - string state_q_path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.state_q"; - bit rdata_alert; - bit [$bits(rom_ctrl_pkg::fsm_state_e)-1:0] rdata_state; +task rom_ctrl_corrupt_sig_fatal_chk_vseq::force_sig(string path, int value); + `DV_CHECK(uvm_hdl_force(path, value)); + `uvm_info(`gfn, $sformatf("Setting path: %s to value=0x%0x",path,value), UVM_DEBUG) + @(negedge cfg.clk_rst_vif.clk); + `DV_CHECK(uvm_hdl_release(path)); +endtask: force_sig - `DV_CHECK_EQ(rdata_state, rom_ctrl_pkg::Invalid) - endtask: chk_fsm_state +task rom_ctrl_corrupt_sig_fatal_chk_vseq::chk_fsm_state(); + string alert_o_path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.alert_o"; + string state_q_path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.state_q"; + bit rdata_alert; + bit [$bits(rom_ctrl_pkg::fsm_state_e)-1:0] rdata_state; - // Wait until FSM state has progressed to a state within the list. - // The task removes all previous states including the one that has been reached afterwards. - // Note that this assumes no loops and linear progression through the state enum entries. - task wait_for_fsm_state_inside(ref rom_ctrl_pkg::fsm_state_e states_to_visit[$]); - string state_q_path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.state_q"; - bit [$bits(rom_ctrl_pkg::fsm_state_e)-1:0] rdata_state; - string path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.state_q"; - do begin - @(negedge cfg.clk_rst_vif.clk); - `DV_CHECK(uvm_hdl_read(state_q_path, rdata_state)) - end while (!(rdata_state inside {states_to_visit})); - `uvm_info(`gfn, $sformatf("reached FSM state %x", rdata_state), UVM_LOW) - // Remove previous states from queue, including the one that has been reached. - while (states_to_visit.pop_front() != rdata_state); - endtask: wait_for_fsm_state_inside + `DV_CHECK_EQ(rdata_state, rom_ctrl_pkg::Invalid) +endtask: chk_fsm_state - task pick_err_inj_point(); - int wait_clks; - // Pick error injection point. 1 - After ROM check completion. 0 - Before ROM check completion. - bit err_point; - `DV_CHECK_STD_RANDOMIZE_FATAL(err_point) - if(err_point) begin - wait (cfg.rom_ctrl_vif.pwrmgr_data.done == MuBi4True); - wait_clks = 10; - end - else begin - wait_clks = 10000; - end - wait_with_bound(wait_clks); - endtask +// Wait until FSM state has progressed to a state within the list. +// The task removes all previous states including the one that has been reached afterwards. +// Note that this assumes no loops and linear progression through the state enum entries. +task rom_ctrl_corrupt_sig_fatal_chk_vseq::wait_for_fsm_state_inside( + ref rom_ctrl_pkg::fsm_state_e states_to_visit[$]); + string state_q_path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.state_q"; + bit [$bits(rom_ctrl_pkg::fsm_state_e)-1:0] rdata_state; + string path = "tb.dut.gen_fsm_scramble_enabled.u_checker_fsm.state_q"; + do begin + @(negedge cfg.clk_rst_vif.clk); + `DV_CHECK(uvm_hdl_read(state_q_path, rdata_state)) + end while (!(rdata_state inside {states_to_visit})); + `uvm_info(`gfn, $sformatf("reached FSM state %x", rdata_state), UVM_LOW) + // Remove previous states from queue, including the one that has been reached. + while (states_to_visit.pop_front() != rdata_state); +endtask: wait_for_fsm_state_inside - function prim_mubi_pkg::mubi4_t get_invalid_mubi4(); - // This is a bit of a hack and we're basically inlining the contents of mubi4_test_invalid. This - // is because arguments to a function in any particular constraint get fixed before the - // constraint is evaluated. This means that you can't use a constraint of the form "is_good(x)" - // to ensure x is valid. - logic [3:0] val; - `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(val, ~(val inside {MuBi4True, MuBi4False});) - return prim_mubi_pkg::mubi4_t'(val); - endfunction +task rom_ctrl_corrupt_sig_fatal_chk_vseq::pick_err_inj_point(); + int wait_clks; + // Pick error injection point. 1 - After ROM check completion. 0 - Before ROM check completion. + bit err_point; + `DV_CHECK_STD_RANDOMIZE_FATAL(err_point) + if(err_point) begin + wait (cfg.rom_ctrl_vif.pwrmgr_data.done == MuBi4True); + wait_clks = 10; + end + else begin + wait_clks = 10000; + end + wait_with_bound(wait_clks); +endtask -endclass : rom_ctrl_corrupt_sig_fatal_chk_vseq +function prim_mubi_pkg::mubi4_t rom_ctrl_corrupt_sig_fatal_chk_vseq::get_invalid_mubi4(); + // This is a bit of a hack and we're basically inlining the contents of mubi4_test_invalid. This + // is because arguments to a function in any particular constraint get fixed before the + // constraint is evaluated. This means that you can't use a constraint of the form "is_good(x)" + // to ensure x is valid. + logic [3:0] val; + `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(val, ~(val inside {MuBi4True, MuBi4False});) + return prim_mubi_pkg::mubi4_t'(val); +endfunction diff --git a/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_kmac_err_chk_vseq.sv b/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_kmac_err_chk_vseq.sv index c6370064f544b..11cc59588d040 100644 --- a/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_kmac_err_chk_vseq.sv +++ b/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_kmac_err_chk_vseq.sv @@ -7,16 +7,18 @@ class rom_ctrl_kmac_err_chk_vseq extends rom_ctrl_base_vseq; `uvm_object_new - task body(); - cfg.m_kmac_agent_cfg.error_rsp_pct = 100; +extern task body(); - wait(cfg.m_kmac_agent_cfg.vif.mon_cb.rsp_done); +endclass : rom_ctrl_kmac_err_chk_vseq - wait_for_fatal_alert(.max_delay(0), .max_wait_cycle(7)); +task rom_ctrl_kmac_err_chk_vseq::body(); + cfg.m_kmac_agent_cfg.error_rsp_pct = 100; - `DV_CHECK_EQ(cfg.rom_ctrl_vif.pwrmgr_data.done, MuBi4False) - cfg.m_kmac_agent_cfg.error_rsp_pct = 0; - dut_init(); - endtask : body + wait(cfg.m_kmac_agent_cfg.vif.mon_cb.rsp_done); -endclass : rom_ctrl_kmac_err_chk_vseq + wait_for_fatal_alert(.max_delay(0), .max_wait_cycle(7)); + + `DV_CHECK_EQ(cfg.rom_ctrl_vif.pwrmgr_data.done, MuBi4False) + cfg.m_kmac_agent_cfg.error_rsp_pct = 0; + dut_init(); +endtask : body diff --git a/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_smoke_vseq.sv b/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_smoke_vseq.sv index bd0a0914f5661..7e91d90954e46 100644 --- a/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_smoke_vseq.sv +++ b/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_smoke_vseq.sv @@ -6,17 +6,19 @@ class rom_ctrl_smoke_vseq extends rom_ctrl_base_vseq; `uvm_object_utils(rom_ctrl_smoke_vseq) `uvm_object_new - task body(); - bit send_expected = $urandom_range(0, 1); + extern task body(); - // Tell the KMAC app agent whether generate the digest that was expected in the ROM. - configure_kmac_digest(send_expected); +endclass : rom_ctrl_smoke_vseq - // Queue up some memory operations. These will block until the rom check completes. - do_rand_ops($urandom_range(20, 50)); +task rom_ctrl_smoke_vseq::body(); + bit send_expected = $urandom_range(0, 1); - // Read all the digest and exp_digest registers, checking they match the values we expect. - read_digest_regs(); - endtask : body + // Tell the KMAC app agent whether generate the digest that was expected in the ROM. + configure_kmac_digest(send_expected); -endclass : rom_ctrl_smoke_vseq + // Queue up some memory operations. These will block until the rom check completes. + do_rand_ops($urandom_range(20, 50)); + + // Read all the digest and exp_digest registers, checking they match the values we expect. + read_digest_regs(); +endtask : body diff --git a/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_stress_all_vseq.sv b/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_stress_all_vseq.sv index 2b0a908294cd1..db95c10408c98 100644 --- a/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_stress_all_vseq.sv +++ b/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_stress_all_vseq.sv @@ -8,34 +8,37 @@ class rom_ctrl_stress_all_vseq extends rom_ctrl_base_vseq; `uvm_object_new - constraint num_trans_c { - num_trans inside {[3:6]}; - } + extern constraint num_trans_c; + extern task body(); - task body(); - string seq_names[] = {"rom_ctrl_smoke_vseq", - "rom_ctrl_common_vseq"}; - for (int i = 1; i <= num_trans; i++) begin - uvm_sequence seq; - rom_ctrl_base_vseq rom_ctrl_vseq; - uint seq_idx = $urandom_range(0, seq_names.size - 1); - seq = create_seq_by_name(seq_names[seq_idx]); - `downcast(rom_ctrl_vseq, seq) +endclass - // if upper seq disables do_apply_reset for this seq, then can't issue reset - // as upper seq may drive reset - if (do_apply_reset) rom_ctrl_vseq.do_apply_reset = $urandom_range(0, 1); - else rom_ctrl_vseq.do_apply_reset = 0; - rom_ctrl_vseq.set_sequencer(p_sequencer); - `uvm_info(`gfn, $sformatf("Running %s sequence", seq_names[seq_idx]), UVM_LOW) - `DV_CHECK_RANDOMIZE_FATAL(rom_ctrl_vseq) - if (seq_names[seq_idx] == "rom_ctrl_common_vseq") begin - rom_ctrl_common_vseq common_vseq; - `downcast(common_vseq, rom_ctrl_vseq); - common_vseq.common_seq_type = "intr_test"; - end - rom_ctrl_vseq.start(p_sequencer); - end - endtask : body +constraint rom_ctrl_stress_all_vseq::num_trans_c { + num_trans inside {[3:6]}; +} -endclass +task rom_ctrl_stress_all_vseq::body(); + string seq_names[] = {"rom_ctrl_smoke_vseq", + "rom_ctrl_common_vseq"}; + for (int i = 1; i <= num_trans; i++) begin + uvm_sequence seq; + rom_ctrl_base_vseq rom_ctrl_vseq; + uint seq_idx = $urandom_range(0, seq_names.size - 1); + seq = create_seq_by_name(seq_names[seq_idx]); + `downcast(rom_ctrl_vseq, seq) + + // if upper seq disables do_apply_reset for this seq, then can't issue reset + // as upper seq may drive reset + if (do_apply_reset) rom_ctrl_vseq.do_apply_reset = $urandom_range(0, 1); + else rom_ctrl_vseq.do_apply_reset = 0; + rom_ctrl_vseq.set_sequencer(p_sequencer); + `uvm_info(`gfn, $sformatf("Running %s sequence", seq_names[seq_idx]), UVM_LOW) + `DV_CHECK_RANDOMIZE_FATAL(rom_ctrl_vseq) + if (seq_names[seq_idx] == "rom_ctrl_common_vseq") begin + rom_ctrl_common_vseq common_vseq; + `downcast(common_vseq, rom_ctrl_vseq); + common_vseq.common_seq_type = "intr_test"; + end + rom_ctrl_vseq.start(p_sequencer); + end +endtask : body diff --git a/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_throughput_vseq.sv b/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_throughput_vseq.sv index ff38496988621..b07014c0c309d 100644 --- a/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_throughput_vseq.sv +++ b/hw/ip/rom_ctrl/dv/env/seq_lib/rom_ctrl_throughput_vseq.sv @@ -12,40 +12,44 @@ class rom_ctrl_throughput_vseq extends rom_ctrl_base_vseq; // Indicates the number of memory accesses to be performed rand int num_mem_reads; - constraint num_mem_reads_c { - num_mem_reads inside {[20 : 50]}; - } + extern constraint num_mem_reads_c; + extern constraint num_trans_c; + extern task body(); // Indicates the number of iterations rand int num_trans; - constraint num_trans_c { - num_trans inside {[1 : 10]}; - } - - task body(); - int num_cycles; - `DV_CHECK_MEMBER_RANDOMIZE_FATAL(num_trans) - for (int i = 0; i <= num_trans; i++) begin - `uvm_info(`gfn, $sformatf("iterating %0d/%0d", i, num_trans), UVM_LOW) - `DV_CHECK_MEMBER_RANDOMIZE_FATAL(num_mem_reads) - wait (cfg.rom_ctrl_vif .pwrmgr_data.done == prim_mubi_pkg::MuBi4True); - `DV_SPINWAIT_EXIT( - // thread 1 to count cycles - forever begin - // Counting negedge to avoid one extra clock cycle count when d_valid id pulled down - @(negedge cfg.clk_rst_vif.clk); - num_cycles++; - end, - // thread 2 to do ROM OPs - do_rand_ops(num_mem_reads, 1);); - - `uvm_info(`gfn, $sformatf("num_cycles: %0d, num_mem_reads: %0d",num_cycles, num_mem_reads), - UVM_MEDIUM) - - `DV_CHECK_EQ(num_cycles, num_mem_reads + 1); - num_cycles = 0; - end - endtask : body - endclass : rom_ctrl_throughput_vseq + +constraint rom_ctrl_throughput_vseq::num_trans_c { + num_trans inside {[1 : 10]}; +} + +constraint rom_ctrl_throughput_vseq::num_mem_reads_c { + num_mem_reads inside {[20 : 50]}; +} + +task rom_ctrl_throughput_vseq::body(); + int num_cycles; + `DV_CHECK_MEMBER_RANDOMIZE_FATAL(num_trans) + for (int i = 0; i <= num_trans; i++) begin + `uvm_info(`gfn, $sformatf("iterating %0d/%0d", i, num_trans), UVM_LOW) + `DV_CHECK_MEMBER_RANDOMIZE_FATAL(num_mem_reads) + wait (cfg.rom_ctrl_vif .pwrmgr_data.done == prim_mubi_pkg::MuBi4True); + `DV_SPINWAIT_EXIT( + // thread 1 to count cycles + forever begin + // Counting negedge to avoid one extra clock cycle count when d_valid id pulled down + @(negedge cfg.clk_rst_vif.clk); + num_cycles++; + end, + // thread 2 to do ROM OPs + do_rand_ops(num_mem_reads, 1);); + + `uvm_info(`gfn, $sformatf("num_cycles: %0d, num_mem_reads: %0d",num_cycles, num_mem_reads), + UVM_MEDIUM) + + `DV_CHECK_EQ(num_cycles, num_mem_reads + 1); + num_cycles = 0; + end +endtask : body