Minimal code for the 'core agent' in icache UVM testbench

This fills in the sequencer, driver etc. to actually drive signals.
You can "run" a test with

  make -C dv/uvm/icache/dv run

This won't do anything useful (it will stop with a timeout) because
there is no memory agent yet.
This commit is contained in:
Rupert Swarbrick 2020-03-27 11:20:29 +00:00 committed by Rupert Swarbrick
parent 754a8f3d09
commit 38422a03bc
13 changed files with 305 additions and 49 deletions

View file

@ -19,22 +19,22 @@ SEED=1
ibex-top := ../../../..
dvsim-py := $(ibex-top)/vendor/lowrisc_ip/dvsim/dvsim.py
dvsim-std-args := --skip-ral --scratch-root $(ibex-top)/build -v h
dvsim-std-args := --skip-ral --scratch-root $(scratch-root)
waves-arg := $(if $(filter-out 0,$(WAVES)),--waves,)
verbosity-arg := $(if $(VERBOSITY),--verbosity $(VERBOSITY),)
seed-arg := $(if $(SEED),--fixed-seed $(SEED),)
dvsim-mk-args := $(waves-arg) $(verbosity-arg) $(seed-arg)
run-dvsim := $(dvsim-py) $(dvsim-std-args) $(dvsim-mk-args)
dvsim-mk-args := $(waves-arg) $(verbosity-arg) $(seed-arg)
run-icache-dvsim := $(dvsim-py) ibex_icache_sim_cfg.hjson $(dvsim-std-args) $(dvsim-mk-args)
.PHONY: all
all: run
.PHONY: just-build
just-build:
$(run-dvsim) --build-only ibex_icache_sim_cfg.hjson
$(run-icache-dvsim) --build-only
.PHONY: run
run:
$(run-dvsim) ibex_icache_sim_cfg.hjson
$(run-icache-dvsim)

View file

@ -10,7 +10,7 @@ class ibex_icache_env extends dv_base_env #(
);
`uvm_component_utils(ibex_icache_env)
ibex_icache_agent m_ibex_icache_agent;
ibex_icache_agent m_ibex_icache_agent;
ibex_mem_intf_slave_agent m_ibex_mem_intf_slave_agent;
`uvm_component_new
@ -31,10 +31,10 @@ class ibex_icache_env extends dv_base_env #(
m_ibex_mem_intf_slave_agent.monitor.addr_ph_port.connect(scoreboard.ibex_mem_intf_slave_fifo.analysis_export);
end
if (cfg.is_active && cfg.m_ibex_icache_agent_cfg.is_active) begin
virtual_sequencer.ibex_icache_sequencer_h = m_ibex_icache_agent.sequencer;
virtual_sequencer.core_sequencer_h = m_ibex_icache_agent.sequencer;
end
if (cfg.is_active && m_ibex_mem_intf_slave_agent.get_is_active()) begin
virtual_sequencer.ibex_mem_intf_slave_sequencer_h = m_ibex_mem_intf_slave_agent.sequencer;
virtual_sequencer.mem_sequencer_h = m_ibex_mem_intf_slave_agent.sequencer;
end
endfunction

View file

@ -8,8 +8,8 @@ class ibex_icache_virtual_sequencer extends dv_base_virtual_sequencer #(
);
`uvm_component_utils(ibex_icache_virtual_sequencer)
ibex_icache_sequencer ibex_icache_sequencer_h;
ibex_mem_intf_slave_sequencer ibex_mem_intf_slave_sequencer_h;
ibex_icache_sequencer core_sequencer_h;
ibex_mem_intf_slave_sequencer mem_sequencer_h;
`uvm_component_new

View file

@ -8,15 +8,10 @@ class ibex_icache_base_vseq extends dv_base_vseq #(
.VIRTUAL_SEQUENCER_T (ibex_icache_virtual_sequencer)
);
`uvm_object_utils(ibex_icache_base_vseq)
// various knobs to enable certain routines
bit do_ibex_icache_init = 1'b1;
`uvm_object_new
virtual task dut_init(string reset_kind = "HARD");
super.dut_init();
if (do_ibex_icache_init) ibex_icache_init();
endtask
virtual task dut_shutdown();
@ -24,9 +19,4 @@ class ibex_icache_base_vseq extends dv_base_vseq #(
// TODO
endtask
// setup basic ibex_icache features
virtual task ibex_icache_init();
`uvm_error(`gfn, "FIXME")
endtask
endclass : ibex_icache_base_vseq

View file

@ -2,14 +2,21 @@
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// basic sanity test vseq
// Basic sanity test
class ibex_icache_sanity_vseq extends ibex_icache_base_vseq;
`uvm_object_utils(ibex_icache_sanity_vseq)
`uvm_object_new
// A sanity sequence for the core agent
ibex_icache_sanity_seq core_seq;
task body();
`uvm_error(`gfn, "FIXME")
// TODO: This currently just drives the core sequence (which clearly isn't going to work!)
`uvm_create_on(core_seq, p_sequencer.core_sequencer_h)
`DV_CHECK_RANDOMIZE_FATAL(core_seq)
core_seq.start(p_sequencer.core_sequencer_h);
endtask : body
endclass : ibex_icache_sanity_vseq

View file

@ -19,6 +19,7 @@ filesets:
- ibex_icache_monitor.sv: {is_include_file: true}
- ibex_icache_agent.sv: {is_include_file: true}
- seq_lib/ibex_icache_base_seq.sv: {is_include_file: true}
- seq_lib/ibex_icache_sanity_seq.sv: {is_include_file: true}
- seq_lib/ibex_icache_seq_list.sv: {is_include_file: true}
file_type: systemVerilogSource

View file

@ -8,6 +8,11 @@ package ibex_icache_agent_pkg;
import dv_utils_pkg::*;
import dv_lib_pkg::*;
typedef enum {
ICacheTransTypeBranch,
ICacheTransTypeReq
} ibex_icache_trans_type_e;
// macro includes
`include "uvm_macros.svh"
`include "dv_macros.svh"
@ -19,7 +24,7 @@ package ibex_icache_agent_pkg;
typedef class ibex_icache_item;
typedef class ibex_icache_agent_cfg;
// reuse dv_base_seqeuencer as is with the right parameter set
// reuse dv_base_sequencer as is with the right parameter set
typedef dv_base_sequencer #(.ITEM_T(ibex_icache_item),
.CFG_T (ibex_icache_agent_cfg)) ibex_icache_sequencer;

View file

@ -4,34 +4,124 @@
class ibex_icache_driver extends dv_base_driver #(ibex_icache_item, ibex_icache_agent_cfg);
`uvm_component_utils(ibex_icache_driver)
// the base class provides the following handles for use:
// ibex_icache_agent_cfg: cfg
`uvm_component_new
virtual task run_phase(uvm_phase phase);
// base class forks off reset_signals() and get_and_drive() tasks
super.run_phase(phase);
endtask
// reset signals
virtual task reset_signals();
virtual task automatic reset_signals();
cfg.vif.reset();
endtask
// drive trans received from sequencer
virtual task get_and_drive();
virtual task automatic get_and_drive();
forever begin
seq_item_port.get_next_item(req);
$cast(rsp, req.clone());
rsp.set_id_info(req);
`uvm_info(`gfn, $sformatf("rcvd item:\n%0s", req.sprint()), UVM_HIGH)
// TODO: do the driving part
//
// send rsp back to seq
case (req.trans_type)
ICacheTransTypeBranch: drive_branch_trans(req);
ICacheTransTypeReq: drive_req_trans(req);
default: `uvm_fatal(`gfn, "Unknown transaction type")
endcase
`uvm_info(`gfn, "item sent", UVM_HIGH)
seq_item_port.item_done(rsp);
seq_item_port.item_done();
end
endtask
// Drive the cache for a "branch" transaction.
//
// This concurrently asserts branch with a given address for a cycle while doing the usual
// (enable/disable, invalidate, read instructions).
virtual task automatic drive_branch_trans(ibex_icache_item req);
// Make sure that req is enabled (has no effect unless this is the first transaction)
cfg.vif.req <= 1'b1;
fork
cfg.vif.branch_to(req.branch_addr);
if (req.toggle_enable) toggle_enable();
if (req.invalidate) invalidate();
read_insns(req.num_insns);
join
endtask
// Drive the cache for a "req transaction".
//
// This lowers req for zero or more cycles, at the same time as setting the enable pin and (maybe)
// pulsing the invalidate line. Once that is done, it reads zero or more instructions.
virtual task automatic drive_req_trans(ibex_icache_item req);
int unsigned req_low_cycles;
bit allow_no_low_cycles;
// How many cycles should we lower the request line? If there aren't any instructions to read,
// this is constrained to be positive (to avoid a confusing zero-time transaction).
allow_no_low_cycles = req.num_insns > 0;
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(req_low_cycles,
req_low_cycles dist {
0 :/ (allow_no_low_cycles ? 20 : 0),
[1:33] :/ 5,
[100:200] :/ 2,
[1000:1200] :/ 1 };)
fork
if (req_low_cycles > 0) lower_req(req_low_cycles);
if (req.toggle_enable) toggle_enable();
if (req.invalidate) invalidate();
join
read_insns(req.num_insns);
endtask
// Toggle whether the cache is enabled
virtual task automatic toggle_enable();
cfg.vif.enable <= ~cfg.vif.enable;
endtask
// Read up to num_insns instructions from the cache, stopping early on an error
virtual task automatic read_insns(int num_insns);
for (int i = 0; i < num_insns; i++) begin
read_insn();
// Spot any error and exit early
if (cfg.vif.err)
break;
end
endtask
// Read a single instruction from the cache
virtual task automatic read_insn();
int unsigned delay;
// Maybe (1 time in 10) wait for a valid signal before even considering asserting ready.
if ($urandom_range(9) == 0)
wait (cfg.vif.valid);
// Then pick how long we wait before asserting that we are ready.
//
// TODO: Make this configurable and weight 0 more heavily.
cfg.vif.wait_clks($urandom_range(3));
// Assert ready and then wait until valid
cfg.vif.ready <= 1'b1;
while (1'b1) begin
@(posedge cfg.vif.clk);
if (cfg.vif.valid)
break;
end
cfg.vif.ready <= 1'b0;
endtask
// Lower the req line for the given number of cycles
virtual task automatic lower_req(int unsigned num_cycles);
cfg.vif.req <= 1'b0;
repeat (num_cycles) @(posedge cfg.vif.clk);
cfg.vif.req <= 1'b1;
endtask
// Raise the invalidate line for a randomly chosen number of cycles > 0.
virtual task automatic invalidate();
int unsigned num_cycles;
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(num_cycles,
num_cycles dist { 0 :/ 499, [1:20] :/ 1 };)
cfg.vif.invalidate_pulse(num_cycles);
endtask
endclass

View file

@ -2,10 +2,66 @@
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
interface ibex_icache_if ();
interface ibex_icache_if (input clk);
// interface pins
// Set when core is enabled (and might request instructions soon)
logic req;
// debug signals
// Branch request
logic branch;
logic [31:0] branch_addr;
// Passing instructions back to the core
logic ready;
logic valid;
logic [31:0] rdata;
logic [31:0] addr;
logic err;
logic err_plus2;
// Enable/disable or invalidate the cache
logic enable;
logic invalidate;
// Busy signal from the cache (either there's a request on the bus or the cache is invalidating
// itself)
logic busy;
// Drive the branch pin for a single cycle, redirecting the cache to the given instruction
// address.
task automatic branch_to(logic [31:0] addr);
branch <= 1'b1;
branch_addr <= addr;
@(posedge clk);
branch <= 1'b0;
branch_addr <= 'X;
endtask
// Raise a pulse on the invalidate line for the given number of cycles.
//
// A one-cycle pulse will start an invalidation, but testing might want a longer pulse (which the
// cache should support)
task automatic invalidate_pulse(int num_cycles);
invalidate <= 1'b1;
repeat (num_cycles) @(posedge clk);
invalidate <= 1'b0;
endtask
// A task that waits for num_clks posedges on the clk signal
task automatic wait_clks(int num_clks);
repeat (num_clks) @(posedge clk);
endtask
// Reset all the signals from the core to the cache (the other direction is controlled by the
// DUT)
task automatic reset();
req <= 1'b0;
branch <= 1'b0;
ready <= 1'b0;
enable <= 1'b0;
invalidate <= 1'b0;
endtask
endinterface

View file

@ -4,9 +4,70 @@
class ibex_icache_item extends uvm_sequence_item;
// random variables
// The type of transaction
rand ibex_icache_trans_type_e trans_type;
// The branch address for a branch transaction (only has effect if trans_type is
// ICacheTransTypeBranch)
rand bit [31:0] branch_addr;
// Whether the cache enable/disable should be toggled
rand bit toggle_enable;
// Whether to invalidate the cache
rand bit invalidate;
// The number of instructions to read (always non-negative, but may be zero)
rand int num_insns;
constraint c_non_branch_trans_addr {
// If the transaction type is ICacheTransTypeBranch then branch_addr can be anything. To make
// reading debug logs a bit easier, we force it to be zero otherwise.
(trans_type != ICacheTransTypeBranch) -> branch_addr == 0;
// Pick trans_type before the other values. We need to do this because constraining branch_addr
// to 0 for non-branch transactions would otherwise mean branch transactions got weighted 2^32
// times higher.
solve trans_type before branch_addr;
}
constraint c_branch_addr_alignment {
// The branch address is always required to be half-word aligned. We don't bother conditioning
// this on the type of transaction because branch_addr is forced to be zero in anything but a
// branch transaction.
!branch_addr[0];
}
constraint c_toggle_enable_dist {
// Toggle the cache enable line one time in 50. This should allow us a reasonable amount of time
// in each mode (note that each transaction here results in multiple instruction fetches)
toggle_enable dist { 0 :/ 49, 1 :/ 1 };
}
constraint c_invalidate_dist {
// Poke the cache invalidate line one time in 500. This takes ages and we don't want to
// accidentally spend most of the test waiting for invalidation.
invalidate dist { 0 :/ 499, 1 :/ 1 };
}
constraint c_num_insns_dist {
// For branch transactions, we want to read zero instructions reasonably frequently. For req
// transactions, much less so. Also, we don't bother with long sequences for req transactions:
// they won't look any different from the tail end of branch transactions from the cache's point
// of view.
if (trans_type == ICacheTransTypeBranch)
num_insns dist { 0 :/ 5, [1:20] :/ 20, [21:100] :/ 1 };
else
num_insns dist { 0 :/ 1, [1:20] :/ 20 };
}
`uvm_object_utils_begin(ibex_icache_item)
`uvm_field_enum(ibex_icache_trans_type_e, trans_type, UVM_DEFAULT)
`uvm_field_int (branch_addr, UVM_DEFAULT | UVM_HEX)
`uvm_field_int (toggle_enable, UVM_DEFAULT)
`uvm_field_int (invalidate, UVM_DEFAULT)
`uvm_field_int (num_insns, UVM_DEFAULT)
`uvm_object_utils_end
`uvm_object_new

View file

@ -0,0 +1,31 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Sanity test seq
//
// This is unlikely to find many cache hits (since it branches all over the 4GiB address space).
class ibex_icache_sanity_seq extends ibex_icache_base_seq;
`uvm_object_utils(ibex_icache_sanity_seq)
`uvm_object_new
rand int count;
constraint c_count { count > 0; count < 100; }
task body();
// Generate a request which is constrained to have trans_type ICacheTransTypeBranch: the core
// must start with a branch to tell the cache where to fetch from in the first place.
req = ibex_icache_item::type_id::create("req");
start_item(req);
`DV_CHECK_RANDOMIZE_WITH_FATAL(req, req.trans_type == ICacheTransTypeBranch;)
finish_item(req);
// Generate and run count ibex_icache_item sequence items (with no other constraint) through the
// req port.
repeat (count - 1) begin
start_item(req);
`DV_CHECK_RANDOMIZE_FATAL(req)
finish_item(req);
end
endtask
endclass

View file

@ -3,3 +3,4 @@
// SPDX-License-Identifier: Apache-2.0
`include "ibex_icache_base_seq.sv"
`include "ibex_icache_sanity_seq.sv"

View file

@ -17,13 +17,27 @@ module tb;
// interfaces
clk_rst_if clk_rst_if(.clk(clk), .rst_n(rst_n));
ibex_icache_if ibex_icache_if();
ibex_mem_intf ibex_mem_intf();
ibex_icache_if core_if (.clk(clk));
ibex_mem_intf ibex_mem_intf();
// dut
ibex_icache dut (
.clk_i (clk ),
.rst_ni (rst_n )
.clk_i (clk),
.rst_ni (rst_n),
// Connect icache <-> core interface
.req_i (core_if.req),
.branch_i (core_if.branch),
.addr_i (core_if.branch_addr),
.ready_i (core_if.ready),
.valid_o (core_if.valid),
.rdata_o (core_if.rdata),
.addr_o (core_if.addr),
.err_o (core_if.err),
.err_plus2_o (core_if.err_plus2),
.icache_enable_i (core_if.enable),
.icache_inval_i (core_if.invalidate),
.busy_o (core_if.busy)
// TODO: add remaining IOs and hook them
);
@ -32,7 +46,7 @@ module tb;
// drive clk and rst_n from clk_if
clk_rst_if.set_active();
uvm_config_db#(virtual clk_rst_if)::set(null, "*.env", "clk_rst_vif", clk_rst_if);
uvm_config_db#(virtual ibex_icache_if)::set(null, "*.env.m_ibex_icache_agent*", "vif", ibex_icache_if);
uvm_config_db#(virtual ibex_icache_if)::set(null, "*.env.m_ibex_icache_agent*", "vif", core_if);
uvm_config_db#(virtual ibex_mem_intf)::set(null, "*.env.m_ibex_mem_intf_slave_agent*", "vif", ibex_mem_intf);
$timeformat(-12, 0, " ps", 12);
run_test();