APB协议UVM验证环境的搭建
一、编译文件
只需编译这两个文件即可
apb_pkg.sv
里面包含了"apb.svh",即编译apb_pkg.sv这个文件的同时,也会编译所需要的所有的头文件。
`ifndef APB_PKG_SV
`define APB_PKG_SV
package apb_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "apb.svh"
endpackage : apb_pkg
`endif // `ifndef APB_PKG_SV
apb.svh
`ifndef APB_SVH
`define APB_SVH
`include "apb_transfer.sv"
`include "apb_config.sv"
//master所有的头文件
`include "apb_master_driver.svh"
`include "apb_master_monitor.svh"
`include "apb_master_sequencer.svh"
`include "apb_master_agent.svh"
//slave所有的头文件
`include "apb_slave_driver.svh"
`include "apb_slave_monitor.svh"
`include "apb_slave_sequencer.svh"
`include "apb_slave_agent.svh"
//master头文件里面具体的实现方法
`include "apb_master_driver.sv"
`include "apb_master_monitor.sv"
`include "apb_master_sequencer.sv"
`include "apb_master_agent.sv"
`include "apb_master_seq_lib.sv"
//slave头文件里面具体的实现方法
`include "apb_slave_driver.sv"
`include "apb_slave_monitor.sv"
`include "apb_slave_sequencer.sv"
`include "apb_slave_agent.sv"
`include "apb_slave_seq_lib.sv"
`endif // `ifndef APB_SVH
再来编译apb_tb.sv文件
编译的同时,也会编译"apb_tests.svh"、"apb_if.sv"这两个文件。例化协议接口,配置顶层环境的master和slave,默认执行“apb_single_transaction_test”这个测试用例。
`timescale 1ps/1ps
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "apb_tests.svh"
`include "apb_if.sv"
module apb_tb;
bit clk, rstn;
initial begin
fork
begin
forever #5ns clk = !clk;
end
begin
#100ns;
rstn <= 1'b1;
#100ns;
rstn <= 1'b0;
#100ns;
rstn <= 1'b1;
end
join_none
end
apb_if intf(clk, rstn);
initial begin
uvm_config_db#(virtual apb_if)::set(uvm_root::get(), "uvm_test_top.env.mst", "vif", intf);
uvm_config_db#(virtual apb_if)::set(uvm_root::get(), "uvm_test_top.env.slv", "vif", intf);
run_test("apb_single_transaction_test");
end
endmodule
apb_tests.svh
`ifndef APB_TESTS_SV
`define APB_TESTS_SV
import apb_pkg::*;
class apb_env extends uvm_env;
apb_master_agent mst;
apb_slave_agent slv;
`uvm_component_utils(apb_env)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mst = apb_master_agent::type_id::create("mst", this);
slv = apb_slave_agent::type_id::create("slv", this);
endfunction
endclass
class apb_base_test extends uvm_test;
apb_env env;
`uvm_component_utils(apb_base_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = apb_env::type_id::create("env", this);
endfunction
endclass
class apb_base_test_sequence extends uvm_sequence #(apb_transfer);
bit[31:0] mem[bit[31:0]]; //关联数组mem,用来master和slave之间的数据比对,test和slave中都有一个mem
`uvm_object_utils(apb_base_test_sequence)
function new(string name="");
super.new(name);
endfunction : new
function bit check_mem_data(bit[31:0] addr, bit[31:0] data);
if(mem.exists(addr)) begin
if(data != mem[addr]) begin
`uvm_error("CMPDATA", $sformatf("addr 32'h%8x, READ DATA expected 32'h%8x != actual 32'h%8x", addr, mem[addr], data))
return 0;
end
else begin
`uvm_info("CMPDATA", $sformatf("addr 32'h%8x, READ DATA 32'h%8x comparing success!", addr, data), UVM_LOW)
return 1;
end
end
else begin
if(data != 0) begin
`uvm_error("CMPDATA", $sformatf("addr 32'h%8x, READ DATA expected 32'h00000000 != actual 32'h%8x", addr, data))
return 0;
end
else begin
`uvm_info("CMPDATA", $sformatf("addr 32'h%8x, READ DATA 32'h%8x comparing success!", addr, data), UVM_LOW)
return 1;
end
end
endfunction: check_mem_data
task wait_reset_release();
@(negedge apb_tb.rstn);
@(posedge apb_tb.rstn);
endtask
task wait_cycles(int n);
repeat(n) @(posedge apb_tb.clk);
endtask
function bit[31:0] get_rand_addr();
bit[31:0] addr;
void'(std::randomize(addr) with {addr[31:12] == 0; addr[1:0] == 0;});
return addr;
endfunction
endclass
class apb_single_transaction_sequence extends apb_base_test_sequence;
apb_master_single_write_sequence single_write_seq;
apb_master_single_read_sequence single_read_seq;
apb_master_write_read_sequence write_read_seq;
rand int test_num = 100;
constraint cstr{
soft test_num == 100;
}
`uvm_object_utils(apb_single_transaction_sequence)
function new(string name="");
super.new(name);
endfunction : new
task body();
bit[31:0] addr;
this.wait_reset_release();
this.wait_cycles(10);
// TEST continous write transaction
`uvm_info(get_type_name(), "TEST continous write transaction...", UVM_LOW)
repeat(test_num) begin
addr = this.get_rand_addr();
`uvm_do_with(single_write_seq, {addr == local::addr; data == local::addr;})
mem[addr] = addr;
end
// TEST continous read transaction
`uvm_info(get_type_name(), "TEST continous read transaction...", UVM_LOW)
repeat(test_num) begin
addr = this.get_rand_addr();
`uvm_do_with(single_read_seq, {addr == local::addr;})
void'(this.check_mem_data(addr, single_read_seq.data));
end
// TEST read transaction after write transaction
`uvm_info(get_type_name(), "TEST read transaction after write transaction...", UVM_LOW)
repeat(test_num) begin
addr = this.get_rand_addr();
`uvm_do_with(single_write_seq, {addr == local::addr; data == local::addr;})
mem[addr] = addr;
`uvm_do_with(single_read_seq, {addr == local::addr;})
void'(this.check_mem_data(addr, single_read_seq.data));
end
// TEST read transaction immediately after write transaction
`uvm_info(get_type_name(), "TEST read transaction immediately after write transaction", UVM_LOW)
repeat(test_num) begin
addr = this.get_rand_addr();
`uvm_do_with(write_read_seq, {addr == local::addr; data == local::addr;})
mem[addr] = addr;
void'(this.check_mem_data(addr, write_read_seq.data));
end
this.wait_cycles(10);
endtask
endclass: apb_single_transaction_sequence
class apb_single_transaction_test extends apb_base_test;
`uvm_component_utils(apb_single_transaction_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
task run_phase(uvm_phase phase);
apb_single_transaction_sequence seq = new();
phase.raise_objection(this);
super.run_phase(phase);
seq.start(env.mst.sequencer);
phase.drop_objection(this);
endtask
endclass: apb_single_transaction_test
class apb_burst_transaction_sequence extends apb_base_test_sequence;
apb_master_burst_write_sequence burst_write_seq;
apb_master_burst_read_sequence burst_read_seq;
rand int test_num = 100;
constraint cstr{
soft test_num == 100;
}
`uvm_object_utils(apb_burst_transaction_sequence)
function new(string name="");
super.new(name);
endfunction : new
task body();
bit[31:0] addr;
this.wait_reset_release();
this.wait_cycles(10);
// TEST continous write transaction
repeat(test_num) begin
addr = this.get_rand_addr();
`uvm_do_with(burst_write_seq, {addr == local::addr;})
foreach(burst_write_seq.data[i]) begin
mem[addr+(i<<2)] = burst_write_seq.data[i];
end
`uvm_do_with(burst_read_seq, {addr == local::addr; data.size() == burst_write_seq.data.size();})
foreach(burst_read_seq.data[i]) begin
void'(this.check_mem_data(addr+(i<<2), burst_write_seq.data[i]));
end
end
this.wait_cycles(10);
endtask
endclass: apb_burst_transaction_sequence
class apb_burst_transaction_test extends apb_base_test;
`uvm_component_utils(apb_burst_transaction_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
task run_phase(uvm_phase phase);
apb_burst_transaction_sequence seq = new();
phase.raise_objection(this);
super.run_phase(phase);
seq.start(env.mst.sequencer);
phase.drop_objection(this);
endtask
endclass: apb_burst_transaction_test
`endif // APB_TESTS_SV
apb_if.sv
`ifndef APB_IF_SV
`define APB_IF_SV
interface apb_if (input clk, input rstn);
logic [31:0] paddr;
logic pwrite;
logic psel;
logic penable;
logic [31:0] pwdata;
logic [31:0] prdata;
// Control flags
bit has_checks = 1;
bit has_coverage = 1;
// Actual Signals
// USER: Add interface signals
clocking cb_mst @(posedge clk);
// USER: Add clocking block detail
default input #1ps output #1ps;
output paddr, pwrite, psel, penable, pwdata;
input prdata;
endclocking : cb_mst
clocking cb_slv @(posedge clk);
// USER: Add clocking block detail
default input #1ps output #1ps;
input paddr, pwrite, psel, penable, pwdata;
output prdata;
endclocking : cb_slv
clocking cb_mon @(posedge clk);
// USER: Add clocking block detail
default input #1ps output #1ps;
input paddr, pwrite, psel, penable, pwdata, prdata;
endclocking : cb_mon
// Coverage and assertions to be implemented here.
// USER: Add assertions/coverage here
// APB command covergroup
covergroup cg_apb_command @(posedge clk iff rstn);
pwrite: coverpoint pwrite{
type_option.weight = 0;
bins write = {1};
bins read = {0};
}
psel : coverpoint psel{
type_option.weight = 0;
bins sel = {1};
bins unsel = {0};
}
cmd : cross pwrite, psel{
bins cmd_write = binsof(psel.sel) && binsof(pwrite.write);
bins cmd_read = binsof(psel.sel) && binsof(pwrite.read);
bins cmd_idle = binsof(psel.unsel);
}
endgroup
// APB transaction timing group
covergroup cg_apb_trans_timing_group @(posedge clk iff rstn);
psel: coverpoint psel{
bins single = (0 => 1 => 1 => 0);
bins burst_2 = (0 => 1 [*4] => 0);
bins burst_4 = (0 => 1 [*8] => 0);
bins burst_8 = (0 => 1 [*16] => 0);
bins burst_16 = (0 => 1 [*32] => 0);
bins burst_32 = (0 => 1 [*64] => 0);
}
penable: coverpoint penable {
bins single = (0 => 1 => 0 [*2:10] => 1);
bins burst = (0 => 1 => 0 => 1);
}
endgroup
// APB write & read order group
covergroup cg_apb_write_read_order_group @(posedge clk iff (rstn && penable));
write_read_order: coverpoint pwrite{
bins write_write = (1 => 1);
bins write_read = (1 => 0);
bins read_write = (0 => 1);
bins read_read = (0 => 0);
}
endgroup
initial begin
automatic cg_apb_command cg0 = new();
automatic cg_apb_trans_timing_group cg1 = new();
automatic cg_apb_write_read_order_group cg2 = new();
end
endinterface : apb_if
`endif // APB_IF_SV
二、apb_tests.sv
代码分析
apb_base_test_sequence
类
check_mem_data()
方法原理结构框图:
关联数组mem,用来master和slave之间的数据比对,test和slave中都有一个mem,master通过接口发送数据给slave,slave中的mem和test中的mem都会存储这个数据,等从slave读回数据时,就可以和test中mem里面的数据进行比较。
apb_single_transaction_sequence
类
随机化addr,测试连续写操作
// TEST continous write transaction
`uvm_info(get_type_name(), "TEST continous write transaction...", UVM_LOW)
repeat(test_num) begin
addr = this.get_rand_addr();
`uvm_do_with(single_write_seq, {addr == local::addr; data == local::addr;})
mem[addr] = addr;
end
随机化addr,测试连续读操作,并比较数据是否一致
// TEST continous read transaction
`uvm_info(get_type_name(), "TEST continous read transaction...", UVM_LOW)
repeat(test_num) begin
addr = this.get_rand_addr();
`uvm_do_with(single_read_seq, {addr == local::addr;})
void'(this.check_mem_data(addr, single_read_seq.data));
end
随机化addr,先进行写操作,再进行读操作,并比较读取的数据是否一致
// TEST read transaction after write transaction
`uvm_info(get_type_name(), "TEST read transaction after write transaction...", UVM_LOW)
repeat(test_num) begin
addr = this.get_rand_addr();
`uvm_do_with(single_write_seq, {addr == local::addr; data == local::addr;})
mem[addr] = addr;
`uvm_do_with(single_read_seq, {addr == local::addr;})
void'(this.check_mem_data(addr, single_read_seq.data));
end
随机化addr,写完立即读,中间没有idle空闲,并检查读取数据是否一致
// TEST read transaction immediately after write transaction
`uvm_info(get_type_name(), "TEST read transaction immediately after write transaction", UVM_LOW)
repeat(test_num) begin
addr = this.get_rand_addr();
`uvm_do_with(write_read_seq, {addr == local::addr; data == local::addr;})
mem[addr] = addr;
void'(this.check_mem_data(addr, write_read_seq.data));
end
例化并挂载
class apb_single_transaction_test extends apb_base_test;
`uvm_component_utils(apb_single_transaction_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
task run_phase(uvm_phase phase);
apb_single_transaction_sequence seq = new();
phase.raise_objection(this);
super.run_phase(phase);
seq.start(env.mst.sequencer);
phase.drop_objection(this);
endtask
endclass: apb_single_transaction_test
apb_burst_transaction_sequence
类
先全部写操作完毕,在完全读出来,地址是连续增长的
// TEST continous write transaction
repeat(test_num) begin
addr = this.get_rand_addr();
`uvm_do_with(burst_write_seq, {addr == local::addr;})
foreach(burst_write_seq.data[i]) begin
mem[addr+(i<<2)] = burst_write_seq.data[i];
end
`uvm_do_with(burst_read_seq, {addr == local::addr; data.size() == burst_write_seq.data.size();})
foreach(burst_read_seq.data[i]) begin
void'(this.check_mem_data(addr+(i<<2), burst_write_seq.data[i]));
end
end
例化并挂载
class apb_burst_transaction_test extends apb_base_test;
`uvm_component_utils(apb_burst_transaction_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
task run_phase(uvm_phase phase);
apb_burst_transaction_sequence seq = new();
phase.raise_objection(this);
super.run_phase(phase);
seq.start(env.mst.sequencer);
phase.drop_objection(this);
endtask
endclass: apb_burst_transaction_test
三、apb_master_agent.sv
代码分析
agent
包括三个组件driver
、sequencer
、monitor
,以及config
和interface
。
例化monitor
,根据配置决定是否例化driver
和sequencer
function void apb_master_agent::build();
super.build();
// get config
if( !uvm_config_db#(apb_config)::get(this,"","cfg", cfg)) begin
`uvm_warning("GETCFG","cannot get config object from config DB")
cfg = apb_config::type_id::create("cfg");
end
// get virtual interface
if( !uvm_config_db#(virtual apb_if)::get(this,"","vif", vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
monitor = apb_master_monitor::type_id::create("monitor",this);
monitor.cfg = cfg;
if(cfg.is_active == UVM_ACTIVE) begin
sequencer = apb_master_sequencer::type_id::create("sequencer",this);
sequencer.cfg = cfg;
driver = apb_master_driver::type_id::create("driver",this);
driver.cfg = cfg;
end
endfunction : build
根据配置决定是否连接driver
和sequencer
function void apb_master_agent::connect();
assign_vi(vif);
if(is_active == UVM_ACTIVE) begin
driver.seq_item_port.connect(sequencer.seq_item_export);
end
endfunction : connect
根据配置决定是否vif和driver、sequencer之间的连接
function void apb_master_agent::assign_vi(virtual apb_if vif);
monitor.vif = vif;
if (is_active == UVM_ACTIVE) begin
sequencer.vif = vif;
driver.vif = vif;
end
endfunction : assign_vi
四、apb_master_driver.sv
代码分析
并行触发get_and_drive()
、reset_listener()
task apb_master_driver::run();
fork
get_and_drive();
reset_listener();
join_none
endtask : run
捕捉到复位信号以后,所以信号清零
task apb_master_driver::reset_listener();
`uvm_info(get_type_name(), "reset_listener ...", UVM_HIGH)
fork
forever begin
@(negedge vif.rstn); // ASYNC reset
vif.paddr <= 0;
vif.pwrite <= 0;
vif.psel <= 0;
vif.penable <= 0;
vif.pwdata <= 0;
end
join_none
endtask
sequence
和sequencer
需要握手,获取transaction
以后调用driver_transfer()
发送。发送成功以后克隆request
生成新的response
,作为响应发送回去。
task apb_master_driver::get_and_drive();
forever begin
seq_item_port.get_next_item(req);
`uvm_info(get_type_name(), "sequencer got next item", UVM_HIGH)
drive_transfer(req);
void'($cast(rsp, req.clone()));
rsp.set_sequence_id(req.get_sequence_id());
seq_item_port.item_done(rsp);
`uvm_info(get_type_name(), "sequencer item_done_triggered", UVM_HIGH)
end
endtask : get_and_drive
task apb_master_driver::drive_transfer (apb_transfer t);
`uvm_info(get_type_name(), "drive_transfer", UVM_HIGH)
case(t.trans_kind)
IDLE : this.do_idle();
WRITE : this.do_write(t);
READ : this.do_read(t);
default : `uvm_error("ERRTYPE", "unrecognized transaction type")
endcase
endtask : drive_transfer
根据trans_kind
判断操作命令,分别调用相对应的方法。
task apb_master_driver::do_write(apb_transfer t);
`uvm_info(get_type_name(), "do_write ...", UVM_HIGH)
//写操作一共分为两个周期,根据协议第一个周期setup准备阶段需要如下操作
@(vif.cb_mst);
vif.cb_mst.paddr <= t.addr;
vif.cb_mst.pwrite <= 1;
vif.cb_mst.psel <= 1;
vif.cb_mst.penable <= 0;
vif.cb_mst.pwdata <= t.data;
//第二个阶段拉高penable信号,发送数据
@(vif.cb_mst);
vif.cb_mst.penable <= 1;
repeat(t.idle_cycles) this.do_idle(); //取决于transaction里面的idle
endtask: do_write
task apb_master_driver::do_read(apb_transfer t);
`uvm_info(get_type_name(), "do_write ...", UVM_HIGH)
//第一个阶段
@(vif.cb_mst);
vif.cb_mst.paddr <= t.addr;
vif.cb_mst.pwrite <= 0;
vif.cb_mst.psel <= 1;
vif.cb_mst.penable <= 0;
//第二个阶段
@(vif.cb_mst);
vif.cb_mst.penable <= 1;
#100ps; //需要采样数据,人为添加100ps的delay,是为了避免delta-cycle
t.data = vif.prdata; //采样数据
repeat(t.idle_cycles) this.do_idle();
endtask: do_read
task apb_master_driver::do_idle();
`uvm_info(get_type_name(), "do_idle ...", UVM_HIGH)
@(vif.cb_mst);
//根据协议,paddr、pwrite可以保持不变,等待下一次的传输,这是为了省电
//vif.cb_mst.paddr <= 0;
//vif.cb_mst.pwrite <= 0;
vif.cb_mst.psel <= 0;
vif.cb_mst.penable <= 0;
vif.cb_mst.pwdata <= 0;
endtask:do_idle
五、apb_master_monitor.sv
代码分析
collect_transfer()
方法
在时钟上升沿,同时psel=1
和penabl=0
的时候,判断当前情况下pwrite
信号,在第二个周期进行读或者写操作。
task apb_master_monitor::collect_transfer();
apb_transfer t;
// Advance clock
@(vif.cb_mon);
if(vif.cb_slv.psel === 1'b1 && vif.cb_slv.penable === 1'b0) begin
t = apb_transfer::type_id::create("t");
case(vif.cb_slv.pwrite)
1'b1 : begin
@(vif.cb_mon);
t.addr = vif.cb_mon.paddr;
t.data = vif.cb_mon.pwdata;
t.trans_kind = WRITE;
end
1'b0 : begin
@(vif.cb_mon);
t.addr = vif.cb_mon.paddr;
t.data = vif.cb_mon.prdata;
t.trans_kind = READ;
end
default : `uvm_error(get_type_name(), "ERROR pwrite signal value")
endcase
item_collected_port.write(t);
end
endtask: collect_transfer
六、apb_master_seq_lib.sv
代码分析
apb_master_single_write_sequence
类
使用宏'uvm_do_with
发送数据。
class apb_master_single_write_sequence extends apb_master_base_sequence;
rand bit [31:0] addr;
rand bit [31:0] data;
`uvm_object_utils(apb_master_single_write_sequence)
function new(string name="");
super.new(name);
endfunction : new
virtual task body();
`uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
`uvm_do_with(req, {trans_kind == WRITE; addr == local::addr; data == local::data;})
get_response(rsp);
`uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
endtask: body
endclass: apb_master_single_write_sequence
apb_master_single_read_sequence
类
读操作,拿到返回的rsp
的数据后存储在成员变量data
里。
class apb_master_single_read_sequence extends apb_master_base_sequence;
rand bit [31:0] addr;
rand bit [31:0] data;
`uvm_object_utils(apb_master_single_read_sequence)
function new(string name="");
super.new(name);
endfunction : new
virtual task body();
`uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
`uvm_do_with(req, {trans_kind == READ; addr == local::addr;})
get_response(rsp);
data = rsp.data;
`uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
endtask: body
endclass: apb_master_single_read_sequence
apb_master_write_read_sequence
类
写操作后进行读操作,所以idle_cycles == 0
class apb_master_write_read_sequence extends apb_master_base_sequence;
rand bit [31:0] addr;
rand bit [31:0] data;
rand int idle_cycles;
constraint cstr{
idle_cycles == 0;
}
`uvm_object_utils(apb_master_write_read_sequence)
function new(string name="");
super.new(name);
endfunction : new
virtual task body();
`uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
`uvm_do_with(req, {trans_kind == WRITE;
addr == local::addr;
data == local::data;
idle_cycles == local::idle_cycles;
})
get_response(rsp);
`uvm_do_with(req, {trans_kind == READ; addr == local::addr;})
get_response(rsp);
data = rsp.data;
`uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
endtask: body
endclass: apb_master_write_read_sequence
apb_master_burst_write_sequence
类
连续的写操作,按照地址增长的顺序,把所有的数据写到data
数组中。因为是连续写操作,所以idle_cycles == 0
,即数据之间没有空闲周期。
class apb_master_burst_write_sequence extends apb_master_base_sequence;
rand bit [31:0] addr;
rand bit [31:0] data[];
constraint cstr{
soft data.size() inside {4, 8, 16, 32};
foreach(data[i]) soft data[i] == addr + (i << 2);
}
`uvm_object_utils(apb_master_burst_write_sequence)
function new(string name="");
super.new(name);
endfunction : new
virtual task body();
`uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
foreach(data[i]) begin
`uvm_do_with(req, {trans_kind == WRITE;
addr == local::addr + (i<<2);
data == local::data[i];
idle_cycles == 0;
})
get_response(rsp);
end
`uvm_do_with(req, {trans_kind == IDLE;})
get_response(rsp);
`uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
endtask: body
endclass: apb_master_burst_write_sequence
apb_master_burst_read_sequence
类
连续的读操作,每次读取回来的数据,从rsp
中拿出来放到data
数组中。全部读取完成之后,将总线置为IDLE
。
class apb_master_burst_read_sequence extends apb_master_base_sequence;
rand bit [31:0] addr;
rand bit [31:0] data[];
constraint cstr{
soft data.size() inside {4, 8, 16, 32};
}
`uvm_object_utils(apb_master_burst_read_sequence)
function new(string name="");
super.new(name);
endfunction : new
virtual task body();
`uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
foreach(data[i]) begin
`uvm_do_with(req, {trans_kind == READ;
addr == local::addr + (i<<2);
idle_cycles == 0;
})
get_response(rsp);
data[i] = rsp.data;
end
`uvm_do_with(req, {trans_kind == IDLE;})
get_response(rsp);
`uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
endtask: body
endclass: apb_master_burst_read_sequence
七、apb_slave_driver.sv代码分析
slave
要接收master
发送过来的数据,所以要模拟一个存储功能,即关联数组mem
。
bit[31:0] mem [bit[31:0]];
run()
方法
三个方法并行执行
task apb_slave_driver::run();
fork
get_and_drive();
reset_listener();
drive_response();
join_none
endtask : run
get_and_drive()
方法
task apb_slave_driver::get_and_drive();
forever begin
seq_item_port.get_next_item(req);
`uvm_info(get_type_name(), "sequencer got next item", UVM_HIGH)
void'($cast(rsp, req.clone()));
rsp.set_sequence_id(req.get_sequence_id());
seq_item_port.item_done(rsp);
`uvm_info(get_type_name(), "sequencer item_done_triggered", UVM_HIGH)
end
endtask : get_and_drive
reset_listener()
方法
等待复位信号,将prdata <= 0
,同时清空mem
里面的数据。
task apb_slave_driver::reset_listener();
`uvm_info(get_type_name(), "reset_listener ...", UVM_HIGH)
fork
forever begin
@(negedge vif.rstn); // ASYNC reset
vif.prdata <= 0;
this.mem.delete(); // reset internal memory
end
join_none
endtask: reset_listener
drive_response()
方法
如果当前这一周期是SETUP阶段,即psel = 1 && penable = 0
,进而判断是写操作还是读操作,然后调用相对应的方法。
task apb_slave_driver::drive_response();
`uvm_info(get_type_name(), "drive_response", UVM_HIGH)
forever begin
@(vif.cb_slv);
if(vif.cb_slv.psel === 1'b1 && vif.cb_slv.penable === 1'b0) begin
case(vif.cb_slv.pwrite)
1'b1 : this.do_write();
1'b0 : this.do_read();
default : `uvm_error(get_type_name(), "ERROR pwrite signal value")
endcase
end
else begin
this.do_idle();
end
end
endtask : drive_response
do_write()
方法
如果是写操作,那么等待时钟下一拍,拿到addr
和data
并放到mem
中。
task apb_slave_driver::do_write();
bit[31:0] addr;
bit[31:0] data;
`uvm_info(get_type_name(), "do_write", UVM_HIGH)
@(vif.cb_slv);
addr = vif.cb_slv.paddr;
data = vif.cb_slv.pwdata;
mem[addr] = data;
endtask: do_write
do_read()
方法
如果是读操作,等待penable=1
,并且判断mem
中是否写过该addr
,如果有则写入data
,没有则将data置为0,即还是初始化的数据。等待一个延迟后,将data
驱动到总线上面。
task apb_slave_driver::do_read();
bit[31:0] addr;
bit[31:0] data;
`uvm_info(get_type_name(), "do_read", UVM_HIGH)
wait(vif.penable === 1'b1);
addr = vif.cb_slv.paddr;
if(mem.exists(addr))
data = mem[addr];
else
data = 0;
#1ps;
vif.prdata <= data;
@(vif.cb_slv);
endtask: do_read
八、运行仿真
执行命令
run -all
验证环境结构
写操作:写入地址和写入数据相同,只有penable
拉高才会写入,数据之间有一个空闲。
读操作:只有penable
拉高才会读数据,没有写入过数据的地址,读出来的值为0。
先写后读:
写完立即读操作:
仿真结果:
覆盖率:
// APB command covergroup
covergroup cg_apb_command @(posedge clk iff rstn);
pwrite: coverpoint pwrite{
type_option.weight = 0;
bins write = {1};
bins read = {0};
}
psel : coverpoint psel{
type_option.weight = 0;
bins sel = {1};
bins unsel = {0};
}
cmd : cross pwrite, psel{
bins cmd_write = binsof(psel.sel) && binsof(pwrite.write);
bins cmd_read = binsof(psel.sel) && binsof(pwrite.read);
bins cmd_idle = binsof(psel.unsel);
}
endgroup
// APB transaction timing group
covergroup cg_apb_trans_timing_group @(posedge clk iff rstn);
psel: coverpoint psel{
bins single = (0 => 1 => 1 => 0);
bins burst_2 = (0 => 1 [*4] => 0);
bins burst_4 = (0 => 1 [*8] => 0);
bins burst_8 = (0 => 1 [*16] => 0);
bins burst_16 = (0 => 1 [*32] => 0);
bins burst_32 = (0 => 1 [*64] => 0);
}
penable: coverpoint penable {
bins single = (0 => 1 => 0 [*2:10] => 1);
bins burst = (0 => 1 => 0 => 1);
}
endgroup
// APB write & read order group
covergroup cg_apb_write_read_order_group @(posedge clk iff (rstn && penable));
write_read_order: coverpoint pwrite{
bins write_write = (1 => 1);
bins write_read = (1 => 0);
bins read_write = (0 => 1);
bins read_read = (0 => 0);
}
endgroup