blob: 6bf5a26c77a517f1b7e8e8fb68a691aff769165e [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class flash_ctrl_otf_scoreboard extends uvm_scoreboard;
`uvm_component_utils(flash_ctrl_otf_scoreboard)
`uvm_component_new
// OTF data path fifos
// Assuming egress (host -> flash) ordering is maintained per bank.
uvm_tlm_analysis_fifo #(flash_otf_item) eg_exp_ctrl_fifo[NumBanks];
uvm_tlm_analysis_fifo #(flash_otf_item) eg_exp_host_fifo[NumBanks];
uvm_tlm_analysis_fifo #(flash_otf_item) eg_rtl_ctrl_fifo[NumBanks];
uvm_tlm_analysis_fifo #(flash_otf_item) eg_rtl_host_fifo[NumBanks];
uvm_tlm_analysis_fifo #(flash_phy_prim_item) eg_rtl_fifo[NumBanks];
uvm_tlm_analysis_fifo #(flash_phy_prim_item) rd_cmd_fifo[NumBanks];
// Check last mile write /erase transactions
uvm_tlm_analysis_fifo #(flash_phy_prim_item) eg_exp_lm_fifo[NumBanks];
// tb memory model
// This is used for the last mile write data integrity check
bit[flash_phy_pkg::FullDataWidth-1:0] data_mem[NumBanks][bit[BankAddrW-1:0]];
bit[flash_phy_pkg::FullDataWidth-1:0] info_mem[NumBanks][InfoTypes][bit[BankAddrW-1:0]];
// register double written entries
bit corrupt_entry[rd_cache_t];
flash_ctrl_env_cfg cfg;
int eg_exp_cnt = 0;
bit comp_off = 0;
bit derr_expected = 0;
// monitor_tb_mem off
bit mem_mon_off = 0;
// Stop egress forever process
bit stop = 0;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
foreach (eg_exp_ctrl_fifo[i]) begin
eg_exp_ctrl_fifo[i] = new($sformatf("eg_exp_ctrl_fifo[%0d]", i), this);
eg_exp_host_fifo[i] = new($sformatf("eg_exp_host_fifo[%0d]", i), this);
eg_rtl_ctrl_fifo[i] = new($sformatf("eg_rtl_ctrl_fifo[%0d]", i), this);
eg_rtl_host_fifo[i] = new($sformatf("eg_rtl_host_fifo[%0d]", i), this);
eg_rtl_fifo[i] = new($sformatf("eg_rtl_fifo[%0d]", i), this);
rd_cmd_fifo[i] = new($sformatf("rd_cmd_fifo[%0d]", i), this);
eg_exp_lm_fifo[i] = new($sformatf("eg_exp_lm_fifo[%0d]", i), this);
end
endfunction
task clear_fifos();
flash_otf_item dummy1;
flash_phy_prim_item dummy2;
foreach (eg_exp_ctrl_fifo[i]) begin
while (eg_exp_ctrl_fifo[i].used() > 0) eg_exp_ctrl_fifo[i].get(dummy1);
while (eg_exp_host_fifo[i].used() > 0) eg_exp_host_fifo[i].get(dummy1);
while (eg_rtl_ctrl_fifo[i].used() > 0) eg_rtl_ctrl_fifo[i].get(dummy1);
while (eg_rtl_host_fifo[i].used() > 0) eg_rtl_host_fifo[i].get(dummy1);
while (eg_rtl_fifo[i].used() > 0) eg_rtl_fifo[i].get(dummy2);
while (rd_cmd_fifo[i].used() > 0) rd_cmd_fifo[i].get(dummy2);
while (eg_exp_lm_fifo[i].used() > 0) eg_exp_lm_fifo[i].get(dummy2);
end
endtask
virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
uvm_config_db#(flash_ctrl_env_cfg)::get(this, "", "cfg", cfg);
cfg.otf_scb_h = this;
endfunction // connect_phase
task run_phase(uvm_phase phase);
flash_otf_item exp_ctrl_item[NumBanks];
flash_otf_item exp_host_item[NumBanks];
flash_phy_prim_item phy_item[NumBanks];
flash_phy_prim_item rcmd[NumBanks];
for (int i = 0; i < NumBanks; i++) begin
int j = i;
fork begin
forever begin
eg_exp_ctrl_fifo[j].get(exp_ctrl_item[j]);
process_eg(exp_ctrl_item[j], j);
end
end join_none
fork begin
forever begin
eg_exp_host_fifo[j].get(exp_host_item[j]);
process_eg_host(exp_host_item[j], j);
end
end join_none
fork begin
forever begin
eg_rtl_fifo[j].get(phy_item[j]);
process_phy_item(phy_item[j], j);
end
end join_none
fork begin
forever begin
rd_cmd_fifo[j].get(rcmd[j]);
process_rcmd(rcmd[j], j);
end
end join_none
fork begin
monitor_tb_mem(j);
end join_none
end
endtask // run_phase
task process_eg_host(flash_otf_item exp, int bank);
flash_otf_item obs;
data_4s_t rcvd_data;
fdata_q_t fq;
addr_t err_addr;
string str = $sformatf("host_read_comp_bank%0d", bank);
`uvm_info("EXPGET_HOST", $sformatf(" addr %x data:%x cnt:%0d rtlff:%0d ctrlff:%0d",
exp.start_addr, exp.dq[0], eg_exp_cnt++,
eg_rtl_host_fifo[bank].used(),
eg_rtl_ctrl_fifo[bank].used()),
UVM_MEDIUM)
// bankdoor read from memory model
`uvm_create_obj(flash_otf_item, obs)
// Host can only access data partitions.
obs.cmd.partition = FlashPartData;
obs.cmd.op = FlashOpRead;
obs.cmd.addr = exp.start_addr; // tl_addr
// for debug print
obs.start_addr = exp.start_addr;
obs.cmd.num_words = 1;
obs.mem_addr = exp.start_addr >> 3;
obs.print("RAW");
cfg.flash_mem_otf_read(obs.cmd, obs.fq);
obs.print("rtl_host: before");
obs.region = exp.region;
if (cfg.ecc_mode > FlashSerrTestMode) obs.skip_err_chk = 1;
obs.skip_err_chk |= exp.derr;
// descramble needs 2 buswords
obs.cmd.num_words = 2;
obs.descramble(exp.addr_key, exp.data_key);
obs.print("rtl_host: after");
`uvm_info("process_eg_host", $sformatf(" rcvd:%0d",cfg.otf_host_rd_sent), UVM_MEDIUM)
if (cfg.ecc_mode > FlashSerrTestMode && obs.derr == 1) begin
err_addr = {obs.cmd.addr[31:3],3'h0};
// check expected derr
if (cfg.derr_addr_tbl[err_addr].exists(FlashPartData)) begin
`uvm_info("process_eg_host",
$sformatf("expected double bit error 0x%x", err_addr), UVM_MEDIUM)
end else if (cfg.ierr_addr_tbl[err_addr].exists(FlashPartData)) begin
`uvm_info("process_eg_host",
$sformatf("expected icv error 0x%x", err_addr), UVM_MEDIUM)
end else begin
`uvm_error("process_eg_host",
$sformatf("unexpected double bit error 0x%x", err_addr))
end
end else begin
if (exp.exp_err) begin
`uvm_info("process_eg_host",
$sformatf("expected other tlul error start:%x",
exp.start_addr),
UVM_MEDIUM)
end else if (exp.derr & obs.derr) begin
`uvm_info("process_eg_host",
$sformatf("expected double bit error by redundant write start:%x",
exp.start_addr),
UVM_MEDIUM)
end else begin
if (!exp.derr) begin
if (exp.start_addr[2]) begin
rcvd_data = obs.dq[1];
end else begin
rcvd_data = obs.dq[0];
end
if (rcvd_data == exp.dq[0]) begin
`dv_info("data match!!", UVM_MEDIUM, str)
end else begin
`dv_error($sformatf(" : obs:exp %8x:%8x mismatch!!",
rcvd_data, exp.dq[0]), str)
end
end else begin // if (!exp.derr)
`uvm_error("process_eg_host",
$sformatf("expected double bit error does not occur start:%x",
exp.start_addr))
end
end
end
cfg.otf_host_rd_sent++;
endtask // process_eg_host
task process_eg(flash_otf_item item, int bank);
`uvm_info("EG_EXPGET",
$sformatf("op:%s fq:%0d cnt:%0d rtlff:%0d", item.cmd.op.name(),
item.fq.size(), eg_exp_cnt++, eg_rtl_fifo[bank].used()),
UVM_MEDIUM)
fork
begin : isolation_fork
fork
begin
case (item.cmd.op)
FlashOpProgram:begin
process_write(item, bank);
end
FlashOpRead:begin
process_read(item, bank);
end
default:begin
// Do nothing for the other commands
end
endcase // case (item.cmd.op)
end // fork begin
begin
wait (stop);
end
join_any
#0;
disable fork;
end // block: isolation_fork
join
endtask
// Scoreboard process read in following order.
// - Received expected transactions (exp).
// - Pop the same number (col_sz) of transaction from rtl received q.
// - Transform rtl transactions to have the same data format as exp.
// - Compare read data.
task process_read(flash_otf_item exp, int bank);
flash_otf_item send;
addr_t err_addr, cp_addr;
int page;
int col_sz = exp.fq.size;
`uvm_info("process_read", $sformatf("bank:%0d colsz:%0d ffsz:%0d",
bank, col_sz, eg_rtl_fifo[bank].used()), UVM_MEDIUM)
exp.print("obs_read");
`uvm_create_obj(flash_otf_item, send)
send.cmd = exp.cmd;
send.cmd.addr[OTFBankId] = bank;
// print purpose
send.start_addr = exp.start_addr;
cfg.flash_mem_otf_read(send.cmd, send.fq);
send.print("exp_read: enc_data");
if (cfg.ecc_mode > FlashSerrTestMode) send.skip_err_chk = 1;
send.skip_err_chk |= exp.derr;
if (exp.cmd.addr[2]) begin
send.head_pad = 1;
send.cmd.num_words++;
end
if (send.cmd.num_words % 2) begin
send.cmd.num_words++;
send.tail_pad = 1;
end
// Read descramble has to be done Qword by Qword because
// Each Qword can be in different region.
send.mem_addr = exp.start_addr >> 3;
send.ctrl_rd_region_q = exp.ctrl_rd_region_q;
send.descramble(exp.addr_key, exp.data_key);
send.print("exp_read: raw_data");
`dv_info($sformatf("RDATA size: %d x 8B bank:%0d sent_cnt:%0d",
send.raw_fq.size(), bank, cfg.otf_ctrl_rd_sent++),
UVM_MEDIUM, "process_read")
if (cfg.ecc_mode > FlashSerrTestMode && send.derr == 1) begin
foreach(send.eaddr_q[i]) begin
err_addr = send.eaddr_q[i];
err_addr[OTFBankId] = bank;
// check expected derr
if (cfg.derr_addr_tbl[err_addr].exists(exp.cmd.partition)) begin
`uvm_info("process_read",
$sformatf("expected double bit error 0x%x", err_addr), UVM_MEDIUM)
end else if (cfg.ierr_addr_tbl[err_addr].exists(exp.cmd.partition)) begin
`uvm_info("process_read",
$sformatf("expected icv error 0x%x", err_addr), UVM_MEDIUM)
end else if (derr_expected == 0) begin
`uvm_error("process_read",
$sformatf("unexpected double bit error 0x%x", err_addr))
end
end
end else begin // if (cfg.ecc_mode > FlashSerrTestMode && send.derr == 1)
// Skip data comp for no ecc erase workd read
foreach (send.ctrl_rd_region_q[i]) begin
if (send.ctrl_rd_region_q[i].ecc_en != MuBi4True &&
(exp.fq[i][63:32] == ALL_ONES || exp.fq[i][31:0] == ALL_ONES)) begin
send.raw_fq[i] = exp.fq[i];
end
end
if (exp.exp_err) begin
`uvm_info("process_read",
$sformatf("expected other tlul error start:%x",
exp.start_addr),
UVM_MEDIUM)
end else if (exp.derr & send.derr) begin
`uvm_info("process_read",
$sformatf("expected double bit error by redundant write start:%x",
exp.start_addr),
UVM_MEDIUM)
end else begin
if (!exp.derr) compare_data(send.raw_fq, exp.fq, bank, "rdata");
else `uvm_error("process_read",
$sformatf("expected double bit error does not occur start:%x",
exp.start_addr))
end
end
endtask
// Scoreboard process write in following order.
// - Received expected transactions (exp).
// - Pop the same number (col_sz) of transaction from rtl received q.
// - Transform rtl transactions to have the same data format as exp.
// - Compare read data.
task process_write(flash_otf_item exp, int bank);
flash_otf_item item;
flash_otf_item obs;
// Write transactions coalesce upto 8 transactions.
// So each pop becomes 8 times of fqs.
int col_sz = exp.fq.size / 8;
`uvm_info("process_write", $sformatf("process_write: addr:0x%x bank:%0d colsz:%0d ffsz:%0d",
exp.cmd.otf_addr, bank, col_sz, eg_rtl_ctrl_fifo[bank].used()), UVM_MEDIUM)
eg_rtl_ctrl_fifo[bank].get(item);
`uvm_create_obj(flash_otf_item, obs)
obs = item;
repeat(col_sz - 1) begin
eg_rtl_ctrl_fifo[bank].get(item);
obs.fq = {obs.fq, item.fq};
end
`dv_info($sformatf("WDATA size: %d x 8B bank:%0d rcvd_cnt:%0d",
obs.fq.size(), bank, cfg.otf_ctrl_wr_rcvd++), UVM_MEDIUM, "process_write")
compare_data(obs.fq, exp.fq, bank, $sformatf("wdata_page%0d", exp.page), exp.ecc_en);
endtask // process_eg
// Compare 64 bit for now
task compare_data(fdata_q_t obs, fdata_q_t exp, int bank, string rw, bit is_ecc = 0);
string str = $sformatf("%s_comp_bank%0d", rw, bank);
bit err = 0;
// Fatal alert from host interface can disturb core tlul
if (cfg.scb_h.alert_count["fatal_err"]) begin
`uvm_info(`gfn, "comparison skipped due to fatal error is detected", UVM_MEDIUM)
return;
end
if (comp_off) return;
foreach (obs[i]) begin
if(is_ecc) begin
if (obs[i] != exp[i]) begin
err = 1;
`dv_error($sformatf("%4d: obs:exp %2x_%1x_%8x_%8x:%2x_%1x_%8x_%8x mismatch!!", i,
obs[i][75:68], obs[i][67:64], obs[i][63:32], obs[i][31:0],
exp[i][75:68], exp[i][67:64], exp[i][63:32], exp[i][31:0]),
str)
end
end else begin
if (obs[i][63:0] != exp[i][63:0]) begin
err = 1;
`dv_error($sformatf("%4d: obs:exp %8x_%8x:%8x_%8x mismatch!!", i,
obs[i][63:32], obs[i][31:0], exp[i][63:32], exp[i][31:0]),
str)
end
end
end
if (err == 0) begin
`dv_info("data match!!", UVM_MEDIUM, str)
end
endtask // compare_data
// Transform phy_item to otf_item and
// classify to host or ctrl transaction
task process_phy_item(flash_phy_prim_item item, int bank);
flash_otf_item obs;
`uvm_create_obj(flash_otf_item, obs);
if (item.req.prog_req) begin
obs.get_from_phy(item, "w");
eg_rtl_ctrl_fifo[bank].write(obs);
end else begin // read request, guaranteed by monitor
obs.get_from_phy(item, "r");
end
endtask // process_phy_item
task process_rcmd(flash_phy_prim_item item, int bank);
addr_t serr_addr;
flash_dv_part_e part = get_part_name(item.req);
if (cfg.ecc_mode == FlashSerrTestMode) begin
serr_addr = item.req.addr << 3;
serr_addr[OTFBankId] = bank;
if (cfg.serr_addr_tbl[serr_addr].exists(part)) begin
cfg.inc_serr_cnt(bank);
cfg.serr_addr[bank] = serr_addr;
end
end
endtask
task monitor_tb_mem(int bank);
flash_phy_prim_item exp;
flash_otf_mem_entry rcv;
string name = $sformatf("mon_tb_mem%0d", bank);
forever begin
@(posedge cfg.flash_ctrl_mem_vif[bank].mem_wr);
if (mem_mon_off == 0) begin
`uvm_info("mem_if", "got posedge wr", UVM_MEDIUM)
`uvm_create_obj(flash_otf_mem_entry, rcv)
rcv.mem_addr = cfg.flash_ctrl_mem_vif[bank].mem_addr;
rcv.mem_wdata = cfg.flash_ctrl_mem_vif[bank].mem_wdata;
rcv.mem_part = cfg.flash_ctrl_mem_vif[bank].mem_part;
rcv.mem_info_sel = cfg.flash_ctrl_mem_vif[bank].mem_info_sel;
@(negedge cfg.flash_ctrl_mem_vif[bank].clk_i);
if (rcv.mem_part == FlashPartData) begin
`DV_CHECK_EQ(cfg.flash_ctrl_mem_vif[bank].data_mem_req, 1,,, name)
end else begin
case (rcv.mem_info_sel)
0: `DV_CHECK_EQ(cfg.flash_ctrl_mem_vif[bank].info0_mem_req, 1,,, name)
1: `DV_CHECK_EQ(cfg.flash_ctrl_mem_vif[bank].info1_mem_req, 1,,, name)
2: `DV_CHECK_EQ(cfg.flash_ctrl_mem_vif[bank].info2_mem_req, 1,,, name)
default: `uvm_error(name, $sformatf("bank%0d infosel%0d doesn't exists",
bank, rcv.mem_info_sel))
endcase
end
// collect ref data
eg_exp_lm_fifo[bank].get(exp);
lm_wdata_comp(exp, rcv, bank);
end
end
endtask // monitor_tb_mem
task lm_wdata_comp(flash_phy_prim_item exp, flash_otf_mem_entry rcv, int bank);
bit[flash_phy_pkg::FullDataWidth] rd_data;
rd_cache_t entry;
string name = $sformatf("lm_wdata_comp_bank%0d", bank);
if (rcv.mem_addr == exp.req.addr) begin
`dv_info($sformatf("addr match %x", rcv.mem_addr), UVM_MEDIUM, name)
end else begin
`DV_CHECK_EQ(rcv.mem_addr, exp.req.addr,,, name)
end
// check if this is page erase
if (exp.req.pg_erase_req | exp.req.bk_erase_req) begin
int cnt = 0;
int cnt_max = (exp.req.bk_erase_req)? 65536 : 256;
`uvm_info(name, $sformatf("erase detected pg_erase:%0d bk_erase:%0d",
exp.req.pg_erase_req, exp.req.bk_erase_req),
UVM_MEDIUM)
repeat (cnt_max) begin
rcv.mem_addr = cfg.flash_ctrl_mem_vif[bank].mem_addr;
rcv.mem_wdata = cfg.flash_ctrl_mem_vif[bank].mem_wdata;
rcv.mem_part = cfg.flash_ctrl_mem_vif[bank].mem_part;
rcv.mem_info_sel = cfg.flash_ctrl_mem_vif[bank].mem_info_sel;
entry.bank = bank;
entry.addr = (rcv.mem_addr<<3);
entry.part = cfg.get_part(rcv.mem_part, rcv.mem_info_sel);
corrupt_entry.delete(entry);
if (rcv.mem_addr == exp.req.addr) begin
`dv_info($sformatf("addr match %x", rcv.mem_addr), UVM_MEDIUM, name)
end else begin
`DV_CHECK_EQ(rcv.mem_addr, exp.req.addr,,, name)
end
`DV_CHECK_EQ(rcv.mem_wdata, {flash_phy_pkg::FullDataWidth{1'b1}},,, name)
if (rcv.mem_part == FlashPartData) begin
data_mem[bank].delete(rcv.mem_addr);
end else begin
info_mem[bank][rcv.mem_info_sel].delete(rcv.mem_addr);
end
exp.req.addr++;
@(negedge cfg.flash_ctrl_mem_vif[bank].clk_i);
end
end else begin
entry.bank = bank;
entry.addr = (rcv.mem_addr<<3);
entry.part = cfg.get_part(rcv.mem_part, rcv.mem_info_sel);
if (rcv.mem_part == FlashPartData) begin
if (data_mem[bank].exists(rcv.mem_addr)) begin
corrupt_entry[entry] = 1;
rd_data = data_mem[bank][rcv.mem_addr];
exp.req.prog_full_data &= rd_data;
data_mem[bank].delete(rcv.mem_addr);
end
data_mem[bank][rcv.mem_addr] = exp.req.prog_full_data;
end else begin
`uvm_info(`gfn, $sformatf("bank:%0d sel:%0d addr:%x",
bank, rcv.mem_info_sel, rcv.mem_addr), UVM_HIGH)
`uvm_info(`gfn, $sformatf("scb_rd_data:%x prog_data:%x",
info_mem[bank][rcv.mem_info_sel][rcv.mem_addr], exp.req.prog_full_data), UVM_HIGH)
if (info_mem[bank][rcv.mem_info_sel].exists(rcv.mem_addr)) begin
corrupt_entry[entry] = 1;
rd_data = info_mem[bank][rcv.mem_info_sel][rcv.mem_addr];
exp.req.prog_full_data &= rd_data;
info_mem[bank][rcv.mem_info_sel].delete(rcv.mem_addr);
end
info_mem[bank][rcv.mem_info_sel][rcv.mem_addr] = exp.req.prog_full_data;
end
if (rcv.mem_wdata == exp.req.prog_full_data) begin
`dv_info($sformatf("wdata match %x", rcv.mem_wdata), UVM_MEDIUM, name)
end else begin
`DV_CHECK_EQ(rcv.mem_wdata, exp.req.prog_full_data,,, name)
end
end
endtask // lm_wdata_cmp
endclass