Commenting UVM testbench code, tidy formatting, minor refactoring

- Adds comments for quicker explanation of test and library functionality
- Refactor types and naming of control knob signals for clarity
- Move constraints from MEMBER to CLASS for more flexibility
- Add missing license header

Signed-off-by: Harry Callahan <hcallahan@lowrisc.org>
This commit is contained in:
Harry Callahan 2022-06-30 14:13:38 +01:00 committed by hcallahan-lowrisc
parent 3459d7f8df
commit 806989a745
7 changed files with 168 additions and 117 deletions

View file

@ -6,7 +6,7 @@ class irq_request_driver extends uvm_driver #(irq_seq_item);
// The virtual interface used to drive and view HDL signals.
protected virtual irq_if vif;
`uvm_component_utils(irq_request_driver)
`uvm_component_utils(irq_request_driver)
`uvm_component_new
function void build_phase(uvm_phase phase);
@ -21,6 +21,8 @@ class irq_request_driver extends uvm_driver #(irq_seq_item);
wait (vif.driver_cb.reset === 1'b0);
forever begin
fork : drive_irq
// Setup a single get_REQ -> drive -> send_RSP long-running task.
// This seq_item contains all signals on the interface at once.
get_and_drive();
wait (vif.driver_cb.reset === 1'b1);
join_any
@ -42,6 +44,8 @@ class irq_request_driver extends uvm_driver #(irq_seq_item);
reset_signals();
endtask
// Every cycle, check for a new REQ and drive it.
// Simultaneously, return the same REQ as a RSP back to the sequence.
virtual protected task get_and_drive();
forever begin
seq_item_port.try_next_item(req);

View file

@ -9,7 +9,7 @@ class core_ibex_vseqr extends uvm_sequencer;
ibex_mem_intf_response_sequencer data_if_seqr;
ibex_mem_intf_response_sequencer instr_if_seqr;
irq_request_sequencer irq_seqr;
irq_request_sequencer irq_seqr;
`uvm_component_utils(core_ibex_vseqr)
`uvm_component_new

View file

@ -210,11 +210,16 @@ class core_ibex_base_test extends uvm_test;
virtual task wait_for_mem_txn(input bit[ibex_mem_intf_agent_pkg::ADDR_WIDTH-1:0] ref_addr,
input signature_type_t ref_type);
ibex_mem_intf_seq_item mem_txn;
`uvm_info(`gfn, $sformatf("Awaiting riscv-dv handshake at 0x%0h, Type : %0s",
ref_addr, ref_type), UVM_HIGH)
forever begin
// The first write to this address is guaranteed to contain the signature type in bits [7:0]
item_collected_port.get(mem_txn);
if (mem_txn.addr == ref_addr && mem_txn.data[7:0] === ref_type &&
if (mem_txn.addr == ref_addr &&
mem_txn.data[7:0] === ref_type &&
mem_txn.read_write == WRITE) begin
`uvm_info(`gfn, $sformatf("riscv-dv handshake received at 0x%0h, Type : %0s",
ref_addr, ref_type), UVM_HIGH)
signature_data = mem_txn.data;
case (ref_type)
// The very first write to the signature address in every test is guaranteed to be a write

View file

@ -1,72 +1,87 @@
typedef enum bit [1:0] {
SingleRun, // Singl iteration
InfiniteRuns, // Run forever until stop is specified
MultipleRuns, // Multiple runs with configurable or randomizable iteration count
InvalidRuns // Default state
} run_type_e;
typedef enum bit [1:0] {
IsideErr, // Inject error in instruction side memory.
DsideErr, // Inject error in data side memory.
PickErr // Pick which memory to inject error in.
} error_type_e;
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class core_base_new_seq #(type REQ = uvm_sequence_item) extends uvm_sequence #(REQ);
// Default set to 50% for zero delay to be picked. Set to 100 if zero delay is required always.
int unsigned zero_delay_pct = 50;
rand bit zero_delays;
rand int delay = 0;
int unsigned iteration_cnt = 0;
int unsigned max_delay = 500;
virtual clk_rst_if clk_vif;
// Virtual interfaces for driving stimulus directly
virtual clk_rst_if clk_vif;
virtual core_ibex_dut_probe_if dut_vif;
bit stop_seq;
bit seq_finished;
rand run_type_e num_of_iterations = InvalidRuns;
virtual core_ibex_dut_probe_if dut_vif;
// Use this bit to start any unique sequence once
bit start_seq = 0;
rand bit zero_delays;
// CONTROL_KNOB: Randomly override the delay between stimulus items to be zero
// Default set to 50% for zero delay to be picked. Set to 100 if zero delay is required always.
int unsigned zero_delay_pct = 50;
constraint zero_delays_c {
zero_delays dist {1 :/ zero_delay_pct,
0 :/ 100 - zero_delay_pct};
}
rand int unsigned stimulus_delay_cycles;
// CONTROL_KNOB: Delay the start of each stimulus a randomized amount (transaction to transaction delay)
// Can be randomly overwritten to no delay at all with 'zero_delays'
int unsigned stimulus_delay_cycles_min = 200;
int unsigned stimulus_delay_cycles_max = 400;
constraint reasonable_delay_c {
stimulus_delay_cycles inside {[stimulus_delay_cycles_min : stimulus_delay_cycles_max]};
}
// CONTROL_KNOB: Set this per-instance to make the stimulus generation repeat : {Once, Multiple, Forever}
run_type_e iteration_modes = MultipleRuns;
rand int unsigned iteration_cnt;
// CONTROL_KNOB: This controls the number of stimulus items generated in a loop for {Multiple} cfg
int unsigned iteration_cnt_max = 20;
constraint iterations_cnt_c {
iteration_cnt inside {[1:iteration_cnt_max]};
}
`uvm_object_param_utils(core_base_new_seq#(REQ))
function new (string name = "");
super.new(name);
if(!uvm_config_db#(virtual clk_rst_if)::get(null, "", "clk_if", clk_vif)) begin
`uvm_fatal(get_full_name(), "Cannot get clk_if")
`uvm_fatal(`gfn, "Cannot get clk_if")
end
if (!uvm_config_db#(virtual core_ibex_dut_probe_if)::get(null, "", "dut_if", dut_vif)) begin
`uvm_fatal(get_full_name(), "Cannot get dut_if")
`uvm_fatal(`gfn, "Cannot get dut_if")
end
endfunction
constraint zero_delays_c {
zero_delays dist {1 :/ zero_delay_pct,
0 :/ 100 - zero_delay_pct};
}
constraint reasonable_delay_c {
delay inside {[0 : max_delay]};
}
virtual task pre_body();
// Randomize once before starting to ensure all unininitialized rand variables have a valid starting value
this.randomize();
endtask: pre_body
virtual task body();
if(num_of_iterations == InvalidRuns) begin
`DV_CHECK_MEMBER_RANDOMIZE_WITH_FATAL(num_of_iterations, num_of_iterations != InvalidRuns;)
end
`uvm_info(`gfn, $sformatf("Doing %s", num_of_iterations.name()), UVM_LOW)
case (num_of_iterations)
// core_base_new_seq::body() provides a flexible sequence scheduler, where
// 'iteration_modes' allows different tests to change the frequency
// stimulus is generated.
`uvm_info(`gfn, $sformatf("Running the \"%s\" schedule for stimulus generation",
iteration_modes.name()), UVM_LOW)
case (iteration_modes)
SingleRun: begin
drive_stimulus(delay);
drive_stimulus();
end
MultipleRuns: begin
// Randomize iteration_cnt if not specified externally by test
if(iteration_cnt == 0) begin
`DV_CHECK_MEMBER_RANDOMIZE_WITH_FATAL(iteration_cnt,
iteration_cnt > 0 && iteration_cnt <= 20; )
// with {"CONSTRAINTS"}
iteration_cnt > 0;)
end
`uvm_info(`gfn, $sformatf("Number of stimulus iterations = %0d", iteration_cnt), UVM_LOW)
for (int i = 0; i <= iteration_cnt; i++) begin
drive_stimulus(delay);
drive_stimulus();
end
end
InfiniteRuns: begin
while (!stop_seq) begin
drive_stimulus(delay);
drive_stimulus();
end
seq_finished = 1'b1;
end
@ -76,28 +91,30 @@ class core_base_new_seq #(type REQ = uvm_sequence_item) extends uvm_sequence #(R
endcase
endtask: body
task drive_stimulus(int unsigned delay);
if(delay == 0) begin
`DV_CHECK_MEMBER_RANDOMIZE_FATAL(zero_delays)
if(zero_delays) begin
delay = 0;
end else begin
`DV_CHECK_MEMBER_RANDOMIZE_FATAL(delay)
end
task drive_stimulus();
`DV_CHECK_MEMBER_RANDOMIZE_FATAL(zero_delays)
if(!zero_delays) begin
// Delay for a randomized amount of cycles
`DV_CHECK_MEMBER_RANDOMIZE_FATAL(stimulus_delay_cycles)
`uvm_info(`gfn, $sformatf("stimulus_delay_cycles = %0d", stimulus_delay_cycles), UVM_HIGH)
clk_vif.wait_clks(stimulus_delay_cycles);
end
clk_vif.wait_clks(delay);
`uvm_info(get_full_name(), "Starting sequence...", UVM_MEDIUM)
send_req();
`uvm_info(get_full_name(), "Exiting sequence", UVM_MEDIUM)
endtask: drive_stimulus
// Generate the stimulus with a sequence-specific request
// Seqs can each implement send_req() differently, such as sending a seq_item,
// or driving the interface directly.
virtual task send_req();
`uvm_fatal(get_full_name(), "This task must be implemented in the extended class")
endtask
// Can be called by the sequencer to break out of infinite-loops in body()
virtual task stop();
stop_seq = 1'b1;
`uvm_info(get_full_name(), "Stopping sequence", UVM_MEDIUM)
`uvm_info(`gfn, "Stopping sequence", UVM_MEDIUM)
wait (seq_finished == 1'b1);
endtask
@ -108,13 +125,16 @@ class irq_new_seq extends core_base_new_seq #(irq_seq_item);
`uvm_object_utils(irq_new_seq)
`uvm_object_new
// Set these 'no_*' bits at the test-level to disable the randomizer from
// generating each particular type of irq.
bit no_nmi;
bit no_fast;
bit no_external;
bit no_timer;
bit no_software;
int max_delay = 500;
int min_delay = 50;
int unsigned max_delay = 500;
int unsigned min_delay = 50;
rand int interval = min_delay;
@ -125,29 +145,25 @@ class irq_new_seq extends core_base_new_seq #(irq_seq_item);
virtual task send_req();
irq_seq_item irq;
irq = irq_seq_item::type_id::create("irq");
// Raise interrupts
// Raise randomized num of interrupts
start_item(irq);
`DV_CHECK_RANDOMIZE_WITH_FATAL(irq, num_of_interrupt > 1;
if (no_nmi) {
irq_nm == 0;
}
if (no_fast) {
irq_fast == '0;
}
if (no_external) {
irq_external == 0;
}
if (no_timer) {
irq_timer == 0;
}
if (no_software) {
irq_software == 0;
})
`DV_CHECK_RANDOMIZE_WITH_FATAL(irq,
// with {"CONSTRAINTS"}
num_of_interrupt inside {[1:5]};
no_nmi -> irq_nm == 0;
no_fast -> irq_fast == '0;
no_external -> irq_external == 0;
no_timer -> irq_timer == 0;
no_software -> irq_software == 0;)
finish_item(irq);
get_response(irq);
// Delay a randomized amount while interrupts are active
`DV_CHECK_MEMBER_RANDOMIZE_FATAL(interval)
clk_vif.wait_clks(interval);
// Drop interrupts
// Drop all interrupts
start_item(irq);
`DV_CHECK_RANDOMIZE_WITH_FATAL(irq, num_of_interrupt == 0;)
finish_item(irq);
@ -163,23 +179,24 @@ class debug_new_seq extends core_base_new_seq#(irq_seq_item);
`uvm_object_utils(debug_new_seq)
`uvm_object_new
int max_delay = 500;
int min_delay = 75;
rand int unsigned drop_delay = 0;
rand int unsigned pulse_length_cycles;
int unsigned pulse_length_cycles_min = 75;
int unsigned pulse_length_cycles_max = 500;
constraint reasonable_pulse_length_c {
pulse_length_cycles inside {[pulse_length_cycles_min : pulse_length_cycles_max]};
}
virtual task body();
dut_vif.dut_cb.debug_req <= 1'b0;
if(drop_delay == 0) begin
`DV_CHECK_MEMBER_RANDOMIZE_WITH_FATAL(drop_delay,
drop_delay inside {[min_delay : max_delay]};)
end
super.body();
endtask
endtask: body
virtual task send_req();
`uvm_info(get_full_name(), "Sending debug request", UVM_HIGH)
`uvm_info(`gfn, "Sending debug request", UVM_HIGH)
dut_vif.dut_cb.debug_req <= 1'b1;
clk_vif.wait_clks(drop_delay);
`DV_CHECK_MEMBER_RANDOMIZE_FATAL(pulse_length_cycles);
clk_vif.wait_clks(pulse_length_cycles);
dut_vif.dut_cb.debug_req <= 1'b0;
endtask
@ -187,8 +204,10 @@ endclass
class memory_error_seq extends core_base_new_seq#(irq_seq_item);
core_ibex_vseq vseq;
rand error_type_e err_type = PickErr;
rand bit choose_side;
bit start_seq = 0; // Use this bit to start any unique sequence once
rand error_type_e err_type = PickErr;
`uvm_object_utils(memory_error_seq)
`uvm_declare_p_sequencer(core_ibex_vseqr)
@ -233,26 +252,28 @@ class fetch_enable_seq extends core_base_new_seq#(irq_seq_item);
ibex_pkg::fetch_enable_t fetch_enable;
int unsigned on_bias_pc = 50;
int max_delay = 500;
int min_delay = 75;
int unsigned max_delay = 500;
int unsigned min_delay = 75;
rand int unsigned off_delay = 0;
virtual task body();
dut_vif.dut_cb.fetch_enable <= ibex_pkg::FetchEnableOn;
if(off_delay == 0) begin
`DV_CHECK_MEMBER_RANDOMIZE_WITH_FATAL(off_delay,
off_delay inside {[min_delay : max_delay]};)
// with {"CONSTRAINTS"}
off_delay inside {[min_delay : max_delay]};)
end
super.body();
endtask
endtask: body
virtual task send_req();
`uvm_info(get_full_name(), "Sending fetch enable request", UVM_LOW)
`DV_CHECK_MEMBER_RANDOMIZE_WITH_FATAL(fetch_enable,
fetch_enable dist {ibex_pkg::FetchEnableOn :/ on_bias_pc,
[0:15] :/ 100 - on_bias_pc};
)
// with {"CONSTRAINTS"}
fetch_enable dist {ibex_pkg::FetchEnableOn :/ on_bias_pc,
[0:15] :/ 100 - on_bias_pc};)
`uvm_info(`gfn, "Sending fetch enable request", UVM_LOW)
`uvm_info(`gfn, $sformatf("fetch_enable = %d", fetch_enable), UVM_LOW)
dut_vif.dut_cb.fetch_enable <= fetch_enable;
clk_vif.wait_clks(off_delay);
dut_vif.dut_cb.fetch_enable <= ibex_pkg::FetchEnableOn;

View file

@ -36,6 +36,10 @@ class core_base_seq #(type REQ = uvm_sequence_item) extends uvm_sequence#(REQ);
end
`DV_CHECK_MEMBER_RANDOMIZE_FATAL(delay)
clk_vif.wait_clks(delay);
// Loop around until 'stop_seq' is set to 1 externally, or "num_of_iterations" times,
// whichever is longer.
// Send a request() each time with a randomized interval between items.
`uvm_info(get_full_name(), "Starting sequence...", UVM_LOW)
if (!is_started) is_started = 1'b1;
while (!stop_seq) begin
@ -98,17 +102,16 @@ class irq_raise_seq extends irq_base_seq;
bit no_fast;
virtual function void randomize_item(irq_seq_item irq);
`DV_CHECK_RANDOMIZE_WITH_FATAL(irq, num_of_interrupt > 1;
if (no_nmi) {
irq_nm == 0;
}
if (no_fast) {
irq_fast == '0;
})
`DV_CHECK_RANDOMIZE_WITH_FATAL(irq,
// with {"CONSTRAINTS"}
num_of_interrupt > 1;
no_nmi -> irq_nm == 0;
no_fast -> irq_fast == '0;)
endfunction
endclass
// Irq sequence that raises a single interrupt input signal
class irq_raise_single_seq extends irq_base_seq;
`uvm_object_utils(irq_raise_single_seq)
@ -118,25 +121,26 @@ class irq_raise_single_seq extends irq_base_seq;
bit no_fast;
virtual function void randomize_item(irq_seq_item irq);
`DV_CHECK_RANDOMIZE_WITH_FATAL(irq, num_of_interrupt == 1;
if (no_nmi) {
irq_nm == 0;
}
if (no_fast) {
irq_fast == '0;
})
`DV_CHECK_RANDOMIZE_WITH_FATAL(irq,
// with {"CONSTRAINTS"}
num_of_interrupt == 1;
no_nmi -> irq_nm == 0;
no_fast -> irq_fast == '0;)
endfunction
endclass
// Irq sequence that ONLY raises the nmi signal
class irq_raise_nmi_seq extends irq_base_seq;
`uvm_object_utils(irq_raise_nmi_seq)
`uvm_object_new
virtual function void randomize_item(irq_seq_item irq);
`DV_CHECK_RANDOMIZE_WITH_FATAL(irq, num_of_interrupt == 1;
irq_nm == 1;)
`DV_CHECK_RANDOMIZE_WITH_FATAL(irq,
// with {"CONSTRAINTS"}
num_of_interrupt == 1;
irq_nm == 1;)
endfunction
endclass

View file

@ -25,6 +25,19 @@ package core_ibex_test_pkg;
typedef class core_ibex_vseq;
// For new_seq_lib...
typedef enum bit [1:0] {
SingleRun, // Single iteration
InfiniteRuns, // Run forever until stop is specified
MultipleRuns // Multiple runs with configurable or randomizable iteration count
} run_type_e;
typedef enum bit [1:0] {
IsideErr, // Inject error in instruction side memory.
DsideErr, // Inject error in data side memory.
PickErr // Pick which memory to inject error in.
} error_type_e;
`include "core_ibex_report_server.sv"
`include "core_ibex_seq_lib.sv"
`include "core_ibex_new_seq_lib.sv"

View file

@ -8,8 +8,8 @@
class core_ibex_vseq extends uvm_sequence;
ibex_mem_intf_response_seq instr_intf_seq;
ibex_mem_intf_response_seq data_intf_seq;
ibex_mem_intf_response_seq instr_intf_seq;
ibex_mem_intf_response_seq data_intf_seq;
mem_model_pkg::mem_model mem;
irq_raise_seq irq_raise_seq_h;
irq_raise_single_seq irq_raise_single_seq_h;
@ -30,6 +30,16 @@ class core_ibex_vseq extends uvm_sequence;
data_intf_seq.is_dmem_seq = 1'b1;
endfunction
// Start the memory-model sequences, which run forever() loops to respond to bus events
virtual task pre_body();
instr_intf_seq.m_mem = mem;
data_intf_seq.m_mem = mem;
fork
instr_intf_seq.start(p_sequencer.instr_if_seqr);
data_intf_seq.start(p_sequencer.data_if_seqr);
join_none
endtask // pre_body
virtual task body();
if (cfg.enable_irq_single_seq) begin
irq_raise_single_seq_h = irq_raise_single_seq::type_id::create("irq_single_seq_h");
@ -70,12 +80,6 @@ class core_ibex_vseq extends uvm_sequence;
debug_seq_single_h.interval.rand_mode(0);
debug_seq_single_h.interval = 0;
end
instr_intf_seq.m_mem = mem;
data_intf_seq.m_mem = mem;
fork
instr_intf_seq.start(p_sequencer.instr_if_seqr);
data_intf_seq.start(p_sequencer.data_if_seqr);
join_none
endtask
virtual task stop();