| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| class flash_ctrl_base_vseq extends cip_base_vseq #( |
| .RAL_T (flash_ctrl_core_reg_block), |
| .CFG_T (flash_ctrl_env_cfg), |
| .COV_T (flash_ctrl_env_cov), |
| .VIRTUAL_SEQUENCER_T(flash_ctrl_virtual_sequencer) |
| ); |
| |
| `uvm_object_utils(flash_ctrl_base_vseq) |
| |
| // OTP Scramble Keys, Used In OTP MODEL |
| logic [KeyWidth-1:0] otp_addr_key; |
| logic [KeyWidth-1:0] otp_addr_rand_key; |
| logic [KeyWidth-1:0] otp_data_key; |
| logic [KeyWidth-1:0] otp_data_rand_key; |
| |
| // flash ctrl configuration settings. |
| bit [1:0] otp_key_init_done; |
| |
| constraint num_trans_c {num_trans inside {[1 : cfg.seq_cfg.max_num_trans]};} |
| |
| `uvm_object_new |
| |
| // Determine post-reset initialization method. |
| rand flash_mem_init_e flash_init; |
| |
| // rand data for program |
| rand data_q_t flash_program_data; |
| |
| constraint flash_program_data_c {flash_program_data.size == 16;} |
| |
| // By default, in 30% of the times initialize flash as in initial state (all 1s), |
| // while in 70% of the times the initialization will be randomized (simulating working flash). |
| constraint flash_init_c { |
| flash_init dist { |
| FlashMemInitSet :/ cfg.seq_cfg.flash_init_set_pc, |
| FlashMemInitRandomize :/ 100 - cfg.seq_cfg.flash_init_set_pc |
| }; |
| } |
| |
| // Page to region map. |
| // This is used to validate transactions based on their page address |
| // and policy config associate with it. |
| // 8 : default region |
| |
| // Vseq to do some initial post-reset actions. Can be overriden by extending envs. |
| flash_ctrl_callback_vseq callback_vseq; |
| |
| // Check permission and page range of info partition |
| // Set exp recov_err alert if check fails |
| function bit check_info_part(flash_op_t flash_op, string str); |
| bit drop = 0; |
| int bank, page, end_page, loop; |
| |
| bank = flash_op.addr[OTFBankId]; |
| page = cfg.addr2page(flash_op.otf_addr); |
| end_page = cfg.addr2page(flash_op.otf_addr + (flash_op.num_words * 4) - 1); |
| // For read, cross page boundary is allowed. So you need to check |
| // both start and end address |
| if (flash_op.op == FlashOpRead) loop = 2; |
| else loop = 1; |
| |
| for (int i= 0; i < loop; ++i) begin |
| if (i == 1) page = end_page; |
| if (flash_op.partition == FlashPartInfo && bank == 0 && |
| page inside {[1:3]}) begin |
| if (!cfg.allow_spec_info_acc[page-1]) begin |
| `uvm_info(str, $sformatf("check_info:page:%0d access is not allowed", page), UVM_MEDIUM) |
| set_otf_exp_alert("recov_err"); |
| drop = 1; |
| end |
| end |
| // check info page range |
| if (page >= InfoTypeSize[flash_op.partition>>1]) begin |
| `uvm_info(str, $sformatf("check_info:bank:%0d page:%0d addr:0x%0h is out of range", |
| bank, page, flash_op.otf_addr), UVM_MEDIUM) |
| set_otf_exp_alert("recov_err"); |
| drop = 1; |
| end |
| if (drop) break; |
| end |
| return drop; |
| endfunction // check_info_part |
| |
| virtual task pre_start(); |
| `uvm_create_on(callback_vseq, p_sequencer); |
| |
| //Create key before mem init |
| otp_addr_key = {$urandom, $urandom, $urandom, $urandom}; |
| otp_addr_rand_key = {$urandom, $urandom, $urandom, $urandom}; |
| otp_data_key = {$urandom, $urandom, $urandom, $urandom}; |
| otp_data_rand_key = {$urandom, $urandom, $urandom, $urandom}; |
| |
| cfg.otp_addr_key = otp_addr_key; |
| cfg.otp_data_key = otp_data_key; |
| |
| cfg.flash_ctrl_vif.rma_req <= lc_ctrl_pkg::Off; |
| cfg.flash_ctrl_vif.rma_seed <= LC_FLASH_RMA_SEED_DEFAULT; |
| otp_model(); // Start OTP Model |
| super.pre_start(); |
| cfg.alert_max_delay_in_ns = cfg.alert_max_delay * (cfg.clk_rst_vif.clk_period_ps / 1000.0); |
| |
| // Some test needs pre init states. |
| // Use this option to skip waiting for buffer enable. |
| if (cfg.skip_init == 0) begin |
| csr_wr(.ptr(ral.init), .value(1)); |
| `DV_SPINWAIT(wait(cfg.flash_ctrl_vif.rd_buf_en == 1 || cfg.skip_init_buf_en == 1);, |
| "Timed out waiting for rd_buf_en", |
| cfg.wait_rd_buf_en_timeout_ns) |
| end |
| endtask : pre_start |
| |
| virtual task hw_info_cfg_update(); |
| endtask |
| |
| virtual task dut_shutdown(); |
| // check for pending flash_ctrl operations and wait for them to complete |
| // TODO |
| endtask : dut_shutdown |
| |
| // Reset the Flash Device |
| virtual task reset_flash(); |
| // Set all flash partitions to 1s. |
| flash_dv_part_e part = part.first(); |
| |
| do begin |
| cfg.flash_mem_bkdr_init(part, flash_init); |
| part = part.next(); |
| end while (part != part.first()); |
| // After initialize flash, scramble secret partition |
| update_secret_partition(); |
| |
| // Wait for flash_ctrl to finish initializing on every reset |
| if (cfg.seq_cfg.wait_init_done) csr_spinwait(.ptr(ral.status.init_wip), .exp_data(1'b0)); |
| endtask : reset_flash |
| |
| // Apply a Reset to the DUT, then do some additional required actions with callback_vseq |
| virtual task apply_reset(string kind = "HARD"); |
| uvm_reg_data_t data; |
| |
| bit init_busy; |
| if (kind == "HARD") begin |
| // reset assertion time in clock cycles (from assertion to deassertion). |
| // Limits can be configured to control randomization. |
| // Will be randomized before each apply_reset. |
| uint reset_width_clks; |
| `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(reset_width_clks, |
| reset_width_clks inside |
| {[cfg.seq_cfg.reset_width_clks_lo:cfg.seq_cfg.reset_width_clks_hi]};) |
| cfg.clk_rst_vif.apply_reset(.reset_width_clks(reset_width_clks)); |
| cfg.clk_rst_vif.wait_clks(cfg.post_reset_delay_clks); |
| end |
| |
| if (cfg.seq_cfg.disable_flash_init == 0) begin |
| reset_flash(); // Randomly Inititalise Flash After Reset |
| end |
| |
| if (cfg.seq_cfg.en_init_keys_seeds == 1) begin |
| // Update hw_info_cfg if necessary |
| hw_info_cfg_update(); |
| csr_wr(.ptr(ral.init), .value(1)); // Enable Secret Seed Output during INIT |
| end |
| |
| // Do some additional required actions |
| callback_vseq.apply_reset_callback(); |
| endtask : apply_reset |
| |
| task post_apply_reset(string reset_kind = "HARD"); |
| super.post_apply_reset(reset_kind); |
| // Polling init wip is done |
| csr_spinwait(.ptr(ral.status.init_wip), .exp_data(1'b0)); |
| endtask |
| // Configure the memory protection regions. |
| virtual task flash_ctrl_mp_region_cfg(uint index, |
| flash_mp_region_cfg_t region_cfg = cfg.default_region_cfg); |
| uvm_reg_data_t data; |
| uvm_reg csr; |
| update_mp_region_cfg_mubifalse(region_cfg); |
| data = get_csr_val_with_updated_field(ral.mp_region_cfg[index].en, data, |
| region_cfg.en); |
| data = data | get_csr_val_with_updated_field(ral.mp_region_cfg[index].rd_en, data, |
| region_cfg.read_en); |
| data = data | get_csr_val_with_updated_field(ral.mp_region_cfg[index].prog_en, data, |
| region_cfg.program_en); |
| data = data | get_csr_val_with_updated_field(ral.mp_region_cfg[index].erase_en, data, |
| region_cfg.erase_en); |
| data = data | get_csr_val_with_updated_field(ral.mp_region_cfg[index].scramble_en, |
| data, region_cfg.scramble_en); |
| data = data | get_csr_val_with_updated_field(ral.mp_region_cfg[index].ecc_en, data, |
| region_cfg.ecc_en); |
| data = data | get_csr_val_with_updated_field(ral.mp_region_cfg[index].he_en, data, |
| region_cfg.he_en); |
| csr_wr(.ptr(ral.mp_region_cfg[index]), .value(data)); |
| |
| // reset for base/size register |
| data = 0; |
| data = get_csr_val_with_updated_field(ral.mp_region[index].base, data, |
| region_cfg.start_page); |
| data = data | get_csr_val_with_updated_field(ral.mp_region[index].size, data, |
| region_cfg.num_pages); |
| csr_wr(.ptr(ral.mp_region[index]), .value(data)); |
| endtask : flash_ctrl_mp_region_cfg |
| |
| // Configure the protection for the "default" region (all pages that do not fall |
| // into one of the memory protection regions). |
| virtual task flash_ctrl_default_region_cfg(mubi4_t read_en = MuBi4True, |
| mubi4_t program_en = MuBi4True, |
| mubi4_t erase_en = MuBi4True, |
| mubi4_t scramble_en = MuBi4False, |
| mubi4_t ecc_en = MuBi4False, |
| mubi4_t he_en = MuBi4False); |
| uvm_reg_data_t data; |
| |
| cfg.default_region_cfg.read_en = read_en; |
| cfg.default_region_cfg.program_en = program_en; |
| cfg.default_region_cfg.erase_en = erase_en; |
| cfg.default_region_cfg.scramble_en = scramble_en; |
| cfg.default_region_cfg.ecc_en = ecc_en; |
| cfg.default_region_cfg.he_en = he_en; |
| |
| data = get_csr_val_with_updated_field(ral.default_region.rd_en, data, read_en); |
| data = data | |
| get_csr_val_with_updated_field(ral.default_region.prog_en, data, program_en); |
| data = data | |
| get_csr_val_with_updated_field(ral.default_region.erase_en, data, erase_en); |
| data = data | |
| get_csr_val_with_updated_field(ral.default_region.scramble_en, data, scramble_en); |
| data = data | get_csr_val_with_updated_field(ral.default_region.ecc_en, data, ecc_en); |
| data = data | get_csr_val_with_updated_field(ral.default_region.he_en, data, he_en); |
| csr_wr(.ptr(ral.default_region), .value(data)); |
| endtask : flash_ctrl_default_region_cfg |
| |
| // Configure the memory protection of some selected page in one of the information partitions in |
| // one of the banks. |
| virtual task flash_ctrl_mp_info_page_cfg( |
| uint bank, uint info_part, uint page, |
| flash_bank_mp_info_page_cfg_t page_cfg = cfg.default_info_page_cfg); |
| |
| uvm_reg_data_t data; |
| uvm_reg csr; |
| |
| string csr_name = $sformatf("bank%0d_info%0d_page_cfg", bank, info_part); |
| // If the selected information partition has only 1 page, no suffix needed to the register |
| // name, if there is more than one page, the page index should be added to the register name. |
| if (flash_ctrl_pkg::InfoTypeSize[info_part] > 1) begin |
| csr_name = $sformatf({csr_name, "_%0d"}, page); |
| end |
| `uvm_info("mp_info_page_cfg", $sformatf("%s: %p", csr_name, page_cfg), UVM_DEBUG) |
| csr = ral.get_reg_by_name(csr_name); |
| update_mp_info_cfg_mubifalse(page_cfg); |
| data = get_csr_val_with_updated_field(csr.get_field_by_name("en"), data, page_cfg.en); |
| data = data | |
| get_csr_val_with_updated_field(csr.get_field_by_name("rd_en"), data, page_cfg.read_en); |
| data = data | |
| get_csr_val_with_updated_field(csr.get_field_by_name("prog_en"), data, page_cfg.program_en); |
| data = data | |
| get_csr_val_with_updated_field(csr.get_field_by_name("erase_en"), data, page_cfg.erase_en); |
| data = data | get_csr_val_with_updated_field(csr.get_field_by_name("scramble_en"), data, |
| page_cfg.scramble_en); |
| data = data | |
| get_csr_val_with_updated_field(csr.get_field_by_name("ecc_en"), data, page_cfg.ecc_en); |
| data = data | |
| get_csr_val_with_updated_field(csr.get_field_by_name("he_en"), data, page_cfg.he_en); |
| csr_wr(.ptr(csr), .value(data)); |
| endtask : flash_ctrl_mp_info_page_cfg |
| |
| // Configure bank erasability. |
| virtual task flash_ctrl_bank_erase_cfg(bit [flash_ctrl_pkg::NumBanks-1:0] bank_erase_en); |
| csr_wr(.ptr(ral.mp_bank_cfg_shadowed[0]), .value(bank_erase_en)); |
| endtask : flash_ctrl_bank_erase_cfg |
| |
| // Configure read and program fifo levels for interrupt. |
| virtual task flash_ctrl_fifo_levels_cfg_intr(uint read_fifo_intr_level, |
| uint program_fifo_intr_level); |
| uvm_reg_data_t data; |
| data = get_csr_val_with_updated_field(ral.fifo_lvl.prog, data, program_fifo_intr_level) | |
| get_csr_val_with_updated_field(ral.fifo_lvl.rd, data, read_fifo_intr_level); |
| csr_wr(.ptr(ral.fifo_lvl), .value(data)); |
| endtask : flash_ctrl_fifo_levels_cfg_intr |
| |
| // Reset the program and read fifos. |
| virtual task flash_ctrl_fifo_reset(bit reset = 1'b1); |
| csr_wr(.ptr(ral.fifo_rst), .value(reset)); |
| endtask : flash_ctrl_fifo_reset |
| |
| // Configure intr_enable |
| virtual task flash_ctrl_intr_enable(bit[5:0] enable); |
| csr_wr(.ptr(ral.intr_enable), .value(enable)); |
| endtask |
| |
| // Wait for flash_ctrl op to finish. |
| virtual task wait_flash_op_done( |
| bit clear_op_status = 1'b1, time timeout_ns = 10_000_000 |
| ); // Added because mass(bank) erase is longer then default timeout. |
| uvm_reg_data_t data; |
| csr_spinwait(.ptr(ral.op_status.done), |
| .exp_data(1'b1), |
| .timeout_ns(timeout_ns)); |
| |
| if (clear_op_status) begin |
| csr_rd(.ptr(ral.op_status), .value(data)); |
| data = get_csr_val_with_updated_field(ral.op_status.done, data, 0); |
| csr_wr(.ptr(ral.op_status), .value(data)); |
| end |
| endtask : wait_flash_op_done |
| |
| // Wait for flash_ctrl op to finish with error. |
| virtual task wait_flash_op_err(bit clear_op_status = 1'b1); |
| uvm_reg_data_t data; |
| bit err; |
| `DV_SPINWAIT(do begin |
| csr_rd(.ptr(ral.op_status), .value(data)); |
| err = get_field_val(ral.op_status.err, data); |
| end while (err == 1'b0);, "wait_flash_op_err timeout occurred!") |
| if (clear_op_status) begin |
| data = get_csr_val_with_updated_field(ral.op_status.err, data, 0); |
| csr_wr(.ptr(ral.op_status), .value(data)); |
| end |
| endtask : wait_flash_op_err |
| |
| // Wait for prog fifo to not be full. |
| virtual task wait_flash_ctrl_prog_fifo_not_full(); |
| // TODO: if intr enabled, then check interrupt, else check status. |
| bit prog_full; |
| csr_spinwait(.ptr(ral.status.prog_full), |
| .compare_op(CompareOpNe), |
| .exp_data(1)); |
| endtask : wait_flash_ctrl_prog_fifo_not_full |
| |
| // Wait for rd fifo to not be empty. |
| virtual task wait_flash_ctrl_rd_fifo_not_empty(); |
| // TODO: if intr enabled, then check interrupt, else check status. |
| bit read_empty; |
| csr_spinwait(.ptr(ral.status.rd_empty), |
| .compare_op(CompareOpNe), |
| .exp_data(1)); |
| endtask : wait_flash_ctrl_rd_fifo_not_empty |
| |
| // Starts an Operation on the Flash Controller |
| virtual task flash_ctrl_start_op(flash_op_t flash_op); |
| uvm_reg_data_t data; |
| flash_part_e partition_sel; |
| bit [InfoTypesWidth-1:0] info_sel; |
| |
| csr_wr(.ptr(ral.addr), .value(flash_op.addr)); |
| |
| // flash_op.partition -> partition_sel , info_sel | |
| // (flash_dv_part_e) | (flash_part_e) | bit[InfoTypesWidth] | |
| // --------------------------|-----------------|---------------------| |
| // FlashPartData = 0 | FlashPartData=0 | 0 | |
| // FlashPartInfo = 1 | FlashPartInfo=1 | 0 | |
| // FlashPartInfo1 = 2 | FlashPartInfo=1 | 1 | |
| // FlashPartInfo2 = 4 | FlashPartInfo=1 | 2 | |
| |
| partition_sel = |flash_op.partition; |
| info_sel = flash_op.partition >> 1; |
| data = get_csr_val_with_updated_field(ral.control.start, data, 1'b1); |
| data = data | get_csr_val_with_updated_field(ral.control.op, data, flash_op.op); |
| data = data | get_csr_val_with_updated_field(ral.control.prog_sel, data, flash_op.prog_sel); |
| data = data | get_csr_val_with_updated_field(ral.control.erase_sel, data, flash_op.erase_type); |
| data = data | get_csr_val_with_updated_field(ral.control.partition_sel, data, partition_sel); |
| data = data | get_csr_val_with_updated_field(ral.control.info_sel, data, info_sel); |
| data = data | get_csr_val_with_updated_field(ral.control.num, data, flash_op.num_words - 1); |
| csr_wr(.ptr(ral.control), .value(data)); |
| endtask : flash_ctrl_start_op |
| |
| // Program data into flash, stopping whenever full. |
| // The flash op is assumed to have already commenced. |
| virtual task flash_ctrl_write(data_q_t data, bit poll_fifo_status); |
| foreach (data[i]) begin |
| // Check if prog fifo is full. If yes, then wait for space to become available. |
| // Note that this polling is not needed since the interface is backpressure enabled. |
| if (poll_fifo_status) begin |
| wait_flash_ctrl_prog_fifo_not_full(); |
| end |
| mem_wr(.ptr(ral.prog_fifo), .offset(0), .data(data[i])); |
| `uvm_info(`gfn, $sformatf("flash_ctrl_write: 0x%0h", data[i]), UVM_HIGH) |
| end |
| endtask : flash_ctrl_write |
| |
| // Read data from flash, stopping whenever empty. |
| // The flash op is assumed to have already commenced. |
| virtual task flash_ctrl_read(uint num_words, ref data_q_t data, |
| input bit poll_fifo_status, bit dis = 0); |
| for (int i = 0; i < num_words; i++) begin |
| // Check if rd fifo is empty. If yes, then wait for data to become available. |
| // Note that this polling is not needed since the interface is backpressure enabled. |
| if (poll_fifo_status) begin |
| wait_flash_ctrl_rd_fifo_not_empty(); |
| end |
| mem_rd(.ptr(ral.rd_fifo), .offset(0), .data(data[i])); |
| `uvm_info(`gfn, $sformatf("flash_ctrl_read: 0x%0h", data[i]), UVM_HIGH) |
| if (dis) begin |
| `uvm_info(`gfn, $sformatf("dis:flash_ctrl_read: 0x%0h", data[i]), UVM_MEDIUM) |
| end |
| end |
| endtask : flash_ctrl_read |
| |
| virtual task clear_intr_state(uvm_reg_data_t data); |
| csr_wr(.ptr(ral.intr_state), .value(data)); |
| endtask |
| |
| virtual task flash_ctrl_intr_read(flash_op_t flash_op, ref data_q_t rdata); |
| uvm_reg_data_t data; |
| bit[31:0] intr_st; |
| int rd_timeout_ns = 200000; // 200 us |
| int curr_rd, rd_idx = 0; |
| |
| `uvm_info("flash_ctrl_intr_read", $sformatf("num_rd:%0d", |
| flash_op.num_words), UVM_MEDIUM) |
| |
| `DV_SPINWAIT(wait(cfg.rd_crd - flash_op.num_words >= 0);, |
| "wait for rd_crd timeout", |
| rd_timeout_ns, "flash_ctrl_intr_read") |
| flash_ctrl_start_op(flash_op); |
| cfg.rd_crd -= flash_op.num_words; |
| while (rd_idx < flash_op.num_words) begin |
| // If read data comes slow, use FlashCtrlIntrRdLvl as non-empty |
| // and read out then clear this interrupt until |
| // read all 'flash_op.num_words' |
| `DV_SPINWAIT(wait(cfg.intr_vif.pins[FlashCtrlIntrRdLvl] == 1 || |
| cfg.intr_vif.pins[FlashCtrlIntrRdFull] == 1 || |
| cfg.intr_vif.pins[FlashCtrlIntrOpDone] == 1);, |
| "wait read intr timeout", |
| rd_timeout_ns, "flash_ctrl_intr_read") |
| // read interrupt status reg |
| csr_rd(.ptr(ral.intr_state), .value(data)); |
| intr_st = data; |
| clear_intr_state(data); |
| csr_rd(.ptr(ral.curr_fifo_lvl.rd), .value(curr_rd)); |
| `uvm_info("intr_read", $sformatf("intr_state: %x", intr_st), UVM_MEDIUM) |
| |
| // the comparison below is done because when the flash reads "too fast", it can fill up |
| // the FIFO before more entries are read out. This creates a siutation where we may have |
| // multiple level or full interrupts even though it is not really the case. |
| // Imagine the level is set to 5, and we get the first interrupt when are 5 entries. |
| // While reading the 5 entries out, the flash fills so fast that the hardware sees another |
| // 4->5 transition, causing a second interrupt. However, there has not actually been 10 |
| // entries deposited, this is just an artifact of one side moving much faster than |
| // anticipated. This kind of level interrupt scheme works better when one side is |
| // much slower, which is not guaranteed based on the parameters of the test. |
| if (intr_st[FlashCtrlIntrOpDone]) begin |
| // just read whatever the current read level is |
| end else if (intr_st[FlashCtrlIntrRdFull]) begin |
| // fifo size is 16 |
| curr_rd = curr_rd >= 16 ? 16 : curr_rd; |
| end else if (intr_st[FlashCtrlIntrRdLvl]) begin |
| curr_rd = curr_rd >= cfg.rd_lvl ? cfg.rd_lvl : curr_rd; |
| end |
| repeat(curr_rd) begin |
| mem_rd(.ptr(ral.rd_fifo), .offset(0), .data(rdata[rd_idx++])); |
| cfg.rd_crd++; |
| end |
| end |
| endtask // flash_ctrl_intr_read |
| |
| virtual task flash_ctrl_intr_write(flash_op_t flash_op, data_q_t wdata); |
| int curr_wr; |
| int wr_cnt, wr_idx = 0; |
| uvm_reg_data_t data; |
| bit [31:0] intr_st; |
| bit wait_done = 0; |
| int prog_timeout_ns = 100000; // 100 us |
| |
| `uvm_info("flash_ctrl_intr_write", $sformatf("num_wd: %0d crd:%0d", flash_op.num_words, |
| cfg.wr_crd), UVM_MEDIUM) |
| // Make sure prog_fifo is available before start program. |
| `DV_SPINWAIT(while(cfg.wr_crd == 0) begin |
| csr_rd(.ptr(ral.curr_fifo_lvl.prog), .value(curr_wr)); |
| cfg.wr_crd = 4 - curr_wr; |
| end, |
| "wait for wr_crd timeout", |
| prog_timeout_ns, "flash_ctrl_intr_write") |
| |
| flash_ctrl_start_op(flash_op); |
| |
| // Initially no interrupts are set. So we have to start writing |
| // data to fifo then wait for interrupt. |
| do begin |
| while (cfg.wr_crd > 0 && wr_idx < flash_op.num_words) begin |
| mem_wr(.ptr(ral.prog_fifo), .offset(0), .data(wdata[wr_idx++])); |
| cfg.wr_crd--; |
| end |
| `DV_SPINWAIT(wait(cfg.intr_vif.pins[FlashCtrlIntrProgEmpty] == 1 || |
| cfg.intr_vif.pins[FlashCtrlIntrProgLvl] == 1 || |
| cfg.intr_vif.pins[FlashCtrlIntrOpDone] == 1);, |
| "wait prog intr timeout", |
| prog_timeout_ns, "flash_ctrl_intr_write") |
| |
| csr_rd(.ptr(ral.intr_state), .value(data)); |
| intr_st = data; |
| clear_intr_state(data); |
| |
| if (intr_st[FlashCtrlIntrOpDone]) begin |
| // out of the loop |
| wait_done = 1; |
| wr_idx = flash_op.num_words; |
| end else if (intr_st[FlashCtrlIntrProgEmpty]) begin |
| cfg.wr_crd = 4; // fifo size is 4 |
| end else if (intr_st[FlashCtrlIntrProgLvl]) begin |
| // fifo depth 4, intr level: 2 |
| // Credit should be depth - fifo level |
| cfg.wr_crd = 4 - cfg.wr_lvl; |
| end |
| end while (wr_idx < flash_op.num_words || wait_done == 0); |
| endtask // flash_ctrl_intr_write |
| |
| // Task to perform a direct Flash read at the specified location |
| // Used timeout is to match the longest waiting timeout possible for the host, which will happen |
| // when the host is waiting for the controller to finish bank-erase |
| virtual task do_direct_read( |
| input addr_t addr, input bit [TL_DBW-1:0] mask = get_rand_contiguous_mask(), |
| input bit blocking = $urandom_range(0, 1), input bit check_rdata = 0, |
| input data_t exp_rdata = '0, input mubi4_t instr_type = MuBi4False, |
| output data_4s_t rdata, output bit completed, |
| input bit exp_err_rsp = 1'b0); |
| |
| bit saw_err; |
| |
| tl_access_w_abort(.addr(addr), .write(1'b0), .completed(completed), .saw_err(saw_err), |
| .tl_access_timeout_ns(cfg.seq_cfg.erase_timeout_ns), .mask(mask), |
| .data(rdata), .exp_err_rsp(exp_err_rsp), .exp_data(exp_rdata), |
| .compare_mask(mask), .check_exp_data(check_rdata), .blocking(blocking), |
| .instr_type(instr_type), |
| .tl_sequencer_h(p_sequencer.tl_sequencer_hs[cfg.flash_ral_name])); |
| endtask : do_direct_read |
| |
| // Task to Read/Erase/Program the Two Secret Seed Partitions (Creator and Owner) |
| virtual task do_flash_op_secret_part(input flash_sec_part_e secret_part, input flash_op_e op, |
| output data_q_t flash_op_data); |
| // Note: |
| // Secret partition 0 (used for creator): Bank 0, information partition 0, page 1 |
| // Secret partition 1 (used for owner): Bank 0, information partition 0, page 2 |
| |
| // Local Signals |
| bit poll_fifo_status; |
| data_q_t exp_data; |
| flash_op_t flash_op; |
| bit scr_en; |
| |
| scr_en = (flash_ctrl_pkg::CfgAllowRead.scramble_en == MuBi4True); |
| // Flash Operation Assignments |
| flash_op.op = op; |
| flash_op.partition = FlashPartInfo; |
| flash_op.erase_type = FlashErasePage; |
| flash_op.num_words = FlashSecretPartWords; |
| poll_fifo_status = 1; |
| |
| // Disable HW Access to Secret Partition from Life Cycle Controller Interface (Write/Read/Erase) |
| cfg.flash_ctrl_vif.lc_seed_hw_rd_en = |
| get_rand_lc_tx_val(.t_weight(0), .f_weight(1), .other_weight(9)); |
| |
| unique case (secret_part) |
| FlashCreatorPart: begin |
| flash_op.addr = FlashCreatorPartStartAddr; |
| cfg.flash_ctrl_vif.lc_creator_seed_sw_rw_en = lc_ctrl_pkg::On; |
| end |
| FlashOwnerPart: begin |
| flash_op.addr = FlashOwnerPartStartAddr; |
| cfg.flash_ctrl_vif.lc_owner_seed_sw_rw_en = lc_ctrl_pkg::On; |
| end |
| default: `uvm_error(`gfn, "Secret Partition Unrecognised, FAIL") |
| endcase |
| |
| // Perform Flash Opeation via Host Interface |
| unique case (flash_op.op) |
| flash_ctrl_pkg::FlashOpErase: begin |
| flash_ctrl_start_op(flash_op); |
| wait_flash_op_done(.timeout_ns(cfg.seq_cfg.erase_timeout_ns)); |
| if (cfg.seq_cfg.check_mem_post_tran) cfg.flash_mem_bkdr_erase_check(flash_op, exp_data); |
| end |
| flash_ctrl_pkg::FlashOpProgram: begin |
| // Write Frontdoor, Read Backdoor |
| // Generate Random Key |
| for (int i = 0; i < flash_op.num_words; i++) begin |
| flash_op_data[i] = $urandom_range(0, 2 ** (TL_DW) - 1); |
| end |
| // Calculate expected data for post-transaction checks |
| exp_data = cfg.calculate_expected_data(flash_op, flash_op_data); |
| flash_ctrl_start_op(flash_op); |
| flash_ctrl_write(flash_op_data, poll_fifo_status); |
| wait_flash_op_done(.timeout_ns(cfg.seq_cfg.prog_timeout_ns)); |
| if (cfg.seq_cfg.check_mem_post_tran) begin |
| cfg.flash_mem_bkdr_read_check(flash_op, exp_data, , scr_en); |
| end |
| end |
| flash_ctrl_pkg::FlashOpRead: begin |
| // Read Frontdoor, Compare Backdoor |
| flash_ctrl_start_op(flash_op); |
| flash_ctrl_read(flash_op.num_words, flash_op_data, poll_fifo_status); |
| wait_flash_op_done(); |
| if (cfg.seq_cfg.check_mem_post_tran) begin |
| cfg.flash_mem_bkdr_read_check(flash_op, flash_op_data, 1, scr_en); |
| end |
| end |
| default: `uvm_error(`gfn, "Flash Operation Unrecognised, FAIL") |
| endcase |
| |
| // Disable Secret Partitions from Life Cycle Controller Interface (Write/Read/Erase) |
| unique case (secret_part) |
| FlashCreatorPart: begin |
| cfg.flash_ctrl_vif.lc_creator_seed_sw_rw_en = lc_ctrl_pkg::Off; |
| end |
| FlashOwnerPart: begin |
| cfg.flash_ctrl_vif.lc_owner_seed_sw_rw_en = lc_ctrl_pkg::Off; |
| end |
| default: `uvm_error(`gfn, "Secret Partition Unrecognised, FAIL") |
| endcase |
| endtask : do_flash_op_secret_part |
| |
| // Task to compare a Secret Seed sent to the Key Manager with the Value in the FLASH |
| virtual task compare_secret_seed(input flash_sec_part_e secret_part, |
| input data_q_t flash_op_data); |
| |
| // Local Variables |
| data_q_t key_data; |
| |
| // Check for the Key being 'x |
| foreach (cfg.flash_ctrl_vif.keymgr.seeds[bit'(secret_part)][i]) begin |
| if (cfg.flash_ctrl_vif.keymgr.seeds[bit'(secret_part)][i] === 'x) begin |
| `uvm_error(`gfn, "Key Manager Keys Sampled : 'x', FAIL") |
| end |
| end |
| |
| // Read Key Manager Interface |
| foreach (flash_op_data[i]) begin |
| key_data[i] = cfg.flash_ctrl_vif.keymgr.seeds[bit'(secret_part)][i*32+:32]; |
| end |
| |
| // Display Keys |
| `uvm_info(`gfn, $sformatf("Secret Partition : %s", secret_part.name()), UVM_LOW) |
| `uvm_info(`gfn, $sformatf("Data Read : %p", flash_op_data), UVM_LOW) |
| `uvm_info(`gfn, $sformatf("KeyMgr Read : %p", key_data), UVM_LOW) |
| |
| // Compare Seeds |
| foreach (key_data[i]) begin |
| `DV_CHECK_EQ(key_data[i], flash_op_data[i], $sformatf( |
| "(Read) Secret Partition : %s : Keys Mismatch, FAIL", secret_part.name())) |
| end |
| |
| endtask : compare_secret_seed |
| |
| // Task to restore the stimulus from the Life Cycle Controller to its Reset State |
| virtual task lc_ctrl_if_rst(); |
| |
| cfg.flash_ctrl_vif.lc_creator_seed_sw_rw_en = lc_ctrl_pkg::Off; |
| cfg.flash_ctrl_vif.lc_owner_seed_sw_rw_en = lc_ctrl_pkg::Off; |
| cfg.flash_ctrl_vif.lc_seed_hw_rd_en = lc_ctrl_pkg::On; |
| |
| cfg.flash_ctrl_vif.lc_iso_part_sw_rd_en = |
| get_rand_lc_tx_val(.t_weight(0), .f_weight(1), .other_weight(4)); |
| cfg.flash_ctrl_vif.lc_iso_part_sw_wr_en = |
| get_rand_lc_tx_val(.t_weight(0), .f_weight(1), .other_weight(4)); |
| |
| cfg.flash_ctrl_vif.lc_nvm_debug_en = |
| get_rand_lc_tx_val(.t_weight(0), .f_weight(1), .other_weight(4)); |
| cfg.flash_ctrl_vif.lc_escalate_en = lc_ctrl_pkg::Off; |
| |
| cfg.flash_ctrl_vif.rma_req = lc_ctrl_pkg::Off; |
| cfg.flash_ctrl_vif.rma_seed = '0; |
| |
| endtask : lc_ctrl_if_rst |
| |
| // Simple Model For The OTP Key Seeds |
| virtual task otp_model(); |
| |
| `uvm_info(`gfn, "Starting OTP Model ...", UVM_LOW) |
| |
| // Initial Values |
| cfg.flash_ctrl_vif.otp_rsp.addr_ack = 1'b0; |
| cfg.flash_ctrl_vif.otp_rsp.data_ack = 1'b0; |
| cfg.flash_ctrl_vif.otp_rsp.seed_valid = 1'b0; |
| cfg.flash_ctrl_vif.otp_rsp.key = '0; |
| cfg.flash_ctrl_vif.otp_rsp.rand_key = '0; |
| otp_key_init_done = 'h0; |
| // Note 'some values' appear in both branches of this fork, this is OK because the |
| // branches never run together by design. |
| // The order is always 'addr' followed by 'data'. |
| fork |
| forever begin // addr |
| @(posedge cfg.clk_rst_vif.rst_n); |
| @(posedge cfg.flash_ctrl_vif.otp_req.addr_req); |
| otp_key_init_done[1] = 0; |
| `uvm_info(`gfn, $sformatf("OTP Addr Key Applied to DUT : otp_addr_key : %0x", |
| otp_addr_key), UVM_MEDIUM) |
| `uvm_info(`gfn, $sformatf("OTP Addr Rand Key Applied to DUT : otp_addr_rand_key : %0x", |
| otp_addr_rand_key), UVM_MEDIUM) |
| cfg.flash_ctrl_vif.otp_rsp.key = otp_addr_key; |
| cfg.flash_ctrl_vif.otp_rsp.rand_key = otp_addr_rand_key; |
| cfg.flash_ctrl_vif.otp_rsp.seed_valid = 1'b1; |
| #1ns; // Positive Hold |
| cfg.flash_ctrl_vif.otp_rsp.addr_ack = 1'b1; |
| @(negedge cfg.flash_ctrl_vif.otp_req.addr_req); |
| #1ns; // Positive Hold |
| cfg.flash_ctrl_vif.otp_rsp.addr_ack = 1'b0; |
| cfg.flash_ctrl_vif.otp_rsp.seed_valid = 1'b0; |
| otp_key_init_done[1] = 1; |
| end |
| forever begin // data |
| @(posedge cfg.clk_rst_vif.rst_n); |
| @(posedge cfg.flash_ctrl_vif.otp_req.data_req); |
| otp_key_init_done[0] = 0; |
| cfg.flash_ctrl_vif.otp_rsp.key = otp_data_key; |
| cfg.flash_ctrl_vif.otp_rsp.rand_key = otp_data_rand_key; |
| `uvm_info(`gfn, $sformatf("OTP Data Key Applied to DUT : otp_data_key : %0x", |
| otp_data_key), UVM_MEDIUM) |
| `uvm_info(`gfn, $sformatf("OTP Data Rand Key Applied to DUT : otp_data_rand_key : %0x", |
| otp_data_rand_key), UVM_MEDIUM) |
| cfg.flash_ctrl_vif.otp_rsp.seed_valid = 1'b1; |
| #1ns; // Positive Hold |
| cfg.flash_ctrl_vif.otp_rsp.data_ack = 1'b1; |
| @(negedge cfg.flash_ctrl_vif.otp_req.data_req); |
| #1ns; // Positive Hold |
| cfg.flash_ctrl_vif.otp_rsp.data_ack = 1'b0; |
| cfg.flash_ctrl_vif.otp_rsp.seed_valid = 1'b0; |
| otp_key_init_done[0] = 1; |
| end |
| join_none |
| |
| endtask : otp_model |
| |
| // Compares Two Queues Of Data |
| virtual function void check_data_match(ref data_q_t data, ref data_q_t exp_data); |
| foreach (exp_data[i]) begin |
| `DV_CHECK_EQ(data[i], exp_data[i], $sformatf( |
| "Expected : 0x%0x, Read : 0x%0x, FAIL", exp_data[i], data[i])) |
| end |
| endfunction : check_data_match |
| |
| // Wait for Flash Operation, or Timeout ... Timeout Expected |
| virtual task wait_flash_op_done_expect_timeout(input time timeout_ns = 10_000_000, |
| output bit result); |
| |
| // Looks for Status Returning in the Timeout Period |
| |
| // Expect a Timeout, with No Status Returned |
| // Result 0 - Response Returned (timeout = 0, status = 1) - FAIL |
| // 1 - Timeout, No Response Returned (timeout = 1, status = 0) - PASS |
| |
| // Local Variables |
| uvm_reg_data_t data; |
| bit timeout; |
| bit status; |
| bit finished; |
| |
| finished = 0; |
| timeout = 0; |
| fork |
| fork |
| |
| begin // Poll Status Bit |
| `uvm_info(`gfn, "Polling Flash Status ...", UVM_LOW) |
| while (finished == 0) begin |
| csr_rd(.ptr(ral.op_status), .value(data)); |
| status = get_field_val(ral.op_status.done, data); |
| if (status == 1) finished = 1; |
| end |
| end |
| |
| begin // Timeout - Expected |
| #(timeout_ns); |
| `uvm_info(`gfn, "Exiting Timeout Check ... Timeout Occured, Expected", UVM_LOW) |
| timeout = 1; |
| finished = 1; |
| end |
| |
| join |
| join |
| // Exit Gracefully |
| |
| // Decide Result |
| if ((timeout == 1'b1) && (status == 1'b0)) result = 1'b1; |
| else result = 1'b0; |
| |
| endtask : wait_flash_op_done_expect_timeout |
| |
| // Task to Read/Erase/Program the RMA Partitions Creator, Owner, Isolated, Data0 and Data1 |
| virtual task do_flash_op_rma(input flash_sec_part_e part, input flash_op_e op, |
| ref data_q_t flash_op_wdata, input bit cmp = READ_CHECK_NORM, |
| input uint data_part_addr, input uint data_part_num_words); |
| |
| // Note: |
| // Special Partition (used for Creator) : Bank 0, Information Partition 0, Page 1 |
| // Special Partition (used for Owner) : Bank 0, Information Partition 0, Page 2 |
| // Special Partition (used for Isolated) : Bank 0, Information Partition 0, Page 3 |
| |
| // Local Variables |
| bit poll_fifo_status; |
| data_q_t exp_data; |
| flash_op_t flash_op; |
| data_q_t flash_op_rdata; |
| int match_cnt; |
| string msg; |
| |
| // Assign |
| flash_op.op = op; |
| poll_fifo_status = 1; |
| |
| `uvm_info(`gfn, $sformatf("Operation : %s, Partition : %s ", op.name(), part.name()), |
| UVM_MEDIUM) |
| |
| // Disable HW Access to Secret Partition from Life Cycle Controller Interface (Write/Read/Erase) |
| cfg.flash_ctrl_vif.lc_seed_hw_rd_en = |
| get_rand_lc_tx_val(.t_weight(0), .f_weight(1), .other_weight(4)); |
| |
| // Select Options |
| unique case (part) |
| FlashCreatorPart: begin |
| flash_op.addr = FlashCreatorPartStartAddr; // Fixed Val |
| flash_op.num_words = FullPageNumWords; // Fixed Val |
| flash_op.partition = FlashPartInfo; |
| cfg.flash_ctrl_vif.lc_creator_seed_sw_rw_en = lc_ctrl_pkg::On; |
| end |
| FlashOwnerPart: begin |
| flash_op.addr = FlashOwnerPartStartAddr; // Fixed Val |
| flash_op.num_words = FullPageNumWords; // Fixed Val |
| flash_op.partition = FlashPartInfo; |
| cfg.flash_ctrl_vif.lc_owner_seed_sw_rw_en = lc_ctrl_pkg::On; |
| end |
| FlashIsolPart: begin |
| flash_op.addr = FlashIsolPartStartAddr; // Fixed Val |
| flash_op.num_words = FullPageNumWords; // Fixed Val |
| flash_op.partition = FlashPartInfo; |
| cfg.flash_ctrl_vif.lc_iso_part_sw_rd_en = lc_ctrl_pkg::On; |
| cfg.flash_ctrl_vif.lc_iso_part_sw_wr_en = lc_ctrl_pkg::On; |
| end |
| FlashData0Part, FlashData1Part: begin |
| flash_op.addr = data_part_addr; // Variable Val |
| flash_op.num_words = data_part_num_words; // Fixed Val |
| flash_op.partition = FlashPartData; |
| end |
| default: `uvm_error(`gfn, "Unrecognised Partiton, FAIL") |
| endcase |
| |
| // Perform Flash Operations via the Host Interface |
| case (flash_op.op) |
| |
| flash_ctrl_pkg::FlashOpErase: begin |
| if (part inside {FlashCreatorPart, FlashOwnerPart, FlashIsolPart}) |
| flash_op.erase_type = flash_ctrl_pkg::FlashErasePage; |
| else flash_op.erase_type = flash_ctrl_pkg::FlashEraseBank; |
| flash_ctrl_start_op(flash_op); |
| wait_flash_op_done(.timeout_ns(cfg.seq_cfg.erase_timeout_ns)); |
| if (cfg.seq_cfg.check_mem_post_tran) cfg.flash_mem_bkdr_erase_check(flash_op, exp_data); |
| end |
| |
| flash_ctrl_pkg::FlashOpProgram: begin |
| // Write Frontdoor, Read/Compare Backdoor |
| // Random Data |
| for (int i = 0; i < flash_op.num_words; i++) |
| flash_op_wdata[i] = $urandom_range(0, 2 ** (TL_DW) - 1); |
| flash_ctrl_write_extra(flash_op, flash_op_wdata); |
| end |
| |
| flash_ctrl_pkg::FlashOpRead: begin |
| flash_ctrl_read_extra(flash_op, flash_op_rdata); |
| // Compare |
| if (cfg.seq_cfg.check_mem_post_tran) begin |
| |
| if (cmp == ReadCheckNorm) begin |
| `uvm_info(`gfn, "Read : Compare Backdoor with Frontdoor", UVM_MEDIUM) |
| cfg.flash_mem_bkdr_read_check(flash_op, |
| flash_op_rdata); // Compare Backdoor with Frontdoor |
| end else if (cmp == ReadCheckErased) begin |
| `uvm_info(`gfn, "Read : Compare Backdoor with Erased Status", UVM_MEDIUM) |
| match_cnt = 0; |
| foreach (flash_op_rdata[i]) begin |
| if (flash_op_rdata[i] === '1) begin |
| // Data Match - Unexpected, but theoretically possible |
| // Theoretically if locations are all '1 then |
| // RMA Erase Worked but RMA Program did not |
| `uvm_info(`gfn, "Read : Data Match (Erased), EXPECTED", UVM_MEDIUM) |
| match_cnt++; |
| end |
| end |
| end else begin |
| `uvm_info(`gfn, "Read : Compare Backdoor with Erased Status", UVM_MEDIUM) |
| match_cnt = 0; |
| foreach (flash_op_rdata[i]) begin |
| if (flash_op_rdata[i] === '1) begin |
| // Data Match - Unexpected, but theoretically possible |
| // Theoretically if locations are all '1 then |
| // RMA Erase Worked but RMA Program did not |
| `uvm_info(`gfn, "Read : Data Match (Erased), UNEXPECTED", UVM_MEDIUM) |
| match_cnt++; |
| end |
| end |
| |
| // Decide Pass/Fail Based on Match Count |
| if (match_cnt > 1) |
| `uvm_error(`gfn, {"Read : Data Matches Seen (Erase), UNEXPECTED", |
| $sformatf("Flash Content Should Be Random (RMA Wipe) (Matches : %0d)", match_cnt)}) |
| |
| `uvm_info(`gfn, "Read : Compare Backdoor with Data Previously Written", UVM_MEDIUM) |
| match_cnt = 0; |
| foreach (flash_op_rdata[i]) begin |
| if (flash_op_rdata[i] === flash_op_wdata[i]) begin |
| // Data Match - Unlikely, but theoretically possible |
| `uvm_info(`gfn, "Read : Data Match, UNEXPECTED", UVM_MEDIUM) |
| match_cnt++; |
| end |
| end |
| // Decide Pass/Fail Based on Match Count |
| if (match_cnt > 1) |
| `uvm_error(`gfn, { |
| "Read : Data Matches Seen, UNEXPECTED, Flash Content Should Be ", |
| $sformatf("Random (RMA Wipe) (Matches : %0d)", match_cnt)}) |
| end |
| end |
| end |
| default: `uvm_error(`gfn, "Unrecognised Partiton, FAIL") |
| endcase |
| |
| // Deselect Life Cycle Controller HW Options |
| unique case (part) |
| FlashCreatorPart: begin |
| cfg.flash_ctrl_vif.lc_creator_seed_sw_rw_en = lc_ctrl_pkg::Off; |
| end |
| FlashOwnerPart: begin |
| cfg.flash_ctrl_vif.lc_owner_seed_sw_rw_en = lc_ctrl_pkg::Off; |
| end |
| FlashIsolPart: begin |
| cfg.flash_ctrl_vif.lc_iso_part_sw_rd_en = lc_ctrl_pkg::Off; |
| cfg.flash_ctrl_vif.lc_iso_part_sw_wr_en = lc_ctrl_pkg::Off; |
| end |
| FlashData0Part, FlashData1Part: ; // No Operation |
| default: `uvm_error(`gfn, "Unrecognised Partiton, FAIL") |
| endcase |
| |
| endtask : do_flash_op_rma |
| |
| // Task to Program the Entire Flash Memory |
| virtual task flash_ctrl_write_extra(flash_op_t flash_op, data_q_t data, |
| bit check_match = 1, bit scr_en = 0); |
| |
| // Local Signals |
| uvm_reg_data_t reg_data; |
| flash_part_e partition_sel; |
| bit [InfoTypesWidth-1:0] info_sel; |
| int num; |
| int num_full; |
| int num_part; |
| data_4s_t fifo_data; |
| addr_t flash_addr; |
| data_q_t exp_data; |
| flash_op_t flash_op_copy; |
| data_q_t data_copy; |
| |
| // Calculate Number of Complete Cycles and Partial Cycle Words |
| num = data.size(); |
| num_full = num / FIFO_DEPTH; |
| num_part = num % FIFO_DEPTH; |
| |
| // Other |
| partition_sel = |flash_op.partition; |
| info_sel = flash_op.partition >> 1; |
| flash_addr = flash_op.addr; |
| |
| `uvm_info(`gfn, $sformatf( |
| "Flash Write Summary : Words : %0d, Full Cycles : %0d, Partial Cycle Words : %0d", |
| num, |
| num_full, |
| num_part |
| ), UVM_LOW) |
| |
| // Copies |
| flash_op_copy = flash_op; |
| data_copy = data; |
| |
| // If num_full > 0 |
| for (int cycle = 0; cycle < num_full; cycle++) begin |
| |
| `uvm_info(`gfn, $sformatf("Write Cycle : %0d, flash_addr = 0x%0x", cycle, flash_addr), |
| UVM_MEDIUM) |
| csr_wr(.ptr(ral.addr), .value(flash_addr)); |
| |
| reg_data = '0; |
| reg_data = get_csr_val_with_updated_field(ral.control.start, reg_data, 1'b1) | |
| get_csr_val_with_updated_field(ral.control.op, reg_data, flash_op.op) | |
| get_csr_val_with_updated_field(ral.control.erase_sel, reg_data, flash_op.erase_type) | |
| get_csr_val_with_updated_field(ral.control.partition_sel, reg_data, partition_sel) | |
| get_csr_val_with_updated_field(ral.control.info_sel, reg_data, info_sel) | |
| get_csr_val_with_updated_field(ral.control.num, reg_data, FIFO_DEPTH - 1); |
| csr_wr(.ptr(ral.control), .value(reg_data)); |
| |
| for (int i = 0; i < FIFO_DEPTH; i++) begin |
| fifo_data = data.pop_front(); |
| mem_wr(.ptr(ral.prog_fifo), .offset(0), .data(fifo_data)); |
| end |
| wait_flash_op_done(.timeout_ns(cfg.seq_cfg.prog_timeout_ns)); |
| |
| flash_addr += FIFO_DEPTH * 4; |
| |
| end |
| |
| // If there is a partial cycle |
| if (num_part > 0) begin |
| if (num_full == 0) flash_addr = flash_op.addr; |
| `uvm_info(`gfn, $sformatf("Last Write : flash_addr = 0x%0x", flash_addr), UVM_MEDIUM) |
| |
| csr_wr(.ptr(ral.addr), .value(flash_addr)); |
| |
| reg_data = '0; |
| reg_data = get_csr_val_with_updated_field(ral.control.start, reg_data, 1'b1) | |
| get_csr_val_with_updated_field(ral.control.op, reg_data, flash_op.op) | |
| get_csr_val_with_updated_field(ral.control.erase_sel, reg_data, flash_op.erase_type) | |
| get_csr_val_with_updated_field(ral.control.partition_sel, reg_data, partition_sel) | |
| get_csr_val_with_updated_field(ral.control.info_sel, reg_data, info_sel) | |
| get_csr_val_with_updated_field(ral.control.num, reg_data, num_part - 1); |
| csr_wr(.ptr(ral.control), .value(reg_data)); |
| for (int i = 0; i < num_part; i++) begin |
| fifo_data = data.pop_front(); |
| mem_wr(.ptr(ral.prog_fifo), .offset(0), .data(fifo_data)); |
| end |
| wait_flash_op_done(.timeout_ns(cfg.seq_cfg.prog_timeout_ns)); |
| |
| end |
| |
| exp_data = cfg.calculate_expected_data(flash_op_copy, data_copy); |
| |
| if (cfg.seq_cfg.check_mem_post_tran) begin |
| flash_op_copy.otf_addr = flash_op_copy.addr; |
| flash_op_copy.otf_addr[BusAddrByteW-2:OTFHostId] = 'h0; |
| cfg.flash_mem_bkdr_read_check(flash_op_copy, exp_data, check_match, scr_en); |
| end |
| endtask : flash_ctrl_write_extra |
| |
| // Task to Program the Entire Flash Memory |
| virtual task flash_ctrl_read_extra(flash_op_t flash_op, ref data_q_t data); |
| |
| // Local Signals |
| uvm_reg_data_t reg_data; |
| flash_part_e partition_sel; |
| bit [InfoTypesWidth-1:0] info_sel; |
| logic [TL_AW:0] flash_addr; |
| int num; |
| int num_full; |
| int num_part; |
| int num_words; |
| int idx; |
| |
| // Calculate Number of Complete Cycles and Partial Cycle Words |
| num = flash_op.num_words; |
| num_full = num / FIFO_DEPTH; |
| num_part = num % FIFO_DEPTH; |
| |
| `uvm_info(`gfn, $sformatf( |
| "Flash Read Summary : Words : %0d, Full Cycles : %0d, Partial Cycle Words : %0d", |
| num, |
| num_full, |
| num_part |
| ), UVM_LOW) |
| |
| // Other |
| partition_sel = |flash_op.partition; |
| info_sel = flash_op.partition >> 1; |
| num_words = flash_op.num_words; |
| flash_addr = flash_op.addr; |
| |
| // If num_full > 0 |
| idx = 0; |
| for (int cycle = 0; cycle < num_full; cycle++) begin |
| |
| `uvm_info(`gfn, $sformatf("Read Cycle : %0d, flash_addr = 0x%0x", cycle, flash_addr), |
| UVM_MEDIUM) |
| |
| csr_wr(.ptr(ral.addr), .value(flash_addr)); // Write Address |
| |
| reg_data = '0; |
| reg_data = get_csr_val_with_updated_field(ral.control.start, reg_data, 1'b1) | |
| get_csr_val_with_updated_field(ral.control.op, reg_data, flash_op.op) | |
| get_csr_val_with_updated_field(ral.control.erase_sel, reg_data, flash_op.erase_type) | |
| get_csr_val_with_updated_field(ral.control.partition_sel, reg_data, partition_sel) | |
| get_csr_val_with_updated_field(ral.control.info_sel, reg_data, info_sel) | |
| get_csr_val_with_updated_field(ral.control.num, reg_data, FIFO_DEPTH - 1); |
| csr_wr(.ptr(ral.control), .value(reg_data)); |
| |
| wait_flash_op_done(.timeout_ns(cfg.seq_cfg.prog_timeout_ns)); |
| |
| // Read from FIFO |
| for (int i = 0; i < FIFO_DEPTH; i++) begin |
| mem_rd(.ptr(ral.rd_fifo), .offset(0), .data(data[idx])); |
| idx++; |
| end |
| flash_addr += FIFO_DEPTH * 4; |
| |
| end |
| |
| // If there is a partial Cycle |
| if (num_part > 0) begin |
| if (num_full == 0) flash_addr = flash_op.addr; |
| `uvm_info(`gfn, $sformatf("Last Read Cycle : flash_addr = 0x%0x", flash_addr), UVM_MEDIUM) |
| |
| csr_wr(.ptr(ral.addr), .value(flash_addr)); // Write Address |
| |
| reg_data = '0; |
| reg_data = get_csr_val_with_updated_field(ral.control.start, reg_data, 1'b1) | |
| get_csr_val_with_updated_field(ral.control.op, reg_data, flash_op.op) | |
| get_csr_val_with_updated_field(ral.control.erase_sel, reg_data, flash_op.erase_type) | |
| get_csr_val_with_updated_field(ral.control.partition_sel, reg_data, partition_sel) | |
| get_csr_val_with_updated_field(ral.control.info_sel, reg_data, info_sel) | |
| get_csr_val_with_updated_field(ral.control.num, reg_data, FIFO_DEPTH - 1); |
| csr_wr(.ptr(ral.control), .value(reg_data)); |
| |
| wait_flash_op_done(.timeout_ns(cfg.seq_cfg.prog_timeout_ns)); |
| |
| // Read from FIFO |
| for (int i = 0; i < FIFO_DEPTH; i++) begin |
| mem_rd(.ptr(ral.rd_fifo), .offset(0), .data(data[idx++])); |
| end |
| |
| end |
| |
| endtask : flash_ctrl_read_extra |
| |
| // Task to send an RMA Request (with a given seed) to the Flash Controller |
| virtual task send_rma_req(lc_flash_rma_seed_t rma_seed = LC_FLASH_RMA_SEED_DEFAULT); |
| |
| // Local Variables |
| lc_ctrl_pkg::lc_tx_t done; |
| time timeout = 15s; |
| time start_time; |
| bit rma_ack_seen; |
| |
| `uvm_info(`gfn, $sformatf("RMA Seed : 0x%08x", rma_seed), UVM_LOW) |
| |
| // Set Seed and Send Req |
| cfg.flash_ctrl_vif.rma_seed <= rma_seed; |
| cfg.flash_ctrl_vif.rma_req <= lc_ctrl_pkg::On; |
| |
| // RMA Start Time |
| start_time = $time(); |
| |
| // Wait for RMA Ack to Rise (NOTE LONG DURATION) |
| `uvm_info(`gfn, "Waiting for RMA to complete ... ", UVM_LOW) |
| |
| done = 0; |
| rma_ack_seen = 0; |
| fork |
| begin |
| fork |
| begin // Poll RMA ACK |
| do begin |
| `uvm_info(`gfn, "Polling RMA ACK ...", UVM_LOW) |
| #10ms; // Jump Ahead (Not Sampling Clocks) |
| @(posedge cfg.clk_rst_vif.clk); // Align to Clock |
| if (cfg.flash_ctrl_vif.rma_ack == lc_ctrl_pkg::On || |
| cfg.rma_ack_polling_stop == 1) done = 1; |
| end while (done == 0); |
| if (cfg.rma_ack_polling_stop) begin |
| `uvm_info(`gfn, "Polling is stopped by external task", UVM_LOW) |
| end |
| end |
| begin // Timeout - Unexpected |
| `uvm_info(`gfn, "Starting RMA Timeout Check ...", UVM_LOW) |
| #(timeout); |
| `uvm_error(`gfn, { |
| "RMA ACK NOT seen within the expected time frame, Timeout - FAIL", |
| $sformatf(" (%0t)", timeout)}) |
| end |
| join_any |
| disable fork; |
| end |
| join |
| |
| // Note: After a valid RMA Ack is sent, the RMA State Machine remains in its last state, |
| // until reset |
| |
| // RMA End Time |
| `uvm_info(`gfn, "RMA complete", UVM_LOW) |
| `uvm_info(`gfn, $sformatf("RMA Duration : %t", $time() - start_time), UVM_LOW); |
| |
| endtask : send_rma_req |
| |
| // Task to Enable/Disable the 'Info' Partitions, Creator, Owner and Isolated, via the Lifetime |
| // Controller Interface |
| virtual task en_sw_rw_part_info(input flash_op_t flash_op, input lc_ctrl_pkg::lc_tx_t val); |
| if (flash_op.partition == FlashPartInfo) begin |
| cfg.flash_ctrl_vif.lc_creator_seed_sw_rw_en = val; |
| cfg.flash_ctrl_vif.lc_owner_seed_sw_rw_en = val; |
| cfg.flash_ctrl_vif.lc_iso_part_sw_rd_en = val; |
| cfg.flash_ctrl_vif.lc_iso_part_sw_wr_en = val; |
| end |
| endtask : en_sw_rw_part_info |
| |
| // Controller read page. |
| virtual task controller_read_page(flash_op_t flash_op_r); |
| data_q_t flash_read_data; |
| bit poll_fifo_status = 1; |
| flash_op_r.op = flash_ctrl_pkg::FlashOpRead; |
| flash_op_r.num_words = 16; |
| flash_op_r.addr = {flash_op_r.addr[19:11], {11{1'b0}}}; |
| for (int i = 0; i < 32; i++) begin |
| flash_ctrl_start_op(flash_op_r); |
| flash_ctrl_read(flash_op_r.num_words, flash_read_data, poll_fifo_status); |
| wait_flash_op_done(); |
| flash_op_r.addr = flash_op_r.addr + 64; //64B was read, 16 words |
| end |
| endtask : controller_read_page |
| |
| // Controller program page. |
| virtual task controller_program_page(flash_op_t flash_op_p); |
| bit poll_fifo_status = 1; |
| flash_op_p.op = flash_ctrl_pkg::FlashOpProgram; |
| flash_op_p.num_words = 16; |
| flash_op_p.addr = {flash_op_p.addr[19:11], {11{1'b0}}}; |
| for (int i = 0; i < 32; i++) begin |
| `uvm_info(`gfn, $sformatf("PROGRAM ADDRESS: 0x%0h", flash_op_p.addr), UVM_HIGH) |
| // Randomize Write Data |
| for (int j = 0; j < 16; j++) begin |
| flash_program_data[j] = $urandom(); |
| end |
| cfg.flash_mem_bkdr_write(.flash_op(flash_op_p), .scheme(FlashMemInitSet)); |
| flash_ctrl_start_op(flash_op_p); |
| flash_ctrl_write(flash_program_data, poll_fifo_status); |
| wait_flash_op_done(.timeout_ns(cfg.seq_cfg.prog_timeout_ns)); |
| flash_op_p.addr = flash_op_p.addr + 64; //64B was written, 16 words |
| end |
| endtask : controller_program_page |
| |
| // Check Expected Alert for prog_type_err and prog_win_err |
| virtual task check_exp_alert_status(input bit exp_alert, input string alert_name, |
| input flash_op_t flash_op, input data_q_t flash_op_data); |
| |
| // Local Variables |
| uvm_reg_data_t reg_data; |
| |
| // Read Status Bits |
| case (alert_name) |
| "prog_type_err" : csr_rd_check(.ptr(ral.err_code.prog_type_err), .compare_value(exp_alert)); |
| "prog_win_err" : csr_rd_check(.ptr(ral.err_code.prog_win_err), .compare_value(exp_alert)); |
| "mp_err" : csr_rd_check(.ptr(ral.err_code.mp_err), .compare_value(exp_alert)); |
| "op_err" : csr_rd_check(.ptr(ral.err_code.op_err), .compare_value(exp_alert)); |
| default : `uvm_fatal(`gfn, "Unrecognized alert_name, FAIL") |
| endcase |
| csr_rd_check(.ptr(ral.op_status.err), .compare_value(exp_alert)); |
| |
| // For 'prog_type_err' and 'prog_win_err' check via backdoor if Pass, |
| // 'mp_err' is Backdoor checked directly within its own test. |
| if ((alert_name inside {"prog_type_err", "prog_win_err"}) && (exp_alert == 0)) begin |
| cfg.flash_mem_bkdr_read_check(flash_op, flash_op_data); |
| end |
| |
| // Clear Status Bits |
| case (alert_name) |
| "prog_type_err" : reg_data = get_csr_val_with_updated_field(ral.err_code.prog_type_err, |
| reg_data, 1); |
| "prog_win_err" : reg_data = get_csr_val_with_updated_field(ral.err_code.prog_win_err, |
| reg_data, 1); |
| "mp_err" : reg_data = get_csr_val_with_updated_field(ral.err_code.mp_err, |
| reg_data, 1); |
| "op_err" : reg_data = get_csr_val_with_updated_field(ral.err_code.op_err, |
| reg_data, 1); |
| default : `uvm_fatal(`gfn, "Unrecognized alert_name") |
| endcase |
| csr_wr(.ptr(ral.err_code), .value(reg_data)); |
| reg_data = get_csr_val_with_updated_field(ral.op_status.err, reg_data, 0); |
| csr_wr(.ptr(ral.op_status), .value(reg_data)); |
| |
| endtask : check_exp_alert_status |
| |
| // Refill table with all default value. |
| function void init_p2r_map(); |
| foreach (cfg.p2r_map[i]) cfg.p2r_map[i] = 8; |
| endfunction |
| |
| // p2r_map needs to be in sync with rtl config. |
| // Same as RTL, lower index has priority when |
| // regions are content. |
| function void update_p2r_map(flash_mp_region_cfg_t mp[]); |
| int num = mp.size() - 1; |
| int base, size; |
| // Lower region has priority. |
| `uvm_info("update_p2r_map", $sformatf("default : %p", cfg.default_region_cfg), UVM_MEDIUM) |
| for (int i = num; i >= 0; --i) begin |
| // Check the region is enabled. |
| if (mp[i].en == MuBi4True) begin |
| `uvm_info("update_p2r_map", $sformatf("region %0d : %p", i, mp[i]), UVM_MEDIUM) |
| base = mp[i].start_page; |
| size = mp[i].num_pages; |
| for (int j = base; j < (base + size); ++j) begin |
| if (cfg.p2r_map[j] > i) cfg.p2r_map[j] = i; |
| end |
| end |
| end |
| |
| `uvm_info("update_p2r_map", $sformatf("after p2r_map update, %p", cfg.p2r_map), UVM_HIGH) |
| endfunction // update_p2r_map |
| |
| // Takes flash_op and region profile and check if the flash_op is legal or not. |
| // return 1 : illegal transaction |
| // return 0 : legal transaction |
| function bit validate_flash_op(flash_op_t flash_op, flash_mp_region_cfg_t my_region); |
| if (my_region.en != MuBi4True) return 1; |
| case(flash_op.op) |
| FlashOpRead:begin |
| return (my_region.read_en != MuBi4True); |
| end |
| FlashOpProgram:begin |
| return (my_region.program_en != MuBi4True); |
| end |
| FlashOpErase:begin |
| return (my_region.erase_en != MuBi4True); |
| end |
| FlashOpInvalid:begin |
| return 1; |
| end |
| default:begin |
| `uvm_error("update_flash_op", $sformatf("got %s command", flash_op.op.name())) |
| return 1; |
| end |
| endcase |
| endfunction // validate_flash_op |
| |
| // Takes flash_op and info profile and check if the flash_op is legal or not. |
| // return 1 : illegal transaction |
| // return 0 : legal transaction |
| // Bank erase doesn't follow rules in this function. |
| function bit validate_flash_info(flash_op_t flash_op, flash_bank_mp_info_page_cfg_t my_info); |
| if(my_info.en != MuBi4True) return 1; |
| case(flash_op.op) |
| FlashOpRead:begin |
| return (my_info.read_en != MuBi4True); |
| end |
| FlashOpProgram:begin |
| return (my_info.program_en != MuBi4True); |
| end |
| FlashOpErase:begin |
| return (my_info.erase_en != MuBi4True); |
| end |
| FlashOpInvalid:begin |
| return 1; |
| end |
| default:begin |
| `uvm_error("update_flash_op", $sformatf("got %s command", flash_op.op.name())) |
| return 1; |
| end |
| endcase |
| endfunction // validate_flash_info |
| |
| function void set_otf_exp_alert(string str); |
| cfg.scb_h.exp_alert_ff[str].push_back(1); |
| cfg.scb_h.expected_alert[str].max_delay = 2000; |
| `uvm_info("set_otf_exp_alert", |
| $sformatf("exp_alert_ff[%s] size: %0d", |
| str, cfg.scb_h.exp_alert_ff[str].size()), UVM_MEDIUM) |
| endfunction // set_otf_exp_alert |
| |
| // This function checks wheter input 'sig' is lc_ctrl_pkg::On or lc_ctrl_pkg::Off |
| function bit is_lc_ctrl_valid(lc_ctrl_pkg::lc_tx_t sig, bit is_true_valid = 1); |
| |
| return ((sig == lc_ctrl_pkg::On && is_true_valid == 1) || |
| (sig == lc_ctrl_pkg::Off && is_true_valid == 0)); |
| endfunction // is_lc_ctrl_valid |
| |
| function void all_sw_rw_en(); |
| cfg.flash_ctrl_vif.lc_creator_seed_sw_rw_en = lc_ctrl_pkg::On; |
| cfg.flash_ctrl_vif.lc_owner_seed_sw_rw_en = lc_ctrl_pkg::On; |
| cfg.flash_ctrl_vif.lc_iso_part_sw_rd_en = lc_ctrl_pkg::On; |
| cfg.flash_ctrl_vif.lc_iso_part_sw_wr_en = lc_ctrl_pkg::On; |
| endfunction // all_sw_rw_en |
| |
| // Collect cover poiint by reading csr |
| // ral.std_fault_status |
| // ral.fault_status |
| // ral.err_code |
| task collect_err_cov_status(dv_base_reg ptr); |
| uvm_reg_data_t rdata; |
| if (cfg.en_cov) begin |
| csr_rd(.ptr(ptr), .value(rdata), .backdoor(1)); |
| if (ptr.get_name == "std_fault_status") begin |
| cfg.scb_h.cov.std_fault_cg.sample(rdata); |
| end else if (ptr.get_name == "err_code") begin |
| cfg.scb_h.cov.sw_error_cg.sample(rdata); |
| end else begin |
| cfg.scb_h.cov.hw_error_cg.sample(rdata); |
| end |
| end |
| endtask |
| |
| task init_controller(bit non_blocking = 0); |
| int wait_timeout_ns = 50000; // 50 us |
| |
| csr_wr(.ptr(ral.init), .value(1)); |
| `uvm_info(`gfn,"init_controller: OTP",UVM_LOW) |
| otp_model(); |
| if (non_blocking == 0) begin |
| `DV_SPINWAIT(wait(cfg.flash_ctrl_vif.rd_buf_en == 1);, |
| "Timed out waiting for rd_buf_en", |
| wait_timeout_ns) |
| cfg.clk_rst_vif.wait_clks(10); |
| end |
| endtask |
| |
| // Use uvm_hdl_read / force flip 2 bits out of 32 bit bus |
| // Assuming the input path should be 32bit bus |
| function void flip_2bits(string path); |
| bit [31:0] rdata; |
| int idx[32]; |
| foreach (idx[i]) idx[i] = i; |
| `DV_CHECK(uvm_hdl_read(path, rdata)); |
| idx.shuffle(); |
| rdata[idx[0]] = ~rdata[idx[0]]; |
| rdata[idx[1]] = ~rdata[idx[1]]; |
| `DV_CHECK(uvm_hdl_force(path, rdata)); |
| endfunction |
| |
| // Identify secret partition from flash_op. |
| function bit is_secret_part(flash_op_t op); |
| return ( op.partition == FlashPartInfo && |
| op.addr inside {[FlashCreatorPartStartAddr:FlashIsolPartEndAddr]}); |
| endfunction // is_secret_part |
| |
| // Update secret partition with scrambled and ecc enabled data. |
| function void update_secret_partition(bit dis = 0); |
| uvm_hdl_data_t data; |
| flash_otf_item item; |
| bit [BankAddrW-1:0] mem_addr; |
| int page = 1; |
| |
| repeat(3) begin |
| int page_st_addr = page*2048; |
| |
| for (int addr = page_st_addr; addr < (page_st_addr + 8*256); addr += 8) begin |
| `uvm_create_obj(flash_otf_item, item) |
| data = cfg.mem_bkdr_util_h[FlashPartInfo][0].read(addr); |
| item.dq.push_back(data[31:0]); |
| item.dq.push_back(data[63:32]); |
| // only scr/ecc enable counts. |
| if (page != 3) begin |
| item.region.scramble_en = prim_mubi_pkg::mubi4_and_hi( |
| flash_ctrl_pkg::CfgAllowRead.scramble_en, |
| mubi4_t'(~cfg.ovrd_scr_dis)); |
| item.region.ecc_en = prim_mubi_pkg::mubi4_and_hi( |
| flash_ctrl_pkg::CfgAllowRead.ecc_en, |
| mubi4_t'(~cfg.ovrd_ecc_dis)); |
| end else begin |
| item.region.scramble_en = flash_ctrl_pkg::CfgAllowRead.scramble_en; |
| item.region.ecc_en = flash_ctrl_pkg::CfgAllowRead.ecc_en; |
| end |
| item.scramble(otp_addr_key, otp_data_key, addr, dis); |
| cfg.mem_bkdr_util_h[FlashPartInfo][0].write(addr, item.fq[0]); |
| mem_addr = addr >> 3; |
| cfg.otf_scb_h.info_mem[0][0][mem_addr] = item.fq[0]; |
| cfg.scb_flash_info[addr] = item.fq[0][31:0]; |
| cfg.scb_flash_info[addr+4] = item.fq[0][63:32]; |
| end // for (int addr = page_st_addr; addr < (page_st_addr + 8*4); addr += 8) |
| page++; |
| end |
| endfunction // update_secret_partition |
| |
| function void update_mp_region_cfg_mubifalse(ref flash_mp_region_cfg_t cfg); |
| if (cfg.en != MuBi4True) cfg.en = |
| get_rand_mubi4_val(.t_weight(0), .f_weight(1), .other_weight(9)); |
| if (cfg.read_en != MuBi4True) cfg.read_en = |
| get_rand_mubi4_val(.t_weight(0), .f_weight(1), .other_weight(9)); |
| if (cfg.program_en != MuBi4True) cfg.program_en = |
| get_rand_mubi4_val(.t_weight(0), .f_weight(1), .other_weight(9)); |
| if (cfg.erase_en != MuBi4True) cfg.erase_en = |
| get_rand_mubi4_val(.t_weight(0), .f_weight(1), .other_weight(9)); |
| if (cfg.scramble_en != MuBi4True) cfg.scramble_en = |
| get_rand_mubi4_val(.t_weight(0), .f_weight(1), .other_weight(9)); |
| if (cfg.ecc_en != MuBi4True) cfg.ecc_en = |
| get_rand_mubi4_val(.t_weight(0), .f_weight(1), .other_weight(9)); |
| if (cfg.he_en != MuBi4True) cfg.he_en = |
| get_rand_mubi4_val(.t_weight(0), .f_weight(1), .other_weight(9)); |
| endfunction |
| |
| function void update_mp_info_cfg_mubifalse(ref flash_bank_mp_info_page_cfg_t cfg); |
| if (cfg.en != MuBi4True) cfg.en = |
| get_rand_mubi4_val(.t_weight(0), .f_weight(1), .other_weight(9)); |
| if (cfg.read_en != MuBi4True) cfg.read_en = |
| get_rand_mubi4_val(.t_weight(0), .f_weight(1), .other_weight(9)); |
| if (cfg.program_en != MuBi4True) cfg.program_en = |
| get_rand_mubi4_val(.t_weight(0), .f_weight(1), .other_weight(9)); |
| if (cfg.erase_en != MuBi4True) cfg.erase_en = |
| get_rand_mubi4_val(.t_weight(0), .f_weight(1), .other_weight(9)); |
| if (cfg.scramble_en != MuBi4True) cfg.scramble_en = |
| get_rand_mubi4_val(.t_weight(0), .f_weight(1), .other_weight(9)); |
| if (cfg.ecc_en != MuBi4True) cfg.ecc_en = |
| get_rand_mubi4_val(.t_weight(0), .f_weight(1), .other_weight(9)); |
| if (cfg.he_en != MuBi4True) cfg.he_en = |
| get_rand_mubi4_val(.t_weight(0), .f_weight(1), .other_weight(9)); |
| endfunction |
| |
| endclass : flash_ctrl_base_vseq |