| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| typedef class flash_ctrl_scoreboard; |
| typedef class flash_ctrl_otf_scoreboard; |
| |
| class flash_ctrl_env_cfg extends cip_base_env_cfg #( |
| .RAL_T(flash_ctrl_core_reg_block) |
| ); |
| |
| // Memory backdoor util instances for each partition in each bank. |
| mem_bkdr_util mem_bkdr_util_h[flash_dv_part_e][flash_ctrl_pkg::NumBanks]; |
| |
| // Pass scoreboard handle to address multiple exp_alert issue. |
| flash_ctrl_scoreboard scb_h; |
| flash_ctrl_otf_scoreboard otf_scb_h; |
| |
| // seq cfg |
| flash_ctrl_seq_cfg seq_cfg; |
| |
| // Flash phy prim interface agent config |
| flash_phy_prim_agent_cfg m_fpp_agent_cfg; |
| // interface |
| virtual flash_ctrl_if flash_ctrl_vif; |
| virtual clk_rst_if clk_rst_vif_flash_ctrl_eflash_reg_block; |
| virtual clk_rst_if clk_rst_vif_flash_ctrl_prim_reg_block; |
| virtual flash_ctrl_mem_if flash_ctrl_mem_vif[NumBanks]; |
| |
| // knobs |
| // ral.status[init_wip] status is set for the very first clock cycle right out of reset. |
| // This causes problems in the read value especially in CSR tests. |
| int post_reset_delay_clks = 1; |
| |
| // Knob for blocking host reads |
| bit block_host_rd = 0; |
| |
| // Knob for control direct read checking |
| bit dir_rd_in_progress = 1'b0; |
| |
| // Knob for scoreboard write and check on reads |
| bit scb_check = 0; |
| |
| // Knob for scoreboard set expected alert |
| bit scb_set_exp_alert = 0; |
| |
| // Knob for Bank Erase |
| bit bank_erase_enable = 1; |
| |
| // Enable full checks of the scoreboard memory model, enabled by default. |
| bit check_full_scb_mem_model = 1'b1; |
| |
| // mem for scoreboard |
| data_model_t scb_flash_data = '{default: 1}; |
| data_model_t scb_flash_info = '{default: 1}; |
| data_model_t scb_flash_info1 = '{default: 1}; |
| data_model_t scb_flash_info2 = '{default: 1}; |
| |
| // Knob to enable on the fly scoreboard. |
| bit scb_otf_en = 0; |
| |
| // TB ecc support enable |
| // With ecc enabled, read path requires pre encoded data patterns. |
| // 0 : no ecc |
| // 1 : ecc enable |
| // Below mode use address split. |
| // 2 : 1 bit error test mode |
| // Based on serr_pct, single bit error is injected in 'flash_mem_otf_read' |
| // 3 : 2 bit error test mode |
| // 4 : integrity error test mode |
| ecc_mode_e ecc_mode = FlashEccDisabled; |
| |
| // single bit error rate scale of 0~10. 10: 100%. |
| int serr_pct = 0; |
| // Store single bit errored line to hash. |
| // If address exists, skip extra error injection to avoid |
| // creating multi bit errors |
| bit serr_addr_tbl[addr_t][flash_dv_part_e]; |
| int serr_cnt[NumBanks] = '{default : 0}; |
| // latest single bit error address |
| bit [OTFBankId:0] serr_addr[NumBanks]; |
| |
| // Create serr only once. Used in directed test case. |
| bit serr_once = 0; |
| bit serr_created = 0; |
| |
| // Double bit error test |
| int derr_pct = 0; |
| int derr_idx[76]; |
| bit derr_addr_tbl[addr_t][flash_dv_part_e]; |
| bit derr_once = 0; |
| bit derr_created[2] = '{default : 0}; |
| |
| // Mark out standing transactions. |
| // With heavy concurrency, derr can be injected where read transaction |
| // is issued and outstanding. |
| // This can change error expectation of the first transaction. |
| // To handle this conrnercase, don't assert derr on outstanding read location. |
| int derr_otd[rd_cache_t]; |
| |
| // Integrity ecc err |
| int ierr_pct = 0; |
| bit ierr_addr_tbl[addr_t][flash_dv_part_e]; |
| bit ierr_created[2] = '{default : 0}; |
| // Transaction counters for otf |
| int otf_ctrl_wr_sent = 0; |
| int otf_ctrl_wr_rcvd = 0; |
| int otf_ctrl_rd_sent = 0; |
| int otf_ctrl_rd_rcvd = 0; |
| int otf_host_rd_sent = 0; |
| int otf_host_rd_rcvd = 0; |
| |
| // Page to region map. |
| // This is used to validate transactions based on their page address |
| // and policy config associate with it. |
| // 8 : default region |
| int p2r_map[FlashNumPages] = '{default : 8}; |
| |
| flash_mp_region_cfg_t mp_regions[MpRegions]; |
| flash_bank_mp_info_page_cfg_t mp_info[NumBanks][InfoTypes][]; |
| |
| // Permission to access special partition |
| // 0: secret / creator |
| // 1: secret / owner |
| // 2: isolated |
| bit [2:0] allow_spec_info_acc = 3'h0; |
| |
| // Allow multiple expected allert in a single test |
| bit multi_alert_en = 0; |
| |
| // Max delay for alerts in clocks |
| uint alert_max_delay; |
| |
| // Max delay for alerts in ns |
| int alert_max_delay_in_ns; |
| |
| // read data by host if |
| data_q_t flash_rd_data; |
| |
| // 2bit of target prefix. Use with cfg.ecc_mode > FlashEccEnabled |
| // When cfg.ecc_mode > FlashEccEnabled, this will be randomized |
| // before sequence starts. |
| // tgt_pre[0]: rd |
| // tgt_pre[1]: direct_rd |
| // tgt_pre[2]: wr |
| // tgt_pre[3]: erase |
| // then assigned to bit 18:17 |
| bit [1:0] tgt_pre[flash_dv_part_e][NumTgt]; |
| |
| // Store recent 4 read address. |
| // If address is here, there is high chance read data is in cache. |
| // This can nullify error injection so task should avoid injecting error |
| // for these addresses. |
| flash_otf_read_entry otf_read_entry; |
| |
| // OTF Sequence parameters |
| int otf_num_rw = 250; |
| int otf_num_hr = 2500; |
| int otf_wr_pct = 1; |
| int otf_rd_pct = 1; |
| // overflow error rate |
| int otf_bwr_pct = 1; |
| int otf_brd_pct = 1; |
| |
| // interrupt mode |
| bit intr_mode = 0; |
| |
| // interrupt mode buffer credit |
| int rd_crd = 16; |
| int wr_crd = 4; |
| |
| // fifo level to trigger lvl interrupt |
| int rd_lvl = 0; |
| int wr_lvl = 0; |
| |
| // force all region enable to '1' |
| // '0' doesn't affect randomization |
| bit en_always_read = 0; |
| bit en_always_erase = 0; |
| bit en_always_prog = 0; |
| bit en_always_all = 0; |
| |
| // This is not tied to plusarg. |
| // Internal use only. |
| bit en_always_any = 0; |
| |
| bit en_all_info_acc = 0; |
| // tlul error transaction counter |
| // compare at the end of sim |
| int tlul_core_exp_cnt = 0; |
| int tlul_core_obs_cnt = 0; |
| |
| int tlul_eflash_exp_cnt = 0; |
| int tlul_eflash_obs_cnt = 0; |
| |
| // stop rma process |
| bit rma_ack_polling_stop = 0; |
| |
| // Store program data for read back check |
| data_q_t prog_data[flash_op_t]; |
| |
| // Pointer for bkdr mem task. |
| logic [KeyWidth-1:0] otp_addr_key; |
| logic [KeyWidth-1:0] otp_data_key; |
| |
| bit skip_init = 0; |
| bit skip_init_buf_en = 0; |
| bit wr_rnd_data = 1; |
| int wait_rd_buf_en_timeout_ns = 100_000; // 100 us |
| |
| // hw info cfg override |
| mubi4_t ovrd_scr_dis = MuBi4False; |
| mubi4_t ovrd_ecc_dis = MuBi4False; |
| |
| `uvm_object_utils(flash_ctrl_env_cfg) |
| `uvm_object_new |
| |
| string flash_ral_name = "flash_ctrl_eflash_reg_block"; |
| |
| // default region cfg |
| flash_mp_region_cfg_t default_region_cfg = '{ |
| default: MuBi4True, |
| scramble_en: MuBi4False, |
| ecc_en: MuBi4False, |
| he_en: MuBi4False, |
| // Below two values won't be programmed |
| // rtl uses hardcoded values |
| // start:0 |
| // size : 2 * 256 (0x200) |
| num_pages: 512, |
| start_page: 0 |
| }; |
| |
| // default info cfg |
| flash_bank_mp_info_page_cfg_t default_info_page_cfg = '{ |
| default: MuBi4True, |
| scramble_en: MuBi4False, |
| ecc_en: MuBi4False, |
| he_en: MuBi4False |
| }; |
| |
| // 1page : 2048Byte |
| // returns 9 bit (max 512) pages |
| function int addr2page(bit[OTFBankId:0] addr); |
| return (int'(addr[OTFBankId:11])); |
| endfunction // addr2page |
| |
| virtual function flash_mp_region_cfg_t get_region(int page, bit dis = 1); |
| flash_mp_region_cfg_t my_region; |
| if (p2r_map[page] == 8) begin |
| my_region = default_region_cfg; |
| end else begin |
| my_region = mp_regions[p2r_map[page]]; |
| if (my_region.en != MuBi4True) my_region = default_region_cfg; |
| end |
| if (dis) begin |
| `uvm_info("get_region", $sformatf("page:%0d --> region:%0d", |
| page, p2r_map[page]), UVM_MEDIUM) |
| end |
| return my_region; |
| endfunction // get_region |
| |
| function flash_mp_region_cfg_t get_region_from_info(flash_bank_mp_info_page_cfg_t info); |
| flash_mp_region_cfg_t region; |
| region.en = info.en; |
| region.read_en = info.read_en; |
| region.program_en = info.program_en; |
| region.erase_en = info.erase_en; |
| region.scramble_en = info.scramble_en; |
| region.ecc_en = info.ecc_en; |
| region.he_en = info.he_en; |
| return region; |
| endfunction // get_region_from_info |
| |
| virtual function void initialize(addr_t csr_base_addr = '1); |
| string prim_ral_name = "flash_ctrl_prim_reg_block"; |
| |
| list_of_alerts = flash_ctrl_env_pkg::LIST_OF_ALERTS; |
| tl_intg_alert_name = "fatal_std_err"; |
| sec_cm_alert_name = tl_intg_alert_name; |
| // Set up second RAL model for Flash memory |
| ral_model_names.push_back(flash_ral_name); |
| ral_model_names.push_back(prim_ral_name); |
| |
| // both RAL models use same clock frequency |
| clk_freqs_mhz[flash_ral_name] = clk_freq_mhz; |
| clk_freqs_mhz[prim_ral_name] = clk_freq_mhz; |
| super.initialize(csr_base_addr); |
| |
| tl_intg_alert_fields[ral.std_fault_status.reg_intg_err] = 1; |
| shadow_update_err_status_fields[ral.err_code.update_err] = 1; |
| shadow_storage_err_status_fields[ral.std_fault_status.storage_err] = 1; |
| |
| // create the seq_cfg and call configure |
| seq_cfg = flash_ctrl_seq_cfg::type_id::create("seq_cfg"); |
| seq_cfg.configure(); |
| |
| m_fpp_agent_cfg = flash_phy_prim_agent_cfg::type_id::create("m_fpp_agent_cfg"); |
| m_fpp_agent_cfg.is_active = 0; |
| m_fpp_agent_cfg.en_cov = 0; |
| |
| // set num_interrupts & num_alerts |
| begin |
| uvm_reg rg = ral.get_reg_by_name("intr_state"); |
| if (rg != null) begin |
| num_interrupts = ral.intr_state.get_n_used_bits(); |
| end |
| end |
| m_tl_agent_cfg.max_outstanding_req = 1; |
| m_tl_agent_cfgs[flash_ral_name].max_outstanding_req = 2; |
| m_tl_agent_cfgs[prim_ral_name].max_outstanding_req = 1; |
| |
| alert_max_delay = 20000; |
| `uvm_info(`gfn, $sformatf("ral_model_names: %0p", ral_model_names), UVM_LOW) |
| |
| foreach (tgt_pre[FlashPartData][i]) tgt_pre[FlashPartData][i] = i; |
| foreach (tgt_pre[FlashPartInfo][i]) tgt_pre[FlashPartInfo][i] = i; |
| foreach (tgt_pre[FlashPartInfo1][i]) tgt_pre[FlashPartInfo1][i] = i; |
| foreach (tgt_pre[FlashPartInfo2][i]) tgt_pre[FlashPartInfo2][i] = i; |
| |
| foreach (derr_idx[i]) derr_idx[i] = i; |
| foreach (mp_info[i, j]) mp_info[i][j] = new[InfoTypeSize[j]]; |
| otf_read_entry = new("otf_read_entry"); |
| endfunction : initialize |
| |
| // For a given partition returns its size in bytes in each of the banks. |
| function uint get_partition_words_num(flash_dv_part_e part); |
| case(part) |
| FlashPartData: return BytesPerBank / 4; |
| FlashPartInfo: return InfoTypeBytes[0] / 4; |
| FlashPartInfo1: return InfoTypeBytes[1] / 4; |
| FlashPartInfo2: return InfoTypeBytes[2] / 4; |
| default: `uvm_error(`gfn, $sformatf("Undefined partition - %s", part.name())) |
| endcase |
| endfunction : get_partition_words_num |
| |
| // Method to do a back-door update of a selected partition memory model to the actual flash data. |
| // Usualy should only be done after flash initialization. |
| task update_partition_mem_model(flash_dv_part_e part); |
| flash_mem_addr_attrs addr_attr; |
| data_4s_t bkdr_rd_data; |
| uint partition_words_num; |
| data_model_t scb_flash_model; |
| addr_attr = new(); |
| partition_words_num = get_partition_words_num(part); |
| |
| `uvm_info(`gfn, $sformatf("\nStart back-door updating partition %s memory model\n", |
| part.name()), UVM_MEDIUM) |
| |
| for (int i = 0; i < flash_ctrl_pkg::NumBanks; i++) begin : iterate_all_banks |
| addr_attr.set_attrs(i * BytesPerBank); |
| for (int j = 0; j < partition_words_num; j++) begin : iterate_all_bank_partition_words |
| bkdr_rd_data = mem_bkdr_util_h[part][addr_attr.bank].read32(addr_attr.bank_addr); |
| if ($isunknown(bkdr_rd_data)) begin |
| scb_flash_model[addr_attr.addr] = ALL_ONES; |
| end else begin |
| scb_flash_model[addr_attr.addr] = bkdr_rd_data; |
| end |
| addr_attr.incr(flash_ctrl_pkg::BusBytes); |
| end : iterate_all_bank_partition_words |
| end : iterate_all_banks |
| |
| case(part) |
| flash_ctrl_env_pkg::FlashPartData: scb_flash_data = scb_flash_model; |
| flash_ctrl_env_pkg::FlashPartInfo: scb_flash_info = scb_flash_model; |
| flash_ctrl_env_pkg::FlashPartInfo1: scb_flash_info1 = scb_flash_model; |
| flash_ctrl_env_pkg::FlashPartInfo2: scb_flash_info2 = scb_flash_model; |
| default: `uvm_error(`gfn, $sformatf("Undefined partition - %s", part.name())) |
| endcase |
| |
| `uvm_info(`gfn, $sformatf("\nFinished back-door updating partition %s memory model\n", |
| part.name()), UVM_MEDIUM) |
| |
| endtask : update_partition_mem_model |
| |
| // Backdoor initialize flash memory elements. |
| // Applies the initialization scheme to the given flash partition in all banks. |
| // @part is the type of flash partition. |
| // @scheme is the type of initialization to be done. |
| virtual task flash_mem_bkdr_init(flash_dv_part_e part = FlashPartData, |
| flash_mem_init_e scheme); |
| |
| `uvm_info("flash_mem_bkdr_init", $sformatf("scheme: %s", scheme.name), UVM_MEDIUM) |
| case (scheme) |
| FlashMemInitSet: begin |
| foreach (mem_bkdr_util_h[part][i]) mem_bkdr_util_h[part][i].set_mem(); |
| end |
| FlashMemInitClear: begin |
| foreach (mem_bkdr_util_h[part][i]) mem_bkdr_util_h[part][i].clear_mem(); |
| end |
| FlashMemInitRandomize: begin |
| foreach (mem_bkdr_util_h[part][i]) mem_bkdr_util_h[part][i].randomize_mem(); |
| end |
| FlashMemInitInvalidate: begin |
| foreach (mem_bkdr_util_h[part][i]) mem_bkdr_util_h[part][i].invalidate_mem(); |
| end |
| FlashMemInitEccMode: begin |
| foreach (mem_bkdr_util_h[part][i]) mem_bkdr_util_h[part][i].set_mem(); |
| end |
| default: begin |
| `uvm_error(`gfn, $sformatf("Undefined initialization scheme - %s", scheme.name())) |
| end |
| endcase |
| |
| // Update the memory model with the initialization data |
| if (scb_check) update_partition_mem_model(part); |
| endtask : flash_mem_bkdr_init |
| |
| // For a given partition returns its respective memory model. |
| function data_model_t get_partition_mem_model(flash_ctrl_env_pkg::flash_dv_part_e part); |
| case(part) |
| flash_ctrl_env_pkg::FlashPartData: return scb_flash_data; |
| flash_ctrl_env_pkg::FlashPartInfo: return scb_flash_info; |
| flash_ctrl_env_pkg::FlashPartInfo1: return scb_flash_info1; |
| flash_ctrl_env_pkg::FlashPartInfo2: return scb_flash_info2; |
| default: `uvm_error(`gfn, $sformatf("Undefined partition - %s", part.name())) |
| endcase |
| endfunction : get_partition_mem_model |
| |
| // Task to back-door check a selected partition memory model |
| // This will be called in the scoreboard check_phase if the check_full_scb_mem_model will |
| // be set to 1 (enabled by default). |
| function void check_partition_mem_model(flash_ctrl_env_pkg::flash_dv_part_e part); |
| flash_mem_addr_attrs addr_attr; |
| data_4s_t bkdr_rd_data; |
| data_model_t scb_flash_model = get_partition_mem_model(part); |
| addr_attr = new(); |
| foreach (scb_flash_model[addr]) begin |
| addr_attr.set_attrs(addr); |
| bkdr_rd_data = mem_bkdr_util_h[part][addr_attr.bank].read32(addr_attr.bank_addr); |
| if ($isunknown(bkdr_rd_data)) bkdr_rd_data = ALL_ONES; |
| `DV_CHECK_EQ(bkdr_rd_data, scb_flash_model[addr], |
| $sformatf({"Memory model check failed in partition %s, bank %0d, addr 0x%0x ", |
| "(%0d)"}, part.name(), addr_attr.bank, addr_attr.bank_addr, |
| addr_attr.bank_addr)) |
| end |
| endfunction : check_partition_mem_model |
| |
| // Task to back-door check the full memory model |
| // This will be called in the scoreboard check_phase & between calls to the inner rand_ops vseq |
| // (in partner_flash_ctrl_base_vseq post_tran task) if the check_full_scb_mem_model will |
| // be set to 1 (enabled by default). |
| function void check_mem_model(); |
| flash_ctrl_env_pkg::flash_dv_part_e part = part.first(); |
| `uvm_info(`gfn, $sformatf("\nStart checking all memory model\n"), UVM_MEDIUM) |
| do begin |
| check_partition_mem_model(part); |
| part = part.next(); |
| end while (part != part.first()); |
| `uvm_info(`gfn, $sformatf("\nFinished checking all memory model\n"), UVM_MEDIUM) |
| endfunction : check_mem_model |
| |
| // Read full word from the memory. (76bits per word line) |
| // flash_op.op should be FlashOpRead |
| function void flash_mem_otf_read(flash_op_t flash_op, ref fdata_q_t data); |
| flash_dv_part_e partition; |
| int bank; |
| bit [75:0] rdata; |
| int size, is_odd, tail; |
| addr_t aligned_addr; |
| |
| aligned_addr = flash_op.addr; |
| // QW (8byte) align |
| aligned_addr[2:0] = 'h0; |
| bank = flash_op.addr[OTFBankId]; |
| partition = flash_op.partition; |
| // If address is not 8byte aligned, full 76bit has to be read. |
| // This exception is identified using 4Byte address bit, (addr[2]) |
| // and size of 4byte word. |
| is_odd = flash_op.addr[2]; |
| size = (flash_op.num_words + is_odd) / 2; |
| tail = (flash_op.num_words + is_odd) % 2; |
| |
| `uvm_info("flash_mem_otf_read", $sformatf("is_odd:%0d size:%0d tail:%0d wd:%0d", |
| is_odd, size, tail, flash_op.num_words), UVM_MEDIUM) |
| // Use per bank address. |
| aligned_addr[31:OTFBankId] = 'h0; |
| for (int i = 0; i < size; i++) begin |
| rdata = mem_bkdr_util_h[partition][bank].read(aligned_addr); |
| data.push_back(rdata); |
| aligned_addr += 8; |
| end |
| if (tail) begin |
| rdata = mem_bkdr_util_h[partition][bank].read(aligned_addr); |
| data.push_back(rdata); |
| end |
| endfunction // flash_mem_otf_read |
| |
| // Reads flash mem contents via backdoor. |
| // |
| // The addr arg need not be word aligned - its the same addr programmed into the `control` CSR. |
| // TODO: add support for partition. |
| virtual function void flash_mem_bkdr_read(flash_op_t flash_op, ref data_q_t data); |
| flash_mem_addr_attrs addr_attrs = new(flash_op.addr); |
| |
| if (flash_op.op == flash_ctrl_pkg::FlashOpErase) begin |
| case (flash_op.erase_type) |
| flash_ctrl_pkg::FlashErasePage: begin |
| addr_attrs.set_attrs(addr_attrs.bank_start_addr + addr_attrs.page_start_addr); |
| flash_op.num_words = FlashNumBusWordsPerPage; |
| end |
| flash_ctrl_pkg::FlashEraseBank: begin |
| addr_attrs.set_attrs(addr_attrs.bank * BytesPerBank); |
| case (flash_op.partition) |
| FlashPartData: begin |
| flash_op.num_words = FlashNumBusWordsPerBank; |
| end |
| FlashPartInfo: begin |
| flash_op.num_words = InfoTypeBusWords[0]; |
| end |
| default: begin |
| `uvm_fatal(`gfn, $sformatf( |
| { |
| "Invalid partition for bank_erase: %0s. ", |
| "Bank erase is only valid in the data partition ", |
| "(FlashPartData) and the first info partition ", |
| "(FlashPartInfo)." |
| }, |
| flash_op.partition.name() |
| )) |
| end |
| endcase |
| end |
| default: begin |
| `uvm_fatal(`gfn, $sformatf("Invalid erase_type: %0s", flash_op.erase_type.name())) |
| end |
| endcase |
| end |
| |
| data.delete(); |
| for (int i = 0; i < flash_op.num_words; i++) begin |
| data[i] = mem_bkdr_util_h[flash_op.partition][addr_attrs.bank].read32(addr_attrs.bank_addr); |
| `uvm_info(`gfn, $sformatf( |
| "flash_mem_bkdr_read: partition = %s , {%s} = 0x%0h", |
| flash_op.partition.name(), |
| addr_attrs.sprint(), |
| data[i] |
| ), UVM_HIGH) |
| addr_attrs.incr(TL_DBW); |
| end |
| endfunction : flash_mem_bkdr_read |
| |
| // Writes the flash mem contents via backdoor. |
| // |
| // The addr need not be bus word aligned, Its the same addr programmed into the `control` CSR. |
| // The data queue is sized for the bus word. |
| // TODO: support for partition. |
| virtual function void flash_mem_bkdr_write(flash_op_t flash_op, flash_mem_init_e scheme, |
| data_q_t data = {}); |
| flash_mem_addr_attrs addr_attrs = new(flash_op.addr); |
| data_4s_t wr_data; |
| data_b_t mem_data; |
| |
| // Randomize the lower half-word (if Xs) if the first half-word written in the below loop is |
| // corresponding upper half-word. |
| if (addr_attrs.bank_addr[flash_ctrl_pkg::DataByteWidth-1]) begin |
| _randomize_uninitialized_half_word(.partition(flash_op.partition), .bank(addr_attrs.bank), |
| .addr(addr_attrs.word_addr)); |
| end |
| |
| case (scheme) |
| FlashMemInitCustom: begin |
| flash_op.num_words = data.size(); |
| end |
| FlashMemInitSet: begin |
| wr_data = {flash_ctrl_pkg::DataWidth{1'b1}}; |
| end |
| FlashMemInitClear: begin |
| wr_data = {flash_ctrl_pkg::DataWidth{1'b0}}; |
| end |
| FlashMemInitInvalidate: begin |
| wr_data = {flash_ctrl_pkg::DataWidth{1'bx}}; |
| end |
| endcase |
| |
| for (int i = 0; i < flash_op.num_words; i++) begin |
| data_4s_t loc_data = (scheme == FlashMemInitCustom) ? data[i] : |
| (scheme == FlashMemInitRandomize) ? $urandom() : wr_data; |
| |
| _flash_full_write(flash_op.partition, addr_attrs.bank, addr_attrs.bank_addr, loc_data); |
| `uvm_info(`gfn, $sformatf( |
| "flash_mem_bkdr_write: partition = %s, {%s} = 0x%0h", |
| flash_op.partition.name(), |
| addr_attrs.sprint(), |
| loc_data |
| ), UVM_HIGH) |
| |
| // update the scoreboard on backdoor-programs as well |
| mem_data[0] = loc_data; |
| set_scb_mem(1, flash_op.partition, |
| addr_attrs.addr, CustomVal, mem_data); |
| |
| // increment after all updates are complete |
| addr_attrs.incr(TL_DBW); |
| end |
| |
| // Randomize the upper half-word (if Xs) if the last word written in the above loop is |
| // corresponding lower half-word. |
| if (addr_attrs.bank_addr[flash_ctrl_pkg::DataByteWidth-1]) begin |
| _randomize_uninitialized_half_word(.partition(flash_op.partition), .bank(addr_attrs.bank), |
| .addr(addr_attrs.bank_addr)); |
| end |
| endfunction : flash_mem_bkdr_write |
| |
| // Helper function that takes a 32-bit data and correctly populates the integrity ECC |
| // |
| function void _flash_full_write(flash_dv_part_e partition, uint bank, |
| // bus word aligned address |
| addr_t addr, |
| data_t wr_data); |
| |
| // read back the full flash word |
| logic [flash_ctrl_pkg::DataWidth-1:0] data; |
| logic [7:0] intg_data; |
| logic is_upper = addr[flash_ctrl_pkg::DataByteWidth-1]; |
| addr_t aligned_addr = addr; |
| |
| if (is_upper) begin |
| aligned_addr = {addr[TL_AW-1:FlashDataByteWidth], {FlashDataByteWidth{1'b0}}}; |
| end |
| |
| // get the full flash word |
| data = mem_bkdr_util_h[partition][bank].read64(aligned_addr); |
| |
| // writing the upper portion of the flash word |
| if (is_upper) begin |
| data = {wr_data, data[TL_DW-1:0]}; |
| end else begin |
| data = {data[flash_ctrl_pkg::DataWidth-:TL_DW], wr_data}; |
| end |
| |
| // calculate truncated integrity |
| {intg_data, data} = prim_secded_pkg::prim_secded_hamming_72_64_enc(data); |
| |
| // program fully via backdoor |
| // TODO: review this later. |
| // it has to be write(aligned_addr, instead of write64(aligned_addr |
| mem_bkdr_util_h[partition][bank].write64(aligned_addr, {intg_data[3:0], data}); |
| |
| // Update scoreboard memory model with this back-door write |
| if (scb_check) begin |
| write_data_all_part(.part(partition), .addr({bank, addr[FlashMemAddrPageMsbBit:0]}), |
| .is_front_door(1'b0), .data(wr_data)); |
| end |
| |
| endfunction : _flash_full_write |
| |
| |
| // Helper function that randomizes the half-word at the given address if unknown. |
| // |
| // When the 'other' flash half-word is being written by the flash_mem_bkdr_write() method, the |
| // half-word at the given address needs to also be updated, of the data at that address is |
| // unknown. This is needed because the flash_ctrl RTL internally fetches full words. This method |
| // randomizes the data at the given address via backdoor. |
| function void _randomize_uninitialized_half_word(flash_dv_part_e partition, uint bank, |
| addr_t addr); |
| data_4s_t data = mem_bkdr_util_h[partition][bank].read32(addr); |
| if ($isunknown(data)) begin |
| `DV_CHECK_STD_RANDOMIZE_FATAL(data) |
| `uvm_info(`gfn, $sformatf("Data at 0x%0h is Xs, writing random 0x%0h", addr, data), UVM_HIGH) |
| _flash_full_write(partition, bank, addr, data); |
| end |
| endfunction |
| |
| // Checks flash mem contents via backdoor. |
| // |
| // The addr need not be bus word aligned. Its the same addr programmed into the `control` CSR. |
| // The exp data queue is sized for the bus word. |
| // TODO: support for partition. |
| virtual function void flash_mem_bkdr_read_check(flash_op_t flash_op, |
| const ref data_q_t exp_data, |
| input bit check_match = 1, |
| bit scr_en = 0); |
| data_q_t data; |
| flash_otf_item item; |
| |
| // If scramble is enabled, read data and descramble before return. |
| if (scr_en) begin |
| `uvm_create_obj(flash_otf_item, item) |
| flash_mem_otf_read(flash_op, item.fq); |
| flash_op.otf_addr = flash_op.addr; |
| flash_op.otf_addr[BusAddrByteW-2:OTFHostId] = 'h0; |
| |
| item.region.scramble_en = MuBi4True; |
| item.region.ecc_en = MuBi4True; |
| item.mem_addr = flash_op.otf_addr>>3; |
| item.descramble(otp_addr_key, otp_data_key); |
| foreach (item.dq[i]) data[i] = item.dq[i]; |
| end else begin |
| flash_mem_bkdr_read(flash_op, data); |
| end |
| |
| foreach (data[i]) begin |
| if (check_match) begin |
| `DV_CHECK_CASE_EQ(data[i], exp_data[i]) |
| end else begin |
| `DV_CHECK_CASE_NE(data[i], exp_data[i]) |
| end |
| end |
| endfunction : flash_mem_bkdr_read_check |
| |
| // Verifies that the flash page / bank has indeed been erased. |
| virtual function void flash_mem_bkdr_erase_check(flash_op_t flash_op, data_q_t exp_data = {}, |
| bit check_match = 1); |
| flash_mem_addr_attrs addr_attrs = new(flash_op.addr); |
| bit [TL_AW-1:0] erase_check_addr; |
| string erase_page_num_msg; |
| uint num_words; |
| |
| case (flash_op.erase_type) |
| flash_ctrl_pkg::FlashErasePage: begin |
| erase_check_addr = addr_attrs.page_start_addr; |
| num_words = FlashNumBusWordsPerPage; |
| erase_page_num_msg = $sformatf("page = %0d, ", addr_attrs.page); |
| end |
| flash_ctrl_pkg::FlashEraseBank: begin |
| // This address is relative to the bank it's in. |
| erase_check_addr = 0; |
| // No need to state page for bank erase. |
| erase_page_num_msg = ""; |
| case (flash_op.partition) |
| FlashPartData: begin |
| num_words = FlashNumBusWordsPerBank; |
| end |
| FlashPartInfo: begin |
| num_words = InfoTypeBusWords[0]; |
| end |
| default: begin |
| `uvm_fatal(`gfn, $sformatf( |
| { |
| "Invalid partition for bank_erase: %0s. ", |
| "Bank erase is only valid in the data partition ", |
| "(FlashPartData) and the first info partition ", |
| "(FlashPartInfo)." |
| }, |
| flash_op.partition.name() |
| )) |
| end |
| endcase |
| end |
| default: begin |
| `uvm_fatal(`gfn, $sformatf("Invalid erase_type: %0s", flash_op.erase_type.name())) |
| end |
| endcase |
| `uvm_info(`gfn, $sformatf( |
| { |
| "flash_mem_bkdr_erase_check: Erase type = %s, bank = %0d, ", |
| "partition = %s , %snum_words = %0d" |
| }, |
| flash_op.erase_type.name(), |
| addr_attrs.bank, |
| flash_op.partition.name(), |
| erase_page_num_msg, |
| num_words |
| ), UVM_MEDIUM) |
| |
| for (int i = 0; i < num_words; i++) begin |
| data_4s_t data; |
| data = mem_bkdr_util_h[flash_op.partition][addr_attrs.bank].read32(erase_check_addr); |
| `uvm_info(`gfn, $sformatf( |
| { |
| "flash_mem_bkdr_erase_check: Erase type = %s, bank: %0d, ", |
| "partition: %s , %saddr: 0x%0h, data: 0x%0h" |
| }, |
| flash_op.erase_type.name(), |
| addr_attrs.bank, |
| flash_op.partition.name(), |
| erase_page_num_msg, |
| erase_check_addr, |
| data |
| ), UVM_HIGH) |
| // If the expected data is not empty then it should be taken is expected. If it is empty the |
| // default expected value is checked - which for successful erase is all 1s. |
| if (check_match) begin |
| if (exp_data.size() <= i) begin |
| `DV_CHECK_CASE_EQ(data, '1) |
| end else begin |
| `DV_CHECK_CASE_EQ(data, exp_data[i]) |
| end |
| end else begin |
| `DV_CHECK_CASE_NE(data, '1) |
| end |
| erase_check_addr += TL_DBW; |
| end |
| endfunction : flash_mem_bkdr_erase_check |
| |
| // Function to enable changing of the expected data to be checked in the post-transaction |
| // checks. |
| virtual function data_q_t calculate_expected_data(flash_op_t flash_op, |
| const ref data_q_t exp_data); |
| return exp_data; |
| endfunction : calculate_expected_data |
| |
| // Writing data to the scoreboard memory model, this writes one word of data to the selected |
| // address in the selected partition. |
| // is_front_door added to indicate if this method called by front-door |
| // write (program transaction), which is the default, or by back-door methods. |
| // This is required for extending env. |
| virtual function void write_data_all_part(flash_dv_part_e part, addr_t addr, |
| bit is_front_door = 1'b1, ref data_t data); |
| `uvm_info(`gfn, $sformatf("WRITE SCB MEM part: %0s addr:%0h data:0x%0h", |
| part.name, addr, data), UVM_HIGH) |
| case (part) |
| FlashPartData: scb_flash_data[addr] = data; |
| FlashPartInfo: scb_flash_info[addr] = data; |
| FlashPartInfo1: scb_flash_info1[addr] = data; |
| FlashPartInfo2: scb_flash_info2[addr] = data; |
| default: `uvm_fatal(`gfn, "flash_ctrl_scoreboard: Partition type not supported!") |
| endcase |
| endfunction |
| |
| // Task for clean scb memory |
| virtual function reset_scb_mem(); |
| scb_flash_data.delete(); |
| scb_flash_info.delete(); |
| scb_flash_info1.delete(); |
| scb_flash_info2.delete(); |
| endfunction : reset_scb_mem |
| |
| // Task for set scb memory |
| virtual function set_scb_mem(int bkd_num_words, flash_dv_part_e bkd_partition, |
| addr_t write_bkd_addr,flash_scb_wr_e val_type, |
| data_b_t custom_val = {}); |
| addr_t wr_bkd_addr; |
| data_t wr_value; |
| |
| `uvm_info(`gfn, $sformatf( |
| "SET SCB MEM TEST part: %0s addr:%0h data:0x%0h num: %0d", |
| bkd_partition.name, |
| write_bkd_addr, |
| wr_value, |
| bkd_num_words |
| ), UVM_HIGH) |
| wr_bkd_addr = {write_bkd_addr[TL_AW-1:2], 2'b00}; |
| `uvm_info(`gfn, $sformatf("SET SCB MEM ADDR:%0h", wr_bkd_addr), UVM_HIGH) |
| for (int i = 0; i < bkd_num_words; i++) begin |
| case (val_type) |
| AllOnes: begin |
| wr_value = ALL_ONES; |
| end |
| AllZeros: begin |
| wr_value = ALL_ZEROS; |
| end |
| CustomVal: begin |
| wr_value = custom_val[i]; |
| end |
| default: `uvm_fatal(`gfn, "Unknown write type, allowed: AllOnes, AllZeros, CustomVal") |
| endcase |
| `uvm_info(`gfn, $sformatf( |
| "SET SCB MEM part: %0s addr:%0h data:0x%0h num: %0d", |
| bkd_partition.name, |
| wr_bkd_addr, |
| wr_value, |
| bkd_num_words |
| ), UVM_HIGH) |
| write_data_all_part(.part(bkd_partition), .addr(wr_bkd_addr), .is_front_door(1'b0), |
| .data(wr_value)); |
| wr_bkd_addr = wr_bkd_addr + 4; |
| end |
| endfunction : set_scb_mem |
| |
| function int get_serr_idx(); |
| int rnd_odds; |
| int idx = -1; |
| if (serr_once == 1 && serr_created == 1) return -1; |
| if (ecc_mode == FlashSerrTestMode) begin |
| rnd_odds = $urandom_range(0,9); |
| if (rnd_odds < serr_pct) begin |
| idx = $urandom_range(0, 75); |
| serr_created = 1; |
| end |
| end |
| |
| return idx; |
| endfunction // get_serr_idx |
| |
| // Increase single bit error count. |
| function void inc_serr_cnt(int bank, bit dis = 0); |
| if (serr_cnt[bank] < 255) serr_cnt[bank]++; |
| if (dis) begin |
| `uvm_info("inc_serr_cnt", $sformatf("serr_cnt[%0d]=%0d", bank, serr_cnt[bank]), UVM_MEDIUM) |
| end |
| endfunction |
| |
| // Flip a bit at given address. |
| function void flash_bit_flip(mem_bkdr_util _h, addr_t addr, int idx); |
| bit [75:0] rdata; |
| rdata = _h.read(addr); |
| rdata[idx] = ~rdata[idx]; |
| _h.write(addr, rdata); |
| endfunction |
| |
| // Corrupt integrity check value only |
| function void flash_icv_flip(mem_bkdr_util _h, addr_t addr, flash_dv_part_e part, |
| bit bank, flash_otf_item exp_item); |
| flash_otf_item item; |
| |
| `uvm_create_obj(flash_otf_item, item) |
| item.dq.push_back($urandom()); |
| item.dq.push_back($urandom()); |
| if (part == FlashPartData) begin |
| addr_t full_addr = addr; |
| full_addr[OTFBankId] = bank; |
| item.region = get_region(addr2page(full_addr)); |
| end else begin |
| item.region = |
| get_region_from_info(mp_info[bank][part>>1][addr2page(addr)]); |
| end |
| item.scramble(exp_item.addr_key, exp_item.data_key, addr, 1, 1); |
| _h.write(addr, item.fq[0]); |
| item.clear_qs(); |
| endfunction |
| |
| // Create bit error follwing flash_op and ecc_mode. |
| // @caller : 0 controller, 1: host |
| function void add_bit_err(flash_op_t flash_op, read_task_e caller = ReadTaskCtrl, |
| flash_otf_item item = null); |
| flash_dv_part_e partition; |
| int bank; |
| bit [75:0] rdata; |
| int size, is_odd, tail; |
| int err_idx; |
| addr_t aligned_addr, addr_cp; |
| rd_cache_t rd_entry; |
| string name = $sformatf("add_bit_err from %s", caller.name); |
| |
| err_idx = -1; |
| aligned_addr = flash_op.addr; |
| // QW (8byte) align |
| aligned_addr[2:0] = 'h0; |
| bank = flash_op.addr[OTFBankId]; |
| partition = flash_op.partition; |
| rd_entry.bank = bank; |
| rd_entry.part = partition; |
| rd_entry.addr = aligned_addr; |
| // If address is not 8byte aligned, full 76bit has to be read. |
| // This exception is identified using 4Byte address bit, (addr[2]) |
| // and size of 4byte word. |
| is_odd = flash_op.addr[2]; |
| size = (flash_op.num_words + is_odd) / 2; |
| tail = (flash_op.num_words + is_odd) % 2; |
| addr_cp = aligned_addr; |
| |
| // Use per bank address. |
| aligned_addr[31:OTFBankId] = 'h0; |
| for (int i = 0; i < size; i++) begin |
| // For controller initiated read, in the worst case, each word (8byte) can belong to |
| // different memory protection region. |
| // So before inject bit error, we have to check per word ecc_en |
| // to make sure bit error injection become valid. |
| // If ecc is not enabled, bit error cannot be detected. |
| if (caller == ReadTaskHost || (item.ctrl_rd_region_q[i].ecc_en == MuBi4True)) begin |
| rd_entry.addr = addr_cp; |
| if (ecc_mode == FlashSerrTestMode) begin |
| err_idx = get_serr_idx(); |
| if (err_idx >= 0) begin |
| // Make sure only assert error only once per address |
| if (!serr_addr_tbl[addr_cp].exists(partition)) begin |
| serr_addr_tbl[addr_cp][partition] = 1; |
| `uvm_info(name, |
| $sformatf({"single bit error is inserted at line:%0d(0x%x) %sz", |
| " the databit[%0d]"}, |
| i, addr_cp, partition.name, err_idx), UVM_MEDIUM) |
| flash_bit_flip(mem_bkdr_util_h[partition][bank], aligned_addr, err_idx); |
| end |
| end |
| end else if (ecc_mode == FlashIerrTestMode) begin |
| randcase |
| ierr_pct: begin |
| if (derr_otd.exists(rd_entry)) continue; |
| if (ierr_addr_tbl[addr_cp].exists(partition)) begin |
| ierr_created[caller] = 1; |
| end else if (!otf_read_entry.hash.exists(rd_entry)) begin |
| ierr_addr_tbl[addr_cp][partition] = 1; |
| ierr_created[caller] = 1; |
| `uvm_info(name, |
| $sformatf("icv error [%s][%0d] is inserted at line:%0d rd_entry:%p", |
| partition.name, bank, i, rd_entry), UVM_MEDIUM) |
| flash_icv_flip(mem_bkdr_util_h[partition][bank], |
| aligned_addr, partition, bank, item); |
| end |
| end |
| 10-ierr_pct: begin |
| end |
| endcase // randcase |
| end else begin // if (ecc_mode == FlashIerrTestMode) |
| derr_idx.shuffle(); |
| err_idx = 0; |
| if (derr_otd.exists(rd_entry)) continue; |
| if (derr_once == 0 || (derr_created[0] | derr_created[1]) == 0 ) begin |
| repeat (2) begin |
| randcase |
| derr_pct: begin |
| `uvm_info(name, |
| $sformatf({"addr:0x%x %x bit error is inserted at line:%0d", |
| " the databit[%0d] err_idx:%0d"}, |
| aligned_addr, addr_cp, i, derr_idx[err_idx], err_idx), |
| UVM_MEDIUM) |
| |
| // If address already had a single bit error, just skip this line. |
| // We could add another bit error instead of modeling read cache behavior. |
| if (err_idx != 0 || serr_addr_tbl[addr_cp].exists(partition) == 0) begin |
| if (!otf_read_entry.hash.exists(rd_entry)) begin |
| flash_bit_flip(mem_bkdr_util_h[partition][bank], aligned_addr, |
| derr_idx[err_idx++]); |
| serr_addr_tbl[addr_cp][partition] = 1; |
| end |
| end |
| if (err_idx == 2) begin |
| `uvm_info(name, $sformatf(" addr:0x%x is added to derr_addr_tbl", addr_cp), |
| UVM_MEDIUM) |
| derr_addr_tbl[addr_cp][partition] = 1; |
| derr_created[caller] = 1; |
| end |
| end |
| 10-derr_pct: begin |
| end |
| endcase // randcase |
| end // repeat (2) |
| end // if (derr_once == 0 || (|derr_created) == 0) |
| end // else: !if(ecc_mode == FlashIerrTestMode) |
| aligned_addr += 8; |
| addr_cp[OTFBankId-1:0] = aligned_addr[OTFBankId-1:0]; |
| end |
| end // for (int i = 0; i < size; i++) |
| |
| if (tail) begin |
| if (caller == ReadTaskHost || (item.ctrl_rd_region_q[size].ecc_en == MuBi4True)) begin |
| rd_entry.addr = addr_cp; |
| if (ecc_mode == FlashSerrTestMode) begin |
| err_idx = get_serr_idx(); |
| if (err_idx >= 0) begin |
| if (!serr_addr_tbl[addr_cp].exists(partition)) begin |
| serr_addr_tbl[addr_cp][partition] = 1; |
| `uvm_info(name, |
| $sformatf({"last:single bit error is inserted at line:%0d(0x%x) %s", |
| " the databit[%0d]"}, |
| size, addr_cp, partition.name, err_idx), UVM_MEDIUM) |
| flash_bit_flip(mem_bkdr_util_h[partition][bank], aligned_addr, err_idx); |
| end |
| end |
| end else if (ecc_mode == FlashIerrTestMode) begin |
| randcase |
| ierr_pct: begin |
| if (derr_otd.exists(rd_entry)) return; |
| if (ierr_addr_tbl[addr_cp].exists(partition)) begin |
| ierr_created[caller] = 1; |
| end else if (!otf_read_entry.hash.exists(rd_entry)) begin |
| ierr_addr_tbl[addr_cp][partition] = 1; |
| ierr_created[caller] = 1; |
| `uvm_info(name, |
| $sformatf("last:icv error[%s][%0d] is inserted at line:%0d rd_entry:%p", |
| partition.name, bank, size, rd_entry), UVM_MEDIUM) |
| flash_icv_flip(mem_bkdr_util_h[partition][bank], |
| aligned_addr, partition, bank, item); |
| end |
| end |
| 10-ierr_pct: begin |
| end |
| endcase // randcase |
| end else begin // if (ecc_mode == FlashIerrTestMode) |
| derr_idx.shuffle(); |
| err_idx = 0; |
| if (derr_otd.exists(rd_entry)) return; |
| if (derr_once == 0 || (derr_created[0] | derr_created[1]) == 0) begin |
| repeat (2) begin |
| randcase |
| derr_pct: begin |
| `uvm_info(name, |
| $sformatf({"last:addr:0x%x %x bit error is inserted at line:%0d", |
| " the databit[%0d] err_idx:%0d"}, |
| aligned_addr, addr_cp, size, derr_idx[err_idx], err_idx), |
| UVM_MEDIUM) |
| if (err_idx != 0 || serr_addr_tbl[addr_cp].exists(partition) == 0) begin |
| if (!otf_read_entry.hash.exists(rd_entry)) begin |
| flash_bit_flip(mem_bkdr_util_h[partition][bank], aligned_addr, |
| derr_idx[err_idx++]); |
| serr_addr_tbl[addr_cp][partition] = 1; |
| end |
| end |
| if (err_idx == 2) begin |
| derr_addr_tbl[addr_cp][partition] = 1; |
| derr_created[caller] = 1; |
| end |
| end |
| 10-derr_pct: begin |
| end |
| endcase // randcase |
| end |
| end // if (derr_once == 0 || (|derr_created) == 0) |
| end // else: !if(ecc_mode == FlashIerrTestMode) |
| end |
| end // if (tail) |
| endfunction // add_bit_err |
| |
| // Increase outstanding table entry. |
| function void inc_otd_tbl(bit bank, addr_t addr, flash_dv_part_e part); |
| rd_cache_t ent; |
| addr[2:0] = 3'h0; |
| ent.bank = bank; |
| ent.addr = addr; |
| ent.part = part; |
| if (!derr_otd.exists(ent)) begin |
| derr_otd[ent] = 1; |
| end else begin |
| derr_otd[ent]++; |
| end |
| endfunction // inc_otd_tbl |
| |
| // Descrease outstanding table entry. |
| function void dec_otd_tbl(bit bank, addr_t addr, flash_dv_part_e part); |
| rd_cache_t ent; |
| addr[2:0] = 3'h0; |
| ent.bank = bank; |
| ent.addr = addr; |
| ent.part = part; |
| if (!derr_otd.exists(ent)) begin |
| `uvm_error("dec_otd_tbl", $sformatf("addr %x %s doesn't exits", addr, part.name)) |
| end else begin |
| derr_otd[ent]--; |
| if (derr_otd[ent] == 0) derr_otd.delete(ent); |
| end |
| endfunction // dec_otd_tbl |
| |
| function flash_dv_part_e get_part(flash_part_e part, |
| logic [InfoTypesWidth-1:0] mem_info_sel); |
| if (part == FlashPartData) begin |
| return FlashPartData; |
| end else begin |
| case (mem_info_sel) |
| 1: return FlashPartInfo1; |
| 2: return FlashPartInfo2; |
| default: return FlashPartInfo; |
| endcase |
| end |
| endfunction // get_part |
| |
| endclass |