| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| // Perform accesses in order to provoke memory permission errors. Test the Software |
| // interface (Erase, Program, Read). |
| |
| // flash_ctrl_error_mp Test |
| |
| // Pseudo Code |
| // Initialize Random Flash MP Regions, Enable All Default Regions |
| // Loop (x) |
| // Randomize a Flash Program Operation (any Partition) |
| // Predict if the selected operation will cause an MP violation, or not |
| // Set scoreboard to accept alert, if predicted |
| // Perform selected Flash Operation |
| // If no violation is expected, check data integrity (via backdoor) |
| // Check Status Registers |
| // End |
| |
| class flash_ctrl_error_mp_vseq extends flash_ctrl_base_vseq; |
| `uvm_object_utils(flash_ctrl_error_mp_vseq) |
| |
| `uvm_object_new |
| |
| // Class Members |
| bit poll_fifo_status = 1; |
| uint num_iter; |
| rand flash_op_t flash_op; |
| rand data_q_t flash_op_data; |
| rand uint bank; |
| uint prog_err_cnt = 0; |
| uint read_err_cnt = 0; |
| uint erase_err_cnt = 0; |
| |
| // Copies of the MP Region Settings (Data and Info Partitions) |
| flash_mp_region_cfg_t mp_data_regions[flash_ctrl_pkg::MpRegions]; |
| flash_bank_mp_info_page_cfg_t |
| mp_info_regions[flash_ctrl_pkg::NumBanks][flash_ctrl_pkg::InfoTypes][$]; |
| |
| // Constraint for Bank. |
| constraint bank_c {bank inside {[0 : flash_ctrl_pkg::NumBanks - 1]};} |
| |
| // Constraint for controller address to be in the relevant range for |
| // the selected partition. |
| constraint addr_c { |
| solve bank before flash_op; |
| flash_op.addr inside {[BytesPerBank * bank : BytesPerBank * (bank + 1)]}; |
| if (flash_op.partition != FlashPartData) { |
| flash_op.addr inside |
| {[0:InfoTypeBytes[flash_op.partition>>1]-1], |
| [BytesPerBank:BytesPerBank+InfoTypeBytes[flash_op.partition>>1]-1]}; |
| } |
| } |
| |
| // Constraint for the Flash Operation |
| constraint flash_op_c { |
| |
| // Bank Erase is only supported for Data & 1st Info Partitions |
| flash_op.partition != FlashPartData && flash_op.partition != FlashPartInfo -> |
| flash_op.erase_type == flash_ctrl_pkg::FlashErasePage; |
| |
| if (cfg.seq_cfg.op_readonly_on_info_partition) { |
| flash_op.partition == FlashPartInfo -> flash_op.op == flash_ctrl_pkg::FlashOpRead; |
| } |
| |
| if (cfg.seq_cfg.op_readonly_on_info1_partition) { |
| flash_op.partition == FlashPartInfo1 -> flash_op.op == flash_ctrl_pkg::FlashOpRead; |
| } |
| |
| if (cfg.seq_cfg.op_readonly_on_info2_partition) { |
| if (flash_op.partition == FlashPartInfo2) {flash_op.op == flash_ctrl_pkg::FlashOpRead;} |
| } |
| |
| flash_op.op inside {flash_ctrl_pkg::FlashOpRead, flash_ctrl_pkg::FlashOpProgram, |
| flash_ctrl_pkg::FlashOpErase}; |
| |
| flash_op.erase_type dist { |
| flash_ctrl_pkg::FlashErasePage :/ (100 - cfg.seq_cfg.op_erase_type_bank_pc), |
| flash_ctrl_pkg::FlashEraseBank :/ cfg.seq_cfg.op_erase_type_bank_pc |
| }; |
| |
| flash_op.num_words inside {[10 : FlashNumBusWords - flash_op.addr[TL_AW-1:TL_SZW]]}; |
| flash_op.num_words <= cfg.seq_cfg.op_max_words; |
| flash_op.num_words < FlashPgmRes - flash_op.addr[TL_SZW+:FlashPgmResWidth]; |
| } |
| |
| // Flash ctrl operation data queue - used for programing or reading the flash. |
| constraint flash_op_data_c { |
| solve flash_op before flash_op_data; |
| if (flash_op.op inside {flash_ctrl_pkg::FlashOpRead, flash_ctrl_pkg::FlashOpProgram}) { |
| flash_op_data.size() == flash_op.num_words; |
| } else { |
| flash_op_data.size() == 0; |
| } |
| } |
| |
| rand flash_mp_region_cfg_t mp_regions[flash_ctrl_pkg::MpRegions]; |
| |
| constraint mp_regions_c { |
| |
| foreach (mp_regions[i]) { |
| |
| mp_regions[i].en dist { |
| MuBi4False := 1, |
| MuBi4True := 4 |
| }; |
| mp_regions[i].program_en dist { |
| MuBi4False := 4, |
| MuBi4True := 1 |
| }; |
| mp_regions[i].erase_en dist { |
| MuBi4False := 4, |
| MuBi4True := 1 |
| }; |
| mp_regions[i].read_en dist { |
| MuBi4False := 4, |
| MuBi4True := 1 |
| }; |
| mp_regions[i].scramble_en == MuBi4False; |
| mp_regions[i].ecc_en == MuBi4False; |
| mp_regions[i].he_en dist { |
| MuBi4False :/ (100 - cfg.seq_cfg.mp_region_he_en_pc), |
| MuBi4True :/ cfg.seq_cfg.mp_region_he_en_pc |
| }; |
| |
| mp_regions[i].start_page inside {[0 : FlashNumPages - 1]}; |
| mp_regions[i].num_pages inside {[1 : FlashNumPages - mp_regions[i].start_page]}; |
| mp_regions[i].num_pages <= cfg.seq_cfg.mp_region_max_pages; |
| |
| // If overlap is not allowed, then each configured region is uniquified. |
| // This creates an ascending order of mp_regions that are configured, so we shuffle it in |
| // post_randomize. |
| if (!cfg.seq_cfg.allow_mp_region_overlap) { |
| foreach (mp_regions[j]) { |
| if (i != j) { |
| !(mp_regions[i].start_page inside { |
| [mp_regions[j].start_page:mp_regions[j].start_page + mp_regions[j].num_pages] |
| }); |
| } |
| } |
| } |
| } |
| } |
| |
| // Information partitions memory protection settings. |
| rand flash_bank_mp_info_page_cfg_t |
| mp_info_pages[flash_ctrl_pkg::NumBanks][flash_ctrl_pkg::InfoTypes][$]; |
| |
| constraint mp_info_pages_c { |
| |
| foreach (mp_info_pages[i, j]) { |
| mp_info_pages[i][j].size() == flash_ctrl_pkg::InfoTypeSize[j]; |
| foreach (mp_info_pages[i][j][k]) { |
| mp_info_pages[i][j][k].scramble_en == MuBi4False; |
| mp_info_pages[i][j][k].ecc_en == MuBi4False; |
| mp_info_pages[i][j][k].he_en dist { |
| MuBi4False :/ (100 - cfg.seq_cfg.mp_info_page_he_en_pc[i][j]), |
| MuBi4True :/ cfg.seq_cfg.mp_info_page_he_en_pc[i][j] |
| }; |
| } |
| } |
| } |
| |
| mubi4_t default_region_read_en; |
| mubi4_t default_region_program_en; |
| mubi4_t default_region_erase_en; |
| mubi4_t default_region_ecc_en; |
| mubi4_t default_region_scramble_en; |
| rand mubi4_t default_region_he_en; |
| |
| // Bank Erasability. |
| rand bit [flash_ctrl_pkg::NumBanks-1:0] bank_erase_en; |
| |
| constraint bank_erase_en_c { |
| foreach (bank_erase_en[i]) { |
| bank_erase_en[i] == 1; |
| } |
| } |
| |
| // High Endurance |
| constraint default_region_he_en_c { |
| default_region_he_en dist { |
| MuBi4True :/ cfg.seq_cfg.default_region_he_en_pc, |
| MuBi4False :/ (100 - cfg.seq_cfg.default_region_he_en_pc) |
| }; |
| } |
| |
| // Configure sequence knobs to tailor it to seq. |
| virtual function void configure_vseq(); |
| |
| // Do no more than 16 words per op (by default) |
| cfg.seq_cfg.op_max_words = 16; |
| |
| // Configure High Endurance |
| cfg.seq_cfg.mp_region_he_en_pc = 50; |
| cfg.seq_cfg.default_region_he_en_pc = 50; |
| |
| // Configure MP Region Enable Prob |
| cfg.seq_cfg.mp_region_en_pc = 70; |
| |
| // Disable Read Only on the Info Partitions |
| cfg.seq_cfg.op_readonly_on_info_partition = 0; |
| cfg.seq_cfg.op_readonly_on_info1_partition = 0; |
| cfg.seq_cfg.op_readonly_on_info2_partition = 0; |
| |
| // Scoreboard knob for blocking host reads |
| cfg.block_host_rd = 1; |
| |
| endfunction : configure_vseq |
| |
| // Body |
| virtual task body(); |
| |
| // Local Variables |
| data_q_t exp_data; |
| bit exp_alert; |
| |
| `uvm_info(`gfn, "TEST : error_mp", UVM_LOW) |
| |
| // Enable/Disable Flash Regions |
| init_flash_regions(); |
| |
| // Iteration Loop |
| num_iter = 200; |
| for (int iter = 0; iter < num_iter; iter++) begin |
| `uvm_info(`gfn, $sformatf("Iteration : %0d : %0d", iter+1, num_iter), UVM_LOW) |
| |
| // Randomize the Members of the Class |
| `DV_CHECK_RANDOMIZE_FATAL(this) |
| |
| // Model Expected Response |
| exp_alert = predict_expected_mp_err_rsp(flash_op); |
| |
| // Control HW Access to Info Partitions if Selected |
| control_hw_access(flash_op); |
| |
| // Initialise Flash Content |
| cfg.flash_mem_bkdr_init(flash_op.partition, FlashMemInitInvalidate); |
| if (flash_op.op == flash_ctrl_pkg::FlashOpProgram) begin |
| cfg.flash_mem_bkdr_write(.flash_op(flash_op), .scheme(FlashMemInitSet)); |
| end else begin |
| cfg.flash_mem_bkdr_write(.flash_op(flash_op), .scheme(FlashMemInitRandomize)); |
| end |
| |
| // Model Expected Response (Error Expected / Pass) |
| if (exp_alert) set_otf_exp_alert("recov_err"); |
| |
| // Do FLASH Operation |
| case (flash_op.op) |
| |
| // ERASE |
| flash_ctrl_pkg::FlashOpErase : begin |
| `uvm_info(`gfn, $sformatf("Flash : ERASE exp_alert:%0d", exp_alert), UVM_LOW) |
| flash_ctrl_start_op(flash_op); |
| wait_flash_op_done(.clear_op_status(0), .timeout_ns(cfg.seq_cfg.erase_timeout_ns)); |
| if (exp_alert == MP_PASS) |
| cfg.flash_mem_bkdr_erase_check(flash_op); |
| else |
| erase_err_cnt++; |
| end |
| |
| // PROGRAM |
| flash_ctrl_pkg::FlashOpProgram : begin |
| `uvm_info(`gfn, $sformatf("Flash : PROGRAM exp_alert:%0d", exp_alert), UVM_LOW) |
| 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(.clear_op_status(0), .timeout_ns(cfg.seq_cfg.prog_timeout_ns)); |
| if (exp_alert == MP_PASS) |
| cfg.flash_mem_bkdr_read_check(flash_op, flash_op_data); |
| else |
| prog_err_cnt++; |
| end |
| |
| // READ |
| flash_ctrl_pkg::FlashOpRead : begin |
| `uvm_info(`gfn, $sformatf("Flash : READ exp_alert:%0d", exp_alert), UVM_LOW) |
| flash_op_data.delete(); |
| flash_ctrl_start_op(flash_op); |
| flash_ctrl_read(flash_op.num_words, flash_op_data, poll_fifo_status); |
| wait_flash_op_done(.clear_op_status(0), .timeout_ns(cfg.seq_cfg.read_timeout_ns)); |
| if (exp_alert == MP_PASS) |
| cfg.flash_mem_bkdr_read_check(flash_op, flash_op_data); |
| else |
| read_err_cnt++; |
| end |
| |
| default : `uvm_fatal(`gfn, "Unrecognized Flash Operation, FAIL") |
| |
| endcase |
| |
| // Predict Status (for RAL) |
| ral.err_code.mp_err.predict(exp_alert); |
| |
| // Check Alert Status |
| check_exp_alert_status(exp_alert, "mp_err", flash_op, flash_op_data); |
| end |
| |
| // Final Statistics for Information |
| display_test_stats(); |
| endtask : body |
| |
| // Task to initialize the Flash Access (Enable All Regions) |
| virtual task init_flash_regions(); |
| |
| // Default Region Settings |
| default_region_read_en = MuBi4True; |
| default_region_program_en = MuBi4True; |
| default_region_erase_en = MuBi4True; |
| default_region_scramble_en = MuBi4False; |
| default_region_ecc_en = MuBi4False; |
| |
| // Configure Bank Erasability |
| flash_ctrl_bank_erase_cfg(.bank_erase_en(bank_erase_en)); |
| |
| // Copy mp_regions settings, so we only apply one configuration per test. |
| mp_data_regions = mp_regions; |
| |
| // Initialize MP Region Settings |
| foreach (mp_data_regions[i]) begin |
| flash_ctrl_mp_region_cfg(i, mp_data_regions[i]); |
| `uvm_info(`gfn, $sformatf("MP DATA Region [%0d] : %p", i, mp_data_regions[i]), UVM_MEDIUM) |
| end |
| |
| // Initialize Default Regions (other than set above) |
| flash_ctrl_default_region_cfg( |
| .read_en(default_region_read_en), .program_en(default_region_program_en), |
| .erase_en(default_region_erase_en), .scramble_en(default_region_scramble_en), |
| .ecc_en(default_region_ecc_en), .he_en(default_region_he_en)); |
| |
| // Initialize Info MP Regions |
| foreach (mp_info_pages[i, j, k]) begin |
| flash_ctrl_mp_info_page_cfg(i, j, k, mp_info_pages[i][j][k]); |
| `uvm_info(`gfn, $sformatf("MP INFO Region [%0d, %0d, %0d] : %p", i, j, k, |
| mp_info_pages[i][j][k]), UVM_MEDIUM) |
| end |
| |
| // Copy mp_info_pages settings, so we only apply one configuration per test. |
| mp_info_regions = mp_info_pages; |
| |
| endtask : init_flash_regions |
| |
| // Predict the expected MP Error Response (Model) |
| virtual function bit predict_expected_mp_err_rsp(input flash_op_t flash_op); |
| |
| // Local Variables |
| bit rsp; |
| |
| /// Flash Operation |
| `uvm_info(`gfn, $sformatf("Flash Operation : flash_op : %p", flash_op), UVM_MEDIUM) |
| |
| unique case (flash_op.partition) |
| FlashPartData : rsp = do_data_part(flash_op); |
| FlashPartInfo, FlashPartInfo1, FlashPartInfo2 : rsp = do_info_part(flash_op); |
| default : `uvm_fatal(`gfn, "Unrecognised Flash Operation, FAIL") |
| endcase |
| |
| // Display Expected Response |
| if (rsp == MP_PASS) |
| `uvm_info(`gfn, "Expect : MP_PASS", UVM_INFO) |
| else |
| `uvm_info(`gfn, "Expect : MP_VIOLATION", UVM_INFO) |
| |
| return (rsp); // Return Status |
| |
| endfunction : predict_expected_mp_err_rsp |
| |
| virtual function void control_hw_access(flash_op_t flash_op); |
| lc_tx_t hw_access; |
| hw_access = (flash_op.partition inside {FlashPartInfo, FlashPartInfo1, FlashPartInfo2}) ? |
| lc_ctrl_pkg::On : lc_ctrl_pkg::Off; |
| cfg.flash_ctrl_vif.lc_creator_seed_sw_rw_en = hw_access; |
| cfg.flash_ctrl_vif.lc_owner_seed_sw_rw_en = hw_access; |
| cfg.flash_ctrl_vif.lc_iso_part_sw_rd_en = hw_access; |
| cfg.flash_ctrl_vif.lc_iso_part_sw_wr_en = hw_access; |
| endfunction : control_hw_access |
| |
| // Model Flash Data Partition Behaviour |
| virtual function do_data_part(flash_op_t flash_op); |
| |
| // Local Variables |
| string op_msg; |
| uint region_start; |
| uint region_end; |
| bit [MpRegions-1:0] rsp_vec = (MpRegions)'(MP_PASS); |
| |
| display_mp_region_info(); |
| |
| // Assign op_msg to Op Type (used below) |
| unique case (flash_op.op) |
| flash_ctrl_pkg::FlashOpErase : op_msg = "Erase"; |
| flash_ctrl_pkg::FlashOpProgram : op_msg = "Program"; |
| flash_ctrl_pkg::FlashOpRead : op_msg = "Read"; |
| default : `uvm_fatal(`gfn, "Unrecognised Flash Operation, FAIL") |
| endcase |
| |
| // Look for MP Area Violations |
| foreach (mp_data_regions[i]) begin |
| // Start and End Regions for the Flash Operation |
| region_start = mp_data_regions[i].start_page*(FullPageNumWords*4); |
| region_end = |
| (mp_data_regions[i].start_page+mp_data_regions[i].num_pages)*(FullPageNumWords*4); |
| if (flash_op.addr inside {[region_start : region_end - 1]}) begin |
| `uvm_info(`gfn, $sformatf("%s : MP Region : Address : HIT : MPR%0d", op_msg, i), |
| UVM_MEDIUM) |
| if (mp_data_regions[i].en == MuBi4True) begin |
| unique case (flash_op.op) |
| flash_ctrl_pkg::FlashOpErase : begin |
| // Bank Erase Defeats the MP Settings |
| if (flash_op.erase_type == flash_ctrl_pkg::FlashEraseBank) |
| rsp_vec[i] = MP_PASS; |
| else |
| rsp_vec[i] = (mp_data_regions[i].erase_en == MuBi4False); |
| end |
| flash_ctrl_pkg::FlashOpProgram : rsp_vec[i] = |
| (mp_data_regions[i].program_en == MuBi4False); |
| flash_ctrl_pkg::FlashOpRead : rsp_vec[i] = |
| (mp_data_regions[i].read_en == MuBi4False); |
| default : `uvm_fatal(`gfn, "Unrecognised Flash Operation, FAIL") |
| endcase |
| end else |
| rsp_vec[i] = MP_PASS; |
| end else begin |
| `uvm_info(`gfn, $sformatf("%s : MP Region : Address : MISS : MPR%0d", op_msg, i), |
| UVM_MEDIUM) |
| rsp_vec[i] = MP_PASS; |
| end |
| end |
| |
| return (|(rsp_vec)); |
| |
| endfunction : do_data_part |
| |
| // Model Flash Info Partition Behaviour |
| virtual function do_info_part(flash_op_t flash_op); |
| |
| // Local Variables |
| uint info_bank; |
| uint info_page; |
| uint info_part; |
| bit rsp; |
| |
| unique case (flash_op.partition) |
| FlashPartInfo : info_part = 0; |
| FlashPartInfo1 : info_part = 1; |
| FlashPartInfo2 : info_part = 2; |
| default : `uvm_fatal(`gfn, "Unrecognised Info Partition, FAIL") |
| endcase |
| |
| // Info Partition Bank and Page |
| info_bank = flash_op.addr[19]; |
| info_page = flash_op.addr[18:11]; |
| `uvm_info(`gfn, $sformatf("Info Partition Selected : Info%0d", info_part), UVM_MEDIUM) |
| `uvm_info(`gfn, $sformatf("Bank : %0d, Page : %0d", flash_op.addr[19], |
| flash_op.addr[18:11]), UVM_MEDIUM) |
| `uvm_info(`gfn, $sformatf("MP INFO Regions [%0d, %0d, %0d] : %p", info_bank, info_part, |
| info_page, mp_info_regions[info_bank][info_part][info_page]), UVM_MEDIUM) |
| |
| // Look for MP Area Violations |
| if (mp_info_regions[info_bank][info_part][info_page].en == MuBi4True) begin |
| unique case (flash_op.op) |
| flash_ctrl_pkg::FlashOpErase : begin |
| // Bank Erase Defeats the MP Settings, Only valid for Info Partition (not Info1 or info2) |
| if ((info_part == 0) && (flash_op.erase_type == flash_ctrl_pkg::FlashEraseBank)) |
| rsp = MP_PASS; |
| else |
| rsp = (mp_info_regions[info_bank][info_part][info_page].erase_en == MuBi4False); |
| end |
| flash_ctrl_pkg::FlashOpProgram : |
| rsp = (mp_info_regions[info_bank][info_part][info_page].program_en == MuBi4False); |
| flash_ctrl_pkg::FlashOpRead : |
| rsp = (mp_info_regions[info_bank][info_part][info_page].read_en == MuBi4False); |
| default : `uvm_fatal(`gfn, "Unrecognised Flash Operation, FAIL") |
| endcase |
| end |
| else |
| begin |
| // Bank Erase Defeats the MP Settings, Only valid for Info Partition (not Info1 or info2) |
| if ((info_part == 0) && (flash_op.op == flash_ctrl_pkg::FlashOpErase) && |
| (flash_op.erase_type == flash_ctrl_pkg::FlashEraseBank)) |
| rsp = MP_PASS; |
| else |
| rsp = MP_VIOLATION; |
| end |
| |
| return (rsp); |
| |
| endfunction : do_info_part |
| |
| virtual function void display_mp_region_info(); |
| string en_msg; |
| `uvm_info(`gfn, "MP REGION INFORMATION (DATA PARTITIONS)", UVM_MEDIUM) |
| foreach (mp_data_regions[i]) begin |
| en_msg = (mp_data_regions[i].en == MuBi4True) ? "Enabled": "Disabled"; |
| `uvm_info(`gfn, |
| $sformatf("MPR%0d : From : 0x%03x, To : 0x%03x : From : 0x%08x, To : 0x%08x, %s", i, |
| mp_data_regions[i].start_page, mp_data_regions[i].start_page+mp_data_regions[i].num_pages, |
| mp_data_regions[i].start_page*(FullPageNumWords*4), |
| (mp_data_regions[i].start_page+mp_data_regions[i].num_pages)*(FullPageNumWords*4), |
| en_msg), UVM_MEDIUM) |
| end |
| endfunction : display_mp_region_info |
| |
| // Display Test Statistics, Error/Pass Counts |
| virtual task display_test_stats(); |
| uint pass_cnt = num_iter - (prog_err_cnt + read_err_cnt + erase_err_cnt); |
| `uvm_info(`gfn, "Test Statistics", UVM_LOW) |
| `uvm_info(`gfn, $sformatf("Program Error : %0d", prog_err_cnt), UVM_LOW) |
| `uvm_info(`gfn, $sformatf("Read Errors : %0d", read_err_cnt), UVM_LOW) |
| `uvm_info(`gfn, $sformatf("Erase Errors : %0d", erase_err_cnt), UVM_LOW) |
| `uvm_info(`gfn, $sformatf("Passes : %0d", pass_cnt), UVM_LOW) |
| endtask : display_test_stats |
| |
| endclass : flash_ctrl_error_mp_vseq |