mirror of
https://github.com/openhwgroup/cva6.git
synced 2025-04-24 22:27:10 -04:00
✅ Added UVM testbench for mem arbiter
This commit is contained in:
parent
1b343e7b11
commit
79d7859d65
13 changed files with 221 additions and 77 deletions
2
Makefile
2
Makefile
|
@ -71,7 +71,7 @@ $(tests):
|
|||
# Optimize top level
|
||||
vopt${questa_version} ${compile_flag} $@_tb -o $@_tb_optimized +acc -check_synthesis
|
||||
# vsim${questa_version} $@_tb_optimized
|
||||
# vsim${questa_version} +UVM_TESTNAME=$@_test -coverage -do "coverage save -onexit $@.ucdb; run -a; quit -code [coverage attribute -name TESTSTATUS -concise]" $@_tb_optimized
|
||||
vsim${questa_version} +UVM_TESTNAME=$@_test -c -coverage -classdebug -do "coverage save -onexit $@.ucdb; run -a; quit -code [coverage attribute -name TESTSTATUS -concise]" $@_tb_optimized
|
||||
# User Verilator to lint the target
|
||||
lint:
|
||||
verilator ${src} --lint-only \
|
||||
|
|
2
fifo.sv
2
fifo.sv
|
@ -42,7 +42,7 @@ module fifo #(
|
|||
// actual memory
|
||||
dtype [DEPTH-1:0] mem_n, mem_q;
|
||||
|
||||
assign full_o = (status_cnt_q == DEPTH);
|
||||
assign full_o = (status_cnt_q == DEPTH-1);
|
||||
assign empty_o = (status_cnt_q == 0);
|
||||
assign single_element_o = (status_cnt_q == 1);
|
||||
// read and write queue logic
|
||||
|
|
|
@ -32,20 +32,18 @@ interface mem_if
|
|||
// as an active or passive device
|
||||
// only helpful thread so far:
|
||||
// https://verificationacademy.com/forums/uvm/getting-multiply-driven-warnings-vsim-passive-agent
|
||||
logic data_gnt_driver = 'z;
|
||||
assign data_gnt = data_gnt_driver;
|
||||
|
||||
// Memory interface configured as master
|
||||
clocking mck @(posedge clk);
|
||||
// default input #1ns output #1ns;
|
||||
input address, data_wdata, data_req, data_we, data_be;
|
||||
output data_gnt, data_rvalid, data_rdata;
|
||||
default input #1ns output #1ns;
|
||||
input address, data_wdata, data_we, data_req, data_be;
|
||||
output data_rvalid, data_rdata, data_gnt;
|
||||
endclocking
|
||||
// Memory interface configured as slave
|
||||
clocking sck @(posedge clk);
|
||||
// default input #1ns output #1ns;
|
||||
output address, data_wdata, data_req, data_we, data_be;
|
||||
input data_gnt, data_rvalid, data_rdata;
|
||||
default input #1ns output #1ns;
|
||||
output address, data_wdata, data_we, data_req, data_be;
|
||||
input data_rvalid, data_rdata, data_gnt;
|
||||
endclocking
|
||||
|
||||
clocking pck @(posedge clk);
|
||||
|
@ -65,5 +63,6 @@ interface mem_if
|
|||
input data_gnt, data_rvalid, data_rdata
|
||||
);
|
||||
// modport Passive (clocking pck);
|
||||
|
||||
endinterface
|
||||
`endif
|
|
@ -130,4 +130,31 @@ module mem_arbiter #(
|
|||
end
|
||||
end
|
||||
|
||||
// ------------
|
||||
// Assertions
|
||||
// ------------
|
||||
|
||||
`ifndef synthesis
|
||||
`ifndef VERILATOR
|
||||
// make sure that we eventually get an rvalid after we received a grant
|
||||
assert property (@(posedge clk_i) data_gnt_i |-> ##[1:$] data_rvalid_i )
|
||||
else begin $error("There was a grant without a rvalid"); $stop(); end
|
||||
// assert that there is no grant without a request
|
||||
assert property (@(negedge clk_i) data_gnt_i |-> data_req_o)
|
||||
else begin $error("There was a grant without a request."); $stop(); end
|
||||
// assert that the address does not contain X when request is sent
|
||||
assert property ( @(posedge clk_i) (data_req_o) |-> (!$isunknown(address_o)) )
|
||||
else begin $error("address contains X when request is set"); $stop(); end
|
||||
|
||||
// there should be no rvalid when we are in IDLE
|
||||
// assert property (
|
||||
// @(posedge clk) (CS == IDLE) |-> (data_rvalid_i == 1'b0) )
|
||||
// else begin $error("Received rvalid while in IDLE state"); $stop(); end
|
||||
|
||||
// assert that errors are only sent at the same time as grant or rvalid
|
||||
// assert property ( @(posedge clk) (data_err_i) |-> (data_gnt_i || data_rvalid_i) )
|
||||
// else begin $error("Error without data grant or rvalid"); $stop(); end
|
||||
|
||||
`endif
|
||||
`endif
|
||||
endmodule
|
|
@ -44,20 +44,27 @@ class mem_if_driver extends uvm_driver #(mem_if_seq_item);
|
|||
logic [63:0] address [$];
|
||||
logic [63:0] addr;
|
||||
logic slave_data_gnt;
|
||||
semaphore lock = new(1);
|
||||
slave_data_gnt = 1'b1;
|
||||
// grant process is combinatorial
|
||||
fork
|
||||
slave_gnt: begin
|
||||
fu.mck.data_gnt <= 1'b1;
|
||||
forever begin
|
||||
slave_data_gnt = 1'b0;
|
||||
fu.data_gnt_driver = slave_data_gnt;
|
||||
|
||||
// @(fu.mck);
|
||||
// wait until we got a valid request
|
||||
wait (fu.data_req);
|
||||
// randomize grant delay - the grant may come in the same clock cycle
|
||||
repeat ($urandom_range(0,3)) @(fu.mck);
|
||||
fu.mck.data_gnt <= 1'b1;
|
||||
repeat ($urandom_range(0,3)) @(fu.mck);
|
||||
fu.mck.data_gnt <= 1'b0;
|
||||
// now set the grant to one
|
||||
slave_data_gnt = 1'b1;
|
||||
fu.data_gnt_driver = slave_data_gnt;
|
||||
@(fu.mck);
|
||||
// if the request is still here go for it
|
||||
// end else begin
|
||||
// fu.mck.data_gnt <= 1'b0;
|
||||
// slave_data_gnt = 1'b0;
|
||||
// end
|
||||
// do we have another request?
|
||||
end
|
||||
end
|
||||
|
@ -70,17 +77,18 @@ class mem_if_driver extends uvm_driver #(mem_if_seq_item);
|
|||
fork
|
||||
// replay interface
|
||||
imem_read: begin
|
||||
@(fu.mck);
|
||||
if (slave_data_gnt) begin
|
||||
if (fu.pck.data_gnt & fu.mck.data_req) begin
|
||||
// $display("Time: %t, Pushing", $time);
|
||||
address.push_back(fu.mck.address);
|
||||
if (address.size() != 0) begin
|
||||
// we an wait a couple of cycles here
|
||||
// but at least one
|
||||
lock.get(1);
|
||||
repeat ($urandom_range(1,3)) @(fu.mck);
|
||||
fu.mck.data_rvalid <= 1'b1;
|
||||
addr = address.pop_front();
|
||||
fu.mck.data_rdata <= addr;
|
||||
lock.put(1);
|
||||
end else
|
||||
fu.mck.data_rvalid <= 1'b0;
|
||||
end
|
||||
|
@ -110,15 +118,16 @@ class mem_if_driver extends uvm_driver #(mem_if_seq_item);
|
|||
// we don't care about results at this point
|
||||
forever begin
|
||||
seq_item_port.get_next_item(cmd);
|
||||
do begin
|
||||
// do begin
|
||||
fu.sck.data_req <= 1'b1;
|
||||
fu.sck.address <= cmd.address;
|
||||
fu.sck.data_be <= cmd.be;
|
||||
fu.sck.data_we <= (cmd.mode == READ) ? 1'b0 : 1'b1;
|
||||
fu.sck.data_wdata <= cmd.data;
|
||||
@(fu.sck);
|
||||
end while (~fu.sck.data_gnt);
|
||||
|
||||
@(fu.sck iff fu.sck.data_gnt);
|
||||
fu.sck.data_req <= 1'b0;
|
||||
|
||||
// delay the next request
|
||||
repeat(cmd.requestDelay) @(fu.sck);
|
||||
seq_item_port.item_done();
|
||||
|
|
|
@ -50,19 +50,42 @@ class mem_if_monitor extends uvm_component;
|
|||
endfunction
|
||||
|
||||
task run_phase(uvm_phase phase);
|
||||
|
||||
logic[63:0] address [$];
|
||||
logic[7:0] be [$];
|
||||
mem_if_seq_item cmd = mem_if_seq_item::type_id::create("cmd");
|
||||
mem_if_seq_item cloned_item;
|
||||
|
||||
// Monitor
|
||||
// we should also distinguish between slave and master here
|
||||
fork
|
||||
// detect a request
|
||||
forever begin
|
||||
@(fu.pck iff fu.pck.data_rvalid);
|
||||
`uvm_info("MEM IF MONITOR", "Got rvalid", UVM_MEDIUM);
|
||||
end
|
||||
// wait until detecting a valid request -> store be and address
|
||||
@(fu.pck iff (fu.pck.data_gnt & fu.pck.data_req));
|
||||
// if (m_cfg.mem_if_config == MASTER)
|
||||
// $display("Pushing Address: %0h", fu.pck.address);
|
||||
address.push_back(fu.pck.address);
|
||||
be.push_back(fu.pck.data_be);
|
||||
|
||||
end
|
||||
// request finished send it to the monitor
|
||||
forever begin
|
||||
mem_if_seq_item cloned_item;
|
||||
automatic logic [63:0] addr;
|
||||
// wait for the rvalid minimum a cycle later
|
||||
@(fu.pck iff fu.pck.data_rvalid);
|
||||
addr = address.pop_front();
|
||||
// if (m_cfg.mem_if_config == MASTER)
|
||||
// $display("Popping Address: %0h", addr);
|
||||
cmd.address = addr;
|
||||
cmd.be = be.pop_front();
|
||||
cmd.data = fu.pck.data_rdata;
|
||||
// was this from a master or slave agent monitor?
|
||||
cmd.isSlaveAnswer = (m_cfg.mem_if_config == SLAVE) ? 1'b1 : 1'b0;
|
||||
// export the item via the analysis port
|
||||
$cast(cloned_item, cmd.clone());
|
||||
m_ap.write(cloned_item);
|
||||
end
|
||||
|
||||
join_none
|
||||
|
||||
endtask : run_phase
|
||||
endclass : mem_if_monitor
|
||||
|
|
|
@ -27,6 +27,7 @@ class mem_if_seq_item extends uvm_sequence_item;
|
|||
rand logic [7:0] be;
|
||||
rand int requestDelay;
|
||||
mode_t mode;
|
||||
logic isSlaveAnswer;
|
||||
|
||||
constraint delay_bounds {
|
||||
requestDelay inside {[0:10]};
|
||||
|
@ -53,6 +54,7 @@ class mem_if_seq_item extends uvm_sequence_item;
|
|||
data = rhs_.data;
|
||||
be = rhs_.be;
|
||||
mode = rhs_.mode;
|
||||
isSlaveAnswer = rhs_.isSlaveAnswer;
|
||||
endfunction:do_copy
|
||||
|
||||
function bit do_compare(uvm_object rhs, uvm_comparer comparer);
|
||||
|
@ -75,7 +77,7 @@ class mem_if_seq_item extends uvm_sequence_item;
|
|||
string s;
|
||||
|
||||
$sformat(s, "%s\n", super.convert2string());
|
||||
$sformat(s, "%s\n Mode: %s\n Address: %0h \nData: %0h \nBE: %0h \n", s, mode.name, address, data, be);
|
||||
$sformat(s, "%s\nMode: %s\nAddress: %0h\nData: %0h\nBE: %0h \nisSlaveAnswer: %h", s, mode.name, address, data, be, isSlaveAnswer);
|
||||
return s;
|
||||
|
||||
endfunction:convert2string
|
||||
|
|
39
tb/env/mem_arbiter/mem_arbiter_env.svh
vendored
39
tb/env/mem_arbiter/mem_arbiter_env.svh
vendored
|
@ -27,6 +27,8 @@ class mem_arbiter_env extends uvm_env;
|
|||
|
||||
mem_if_sequencer m_mem_if_sequencers[3];
|
||||
mem_arbiter_env_config m_cfg;
|
||||
|
||||
mem_arbiter_scoreboard m_scoreboard;
|
||||
//------------------------------------------
|
||||
// Methods
|
||||
//------------------------------------------
|
||||
|
@ -46,35 +48,30 @@ class mem_arbiter_env extends uvm_env;
|
|||
"mem_if_agent_config",
|
||||
m_cfg.m_mem_if_slave_agent);
|
||||
|
||||
uvm_config_db #(mem_if_agent_config)::set(this, "m_mem_if_master0*",
|
||||
"mem_if_agent_config",
|
||||
m_cfg.m_mem_if_master_agents[0]);
|
||||
|
||||
uvm_config_db #(mem_if_agent_config)::set(this, "m_mem_if_master1*",
|
||||
"mem_if_agent_config",
|
||||
m_cfg.m_mem_if_master_agents[1]);
|
||||
|
||||
uvm_config_db #(mem_if_agent_config)::set(this, "m_mem_if_master2*",
|
||||
"mem_if_agent_config",
|
||||
m_cfg.m_mem_if_master_agents[2]);
|
||||
|
||||
m_mem_if_slave_agent = mem_if_agent::type_id::create("m_mem_if_slave_agent", this);
|
||||
|
||||
// create 3 master memory interfaces
|
||||
m_mem_if_master_agents[0] = mem_if_agent::type_id::create("m_mem_if_master0_agent", this);
|
||||
m_mem_if_master_agents[1] = mem_if_agent::type_id::create("m_mem_if_master1_agent", this);
|
||||
m_mem_if_master_agents[2] = mem_if_agent::type_id::create("m_mem_if_master2_agent", this);
|
||||
for (int i = 0; i < 3; i++) begin
|
||||
uvm_config_db #(mem_if_agent_config)::set(this, {"m_mem_if_master", i, "*"},
|
||||
"mem_if_agent_config",
|
||||
m_cfg.m_mem_if_master_agents[i]);
|
||||
|
||||
m_mem_if_master_agents[i] = mem_if_agent::type_id::create({"m_mem_if_master", i, "_agent"}, this);
|
||||
// Get 3 sequencers
|
||||
m_mem_if_sequencers[0] = mem_if_sequencer::type_id::create("m_mem_if_sequencer0", this);
|
||||
m_mem_if_sequencers[1] = mem_if_sequencer::type_id::create("m_mem_if_sequencer1", this);
|
||||
m_mem_if_sequencers[2] = mem_if_sequencer::type_id::create("m_mem_if_sequencer2", this);
|
||||
|
||||
m_mem_if_sequencers[i] = mem_if_sequencer::type_id::create({"m_mem_if_sequencer", i}, this);
|
||||
end
|
||||
// instantiate the scoreboard
|
||||
m_scoreboard = mem_arbiter_scoreboard::type_id::create("m_scoreboard", this);
|
||||
endfunction:build_phase
|
||||
|
||||
function void connect_phase(uvm_phase phase);
|
||||
m_mem_if_sequencers[0] = m_mem_if_master_agents[0].m_sequencer;
|
||||
m_mem_if_sequencers[1] = m_mem_if_master_agents[1].m_sequencer;
|
||||
m_mem_if_sequencers[2] = m_mem_if_master_agents[2].m_sequencer;
|
||||
// connect the sequencers and monitor to the master agents
|
||||
for (int i = 0; i < 3; i++) begin
|
||||
m_mem_if_sequencers[i] = m_mem_if_master_agents[i].m_sequencer;
|
||||
m_mem_if_master_agents[i].m_monitor.m_ap.connect(m_scoreboard.item_export);
|
||||
end
|
||||
// connect the slave monitor to the scoreboard
|
||||
m_mem_if_slave_agent.m_monitor.m_ap.connect(m_scoreboard.item_export);
|
||||
endfunction: connect_phase
|
||||
endclass : mem_arbiter_env
|
||||
|
|
3
tb/env/mem_arbiter/mem_arbiter_env_pkg.sv
vendored
3
tb/env/mem_arbiter/mem_arbiter_env_pkg.sv
vendored
|
@ -19,8 +19,11 @@ package mem_arbiter_env_pkg;
|
|||
`include "uvm_macros.svh"
|
||||
// Testbench related imports
|
||||
import mem_if_agent_pkg::*;
|
||||
// Include the scoreboard
|
||||
`include "mem_arbiter_scoreboard.svh"
|
||||
// Includes for the config for the environment
|
||||
`include "mem_arbiter_env_config.svh"
|
||||
// Includes the environment
|
||||
`include "mem_arbiter_env.svh"
|
||||
|
||||
endpackage
|
||||
|
|
58
tb/env/mem_arbiter/mem_arbiter_scoreboard.svh
vendored
Executable file
58
tb/env/mem_arbiter/mem_arbiter_scoreboard.svh
vendored
Executable file
|
@ -0,0 +1,58 @@
|
|||
// Author: Florian Zaruba, ETH Zurich
|
||||
// Date: 01.05.2017
|
||||
// Description: Memory Arbiter scoreboard
|
||||
//
|
||||
// Copyright (C) 2017 ETH Zurich, University of Bologna
|
||||
// All rights reserved.
|
||||
// This code is under development and not yet released to the public.
|
||||
// Until it is released, the code is under the copyright of ETH Zurich and
|
||||
// the University of Bologna, and may contain confidential and/or unpublished
|
||||
// work. Any reuse/redistribution is strictly forbidden without written
|
||||
// permission from ETH Zurich.
|
||||
// Bug fixes and contributions will eventually be released under the
|
||||
// SolderPad open hardware license in the context of the PULP platform
|
||||
// (http://www.pulp-platform.org), under the copyright of ETH Zurich and the
|
||||
// University of Bologna.
|
||||
class mem_arbiter_scoreboard extends uvm_scoreboard;
|
||||
|
||||
`uvm_component_utils(mem_arbiter_scoreboard);
|
||||
int slave_answer_cnt, master_answer_cnt;
|
||||
|
||||
uvm_analysis_imp #(mem_if_seq_item, mem_arbiter_scoreboard) item_export;
|
||||
|
||||
function new(string name, uvm_component parent);
|
||||
super.new(name, parent);
|
||||
endfunction : new
|
||||
|
||||
function void build_phase(uvm_phase phase);
|
||||
super.build_phase(phase);
|
||||
item_export = new("item_slave_export", this);
|
||||
slave_answer_cnt = 0;
|
||||
master_answer_cnt = 0;
|
||||
endfunction : build_phase
|
||||
|
||||
virtual function void write (mem_if_seq_item seq_item);
|
||||
// $display("%s", seq_item.convert2string());
|
||||
// the answer is from a master
|
||||
// the address and data should be the same when we use the replay system
|
||||
if (~seq_item.isSlaveAnswer) begin
|
||||
if (seq_item.address !== seq_item.data) begin
|
||||
`uvm_error( "Mem Arbiter Scoreboard", $sformatf("Got: %0h Expected: %0h", seq_item.address, seq_item.data) )
|
||||
end
|
||||
master_answer_cnt++;
|
||||
end else begin
|
||||
slave_answer_cnt++;
|
||||
end
|
||||
|
||||
endfunction
|
||||
|
||||
// check here for the total amount of transactions
|
||||
virtual function void extract_phase( uvm_phase phase );
|
||||
super.extract_phase(phase);
|
||||
if (master_answer_cnt !== slave_answer_cnt) begin
|
||||
`uvm_error("Mem Arbiter Scoreboard", $sformatf("Mismatch in overall result count. Expected: %d Got: %d", slave_answer_cnt, master_answer_cnt))
|
||||
end else
|
||||
`uvm_info("Mem Arbiter Scoreboard", $sformatf("Overall result count: Expected: %d Got: %d", slave_answer_cnt, master_answer_cnt), UVM_LOW)
|
||||
endfunction : extract_phase
|
||||
|
||||
endclass : mem_arbiter_scoreboard
|
|
@ -28,6 +28,14 @@ module mem_arbiter_tb;
|
|||
|
||||
mem_if master[3](clk);
|
||||
mem_if slave(clk);
|
||||
// super hack in assigning the wire a value
|
||||
// we need to keep all interface signals as wire as
|
||||
// the simulator does not now if this interface will be used
|
||||
// as an active or passive device
|
||||
// only helpful thread so far:
|
||||
// https://verificationacademy.com/forums/uvm/getting-multiply-driven-warnings-vsim-passive-agent
|
||||
// logic data_gnt_driver = 'z;
|
||||
// assign data_gnt = data_gnt_driver & data_req;
|
||||
|
||||
mem_arbiter dut (
|
||||
.clk_i ( clk ),
|
||||
|
@ -39,7 +47,7 @@ module mem_arbiter_tb;
|
|||
.data_req_o ( slave.data_req ),
|
||||
.data_we_o ( slave.data_we ),
|
||||
.data_be_o ( slave.data_be ),
|
||||
.data_gnt_i ( slave.data_gnt ),
|
||||
.data_gnt_i ( slave.data_req & slave.data_gnt ),
|
||||
.data_rvalid_i ( slave.data_rvalid ),
|
||||
.data_rdata_i ( slave.data_rdata ),
|
||||
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
// Author: Florian Zaruba, ETH Zurich
|
||||
// Date: 01.05.2017
|
||||
// Description: Randomized test sequence
|
||||
//
|
||||
// Copyright (C) 2017 ETH Zurich, University of Bologna
|
||||
// All rights reserved.
|
||||
// This code is under development and not yet released to the public.
|
||||
// Until it is released, the code is under the copyright of ETH Zurich and
|
||||
// the University of Bologna, and may contain confidential and/or unpublished
|
||||
// work. Any reuse/redistribution is strictly forbidden without written
|
||||
// permission from ETH Zurich.
|
||||
// Bug fixes and contributions will eventually be released under the
|
||||
// SolderPad open hardware license in the context of the PULP platform
|
||||
// (http://www.pulp-platform.org), under the copyright of ETH Zurich and the
|
||||
// University of Bologna.
|
||||
|
||||
class mem_arbiter_sequence extends mem_if_sequence;
|
||||
|
||||
`uvm_object_utils(mem_arbiter_sequence);
|
||||
|
|
|
@ -38,9 +38,9 @@ class mem_arbiter_test extends mem_arbiter_test_base;
|
|||
endtask
|
||||
|
||||
task run_phase(uvm_phase phase);
|
||||
uvm_objection objection;
|
||||
phase.raise_objection(this, "mem_arbiter_test");
|
||||
#200ns;
|
||||
//fibonacci_sequence fibonacci;
|
||||
super.run_phase(phase);
|
||||
// fork three sequencers and wait for all of them to finish
|
||||
// until dropping the objection again
|
||||
|
@ -50,7 +50,9 @@ class mem_arbiter_test extends mem_arbiter_test_base;
|
|||
start_sequence(2);
|
||||
join
|
||||
// Testlogic goes here
|
||||
#100ns;
|
||||
// drain time until the objection gets dropped
|
||||
objection = phase.get_objection();
|
||||
objection.set_drain_time(this, 100ns );
|
||||
phase.drop_objection(this, "mem_arbiter_test");
|
||||
endtask
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue