| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| class pattgen_scoreboard extends cip_base_scoreboard #( |
| .CFG_T(pattgen_env_cfg), |
| .RAL_T(pattgen_reg_block), |
| .COV_T(pattgen_env_cov) |
| ); |
| `uvm_component_utils(pattgen_scoreboard) |
| `uvm_component_new |
| |
| // TLM fifos hold the transactions sent by monitor |
| uvm_tlm_analysis_fifo #(pattgen_item) item_fifo[NUM_PATTGEN_CHANNELS]; |
| |
| // interrupt bit vector |
| local bit [NumPattgenIntr-1:0] intr_exp; |
| // queues hold expected transactions for all channels |
| local pattgen_item exp_item_q[NUM_PATTGEN_CHANNELS][$]; |
| // local variables |
| local pattgen_channel_cfg channel_cfg[NUM_PATTGEN_CHANNELS-1:0]; |
| local bit [NumPattgenIntr-1:0] intr_exp_at_addr_phase; |
| |
| function void build_phase(uvm_phase phase); |
| super.build_phase(phase); |
| foreach (channel_cfg[i]) begin |
| item_fifo[i] = new($sformatf("item_fifo[%0d]", i), this); |
| channel_cfg[i] = pattgen_channel_cfg::type_id::create($sformatf("channel_cfg[%d]", i)); |
| end |
| endfunction : build_phase |
| |
| task run_phase(uvm_phase phase); |
| super.run_phase(phase); |
| forever begin |
| `DV_SPINWAIT_EXIT( |
| for (uint i = 0; i < NUM_PATTGEN_CHANNELS; i++) begin |
| fork |
| automatic uint channel = i; |
| compare_trans(channel); |
| join_none |
| end |
| wait fork;, |
| @(negedge cfg.clk_rst_vif.rst_n), |
| ) |
| end |
| endtask : run_phase |
| |
| virtual task process_tl_access(tl_seq_item item, tl_channels_e channel, string ral_name); |
| uvm_reg csr; |
| bit [TL_DW-1:0] reg_value; |
| 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 |
| |
| // address write phase |
| if (addr_phase_write) begin |
| // if 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()) |
| "size": begin |
| reg_value = ral.size.get_mirrored_value(); |
| channel_cfg[0].len = get_field_val(ral.size.len_ch0, reg_value); |
| channel_cfg[0].reps = get_field_val(ral.size.reps_ch0, reg_value); |
| channel_cfg[1].len = get_field_val(ral.size.len_ch1, reg_value); |
| channel_cfg[1].reps = get_field_val(ral.size.reps_ch1, reg_value); |
| `uvm_info(`gfn, $sformatf("\n scb: ral.size len0 %0d reps0 %0d len1 %0d reps1 %0d", |
| channel_cfg[0].len, channel_cfg[0].reps, |
| channel_cfg[1].len, channel_cfg[1].reps), UVM_DEBUG) |
| end |
| "prediv_ch0": begin |
| channel_cfg[0].prediv = ral.prediv_ch0.get_mirrored_value(); |
| end |
| "data_ch0_0": begin |
| channel_cfg[0].data[31:0] = ral.data_ch0[0].get_mirrored_value(); |
| end |
| "data_ch0_1": begin |
| channel_cfg[0].data[63:32] = ral.data_ch0[1].get_mirrored_value(); |
| end |
| "prediv_ch1": begin |
| channel_cfg[1].prediv = ral.prediv_ch1.get_mirrored_value(); |
| end |
| "data_ch1_0": begin |
| channel_cfg[1].data[31:0] = ral.data_ch1[0].get_mirrored_value(); |
| end |
| "data_ch1_1": begin |
| channel_cfg[1].data[63:32] = ral.data_ch1[1].get_mirrored_value(); |
| end |
| "ctrl": begin |
| reg_value = ral.ctrl.get_mirrored_value(); |
| channel_cfg[0].enable = bit'(get_field_val(ral.ctrl.enable_ch0, reg_value)); |
| channel_cfg[1].enable = bit'(get_field_val(ral.ctrl.enable_ch1, reg_value)); |
| channel_cfg[0].polarity = bit'(get_field_val(ral.ctrl.polarity_ch0, reg_value)); |
| channel_cfg[1].polarity = bit'(get_field_val(ral.ctrl.polarity_ch1, reg_value)); |
| `uvm_info(`gfn, $sformatf("\n scb: ctrl reg %b", reg_value[3:0]), UVM_DEBUG); |
| for (uint i = 0; i < NUM_PATTGEN_CHANNELS; i++) begin |
| // channel is started |
| if (channel_cfg[i].enable && !channel_cfg[i].start && !channel_cfg[i].stop) begin |
| channel_cfg[i].start = 1'b1; |
| `uvm_info(`gfn, $sformatf("\n scb: channel %0d is started", i), UVM_DEBUG) |
| end |
| // channel is operating but incorrectly disabled -> error injected |
| if (!channel_cfg[i].enable && channel_cfg[i].start && !channel_cfg[i].stop) begin |
| channel_cfg[i].stop = 1'b1; |
| `uvm_info(`gfn, $sformatf("\n scb: channel config %0d\n%s", |
| i, channel_cfg[i].convert2string()), UVM_DEBUG) |
| generate_exp_items(.channel(i), .error_injected(1'b1)); |
| end |
| 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 |
| pattgen_intr_e intr; |
| foreach (intr_exp[i]) begin |
| intr = pattgen_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 |
| "intr_enable", "alert_test": begin |
| // no special handle is needed |
| end |
| "intr_state": begin |
| bit[TL_DW-1:0] intr_wdata = item.a_data; |
| fork begin |
| bit [NumPattgenIntr-1:0] pre_intr = intr_exp; |
| cfg.clk_rst_vif.wait_clks(1); |
| intr_exp &= ~intr_wdata; |
| end join_none |
| end |
| default: begin |
| `uvm_fatal(`gfn, $sformatf("\n scb: write to invalid csr: %0s", csr.get_full_name())) |
| end |
| endcase |
| end |
| |
| // On reads and & data phase, if do_read_check, is set, then |
| // check mirrored_value against item.d_data |
| if (data_phase_read) begin |
| case (csr.get_name()) |
| "intr_state": begin |
| pattgen_intr_e intr; |
| bit [TL_DW-1:0] intr_en = ral.intr_enable.get_mirrored_value(); |
| do_read_check = 1'b0; |
| // done_ch0/done_ch1 is asserted to indicate a pattern is completely generated |
| reg_value = item.d_data; |
| channel_cfg[0].stop = bit'(get_field_val(ral.intr_state.done_ch0, reg_value)); |
| channel_cfg[1].stop = bit'(get_field_val(ral.intr_state.done_ch1, reg_value)); |
| `uvm_info(`gfn, $sformatf("\n scb: read intr_state %b%b", |
| channel_cfg[1].stop, channel_cfg[0].stop), UVM_DEBUG) |
| for (uint i = 0; i < NUM_PATTGEN_CHANNELS; i++) begin |
| generate_exp_items(.channel(i), .error_injected(1'b0)); |
| end |
| foreach (intr_exp[i]) begin |
| intr = pattgen_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 |
| "ctrl", "size", "intr_test", "intr_enable", |
| "prediv_ch0", "data_ch0_0", "data_ch0_1", |
| "prediv_ch1", "data_ch1_0", "data_ch1_1", "alert_test": begin |
| // no special handle is needed |
| end |
| default: begin |
| `uvm_fatal(`gfn, $sformatf("\n scb: read from invalid csr: %0s", csr.get_full_name())) |
| 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 |
| endtask : process_tl_access |
| |
| task compare_trans(uint channel); |
| pattgen_item exp_item; |
| pattgen_item dut_item; |
| |
| forever begin |
| item_fifo[channel].get(dut_item); |
| wait(exp_item_q[channel].size() > 0); |
| exp_item = exp_item_q[channel].pop_front(); |
| |
| if (!dut_item.compare(exp_item)) begin |
| `uvm_error(`gfn, $sformatf("\n--> channel %0d item mismatch!\n--> EXP:\n%s\--> DUT:\n%s", |
| channel, exp_item.sprint(), dut_item.sprint())) |
| end else begin |
| `uvm_info(`gfn, $sformatf("\n--> channel %0d item match!\n--> EXP:\n%s\--> DUT:\n%s", |
| channel, exp_item.sprint(), dut_item.sprint()), UVM_DEBUG) |
| end |
| end |
| endtask : compare_trans |
| |
| virtual function void generate_exp_items(uint channel, bit error_injected = 1'b0); |
| if (channel_cfg[channel].start && channel_cfg[channel].stop) begin |
| if (!error_injected) begin |
| pattgen_item exp_item; |
| exp_item = pattgen_item::type_id::create("exp_item"); |
| // see the specification document, the effective values of prediv, len, and reps |
| // are incremented from the coresponding register values |
| for (uint r = 0; r <= channel_cfg[channel].reps; r++) begin |
| for (uint l = 0; l <= channel_cfg[channel].len; l++) begin |
| exp_item.data_q.push_back(channel_cfg[channel].data[l]); |
| end |
| end |
| exp_item_q[channel].push_back(exp_item); |
| `uvm_info(`gfn, $sformatf("\n--> scb: get exp_item for channel %0d\n%s", |
| channel, exp_item.sprint()), UVM_DEBUG) |
| end else begin |
| `uvm_info(`gfn, $sformatf("\n--> scb: drop exp_item for channel %0d", channel), UVM_DEBUG) |
| end |
| channel_cfg[channel].reset_channel_config(); |
| end |
| endfunction : generate_exp_items |
| |
| virtual function void reset(string kind = "HARD"); |
| super.reset(kind); |
| for (uint i = 0; i < NUM_PATTGEN_CHANNELS; i++) begin |
| item_fifo[i].flush(); |
| exp_item_q[i].delete(); |
| channel_cfg[i].reset_channel_config(kind); |
| end |
| endfunction : reset |
| |
| function void check_phase(uvm_phase phase); |
| super.check_phase(phase); |
| for (int i = 0; i < NUM_PATTGEN_CHANNELS; i++) begin |
| `DV_EOT_PRINT_Q_CONTENTS(pattgen_item, exp_item_q[i]) |
| `DV_EOT_PRINT_TLM_FIFO_CONTENTS(pattgen_item, item_fifo[i]) |
| end |
| endfunction : check_phase |
| |
| 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")}; |
| `uvm_info(`gfn, $sformatf("%s", str), UVM_DEBUG) |
| end |
| endfunction : report_phase |
| endclass : pattgen_scoreboard |