Initial memory agent for ICache UVM testbench

As with the core agent, this doesn't yet have any proper monitoring,
and the testbench has no scoreboard, so we're not actually checking
anything.

However, it *does* implement a slave agent which can respond to
instruction fetches from the icache. This runs a test to completion,
try:

   make -C dv/uvm/icache/dv run WAVES=1

Note that the timeout in the hjson file was too small, so this bumps
it to a larger value. A later patch will replace the timeout with a
heartbeat monitor, which should be much cleaner.

The exact dance for the UVM reactive slave is complicated. See
dv/uvm/icache/dv/ibex_icache_mem_agent/README.md for the details.
This commit is contained in:
Rupert Swarbrick 2020-03-27 17:10:10 +00:00 committed by Rupert Swarbrick
parent d0ea6fe449
commit 5cd874ea71
29 changed files with 845 additions and 72 deletions

View file

@ -9,7 +9,7 @@ filesets:
depend:
- lowrisc:dv:dv_lib
- lowrisc:dv:ibex_icache_core_agent
- lowrisc:dv:ibex_mem_intf_agent
- lowrisc:dv:ibex_icache_mem_agent
files:
- ibex_icache_env_pkg.sv
- ibex_icache_env_cfg.sv: {is_include_file: true}
@ -19,7 +19,6 @@ filesets:
- ibex_icache_env.sv: {is_include_file: true}
- seq_lib/ibex_icache_vseq_list.sv: {is_include_file: true}
- seq_lib/ibex_icache_base_vseq.sv: {is_include_file: true}
- seq_lib/ibex_icache_common_vseq.sv: {is_include_file: true}
- seq_lib/ibex_icache_sanity_vseq.sv: {is_include_file: true}
file_type: systemVerilogSource

View file

@ -10,31 +10,32 @@ class ibex_icache_env extends dv_base_env #(
);
`uvm_component_utils(ibex_icache_env)
ibex_icache_core_agent m_ibex_icache_core_agent;
ibex_mem_intf_slave_agent m_ibex_mem_intf_slave_agent;
ibex_icache_core_agent core_agent;
ibex_icache_mem_agent mem_agent;
`uvm_component_new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// create components
m_ibex_icache_core_agent = ibex_icache_core_agent::type_id::create("m_ibex_icache_core_agent", this);
uvm_config_db#(ibex_icache_core_agent_cfg)::set(this, "m_ibex_icache_core_agent*", "cfg", cfg.m_ibex_icache_core_agent_cfg);
// create components
m_ibex_mem_intf_slave_agent = ibex_mem_intf_slave_agent::type_id::create("m_ibex_mem_intf_slave_agent", this);
core_agent = ibex_icache_core_agent::type_id::create("core_agent", this);
uvm_config_db#(ibex_icache_core_agent_cfg)::set(this, "core_agent*", "cfg", cfg.core_agent_cfg);
mem_agent = ibex_icache_mem_agent::type_id::create("mem_agent", this);
uvm_config_db#(ibex_icache_mem_agent_cfg)::set(this, "mem_agent*", "cfg", cfg.mem_agent_cfg);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
if (cfg.en_scb) begin
m_ibex_icache_core_agent.monitor.analysis_port.connect(scoreboard.core_fifo.analysis_export);
m_ibex_mem_intf_slave_agent.monitor.addr_ph_port.connect(scoreboard.mem_fifo.analysis_export);
core_agent.monitor.analysis_port.connect(scoreboard.core_fifo.analysis_export);
mem_agent.monitor.analysis_port.connect(scoreboard.mem_fifo.analysis_export);
end
if (cfg.is_active && cfg.m_ibex_icache_core_agent_cfg.is_active) begin
virtual_sequencer.core_sequencer_h = m_ibex_icache_core_agent.sequencer;
if (cfg.is_active && cfg.core_agent_cfg.is_active) begin
virtual_sequencer.core_sequencer_h = core_agent.sequencer;
end
if (cfg.is_active && m_ibex_mem_intf_slave_agent.get_is_active()) begin
virtual_sequencer.mem_sequencer_h = m_ibex_mem_intf_slave_agent.sequencer;
if (cfg.is_active && cfg.mem_agent_cfg.is_active) begin
virtual_sequencer.mem_sequencer_h = mem_agent.sequencer;
end
endfunction

View file

@ -5,19 +5,19 @@
class ibex_icache_env_cfg extends dv_base_env_cfg;
// ext component cfgs
rand ibex_icache_core_agent_cfg m_ibex_icache_core_agent_cfg;
rand ibex_icache_core_agent_cfg core_agent_cfg;
rand ibex_icache_mem_agent_cfg mem_agent_cfg;
`uvm_object_utils_begin(ibex_icache_env_cfg)
`uvm_field_object(m_ibex_icache_core_agent_cfg, UVM_DEFAULT)
`uvm_field_object(core_agent_cfg, UVM_DEFAULT)
`uvm_field_object(mem_agent_cfg, UVM_DEFAULT)
`uvm_object_utils_end
`uvm_object_new
virtual function void initialize(bit [TL_AW-1:0] csr_base_addr = '1);
// create ibex_icache agent config obj
m_ibex_icache_core_agent_cfg = ibex_icache_core_agent_cfg::type_id::create("m_ibex_icache_core_agent_cfg");
// create ibex_mem_intf_slave agent config obj
core_agent_cfg = ibex_icache_core_agent_cfg::type_id::create("core_agent_cfg");
mem_agent_cfg = ibex_icache_mem_agent_cfg::type_id::create ("mem_agent_cfg");
endfunction
endclass

View file

@ -8,7 +8,8 @@
* Covergroups may also be wrapped inside helper classes if needed.
*/
class ibex_icache_env_cov extends dv_base_env_cov #(.CFG_T(ibex_icache_env_cfg));
class ibex_icache_env_cov extends dv_base_env_cov #(.CFG_T (ibex_icache_env_cfg));
`uvm_component_utils(ibex_icache_env_cov)
// the base class provides the following handles for use:

View file

@ -8,7 +8,7 @@ package ibex_icache_env_pkg;
import top_pkg::*;
import dv_utils_pkg::*;
import ibex_icache_core_agent_pkg::*;
import ibex_mem_intf_agent_pkg::*;
import ibex_icache_mem_agent_pkg::*;
import dv_lib_pkg::*;
// macro includes
@ -18,7 +18,6 @@ package ibex_icache_env_pkg;
// parameters
// types
typedef dv_base_reg_block ibex_icache_reg_block;
// functions

View file

@ -2,17 +2,16 @@
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class ibex_icache_scoreboard extends dv_base_scoreboard #(
.CFG_T(ibex_icache_env_cfg),
.COV_T(ibex_icache_env_cov)
);
class ibex_icache_scoreboard
extends dv_base_scoreboard #(.CFG_T(ibex_icache_env_cfg), .COV_T(ibex_icache_env_cov));
`uvm_component_utils(ibex_icache_scoreboard)
// local variables
// TLM agent fifos
uvm_tlm_analysis_fifo #(ibex_icache_core_item) core_fifo;
uvm_tlm_analysis_fifo #(ibex_mem_intf_seq_item) mem_fifo;
uvm_tlm_analysis_fifo #(ibex_icache_core_item) core_fifo;
uvm_tlm_analysis_fifo #(ibex_icache_mem_resp_item) mem_fifo;
`uvm_component_new
@ -29,12 +28,12 @@ class ibex_icache_scoreboard extends dv_base_scoreboard #(
task run_phase(uvm_phase phase);
super.run_phase(phase);
fork
process_ibex_icache_fifo();
process_ibex_mem_intf_slave_fifo();
process_core_fifo();
process_mem_fifo();
join_none
endtask
virtual task process_ibex_icache_fifo();
virtual task process_core_fifo();
ibex_icache_core_item item;
forever begin
core_fifo.get(item);
@ -42,8 +41,8 @@ class ibex_icache_scoreboard extends dv_base_scoreboard #(
end
endtask
virtual task process_ibex_mem_intf_slave_fifo();
ibex_mem_intf_seq_item item;
virtual task process_mem_fifo();
ibex_icache_mem_resp_item item;
forever begin
mem_fifo.get(item);
`uvm_info(`gfn, $sformatf("received ibex_mem_intf_seq item:\n%0s", item.sprint()), UVM_HIGH)

View file

@ -2,14 +2,12 @@
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class ibex_icache_virtual_sequencer extends dv_base_virtual_sequencer #(
.CFG_T(ibex_icache_env_cfg),
.COV_T(ibex_icache_env_cov)
);
class ibex_icache_virtual_sequencer
extends dv_base_virtual_sequencer #(.CFG_T(ibex_icache_env_cfg), .COV_T(ibex_icache_env_cov));
`uvm_component_utils(ibex_icache_virtual_sequencer)
ibex_icache_core_sequencer core_sequencer_h;
ibex_mem_intf_slave_sequencer mem_sequencer_h;
ibex_icache_core_sequencer core_sequencer_h;
ibex_icache_mem_sequencer mem_sequencer_h;
`uvm_component_new

View file

@ -2,7 +2,8 @@
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class ibex_icache_base_vseq extends dv_base_vseq #(
class ibex_icache_base_vseq
extends dv_base_vseq #(
.CFG_T (ibex_icache_env_cfg),
.COV_T (ibex_icache_env_cov),
.VIRTUAL_SEQUENCER_T (ibex_icache_virtual_sequencer)

View file

@ -1,18 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class ibex_icache_common_vseq extends ibex_icache_base_vseq;
`uvm_object_utils(ibex_icache_common_vseq)
constraint num_trans_c {
num_trans inside {[1:2]};
}
`uvm_object_new
virtual task body();
// TODO: implement the body of the common virtual sequence
endtask : body
endclass

View file

@ -5,18 +5,29 @@
// Basic sanity test
class ibex_icache_sanity_vseq extends ibex_icache_base_vseq;
`uvm_object_utils(ibex_icache_sanity_vseq)
`uvm_object_utils(ibex_icache_sanity_vseq)
`uvm_object_new
// A sanity sequence for the core agent
// A sanity sequence for the core agent and a basic slave sequence for the memory agent
ibex_icache_core_sanity_seq core_seq;
ibex_icache_mem_resp_seq mem_seq;
task body();
// 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);
// Start the core and memory sequences. We use fork/join_any so that we don't wait for the
// memory sequence (which is reactive so will never finish).
fork
begin
`uvm_create_on(core_seq, p_sequencer.core_sequencer_h)
`DV_CHECK_RANDOMIZE_FATAL(core_seq)
core_seq.start(p_sequencer.core_sequencer_h);
end
begin
`uvm_create_on(mem_seq, p_sequencer.mem_sequencer_h)
`DV_CHECK_RANDOMIZE_FATAL(mem_seq)
mem_seq.start(p_sequencer.mem_sequencer_h);
end
join_any
endtask : body
endclass : ibex_icache_sanity_vseq

View file

@ -4,4 +4,3 @@
`include "ibex_icache_base_vseq.sv"
`include "ibex_icache_sanity_vseq.sv"
`include "ibex_icache_common_vseq.sv"

View file

@ -0,0 +1,22 @@
# ICache Memory UVM Agent
The ICache memory UVM agent models the instruction memory bus downstream of the ICache.
This also includes the PMP machinery.
The basic idea is that instruction memory is modelled with a single 32-bit *seed*.
A simple hash function takes this seed and calculates 32 bits of memory for each (aligned) base address.
The seed also determines a memory region which should result in PMP errors and another memory region that should result in memory errors.
The agent exposes a slave interface to the core using the usual UVM architecture for a reactive slave, but it looks a little complicated because we need to spot PMP errors immediately (rather than on the next clock edge).
The entire dance is as follows:
1. The monitor spots a new request (either because of a posedge on the `req` line or a change in the `addr` line).
1. It generates an `ibex_icache_mem_req_item` with `is_grant` field false and writes it to the sequencer's request port.
1. This request gets picked up by the sequence (in `ibex_icache_mem_resp_seq.sv`).
1. The sequence checks for a PMP error using its internal memory model and generates a sequence item (of type `ibex_icache_mem_resp_item`) with `is_grant` field false.
1. This sequence item is picked up by the driver, which uses it to drive the PMP line appropriately.
1. If the PMP response didn't squash the request, it will get granted at some point when the `grant` line goes high. At this point, the monitor will spot the request being granted and create another `ibex_icache_mem_req_item`, writing it to the sequencer's request port. This time, the `is_grant` field is true.
1. This request in turn gets picked up by the sequence (in `ibex_icache_mem_resp_seq.sv`).
1. The sequence uses its memory model to decide the data to be fetched and whether the response should have an error. The results get written into a sequence item with `is_grant` field true.
1. When the driver picks up this sequence item, it adds it to a response queue.
1. Responses get popped from the response queue in order. Each causes a delay of zero or more cycles and then the response is finally driven onto the bus with `rvalid`, `rdata` and `err`.

View file

@ -0,0 +1,32 @@
CAPI=2:
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
name: "lowrisc:dv:ibex_icache_mem_agent:0.1"
description: "IBEX_ICACHE DV UVM agent"
filesets:
files_dv:
depend:
- lowrisc:dv:dv_utils
- lowrisc:dv:dv_lib
files:
- ibex_icache_mem_if.sv
- ibex_icache_mem_agent_pkg.sv
- ibex_icache_mem_req_item.sv: {is_include_file: true}
- ibex_icache_mem_resp_item.sv: {is_include_file: true}
- ibex_icache_mem_agent_cfg.sv: {is_include_file: true}
- ibex_icache_mem_agent_cov.sv: {is_include_file: true}
- ibex_icache_mem_driver.sv: {is_include_file: true}
- ibex_icache_mem_monitor.sv: {is_include_file: true}
- ibex_icache_mem_agent.sv: {is_include_file: true}
- ibex_icache_mem_sequencer.sv: {is_include_file: true}
- ibex_icache_mem_model.sv: {is_include_file: true}
- seq_lib/ibex_icache_mem_base_seq.sv: {is_include_file: true}
- seq_lib/ibex_icache_mem_resp_seq.sv: {is_include_file: true}
- seq_lib/ibex_icache_mem_seq_list.sv: {is_include_file: true}
file_type: systemVerilogSource
targets:
default:
filesets:
- files_dv

View file

@ -0,0 +1,32 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class ibex_icache_mem_agent extends dv_base_agent #(
.CFG_T (ibex_icache_mem_agent_cfg),
.DRIVER_T (ibex_icache_mem_driver),
.SEQUENCER_T (ibex_icache_mem_sequencer),
.MONITOR_T (ibex_icache_mem_monitor),
.COV_T (ibex_icache_mem_agent_cov)
);
`uvm_component_utils(ibex_icache_mem_agent)
`uvm_component_new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// get ibex_icache_mem_if handle
if (!uvm_config_db#(virtual ibex_icache_mem_if)::get(this, "", "vif", cfg.vif)) begin
`uvm_fatal(`gfn, "failed to get ibex_icache_mem_if handle from uvm_config_db")
end
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
if (cfg.is_active) begin
// Pass snooped requests from the monitor to the sequencer
monitor.request_port.connect(sequencer.request_fifo.analysis_export);
end
endfunction
endclass

View file

@ -0,0 +1,19 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class ibex_icache_mem_agent_cfg extends dv_base_agent_cfg;
// Knobs
bit disable_pmp_errs = 0;
bit disable_mem_errs = 0;
// interface handle used by driver, monitor & the sequencer, via cfg handle
virtual ibex_icache_mem_if vif;
`uvm_object_param_utils_begin(ibex_icache_mem_agent_cfg)
`uvm_object_utils_end
`uvm_object_new
endclass

View file

@ -0,0 +1,20 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class ibex_icache_mem_agent_cov
extends dv_base_agent_cov #(.CFG_T (ibex_icache_mem_agent_cfg));
`uvm_component_utils(ibex_icache_mem_agent_cov)
// the base class provides the following handles for use:
// ibex_icache_mem_agent_cfg: cfg
// covergroups
function new(string name, uvm_component parent);
super.new(name, parent);
// instantiate all covergroups here
endfunction : new
endclass

View file

@ -0,0 +1,29 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
package ibex_icache_mem_agent_pkg;
// dep packages
import uvm_pkg::*;
import dv_utils_pkg::*;
import dv_lib_pkg::*;
// macro includes
`include "uvm_macros.svh"
`include "dv_macros.svh"
// parameters
// package sources
`include "ibex_icache_mem_req_item.sv"
`include "ibex_icache_mem_resp_item.sv"
`include "ibex_icache_mem_model.sv"
`include "ibex_icache_mem_agent_cfg.sv"
`include "ibex_icache_mem_agent_cov.sv"
`include "ibex_icache_mem_driver.sv"
`include "ibex_icache_mem_monitor.sv"
`include "ibex_icache_mem_sequencer.sv"
`include "ibex_icache_mem_agent.sv"
`include "ibex_icache_mem_seq_list.sv"
endpackage: ibex_icache_mem_agent_pkg

View file

@ -0,0 +1,136 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Drive the memory <-> icache interface
//
// There are 3 different signals getting driven here:
//
// (1) GNT: This toggles randomly, completely ignoring any other signals.
// (2) PMP_ERR: This can be set as a result of taking a bad request.
// (3) RDATA: This gets set with response data some time after granting a request.
class ibex_icache_mem_driver
extends dv_base_driver #(.ITEM_T (ibex_icache_mem_resp_item),
.CFG_T (ibex_icache_mem_agent_cfg));
int unsigned min_grant_delay = 0;
int unsigned max_grant_delay = 10;
mailbox #(ibex_icache_mem_resp_item) rdata_queue;
bit pmp_driven;
`uvm_component_utils(ibex_icache_mem_driver)
`uvm_component_new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
rdata_queue = new("rdata_queue");
pmp_driven = 1'b0;
endfunction
virtual task reset_signals();
cfg.vif.reset();
endtask
virtual task get_and_drive();
// None of these processes terminate
fork
drive_grant();
take_requests();
drive_responses();
join
endtask
// Drive the grant line. This toggles independently of any other signal on the bus.
task automatic drive_grant();
int gnt_delay;
forever begin
// Pick a number of cycles for the grant line to be low
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(gnt_delay,
gnt_delay dist {
min_grant_delay :/ 3,
[min_grant_delay+1 : max_grant_delay-1] :/ 1,
max_grant_delay :/ 1
};)
repeat(gnt_delay) @(cfg.vif.driver_cb);
// Set the grant line high for a cycle then go around again. Note that the grant line will be
// high for two consecutive cycles if gnt_delay is 0.
cfg.vif.driver_cb.gnt <= 1'b1;
@(cfg.vif.driver_cb);
cfg.vif.driver_cb.gnt <= 1'b0;
end
endtask
// Take requests from the sequencer.
task automatic take_requests();
forever begin
seq_item_port.get_next_item(req);
`uvm_info(`gfn, $sformatf("rcvd item:\n%0s", req.sprint()), UVM_HIGH)
// Is this a request or a grant?
if (!req.is_grant) begin
// If a request, we'll deal with it immediately, spawning a process that drives the PMP line
// until the request goes away.
fork
drive_pmp(req.err);
join_none
end else begin
// If a grant, we take a copy and add it to the response queue (handled by drive_responses)
$cast(rsp, req.clone());
rsp.set_id_info(req);
rdata_queue.put(rsp);
end
seq_item_port.item_done();
end
endtask
// Drive the rdata/valid/err response lines
task automatic drive_responses();
int unsigned delay;
ibex_icache_mem_resp_item item;
forever begin
rdata_queue.get(item);
cfg.vif.wait_clks(item.delay);
cfg.vif.send_response(item.err, item.rdata);
end
endtask
// Drive the PMP line.
//
// This task gets spawned by take_requests each time a new address comes in. The driver should
// have at most one instance running at once.
//
// If err is false, there is nothing to do. If err is true, it sets the PMP error flag and then
// waits until either req is de-asserted or the address changes, at which point it clears the flag
// and exits.
task automatic drive_pmp(bit err);
// This is a simple check to make sure that only one instance of the drive_pmp task is running
// at a time. If not, you'll have multiple drivers for the cfg.vif.pmp_err signal, which is
// probably going to be very confusing. The logic in the monitor's collect_requests() task is
// supposed to be in sync with this code so that this can't happen, but it can't hurt to make
// sure.
if (pmp_driven) begin
`uvm_error(`gfn, "drive_pmp is not re-entrant: bug in monitor?")
return;
end
if (! err)
return;
pmp_driven = 1'b1;
cfg.vif.pmp_err <= 1'b1;
// Wait for an address change or req to de-assert
@(negedge cfg.vif.req or cfg.vif.addr);
cfg.vif.pmp_err <= 1'b0;
pmp_driven = 1'b0;
endtask
endclass

View file

@ -0,0 +1,72 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
interface ibex_icache_mem_if (input clk);
// Requests
logic req;
logic gnt;
logic [31:0] addr;
// PMP errors
logic pmp_err;
// Response
logic rvalid;
logic [31:0] rdata;
logic err;
// Clocking block used by the driver
default clocking driver_cb @(posedge clk);
default output negedge;
output gnt;
output pmp_err;
output rvalid;
output rdata;
output err;
endclocking
// Clocking block used by the monitor
clocking monitor_cb @(posedge clk);
input req;
input gnt;
input addr;
input pmp_err;
input rvalid;
input rdata;
input err;
endclocking
// Reset all the signals from the memory bus to the cache (the other direction is controlled by
// the DUT).
task automatic reset();
driver_cb.rvalid <= 1'b0;
driver_cb.pmp_err <= 1'b0;
driver_cb.gnt <= 1'b0;
endtask
// Wait for num_clks posedges on the clk signal
task automatic wait_clks(int num_clks);
repeat (num_clks) @(driver_cb);
endtask
// Drive a response with the given rdata and possible error signals for a single cycle
task automatic send_response(logic rsp_err, logic [31:0] rsp_rdata);
driver_cb.rvalid <= 1'b1;
driver_cb.err <= rsp_err;
driver_cb.rdata <= rsp_rdata;
@(driver_cb);
driver_cb.rvalid <= 1'b0;
driver_cb.err <= 'X;
driver_cb.rdata <= 'X;
endtask
endinterface

View file

@ -0,0 +1,131 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// A simple "memory model" governed by a single seed
//
// We pick a contiguous PMP range that covers ~1/8 of the address space. Set
//
// pmp_lo = seed ^ 32'hdeadbeef
// pmp_hi = pmp_lo + min(32'd1 << (32 - 3), (~32'd0) - pmp_lo)
//
// then the disallowed PMP range will be [pmp_lo, pmp_hi].
//
// We'd like errors to be sparsely distributed in the address space, with 1/128 chance of any given
// address causing an error. We should probably do something more sensible, but for now let's XOR
// the address with something else (32'hf00dbeef) and pass it to the PMP calculation.
//
// For the memory content hash function, we use the first example from [1] (explicitly
// public-domain), except that we have 30+32 = 62 bits of input, which we just XOR together
// (aligning LSBs).
//
// [1] https://burtleburtle.net/bob/hash/integer.html
class ibex_icache_mem_model #(parameter int unsigned BusWidth = 32)
extends uvm_object;
protected bit [31:0] seed = 32'd0;
protected bit no_pmp_errs = 0;
protected bit no_mem_errs = 0;
function new(string name="", bit disable_pmp_errs=0, bit disable_mem_errs=0);
no_pmp_errs = disable_pmp_errs;
no_mem_errs = disable_mem_errs;
endfunction
`uvm_object_utils_begin(ibex_icache_mem_model)
`uvm_field_int (seed, UVM_DEFAULT | UVM_HEX)
`uvm_field_int (no_pmp_errs, UVM_DEFAULT)
`uvm_field_int (no_mem_errs, UVM_DEFAULT)
`uvm_object_utils_end
function void set_seed(bit [31:0] new_seed);
seed = new_seed;
endfunction
// Return true if reading from BusWidth bits from address should give an error
function automatic logic is_error(logic [31:0] address);
logic [31:0] rng_lo, rng_hi, rng_w0, rng_w1;
rng_lo = seed ^ 32'hdeadbeef;
rng_w0 = 32'd1 << (32 - 3);
rng_w1 = (~32'd0) - rng_lo;
rng_hi = rng_lo + ((rng_w0 < rng_w1) ? rng_w0 : rng_w1);
return ranges_overlap(address, address + BusWidth / 8, rng_lo, rng_hi);
endfunction
// Return true iff [lo0, hi0) and [lo1, hi1) have nonempty intersection
function automatic logic ranges_overlap(logic [31:0] lo0, logic [31:0] hi0,
logic [31:0] lo1, logic [31:0] hi1);
return (lo0 < hi1) && (lo1 < hi0);
endfunction
// Return true if reading BusWidth bits from address should give a PMP error
function automatic logic is_pmp_error(logic [31:0] address);
return (! no_pmp_errs) && is_error(address ^ 32'h12344321);
endfunction
// Return true if reading BusWidth bits from address should give a memory error
function automatic logic is_mem_error(logic [31:0] address);
return (! no_mem_errs) && is_error(address ^ 32'hf00dbeef);
endfunction
// Return true if reading BusWidth bits from address should give some sort of error
function automatic logic is_either_error(logic [31:0] address);
return is_pmp_error(address) || is_mem_error(address);
endfunction
// Return BusWidth bits of data from reading at address.
function automatic logic [BusWidth-1:0] read_data(logic [31:0] address);
logic [BusWidth-1:0] acc, word_data;
int word_count, lo_idx, lo_bit;
logic [29:0] word_addr;
// The number of 32-bit words we have to read is the number of words in BusWidth, plus one if
// the address is not word-aligned.
word_count = ((BusWidth + 31) / 32) + ((address & 32'd3) ? 1 : 0);
// The word address of the first word we're going to read.
word_addr = address >> 2;
// The accumulator that we'll fill with bits of data
acc = 0;
// The bottom bit that should be written to this iteration
lo_bit = 0;
for (int i = 0; i < word_count; i++) begin
// Note that the address sum might wrap (if we read off the top of memory), but that's not
// really a problem.
word_data = 0;
word_data[31:0] = read_word(word_addr + i[29:0]);
// The bottom valid byte in word_data is normally 0, but is positive if i is zero (the bottom)
// word and the read was misaligned. In that case, it equals i & 3.
lo_idx = (i > 0) ? 0 : (i & 3);
// We don't bother tracking the top valid byte: our left shifts will just push anything extra
// off the top anyway.
acc = acc | (((word_data >> lo_idx) << lo_idx) << lo_bit);
lo_bit = lo_bit + 32;
end
return acc;
endfunction
// Read 32 bits of data from reading at word_address.
function automatic logic [31:0] read_word(logic [29:0] address);
return hash({2'b0, address} ^ seed);
endfunction
// A 32-bit to 32-bit hash function (see notes at top of file)
function automatic logic [31:0] hash(logic [31:0] in);
in = (in ^ 32'd61) ^ (in >> 16);
in = in + (in << 3);
in = in ^ (in >> 4);
in = in * 32'h27d4eb2d;
return in ^ (in >> 15);
endfunction
endclass

View file

@ -0,0 +1,92 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class ibex_icache_mem_monitor
extends dv_base_monitor #(.ITEM_T (ibex_icache_mem_resp_item),
.CFG_T (ibex_icache_mem_agent_cfg),
.COV_T (ibex_icache_mem_agent_cov));
`uvm_component_utils(ibex_icache_mem_monitor)
`uvm_component_new
// the base class provides the following handles for use:
// ibex_icache_mem_agent_cfg: cfg
// ibex_icache_mem_agent_cov: cov
// uvm_analysis_port #(ibex_icache_resp_item): analysis_port
// Incoming requests. This gets hooked up to the sequencer to service requests
uvm_analysis_port #(ibex_icache_mem_req_item) request_port;
function automatic void build_phase(uvm_phase phase);
super.build_phase(phase);
request_port = new("request_port", this);
endfunction
task run_phase(uvm_phase phase);
super.run_phase(phase);
endtask
// Collect transactions forever. Forked in dv_base_moditor::run_phase
protected task automatic collect_trans(uvm_phase phase);
fork
collect_requests();
collect_grants();
join
endtask
// The collect_requests monitor is in charge of spotting changes to instr_addr_o when the req
// line is high
//
// This must collect the "I have a new address request" immediately, rather than at the next clock
// edge, to make sure that a PMP error can be signalled in the same cycle.
//
// Note that it's possible the underlying memory seed will change after the request has been seen.
// We don't try to spot this, and instead report the error state based on the seed when the
// request appeared.
task automatic collect_requests();
forever begin
// Wait for either a posedge on req or any change to the address
@(posedge cfg.vif.req or cfg.vif.addr);
// If the req line is high, pass the "new address" request to the driver
if (cfg.vif.req)
new_request(cfg.vif.addr);
end
endtask
// The collect_grants monitor is in charge of spotting granted requests which haven't been
// squashed by a PMP error, passing them back to the driver ("Hey, you granted this. Time to
// service it!")
task automatic collect_grants();
logic pending_req = 1'b0;
forever begin
if (cfg.vif.monitor_cb.req & cfg.vif.monitor_cb.gnt & !cfg.vif.monitor_cb.pmp_err)
new_grant(cfg.vif.monitor_cb.addr);
@(cfg.vif.monitor_cb);
end
endtask
// This is called immediately when an address is requested and is used to drive the PMP response
function automatic void new_request(logic [31:0] addr);
ibex_icache_mem_req_item item = new("item");
item.is_grant = 1'b0;
item.address = addr;
item.seed = '0;
request_port.write(item);
endfunction
// This is called on a clock edge when an request is granted
function automatic void new_grant(logic [31:0] addr);
ibex_icache_mem_req_item item = new("item");
item.is_grant = 1'b1;
item.address = addr;
`DV_CHECK_RANDOMIZE_FATAL(item)
request_port.write(item);
endfunction
endclass

View file

@ -0,0 +1,37 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// An item that represents a memory request from the icache and a possible update to the backing
// memory.
//
// When a request first comes in (via a posedge on the req line), it immediately generates a
// req_item with is_grant = 0. This is used by the driver to decide whether to generate a PMP error.
//
// Assuming that the request wasn't squashed by a PMP error, it will be granted on some later clock
// edge. At that point, another req_item is generated with is_grant = 1. This is added to a queue in
// the driver and will be serviced at some later point. This item may also have a seed update.
class ibex_icache_mem_req_item extends uvm_sequence_item;
bit is_grant;
logic [31:0] address;
rand bit [31:0] seed;
// Change the memory seed roughly one time in 500 reads
constraint c_seed_dist {
seed dist {
32'd0 :/ 499,
[1:32'hffffffff] :/ 1
};
}
`uvm_object_utils_begin(ibex_icache_mem_req_item)
`uvm_field_int (is_grant, UVM_DEFAULT)
`uvm_field_int (address, UVM_DEFAULT | UVM_HEX)
`uvm_field_int (seed, UVM_DEFAULT | UVM_HEX)
`uvm_object_utils_end
`uvm_object_new
endclass

View file

@ -0,0 +1,50 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// An item that represents a memory response to the icache
class ibex_icache_mem_resp_item
extends uvm_sequence_item;
int unsigned min_response_delay = 0;
int unsigned mid_response_delay = 5;
int unsigned max_response_delay = 50;
// True if this is a granted request. Otherwise, this is the first time we've seen an address (and
// we might need to drive the PMP line).
bit is_grant;
// If true, drive either a PMP error (if !is_grant) or respond later with a memory error.
bit err;
// Only has an effect if is_grant. The number of cycles to wait between reading this from the
// queue and responding with it.
rand int unsigned delay;
// Only has an effect if is_grant. The memory data to reply with (will be 'X if err is set)
logic [31:0] rdata;
constraint c_delay_dist {
delay dist {
min_response_delay :/ 5,
[min_response_delay+1:mid_response_delay] :/ 5,
[mid_response_delay+1:max_response_delay] :/ 1
};
}
// The delay field has no effect for requests (i.e. if is_grant is false). Force it to zero rather
// than leave mysterious numbers in the logs.
constraint c_no_delay_for_req {
(!is_grant) -> delay == 0;
}
`uvm_object_utils_begin(ibex_icache_mem_resp_item)
`uvm_field_int (is_grant, UVM_DEFAULT)
`uvm_field_int (err, UVM_DEFAULT)
`uvm_field_int (delay, UVM_DEFAULT)
`uvm_field_int (rdata, UVM_DEFAULT | UVM_HEX)
`uvm_object_utils_end
`uvm_object_new
endclass

View file

@ -0,0 +1,21 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// A sequencer class for the icache memory agent.
class ibex_icache_mem_sequencer
extends dv_base_sequencer #(.ITEM_T (ibex_icache_mem_resp_item),
.CFG_T (ibex_icache_mem_agent_cfg));
`uvm_component_utils(ibex_icache_mem_sequencer)
`uvm_component_new
uvm_tlm_analysis_fifo #(ibex_icache_mem_req_item) request_fifo;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
request_fifo = new("request_fifo", this);
endfunction
endclass

View file

@ -0,0 +1,17 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class ibex_icache_mem_base_seq
extends dv_base_seq #(.REQ (ibex_icache_mem_resp_item),
.CFG_T (ibex_icache_mem_agent_cfg),
.SEQUENCER_T (ibex_icache_mem_sequencer));
`uvm_object_utils(ibex_icache_mem_base_seq)
`uvm_object_new
virtual task body();
`uvm_fatal(`gtn, "Need to override this when you extend from this class!")
endtask
endclass

View file

@ -0,0 +1,59 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Slave response sequence
class ibex_icache_mem_resp_seq extends ibex_icache_mem_base_seq;
ibex_icache_mem_model #(.BusWidth (32)) mem_model;
`uvm_object_utils(ibex_icache_mem_resp_seq)
`uvm_object_new
task pre_start();
super.pre_start();
mem_model = new("mem_model", cfg.disable_pmp_errs, cfg.disable_mem_errs);
endtask
task body();
ibex_icache_mem_req_item req_item = new("req_item");
ibex_icache_mem_resp_item resp_item = new("resp_item");
forever begin
// Wait for a transaction request.
p_sequencer.request_fifo.get(req_item);
if (!req_item.is_grant) begin
// If this is a request (not a grant), check the memory model for a PMP error at this
// address. The other fields are ignored.
resp_item.is_grant = 1'b0;
resp_item.err = mem_model.is_pmp_error(req_item.address);
resp_item.rdata = 'X;
`uvm_info(`gfn,
$sformatf("Seen request at address 0x%08h (PMP error? %0d)",
req_item.address, resp_item.err),
UVM_LOW)
end else begin
// If this is a grant, take any new seed then check the memory model for a (non-PMP) error
// at this address. On success, look up the memory data too.
if (req_item.seed != 32'd0) begin
`uvm_info(`gfn, $sformatf("New memory seed: 0x%08h", req_item.seed), UVM_HIGH)
mem_model.set_seed(req_item.seed);
end
resp_item.is_grant = 1'b1;
resp_item.err = mem_model.is_mem_error(req_item.address);
resp_item.rdata = resp_item.err ? 'X : mem_model.read_data(req_item.address);
end
// Use the response item as an entry in the sequence, randomising any delay
start_item(resp_item);
`DV_CHECK_RANDOMIZE_FATAL(resp_item)
finish_item(resp_item);
end
endtask
endclass

View file

@ -0,0 +1,6 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
`include "ibex_icache_mem_base_seq.sv"
`include "ibex_icache_mem_resp_seq.sv"

View file

@ -42,7 +42,7 @@
{
name: ibex_icache_sanity
uvm_test_seq: ibex_icache_sanity_vseq
run_opts: ["+test_timeout_ns=10000"]
run_opts: ["+test_timeout_ns=1000000"]
}
// TODO: add more tests here

View file

@ -17,8 +17,9 @@ module tb;
// interfaces
clk_rst_if clk_rst_if(.clk(clk), .rst_n(rst_n));
ibex_icache_core_if core_if (.clk(clk));
ibex_mem_intf ibex_mem_intf();
ibex_icache_mem_if mem_if (.clk(clk));
// dut
ibex_icache dut (
@ -37,17 +38,24 @@ module tb;
.err_plus2_o (core_if.err_plus2),
.icache_enable_i (core_if.enable),
.icache_inval_i (core_if.invalidate),
.busy_o (core_if.busy)
.busy_o (core_if.busy),
// TODO: add remaining IOs and hook them
// Connect icache <-> instruction bus interface
.instr_req_o (mem_if.req),
.instr_gnt_i (mem_if.gnt),
.instr_addr_o (mem_if.addr),
.instr_rdata_i (mem_if.rdata),
.instr_err_i (mem_if.err),
.instr_pmp_err_i (mem_if.pmp_err),
.instr_rvalid_i (mem_if.rvalid)
);
initial begin
// 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_core_if)::set(null, "*.env.m_ibex_icache_core_agent*", "vif", core_if);
uvm_config_db#(virtual ibex_mem_intf)::set(null, "*.env.m_ibex_mem_intf_slave_agent*", "vif", ibex_mem_intf);
uvm_config_db#(virtual ibex_icache_core_if)::set(null, "*.env.core_agent*", "vif", core_if);
uvm_config_db#(virtual ibex_icache_mem_if)::set(null, "*.env.mem_agent*", "vif", mem_if);
$timeformat(-12, 0, " ps", 12);
run_test();
end