mirror of
https://github.com/lowRISC/ibex.git
synced 2025-04-22 12:57:13 -04:00
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:
parent
d0ea6fe449
commit
5cd874ea71
29 changed files with 845 additions and 72 deletions
3
dv/uvm/icache/dv/env/ibex_icache_env.core
vendored
3
dv/uvm/icache/dv/env/ibex_icache_env.core
vendored
|
@ -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
|
||||
|
||||
|
|
25
dv/uvm/icache/dv/env/ibex_icache_env.sv
vendored
25
dv/uvm/icache/dv/env/ibex_icache_env.sv
vendored
|
@ -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
|
||||
|
||||
|
|
12
dv/uvm/icache/dv/env/ibex_icache_env_cfg.sv
vendored
12
dv/uvm/icache/dv/env/ibex_icache_env_cfg.sv
vendored
|
@ -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
|
||||
|
|
3
dv/uvm/icache/dv/env/ibex_icache_env_cov.sv
vendored
3
dv/uvm/icache/dv/env/ibex_icache_env_cov.sv
vendored
|
@ -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:
|
||||
|
|
3
dv/uvm/icache/dv/env/ibex_icache_env_pkg.sv
vendored
3
dv/uvm/icache/dv/env/ibex_icache_env_pkg.sv
vendored
|
@ -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
|
||||
|
||||
|
|
21
dv/uvm/icache/dv/env/ibex_icache_scoreboard.sv
vendored
21
dv/uvm/icache/dv/env/ibex_icache_scoreboard.sv
vendored
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -4,4 +4,3 @@
|
|||
|
||||
`include "ibex_icache_base_vseq.sv"
|
||||
`include "ibex_icache_sanity_vseq.sv"
|
||||
`include "ibex_icache_common_vseq.sv"
|
||||
|
|
22
dv/uvm/icache/dv/ibex_icache_mem_agent/README.md
Normal file
22
dv/uvm/icache/dv/ibex_icache_mem_agent/README.md
Normal 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`.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
136
dv/uvm/icache/dv/ibex_icache_mem_agent/ibex_icache_mem_driver.sv
Normal file
136
dv/uvm/icache/dv/ibex_icache_mem_agent/ibex_icache_mem_driver.sv
Normal 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
|
72
dv/uvm/icache/dv/ibex_icache_mem_agent/ibex_icache_mem_if.sv
Normal file
72
dv/uvm/icache/dv/ibex_icache_mem_agent/ibex_icache_mem_if.sv
Normal 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
|
131
dv/uvm/icache/dv/ibex_icache_mem_agent/ibex_icache_mem_model.sv
Normal file
131
dv/uvm/icache/dv/ibex_icache_mem_agent/ibex_icache_mem_model.sv
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue