| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| class i2c_scoreboard extends cip_base_scoreboard #( |
| .CFG_T(i2c_env_cfg), |
| .RAL_T(i2c_reg_block), |
| .COV_T(i2c_env_cov) |
| ); |
| `uvm_component_utils(i2c_scoreboard) |
| |
| virtual i2c_if i2c_vif; |
| |
| local i2c_item exp_rd_item; |
| local i2c_item exp_wr_item; |
| local i2c_item obs_wr_item; |
| local i2c_item obs_rd_item; |
| local i2c_item rd_pending_item; |
| local uint rd_wait; |
| local bit host_init = 1'b0; |
| local uint rdata_cnt = 0; |
| local uint tran_id = 0; |
| local uint num_exp_tran = 0; |
| |
| // queues hold expected read and write transactions |
| local i2c_item exp_wr_q[$]; |
| local i2c_item exp_rd_q[$]; |
| |
| // queues hold partial read transactions (address phase) |
| local i2c_item rd_pending_q[$]; |
| |
| // TLM fifos hold the transactions sent by monitor |
| uvm_tlm_analysis_fifo #(i2c_item) rd_item_fifo; |
| uvm_tlm_analysis_fifo #(i2c_item) wr_item_fifo; |
| |
| uvm_analysis_port #(i2c_item) target_mode_wr_obs_port; |
| |
| // Target mode transactions |
| uvm_tlm_analysis_fifo #(i2c_item) target_mode_wr_exp_fifo; |
| uvm_tlm_analysis_fifo #(i2c_item) target_mode_wr_obs_fifo; |
| uvm_tlm_analysis_fifo #(i2c_item) target_mode_rd_exp_fifo; |
| uvm_tlm_analysis_fifo #(i2c_item) target_mode_rd_obs_fifo; |
| |
| // interrupt bit vector |
| local bit [NumI2cIntr-1:0] intr_exp; |
| |
| int num_obs_rd; |
| int obs_wr_id = 0; |
| // used only for fifo_reset test |
| bit skip_acq_comp = 0; |
| // Target mode read data is created by fetch_txn. |
| // In random tx fifo flush event, this make difficult to chekc read path integrity. |
| // With setting this bit, expected read data collected right at the input of tx fifo |
| // and at the tx fifo reset event, expected read data also get flushed. |
| bit read_rnd_data = 0; |
| bit [7:0] mirrored_txdata[$]; |
| |
| // skip segment comparison |
| bit skip_target_txn_comp = 0; |
| bit skip_target_rd_comp = 0; |
| |
| `uvm_component_new |
| |
| function void build_phase(uvm_phase phase); |
| super.build_phase(phase); |
| rd_item_fifo = new("rd_item_fifo", this); |
| wr_item_fifo = new("wr_item_fifo", this); |
| rd_pending_item = new("rd_pending_item" ); |
| exp_rd_item = new("exp_rd_item"); |
| exp_wr_item = new("exp_wr_item"); |
| obs_wr_item = new("obs_wr_item"); |
| target_mode_wr_exp_fifo = new("target_mode_wr_exp_fifo", this); |
| target_mode_wr_obs_fifo = new("target_mode_wr_obs_fifo", this); |
| target_mode_rd_exp_fifo = new("target_mode_rd_exp_fifo", this); |
| target_mode_rd_obs_fifo = new("target_mode_rd_obs_fifo", this); |
| target_mode_wr_obs_port = new("target_mode_wr_obs_port", this); |
| endfunction : build_phase |
| |
| function void connect_phase(uvm_phase phase); |
| super.connect_phase(phase); |
| target_mode_wr_obs_port.connect(target_mode_wr_obs_fifo.analysis_export); |
| cfg.scb_h = this; |
| endfunction |
| |
| task run_phase(uvm_phase phase); |
| string str; |
| super.run_phase(phase); |
| if (cfg.m_i2c_agent_cfg.if_mode == Host) begin |
| fork |
| forever begin |
| fork begin |
| fork |
| begin |
| target_mode_wr_obs_fifo.get(obs_wr_item); |
| if (!skip_target_txn_comp) begin |
| obs_wr_item.tran_id = obs_wr_id++; |
| target_mode_wr_exp_fifo.get(exp_wr_item); |
| str = (exp_wr_item.start) ? "addr" : (exp_wr_item.stop) ? "stop" : "wr"; |
| `uvm_info(`gfn, $sformatf("exp_%s_txn %0d\n %s", str, |
| exp_wr_item.tran_id, exp_wr_item.sprint()), UVM_MEDIUM) |
| target_txn_comp(obs_wr_item, exp_wr_item, str); |
| end |
| end |
| begin |
| wait(skip_acq_comp); |
| cfg.clk_rst_vif.wait_clks(1); |
| end |
| join_any |
| disable fork; |
| end join |
| end |
| forever begin |
| target_mode_rd_obs_fifo.get(obs_rd_item); |
| if (!skip_target_rd_comp) begin |
| obs_rd_item.pname = "obs_rd"; |
| obs_rd_item.tran_id = num_obs_rd++; |
| if (read_rnd_data) begin |
| // With read_rnd_data mode, only read data can be compared. |
| // Other variables cannot be predictable. |
| `uvm_create_obj(i2c_item, exp_rd_item); |
| exp_rd_item.tran_id = obs_rd_item.tran_id; |
| exp_rd_item.num_data = obs_rd_item.num_data; |
| repeat (exp_rd_item.num_data) begin |
| exp_rd_item.data_q.push_back(mirrored_txdata.pop_front); |
| end |
| end else begin |
| target_mode_rd_exp_fifo.get(exp_rd_item); |
| end |
| exp_rd_item.pname = "exp_rd"; |
| `uvm_info(`gfn, $sformatf("\n%s", exp_rd_item.convert2string()), UVM_MEDIUM) |
| target_rd_comp(obs_rd_item, exp_rd_item); |
| end |
| end |
| join_none |
| end else begin |
| forever begin |
| `DV_SPINWAIT_EXIT( |
| fork |
| compare_trans(BusOpWrite); |
| compare_trans(BusOpRead); |
| join, |
| @(negedge cfg.clk_rst_vif.rst_n), |
| ) |
| end |
| end |
| endtask : run_phase |
| |
| virtual task process_tl_access(tl_seq_item item, tl_channels_e channel, string ral_name); |
| uvm_reg csr; |
| i2c_item sb_exp_wr_item; |
| i2c_item sb_exp_rd_item; |
| i2c_item temp_item; |
| bit fmt_overflow; |
| bit do_read_check = 1'b1; |
| bit write = item.is_write(); |
| |
| bit addr_phase_write = (write && channel == AddrChannel); |
| bit data_phase_read = (!write && channel == DataChannel); |
| |
| uvm_reg_addr_t csr_addr = cfg.ral_models[ral_name].get_word_aligned_addr(item.a_addr); |
| // if access was to a valid csr, get the csr handle |
| if (csr_addr inside {cfg.ral_models[ral_name].csr_addrs}) begin |
| csr = cfg.ral_models[ral_name].default_map.get_reg_by_offset(csr_addr); |
| `DV_CHECK_NE_FATAL(csr, null) |
| end else begin |
| `uvm_fatal(`gfn, $sformatf("\naccess unexpected addr 0x%0h", csr_addr)) |
| end |
| |
| sb_exp_wr_item = new(); |
| sb_exp_rd_item = new(); |
| if (addr_phase_write) begin |
| // incoming access is a write to a valid csr, then make updates right away |
| void'(csr.predict(.value(item.a_data), .kind(UVM_PREDICT_WRITE), .be(item.a_mask))); |
| |
| // process the csr req |
| // for write, update local variable and fifo at address phase |
| // for read, update predication at address phase and compare at data phase |
| case (csr.get_name()) |
| // add individual case item for each csr |
| "ctrl": begin |
| host_init = ral.ctrl.enablehost.get_mirrored_value(); |
| end |
| "target_id": begin |
| cfg.m_i2c_agent_cfg.target_addr0 = get_field_val(ral.target_id.address0, item.a_data); |
| cfg.m_i2c_agent_cfg.target_addr1 = get_field_val(ral.target_id.address1, item.a_data); |
| end |
| "fdata": begin |
| bit [7:0] fbyte; |
| bit start, stop, read, rcont, nakok; |
| |
| if (!cfg.under_reset && host_init) begin |
| fbyte = get_field_val(ral.fdata.fbyte, item.a_data); |
| start = bit'(get_field_val(ral.fdata.start, item.a_data)); |
| stop = bit'(get_field_val(ral.fdata.stop, item.a_data)); |
| read = bit'(get_field_val(ral.fdata.read, item.a_data)); |
| rcont = bit'(get_field_val(ral.fdata.rcont, item.a_data)); |
| nakok = bit'(get_field_val(ral.fdata.nakok, item.a_data)); |
| |
| // target address is begin programmed to begin a transaction |
| if (start) begin |
| tran_id++; |
| if (exp_wr_item.start && !sb_exp_wr_item.stop && |
| exp_wr_item.bus_op == BusOpWrite) begin |
| // write transaction ends with rstart |
| exp_wr_item.rstart = 1'b1; |
| `downcast(sb_exp_wr_item, exp_wr_item.clone()); |
| sb_exp_wr_item.stop = 1'b1; |
| exp_wr_item.clear_all(); |
| end |
| if (fbyte[0]) begin |
| // read transaction |
| exp_rd_item.tran_id = tran_id; |
| exp_rd_item.bus_op = BusOpRead; |
| exp_rd_item.addr = fbyte[7:1]; |
| exp_rd_item.start = start; |
| exp_rd_item.stop = stop; |
| end else begin |
| // write transaction |
| exp_wr_item.tran_id = tran_id; |
| exp_wr_item.bus_op = BusOpWrite; |
| exp_wr_item.addr = fbyte[7:1]; |
| exp_wr_item.start = start; |
| exp_wr_item.stop = stop; |
| end |
| end else begin // transaction begins with started/rstarted |
| // write transaction |
| if (exp_wr_item.start && exp_wr_item.bus_op == BusOpWrite) begin |
| // irq is asserted with 2 latency cycles (#3422) |
| cfg.clk_rst_vif.wait_clks(2); |
| // TODO: Gather all irq verification in SCB instead of distribute in vseq |
| if (cfg.intr_vif.pins[FmtOverflow]) begin |
| exp_wr_item.fmt_ovf_data_q.push_back(fbyte); |
| //wait(!cfg.intr_vif.pins[FmtOverflow]); |
| end else begin |
| // fmt_fifo is underflow then collect data, otherwise drop data |
| exp_wr_item.data_q.push_back(fbyte); |
| exp_wr_item.num_data++; |
| exp_wr_item.stop = stop; |
| if (exp_wr_item.stop) begin |
| // get a complete write |
| `downcast(sb_exp_wr_item, exp_wr_item.clone()); |
| exp_wr_item.clear_all(); |
| end |
| end |
| end |
| // read transaction |
| if (exp_rd_item.start && exp_rd_item.bus_op == BusOpRead) begin |
| if (read) begin |
| i2c_item tmp_rd_item; |
| uint num_rd_bytes = (fbyte == 8'd0) ? 256 : fbyte; |
| // get the number of byte to read |
| if (exp_rd_item.rcont && (rcont || stop)) begin |
| exp_rd_item.num_data += num_rd_bytes; // accumulate for chained reads |
| end else begin |
| exp_rd_item.num_data = num_rd_bytes; |
| end |
| exp_rd_item.stop = stop; |
| exp_rd_item.rcont = rcont; |
| exp_rd_item.read = read; |
| exp_rd_item.nakok = nakok; |
| exp_rd_item.nack = ~exp_rd_item.rcont; |
| exp_rd_item.rstart = (exp_rd_item.stop) ? 1'b0 : 1'b1; |
| // decrement since data is dropped by rx_overflow |
| if (cfg.seq_cfg.en_rx_overflow) exp_rd_item.num_data--; |
| |
| // always push the expected transaction into the queue and handle during |
| // rdata. |
| `downcast(tmp_rd_item, exp_rd_item.clone()); |
| rd_pending_q.push_back(tmp_rd_item); |
| `uvm_info(`gfn, $sformatf("\nrd_pending_q.push_back"), UVM_DEBUG) |
| if (exp_rd_item.stop) begin |
| `uvm_info(`gfn, $sformatf("\nscoreboard, partial exp_rd_item\n\%s", |
| exp_rd_item.sprint()), UVM_DEBUG) |
| exp_rd_item.start = 0; |
| exp_rd_item.clear_data(); |
| end |
| end |
| end |
| end |
| end |
| end |
| "fifo_ctrl": begin |
| // these fields are WO |
| bit fmtrst_val = bit'(get_field_val(ral.fifo_ctrl.fmtrst, item.a_data)); |
| bit rxrst_val = bit'(get_field_val(ral.fifo_ctrl.rxrst, item.a_data)); |
| if (rxrst_val) begin |
| rd_item_fifo.flush(); |
| exp_rd_q.delete(); |
| rd_pending_q.delete(); |
| rd_pending_item.clear_all(); |
| exp_rd_item.clear_all(); |
| end |
| if (cfg.en_cov) begin |
| cov.fmt_fifo_level_cg.sample(.irq(cfg.intr_vif.pins[FmtThreshold]), |
| .fmtlvl(`gmv(ral.fifo_status.fmtlvl)), |
| .rxlvl(0), |
| .rst(fmtrst_val)); |
| end |
| if (cfg.en_cov) begin |
| cov.rx_fifo_level_cg.sample(.irq(cfg.intr_vif.pins[RxThreshold]), |
| .fmtlvl(0), |
| .rxlvl(`gmv(ral.fifo_status.rxlvl)), |
| .rst(rxrst_val)); |
| end |
| end |
| "intr_test": begin |
| bit [TL_DW-1:0] intr_en = ral.intr_enable.get_mirrored_value(); |
| intr_exp |= item.a_data; |
| if (cfg.en_cov) begin |
| i2c_intr_e intr; |
| foreach (intr_exp[i]) begin |
| intr = i2c_intr_e'(i); // cast to enum to get interrupt name |
| cov.intr_test_cg.sample(intr, item.a_data[i], intr_en[i], intr_exp[i]); |
| end |
| end |
| end |
| "txdata": begin |
| if (read_rnd_data) begin |
| mirrored_txdata.push_back(item.a_data[7:0]); |
| end |
| end |
| endcase |
| // get full write transaction |
| if (!cfg.under_reset && host_init && sb_exp_wr_item.start && sb_exp_wr_item.stop) begin |
| exp_wr_q.push_back(sb_exp_wr_item); |
| num_exp_tran++; |
| `uvm_info(`gfn, $sformatf("\nscoreboard, push to queue, exp_wr_item\n\%s", |
| sb_exp_wr_item.sprint()), UVM_DEBUG) |
| end |
| end // end of write address phase |
| |
| // On reads, if do_read_check, is set, then check mirrored_value against item.d_data |
| if (data_phase_read) begin |
| case (csr.get_name()) |
| "rdata": begin |
| do_read_check = 1'b0; |
| if (host_init) begin |
| // If read data count is 0, it means the transaction has not yet started. |
| // If read data count is non-zero and equals the expected number of data while the stop |
| // bit is not set, it means a chained read has been issued and we need to update the |
| // expected transaction. |
| if (rdata_cnt == 0 || |
| rdata_cnt == rd_pending_item.num_data && !rd_pending_item.stop) begin |
| // for on-the-fly reset, immediately finish task to avoid blocking |
| wait(rd_pending_q.size() > 0 || cfg.under_reset); |
| if (cfg.under_reset) return; |
| temp_item = rd_pending_q.pop_front(); |
| |
| if (rdata_cnt == 0) begin |
| // if rdata_cnt is 0, use transaction directly since it is the first |
| rd_pending_item = temp_item; |
| end else begin |
| // if rdata_cnt is non_zero, then we are part of chain read. Update the |
| // expected number of bytes and stop bit as required. |
| rd_pending_item.num_data = temp_item.num_data; |
| rd_pending_item.stop = temp_item.stop; |
| end |
| end |
| rd_pending_item.data_q.push_back(item.d_data); |
| rdata_cnt++; |
| `uvm_info(`gfn, $sformatf("\nscoreboard, rd_pending_item\n\%s", |
| rd_pending_item.sprint()), UVM_DEBUG) |
| // get complete read transactions |
| if (rdata_cnt == rd_pending_item.num_data && rd_pending_item.stop) begin |
| `downcast(sb_exp_rd_item, rd_pending_item.clone()); |
| if (!cfg.under_reset) exp_rd_q.push_back(sb_exp_rd_item); |
| num_exp_tran++; |
| `uvm_info(`gfn, $sformatf("\nscoreboard, push to queue, exp_rd_item\n\%s", |
| sb_exp_rd_item.sprint()), UVM_DEBUG) |
| rdata_cnt = 0; |
| end |
| end |
| end |
| "intr_state": begin |
| i2c_intr_e intr; |
| bit [TL_DW-1:0] intr_en = item.d_data; |
| do_read_check = 1'b0; |
| foreach (intr_exp[i]) begin |
| intr = i2c_intr_e'(i); // cast to enum to get interrupt name |
| if (cfg.en_cov) begin |
| cov.intr_cg.sample(intr, intr_en[intr], intr_exp[intr]); |
| cov.intr_pins_cg.sample(intr, cfg.intr_vif.pins[intr]); |
| end |
| end |
| end |
| "status": begin |
| // check in test |
| do_read_check = 1'b0; |
| end |
| "fifo_status": begin |
| // check in test |
| do_read_check = 1'b0; |
| end |
| "acqdata": begin |
| i2c_item obs; |
| `uvm_create_obj(i2c_item, obs); |
| obs = acq2item(item.d_data); |
| obs.tran_id = cfg.rcvd_acq_cnt++; |
| target_mode_wr_obs_port.write(obs); |
| do_read_check = 1'b0; |
| end |
| default: begin |
| // check in test |
| do_read_check = 1'b0; |
| end |
| endcase |
| |
| if (do_read_check) begin |
| `DV_CHECK_EQ(csr.get_mirrored_value(), item.d_data, |
| $sformatf("reg name: %0s", csr.get_full_name())) |
| end |
| void'(csr.predict(.value(item.d_data), .kind(UVM_PREDICT_READ))); |
| end // end of read data phase |
| endtask : process_tl_access |
| |
| task compare_trans(bus_op_e dir = BusOpWrite); |
| i2c_item exp_trn; |
| i2c_item dut_trn; |
| int lastidx; |
| forever begin |
| if (dir == BusOpWrite) begin |
| wr_item_fifo.get(dut_trn); |
| wait(exp_wr_q.size() > 0); |
| lastidx = dut_trn.data_q.size(); |
| cfg.lastbyte = dut_trn.data_q[lastidx - 1]; |
| exp_trn = exp_wr_q.pop_front(); |
| end else begin // BusOpRead |
| rd_item_fifo.get(dut_trn); |
| wait(exp_rd_q.size() > 0); |
| exp_trn = exp_rd_q.pop_front(); |
| end |
| // when rx_fifo is overflow, drop the last byte from dut_trn |
| if (cfg.seq_cfg.en_rx_overflow && dut_trn.bus_op == BusOpRead) begin |
| void'(dut_trn.data_q.pop_back()); |
| dut_trn.num_data--; |
| end |
| if (!dut_trn.compare(exp_trn)) begin |
| if (!check_overflow_data_fmt_fifo(exp_trn, dut_trn)) begin // see description below |
| `uvm_error(`gfn, $sformatf("\ndirection %s item mismatch!\n--> EXP:\n%0s\--> DUT:\n%0s", |
| (dir == BusOpWrite) ? "WRITE" : "READ", exp_trn.sprint(), dut_trn.sprint())) |
| end |
| end else begin |
| `uvm_info(`gfn, $sformatf("\ndirection %s item match!\n--> EXP:\n%0s\--> DUT:\n%0s", |
| (dir == BusOpWrite) ? "WRITE" : "READ", exp_trn.sprint(), dut_trn.sprint()), UVM_MEDIUM) |
| end |
| end |
| endtask : compare_trans |
| |
| // this function check overflowed data occured in fmt_fifo does not exist |
| // in write transactions sent over busses |
| function bit check_overflow_data_fmt_fifo(i2c_item exp_trn, i2c_item dut_trn); |
| if (exp_trn.fmt_ovf_data_q.size() > 0) begin |
| bit [7:0] unique_q[$] = dut_trn.data_q.find with |
| ( item inside {exp_trn.fmt_ovf_data_q} ); |
| return (unique_q.size() == 0); |
| end |
| endfunction : check_overflow_data_fmt_fifo |
| |
| virtual function void reset(string kind = "HARD"); |
| super.reset(kind); |
| // reset local fifos queues and variables |
| rd_item_fifo.flush(); |
| wr_item_fifo.flush(); |
| exp_rd_q.delete(); |
| exp_wr_q.delete(); |
| rd_pending_q.delete(); |
| rd_pending_item.clear_all(); |
| exp_rd_item.clear_all(); |
| exp_wr_item.clear_all(); |
| host_init = 1'b0; |
| tran_id = 0; |
| rdata_cnt = 0; |
| num_exp_tran = 0; |
| `uvm_info(`gfn, "\n>>> reset scoreboard", UVM_DEBUG) |
| `DV_EOT_PRINT_Q_CONTENTS(i2c_item, exp_wr_q) |
| `DV_EOT_PRINT_Q_CONTENTS(i2c_item, exp_rd_q) |
| `DV_EOT_PRINT_Q_CONTENTS(i2c_item, rd_pending_q) |
| `DV_EOT_PRINT_TLM_FIFO_CONTENTS(i2c_item, rd_item_fifo) |
| `DV_EOT_PRINT_TLM_FIFO_CONTENTS(i2c_item, wr_item_fifo) |
| num_obs_rd = 0; |
| obs_wr_id = 0; |
| target_mode_wr_exp_fifo.flush(); |
| target_mode_wr_obs_fifo.flush(); |
| target_mode_rd_exp_fifo.flush(); |
| target_mode_rd_obs_fifo.flush(); |
| mirrored_txdata.delete(); |
| endfunction : reset |
| |
| function void report_phase(uvm_phase phase); |
| string str; |
| |
| super.report_phase(phase); |
| `uvm_info(`gfn, $sformatf("%s", cfg.convert2string()), UVM_DEBUG) |
| if (cfg.en_scb) begin |
| str = {$sformatf("\n*** SCOREBOARD CHECK\n")}; |
| str = {str, $sformatf(" - total checked trans %0d\n", num_exp_tran)}; |
| `uvm_info(`gfn, $sformatf("%s", str), UVM_DEBUG) |
| end |
| endfunction : report_phase |
| |
| function void check_phase(uvm_phase phase); |
| super.check_phase(phase); |
| `DV_EOT_PRINT_Q_CONTENTS(i2c_item, exp_wr_q) |
| `DV_EOT_PRINT_Q_CONTENTS(i2c_item, exp_rd_q) |
| `DV_EOT_PRINT_Q_CONTENTS(i2c_item, rd_pending_q) |
| `DV_EOT_PRINT_TLM_FIFO_CONTENTS(i2c_item, rd_item_fifo) |
| `DV_EOT_PRINT_TLM_FIFO_CONTENTS(i2c_item, wr_item_fifo) |
| endfunction |
| |
| // Compare start, stop and wdata only |
| function void target_txn_comp(i2c_item obs, i2c_item exp, string str); |
| `uvm_info(`gfn, $sformatf("comp:target obs_%s_txn %0d\n%s", str, obs.tran_id, obs.sprint()), |
| UVM_MEDIUM) |
| |
| `DV_CHECK_EQ(obs.tran_id, exp.tran_id) |
| `DV_CHECK_EQ(obs.start, exp.start) |
| `DV_CHECK_EQ(obs.stop, exp.stop) |
| if (obs.stop == 0 && obs.rstart == 0) begin |
| `DV_CHECK_EQ(obs.wdata, exp.wdata) |
| end |
| endfunction // target_txn_comp |
| |
| function void target_rd_comp(i2c_item obs, i2c_item exp); |
| `uvm_info(`gfn, $sformatf("comp:target obs_rd %0d\n%s", obs.tran_id, obs.convert2string()), |
| UVM_MEDIUM) |
| `DV_CHECK_EQ(obs.tran_id, exp.tran_id) |
| `DV_CHECK_EQ(obs.num_data, exp.num_data) |
| `DV_CHECK_EQ(obs.data_q.size(), exp.data_q.size()) |
| |
| foreach (exp.data_q[i]) begin |
| `DV_CHECK_EQ(obs.data_q[i], exp.data_q[i]) |
| end |
| endfunction // target_rd_comp |
| endclass : i2c_scoreboard |