Added UVM testbench for mem arbiter

This commit is contained in:
Florian Zaruba 2017-05-01 20:43:43 +02:00
parent 1b343e7b11
commit 79d7859d65
13 changed files with 221 additions and 77 deletions

View file

@ -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 \

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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();

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
View 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

View file

@ -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 ),

View file

@ -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);

View file

@ -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