APB_VIP

正式开始学习总线VIP,一方面增加对总线协议的理解,一方面增强自己的coding能力

APB3.0_VIP

apb3.0总线协议

APB 3.0属于AMBA 3 协议系列,它提供了一个低功耗的接口,并降低了接口的复杂性。APB接口用在低带宽和不需要高性能总线的外围设备上。APB是非流水线结构,所有的信号仅与时钟上升沿相关,这样就可以简化APB外围设备的设计流程,每个传输至少耗用两个周期。

APB可以与AMBA高级高性能总线(AHB-Lite) 和AMBA 高级可扩展接口(AXI)连接。该版本新增两个信号,一个准备好信号PREADY, 来扩展APB传输;一个错误信号PSLVERR, 来指示传输失败。

interface

image-20210810094941360

status

img

和之前的apb相比可以很明显的看出:通过ready信号为0,则无法退出该次传输,需等待ready信号拉高后才能进入setup或进入idle

image-20210810095251218

传输时序

写和读都多了一个由ready信号控制的等待状态

写传输

无等待状态:

地址、写入数据、写入信号和选择信号都在时钟上升沿后改变。第一个时钟周期叫做Setup phase。下一个时钟沿后使能信号PENABLE被置位,表示Access phase就位。地址、数据和控制信号在Access phase期间有效。传输在该周期后结束。使能信号PENABLE, 在传输结束后清空。选择信号PSELx同样被置低,除非紧接着下一传输开始。
img

有等待状态:

通過PREADY 信号扩展到器件的传输時間。在Access phase期间,当PENABLE为高,传输可以通过拉低PREADY来扩展传输当PENABLE为低的时候,PREADY可以为任何值。确保外围器件有固定的两个周期来使PREADY为高。推荐地址和写信号在传输结束后不要立即更改,保持当前状态直到下一个传输,这样可以降低功耗。
img

和写的区别主要就是write信号,还有数据传输到总线上的时间

无等待

img
等待

img

错误响应

img

开发阶段

阶段1——定义APB

功能特性提取

APB总线有哪些功能,对这些功能作拆分

单个transaction操作

  • 单个transaction写操作
  • 单个transaction读操作
  • 单个transaction写/读+idle+写/读

多粒度transaction操作——关联数组、队列、动态数组等

  • 对数组进行写操作
  • 对数组进行读操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
`ifndef LVC_APB_TRANSFER_SV
`define LVC_APB_TRANSFER_SV

typedef enum {IDLE, WRITE, READ } apb_trans_kind;
typedef enum {OK, ERROR} apb_trans_status;

class apb_transfer extends uvm_sequence_item;
// USER: Add transaction fields
rand bit [31:0] addr;
rand bit [31:0] data;
rand apb_trans_kind trans_kind; //根据状态机得到,当我拿到这一个trans的时候,应该对interface作何行为
rand apb_trans_status trans_status; //由从端返回的pslverr决定
rand int idle_cycles;

constraint cstr{
soft idle_cycles == 1;
};

// USER: Add constraint blocks
`uvm_object_utils_begin(apb_transfer)
`uvm_field_enum (apb_trans_kind, trans_kind, UVM_ALL_ON)
// USER: Register fields here
`uvm_field_int (addr, UVM_ALL_ON)
`uvm_field_int (data, UVM_ALL_ON)
`uvm_field_int (idle_cycles, UVM_ALL_ON)
`uvm_object_utils_end

// new - constructor
function new (string name = "apb_transfer_inst");
super.new(name);
endfunction : new
endclass : apb_transfer
`endif // LVC_APB_TRANSFER_SV

特性覆盖率创建及映射

简单映射,核心功能检查还是应该依赖断言覆盖率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
bit  has_coverage = 1;

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 连续进行读或写,64个即代表没有扩展情况下的32次读、写
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);
}
////单次(不同的idle cycle)、连续
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 : coverage_control
if(has_coverage) begin
automatic cg_lvc_apb_command cg0 = new();
automatic cg_lvc_apb_trans_timing_group cg1 = new();
automatic cg_lvc_apb_write_read_order_group cg2 = new();
end
end

阶段2——VIP环境搭建

master_agent—driver,sequencer,monitor

master_driver

通过svh可以直观的看到driver的结构

1.拿到一个apb_if的接口vif,不需要管怎么拿到的,拿来用就行,别的交给env,agent来传

2.拿到一个apb_config的配置cfg,同样不要管怎么拿的,要用什么直接用,在交给env,agent中把例化对象传进来就行

  • 比如下面的写、读操作:通过配置文件中进行修改,就能决定碰到错误响应信号时,消息管理的严重级别是error还是warning(感觉完全可以用1/0替代)

    1
    2
    3
    4
    5
    6
    7
    if(vif.pslverr === 1) begin
    t.trans_status = ERROR;
    if(cfg.master_pslverr_status_severity == UVM_ERROR)
    `uvm_error(get_type_name(), "PSLVERR asserted!")
    else
    `uvm_warning(get_type_name(), "PSLVERR asserted!")
    end
  • do_write():两个周期不同的驱动方式,penable为高时需要考虑两点

    • pready是否为1,不为1则等待其为1
    • pslverr是否为1,为1则打印错误或警告
注意这里的两次等待:#10ps;#1ps:

10ps:因为在wait中没有使用时钟块里面的pready和pslverr信号,因为它想采的是当前时钟拍子中的pready信号

1ps:可以看到slave_driver中的驱动任务do_write(),是先将pready<=1驱动到总线上,再将pslverr驱动到总线上,这里就差了一个date_cycle!!需要等待1ps再采pslverr信号

  • do_read():同样是两拍
  • do_idle():停一拍,除paddr和pwrite外全部归零
  • reset_listener():复位信号监控
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//svh
`ifndef APB_MASTER_DRIVER_SVH
`define APB_MASTER_DRIVER_SVH

class apb_master_driver extends uvm_driver #(apb_transfer);
apb_config cfg;
`uvm_component_utils_begin(apb_master_driver)
`uvm_component_utils_end

extern function new (string name, uvm_component parent);

extern virtual task run();

virtual apb_if vif;

extern virtual protected task get_and_drive();

extern virtual protected task drive_transfer(apb_transfer t);

extern virtual protected task reset_listener();

extern protected task do_idle();

extern protected task do_write(apb_transfer t);

extern protected task do_read(apb_transfer t);
endclass : apb_master_driver
`endif // APB_MASTER_DRIVER_SVH

//sv
`ifndef APB_MASTER_DRIVER_SV
`define APB_MASTER_DRIVER_SV

function apb_master_driver::new (string name, uvm_component parent);
super.new(name, parent);
endfunction : new

task apb_master_driver::run();
fork
get_and_drive();
reset_listener();
join_none
endtask : run

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());
rsp.set_transaction_id(req.get_transaction_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

task apb_master_driver::do_write(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 <= 1;
vif.cb_mst.psel <= 1;
vif.cb_mst.penable <= 0;
vif.cb_mst.pwdata <= t.data;
@(vif.cb_mst);
vif.cb_mst.penable <= 1;
#10ps;
wait(vif.pready === 1);
#1ps;
if(vif.pslverr === 1) begin
t.trans_status = ERROR;
if(cfg.master_pslverr_status_severity == UVM_ERROR)
`uvm_error(get_type_name(), "PSLVERR asserted!")
else
`uvm_warning(get_type_name(), "PSLVERR asserted!")
end
else begin
t.trans_status = OK;
end
repeat(t.idle_cycles) this.do_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;
#10ps;
wait(vif.pready === 1);
#1ps;
if(vif.pslverr === 1) begin
t.trans_status = ERROR;
if(cfg.master_pslverr_status_severity == UVM_ERROR)
`uvm_error(get_type_name(), "PSLVERR asserted!")
else
`uvm_warning(get_type_name(), "PSLVERR asserted!")
end
else begin
t.trans_status = OK;
end
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);
vif.cb_mst.psel <= 0;
vif.cb_mst.penable <= 0;
vif.cb_mst.pwdata <= 0;
endtask:do_idle

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
`endif // APB_MASTER_DRIVER_SV

master_monitor

问题1:TLM通信为啥选择用uvm_analysis_port端口?

monitor的run_phase核心就是monitor_transactions()方法;

又因为check和coverage方法都是空的,其实就只执行collect_transfer()采样方法,在penable为0时进入采样准备,在pready信号为1的同一拍进行采样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//svh
`ifndef APB_MASTER_MONITOR_SVH
`define APB_MASTER_MONITOR_SVH

class apb_master_monitor extends uvm_monitor;

apb_config cfg;
// 控制是否在monitor中做check,默认开启(其实check方法为空,都在scoreboard中做)
bit checks_enable = 1;
// 控制是否在monitor采样时收集覆盖率,默认开始(coverage方法同样为空,在interface中定义和采样)
bit coverage_enable = 1;

// 同上,接口肯定要用,等着后面传
virtual apb_if vif;

// 为啥用analysis端口?
uvm_analysis_port #(apb_transfer) item_collected_port;

//组件也可以有域自动化
`uvm_component_utils_begin(apb_master_monitor)
`uvm_field_int(checks_enable, UVM_ALL_ON)
`uvm_field_int(coverage_enable, UVM_ALL_ON)
`uvm_component_utils_end

// new - constructor
extern function new(string name, uvm_component parent=null);

// uvm run phase
extern virtual task run();

// Events needed to trigger covergroups
event apb_master_cov_transaction;

// Transfer collected covergroup
covergroup apb_master_cov_trans @apb_master_cov_transaction;
// USER implemented coverpoints
endgroup : apb_master_cov_trans

//This is the transaction being collected by this monitor
protected apb_transfer trans_collected;

// This method is responsible for collecting transactions, checking,
// and updating coverage
extern virtual protected task monitor_transactions();

// This is the methods that collects transactions
extern virtual protected task collect_transfer();

// This is the method that performs checks on a transaction
extern protected function void perform_transfer_checks();

// This is the method that updates coverage based on a transaction
extern protected function void perform_transfer_coverage();

endclass : apb_master_monitor
`endif // APB_MASTER_MONITOR_SVH

//sv
`ifndef APB_MASTER_MONITOR_SV
`define APB_MASTER_MONITOR_SV

function apb_master_monitor::new(string name, uvm_component parent=null);
super.new(name, parent);
item_collected_port = new("item_collected_port",this);
endfunction:new

task apb_master_monitor::monitor_transactions();
forever begin
collect_transfer();
// Check transaction
if (checks_enable)
perform_transfer_checks();
// Update coverage
if (coverage_enable)
perform_transfer_coverage();
// Publish to subscribers
item_collected_port.write(trans_collected);
end
endtask // monitor_transactions


task apb_master_monitor::run();
fork
monitor_transactions();
join_none
endtask // run


task apb_master_monitor::collect_transfer();
// 因为用的时钟块的penable信号,满足触发条件其实就已经是setup的下一拍时钟上升沿了
@(vif.cb_mon iff (vif.cb_mon.psel === 1'b1 && vif.cb_mon.penable === 1'b0));
//创建一个空的apb_transfer用于接收monitor信息
trans_collected = apb_transfer::type_id::create("trans_collected");
case(vif.cb_mon.pwrite)
1'b1 : begin
// 因为用的时钟块的pready信号,满足触发条件其实就已经是完成数据传输的下一拍时钟上升沿了,在时钟上升沿的setup时间中进行采样
@(vif.cb_mon iff vif.cb_mon.pready === 1'b1);
trans_collected.addr = vif.cb_mon.paddr;
trans_collected.data = vif.cb_mon.pwdata;
trans_collected.trans_kind = WRITE;
trans_collected.trans_status = vif.cb_mon.pslverr === 1'b0 ? OK : ERROR;
end
1'b0 : begin
@(vif.cb_mon iff vif.cb_mon.pready === 1'b1);
trans_collected.addr = vif.cb_mon.paddr;
trans_collected.data = vif.cb_mon.prdata;
trans_collected.trans_kind = READ;
trans_collected.trans_status = vif.cb_mon.pslverr === 1'b0 ? OK : ERROR;
end
default : `uvm_error(get_type_name(), "ERROR pwrite signal value")
endcase
endtask: collect_transfer


// perform_transfer_checks
function void apb_master_monitor::perform_transfer_checks();
// USER: do some checks on the transfer here
endfunction : perform_transfer_checks

// perform_transfer_coverage
function void apb_master_monitor::perform_transfer_coverage();
// USER: coverage implementation
-> apb_master_cov_transaction;
endfunction : perform_transfer_coverage

`endif // APB_MASTER_MONITOR_SV

master_sequencer

只需要注册并定义new()方法

master_agent

  • build_phase:

    • driver,monitor,sequencer的创建

    • config_db传入接口和配置信息

      注意如果配置给agent的is_active为0,则不用创建driver和sequencer

  • connect_phase:

    • driver和sequencer连接

      注意:通过configuration的is_active判断driver和sequencer是否创建,没创建就不用连了

    • 接口的传入,将接口赋值给driver和monitor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// svh
`ifndef APB_MASTER_AGENT_SVH
`define APB_MASTER_AGENT_SVH

class apb_master_agent extends uvm_agent;
apb_config cfg;

// The following are the verification components that make up
// this agent
apb_master_driver driver;
apb_master_sequencer sequencer;
apb_master_monitor monitor;
virtual apb_if vif;

`uvm_component_utils_begin(apb_master_agent)
// USER: Register your fields here
`uvm_component_utils_end

// new - constructor
extern function new (string name, uvm_component parent);

// uvm build phase
extern function void build();

// uvm connection phase
extern function void connect();

// This method assigns the virtual interfaces to the agent's children
extern function void assign_vi(virtual apb_if vif);

endclass : apb_master_agent
`endif // APB_MASTER_AGENT_SVH

//sv
`ifndef APB_MASTER_AGENT_SV
`define APB_MASTER_AGENT_SV

function apb_master_agent::new(string name, uvm_component parent);
super.new(name, parent);
endfunction : new


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

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

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

slave_agent

和master_agent的区别,因为总线是单向传输的,master端发送数据,slave端只需要被动接收master发送的数据就好

slave_driver的get_and_drive()方法就是个摆设,sequence也是个摆设

slave_driver

定义关联数组:bit[31:0] mem [bit[31:0]];用来将master发送的数据进行存储,用于后面的数据比对

核心方法:drive_response(),其中的do_write和do_read方法类似

  • cfg的get_pready_additional_cycles()方法决定了penable信号被扩展几个周期
  • cfg.get_pslverr_status():返回的pslverr信号是1还是0
  • do_write:
    • penable为1,就可以把输入地址和数据写进关联数组,此时将pready信号置为0
    • 等待get_pready_additional_cycles()个时钟周期后,驱动pready信号拉为1,pslverr信号为cfg.get_pslverr_status(),此时是真正的进行数据传输
    • 下一拍将pready置为默认值,pslverr信号置为0
  • do_read:
    • penable为1,就可以根据地址把数据(如果当前地址有的话)拿出来,存到data变量,此时将pready信号置为0
    • 等待get_pready_additional_cycles()个时钟周期后,驱动pready信号拉为1,然后将pready信号,pslverr信号,prdata=data信号驱动到总线
    • 下一拍将pready置为默认值,pslverr信号置为0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
//svh
`ifndef APB_SLAVE_DRIVER_SVH
`define APB_SLAVE_DRIVER_SVH

class apb_slave_driver extends uvm_driver #(apb_transfer);
apb_config cfg;

// USER: Add your fields here
bit[31:0] mem [bit[31:0]];

`uvm_component_utils_begin(apb_slave_driver)
`uvm_component_utils_end

// new - constructor
extern function new (string name, uvm_component parent);

// uvm run phase
extern virtual task run();

// The virtual interface used to drive and view HDL signals.
virtual apb_if vif;

// This is the method that is responsible for getting sequence transactions
// and driving the transaction into the DUT
extern virtual protected task get_and_drive();

// This method drives response onto the interface
extern virtual protected task drive_response();
// This method that is drive idle respone
extern protected task do_idle();
// This method that is proceed write transaction
extern protected task do_write();
// This method that is proceed read transaction
extern protected task do_read();

// This method reset interface signals
extern virtual protected task reset_listener();

endclass : apb_slave_driver
`endif // APB_SLAVE_DRIVER_SVH

//sv
`ifndef APB_SLAVE_DRIVER_SV
`define APB_SLAVE_DRIVER_SV

function apb_slave_driver::new (string name, uvm_component parent);
super.new(name, parent);
endfunction : new

task apb_slave_driver::run();
fork
get_and_drive();
reset_listener();
drive_response();
join_none
endtask : run

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

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)
//写操作:将master写到总线上的数据保存到们里
1'b1 : this.do_write();
//读操作:将mem里的数据放到总线上给master
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

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;
vif.pslverr <= 0;
vif.pready <= cfg.slave_pready_default_value;
this.mem.delete(); // reset internal memory
end
join_none
endtask: reset_listener

task apb_slave_driver::do_idle();
`uvm_info(get_type_name(), "do_idle", UVM_HIGH)
vif.cb_slv.prdata <= 0;
vif.cb_slv.pready <= cfg.slave_pready_default_value;
vif.cb_slv.pslverr <= 0;
endtask: do_idle

task apb_slave_driver::do_write();
bit[31:0] addr;
bit[31:0] data;
int pready_add_cycles = cfg.get_pready_additional_cycles();
bit pslverr_status = cfg.get_pslverr_status();
`uvm_info(get_type_name(), "do_write", UVM_HIGH)
wait(vif.penable === 1'b1);
addr = vif.cb_slv.paddr;
data = vif.cb_slv.pwdata;
mem[addr] = data;
if(pready_add_cycles > 0) begin
#1ps;
vif.pready <= 0;
repeat(pready_add_cycles) @(vif.cb_slv);
end
#1ps;
vif.pready <= 1;
vif.pslverr <= pslverr_status;
fork
begin
@(vif.cb_slv);
vif.cb_slv.pready <= cfg.slave_pready_default_value;
vif.cb_slv.pslverr <= 0;
end
join_none
endtask: do_write

task apb_slave_driver::do_read();
bit[31:0] addr;
bit[31:0] data;
int pready_add_cycles = cfg.get_pready_additional_cycles();
bit pslverr_status = cfg.get_pslverr_status();
`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 = DEFAULT_READ_VALUE;
if(pready_add_cycles > 0) begin
#1ps;
vif.pready <= 0;
repeat(pready_add_cycles) @(vif.cb_slv);
end
#1ps;
vif.pready <= 1;
vif.pslverr <= pslverr_status;
vif.prdata <= data;
fork
begin
@(vif.cb_slv);
vif.cb_slv.pready <= cfg.slave_pready_default_value;
vif.cb_slv.pslverr <= 0;
end
join_none
endtask: do_read
`endif // APB_SLAVE_DRIVER_SV

slave_monitor

无需,master_monitor就是在数据有效传输时进行采样,读和写时的apb_transfer都能够获取到

slave_sequencer

无需,被动接收,和get_and_driver就是摆设

slave_agent

和master_agent完全一样,这也体现了分层次环境构建的好处

apb_env

1
2
3
4
5
6
7
8
9
10
11
12
13
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

configuration

梳理一下哪里需要用到配置

  • master_driver:cfg.master_pslverr_status_severity={UVM_ERROR, UVM_WARNING}选择对于psvlerr信号为1时的通信方式
  • agent:判断组件是否是is_active = UVM_ACTIVE,不是则不会例化sequencer,driver及其创建连接
  • slave_driver:
    • cfg.slave_pready_default_value:在idle状态,reset状态时的默认驱动
    • int pready_add_cycles = cfg.get_pready_additional_cycles():可通过随机化或定义的方式,决定pready信号扩展几个时钟周期
    • bit pslverr_status = cfg.get_pslverr_status():可通过随机化或定义的方式,决定pslverr信号的发送概率

3个bit位可进行随机化:

  • slave_pready_random:对pready信号的随机化,通过get_pready_additional_cycles()方法控制ready信号持续几拍,也就是对传输周期进行扩展
  • slave_pslverr_random:尝试返回错误响应,通过get_pslverr_status()方法,当slave_pslverr_random的值为1,且$urandom_range(0, 20) == 0满足时,返回1,也就是pslverr错误响应信号为1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
`ifndef LVC_APB_CONFIG_SV
`define LVC_APB_CONFIG_SV

class apb_config extends uvm_object;
// COMMON configuration parameter
uvm_active_passive_enum is_active = UVM_ACTIVE;

// MASTER configuration parameter
uvm_severity master_pslverr_status_severity = UVM_WARNING; // {UVM_WARNING, UVM_ERROR}

// SLAVE configuration parameter
rand bit slave_pready_random = 1;
rand bit slave_pslverr_random = 0;
rand bit slave_pready_default_value = 0;

`uvm_object_utils(lvc_apb_config)

function new (string name = "lvc_apb_config");
super.new(name);
endfunction : new

virtual function get_pready_additional_cycles();
if(slave_pready_random)
return $urandom_range(0, 2);
else
return 0;
endfunction

virtual function get_pslverr_status();
if(slave_pslverr_random && $urandom_range(0, 20) == 0)
return 1;
else
return 0;
endfunction

endclass
`endif // LVC_APB_CONFIG_SV

阶段3——基础序列

根据特性覆盖率的映射编写基础的测试序列

  • apb_master_single_write_sequence:单次写,只发送一次data
  • apb_master_single_read_sequence:单次读,只读一次data
  • apb_master_write_read_sequence:单次写,中间等idle_cycle拍,从同一个地址单次读
  • apb_master_burst_write_sequence:连续写
  • apb_master_burst_read_sequence:连续读
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
`ifndef APB_MASTER_SEQ_LIB_SV
`define APB_MASTER_SEQ_LIB_SV

typedef class apb_transfer;
typedef class apb_master_sequencer;

class apb_master_base_sequence extends uvm_sequence #(apb_transfer);

`uvm_object_utils(apb_master_base_sequence)
function new(string name="");
super.new(name);
endfunction : new

endclass : apb_master_base_sequence

// USER: Add your sequences here

class apb_master_single_write_sequence extends apb_master_base_sequence;
rand bit [31:0] addr;
rand bit [31:0] data;
apb_trans_status trans_status;

`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);
trans_status = rsp.trans_status;
`uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
endtask: body

endclass: apb_master_single_write_sequence

class apb_master_single_read_sequence extends apb_master_base_sequence;
rand bit [31:0] addr;
rand bit [31:0] data;
apb_trans_status trans_status;

`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);
trans_status = rsp.trans_status;
data = rsp.data;
`uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
endtask: body

endclass: apb_master_single_read_sequence

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;
apb_trans_status trans_status;
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;
trans_status = rsp.trans_status;
`uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
endtask: body

endclass: apb_master_write_read_sequence

class apb_master_burst_write_sequence extends apb_master_base_sequence;
rand bit [31:0] addr;
rand bit [31:0] data[];
apb_trans_status trans_status;
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)
trans_status = OK;
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);
trans_status = rsp.trans_status == ERROR ? ERROR : trans_status;
`uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
endtask: body
endclass: apb_master_burst_write_sequence

class apb_master_burst_read_sequence extends apb_master_base_sequence;
rand bit [31:0] addr;
rand bit [31:0] data[];
apb_trans_status trans_status;
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)
trans_status = OK;
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);
trans_status = rsp.trans_status == ERROR ? ERROR : trans_status;
`uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
endtask: body
endclass: apb_master_burst_read_sequence

`endif // APB_MASTER_SEQ_LIB_SV

阶段4——顶层序列+test

apb_base_test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class apb_base_test extends uvm_test;
apb_env env;
apb_config cfg;
`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);
cfg = apb_config::type_id::create("cfg");
// USER TODO
// Option-1 manually set the config parameters
// cfg.slave_pready_default_value = 1;
// cfg.slave_pready_random = 1;
// cfg.slave_pslverr_random = 1;
// Option-2 randomize the config parameters
// void'(cfg.randomize());
uvm_config_db#(apb_config)::set(this,"env.*","cfg", cfg);
env = apb_env::type_id::create("env", this);
endfunction
endclass

apb_single_transaction_test

顶层sequence挂载到master_sequencer上

1
2
3
4
5
6
7
8
9
10
11
12
13
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_base_test_sequence

没有body方法,单纯用来给别的test_sequence继承用的

为啥这里不继承virtual_sequence?

因为就一个master的sequencer,所有的sequence全都绑到这一个sequencer上,所以是Hierarchical Sequence

  • check_mem_data(bit[31:0] addr, bit[31:0] data):数据比对
  • wait_reset_release():执行复位
  • wait_cycles(int n):等待一定周期
  • get_rand_addr():获取随机地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class apb_base_test_sequence extends uvm_sequence #(apb_transfer);
bit[31:0] mem[bit[31:0]];
`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 != DEFAULT_READ_VALUE) begin
`uvm_error("CMPDATA", $sformatf("addr 32'h%8x, READ DATA expected 32'h%8x != actual 32'h%8x", addr, DEFAULT_READ_VALUE, 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;addr != 0;});
return addr;
endfunction
endclass

apb_single_transaction_sequence

将3个单次的sequence包装起来,进行组合

  • 重复的随机地址单次写+随机地址单次读+对比

  • 重复的相同地址单次写完就单次读+对比

  • 重复的写后读(这个sequence不用组合)+对比(顶层没有定义idle_cycle,由底层sequence进行随机)

  • 没有复用基层sequence,有重新通过req的挂载加了一个序列

    重复的连续写两次+读一次+对比(idle_cycle为0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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);

// 调用单次写,执行100次,idle_cycle默认为1
`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

// 执行100次单次读,进行数据比对
`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;})
if(single_read_seq.trans_status == OK)
void'(this.check_mem_data(addr, single_read_seq.data));
end

// 执行100次单次写+单次读+数据比对
`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;})
if(single_read_seq.trans_status == OK)
void'(this.check_mem_data(addr, single_read_seq.data));
end

// 执行100次写后读 + 数据比对,写后有个idle_cycle由基层sequence随机生成
`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;
if(write_read_seq.trans_status == OK)
void'(this.check_mem_data(addr, write_read_seq.data));
end

// 执行100次,连续两次写不同数据+读+数据比对
`uvm_info(get_type_name(), "TEST write twice and read immediately with burst transaction...", UVM_LOW)
repeat(test_num) begin
addr = this.get_rand_addr();
// WRITE first time
`uvm_do_with(req, {trans_kind == WRITE;
addr == local::addr;
data == local::addr;
idle_cycles == 0;
})
mem[addr] = addr;
get_response(rsp);
// WRITE second time
`uvm_do_with(req, {trans_kind == WRITE;
addr == local::addr;
data == local::addr<<2;
idle_cycles == 0;
})
mem[addr] = addr<<2;
get_response(rsp);
// READ immediately after WRITE
`uvm_do_with(req, {trans_kind == READ; addr == local::addr;})
get_response(rsp);
if(rsp.trans_status == OK)
void'(this.check_mem_data(addr, rsp.data));
end

this.wait_cycles(10);
endtask
endclass: apb_single_transaction_sequence

apb_burst_transaction_sequence

对两次连续的读、写进行组合

  • 执行连续写,将写的数据放入test的关联数组mem,连续读,将读的数据和test中的mem数据进行比对
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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

apb_if+assertion

注意3个时钟块:

根据主端和从端的时钟块中的信号方向,连接方式一目了然

注意两个initial块:

  • initial begin : coverage_control:通过automatic—+例化执行特性覆盖率收集

    问1:psel的burst和penable的single和实际的test cases应该有关系吧~

    问2:按理说除了cg_lvc_apb_trans_timing_group,别的特性覆盖率定义都是被断言覆盖率覆盖了啊

    不过也有可能断言覆盖率不满足,但是特性覆盖率满足

  • initial begin: assertion_control:通过\$assertoff和$asserton系统方法执行断言覆盖率收集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
`ifndef LVC_APB_IF_SV
`define LVC_APB_IF_SV

interface lvc_apb_if (input clk, input rstn);

logic [31:0] paddr;
logic pwrite;
logic psel;
logic penable;
logic [31:0] pwdata;
logic [31:0] prdata;
logic pready;
logic pslverr;

// Control flags
bit has_checks = 1;
bit has_coverage = 1;

import uvm_pkg::*;
`include "uvm_macros.svh"
// 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, pready, pslverr;
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, pready, pslverr;
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, pready, pslverr;
endclocking : cb_mon

// Coverage and assertions to be implemented here.
// USER: Add assertions/coverage here

// APB command covergroup
covergroup cg_lvc_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: cg_lvc_apb_command

// APB transaction timing group 连续进行读或写,64个即代表没有扩展情况下的32次读、写
covergroup cg_lvc_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);
}
//单次(不同的idle cycle)、连续
penable: coverpoint penable {
bins single = (0 => 1 => 0 [*2:10] => 1);
bins burst = (0 => 1 => 0 => 1);
}
endgroup: cg_lvc_apb_trans_timing_group

// APB write & read order group
covergroup cg_lvc_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: cg_lvc_apb_write_read_order_group

initial begin : coverage_control
if(has_coverage) begin
automatic cg_lvc_apb_command cg0 = new();
automatic cg_lvc_apb_trans_timing_group cg1 = new();
automatic cg_lvc_apb_write_read_order_group cg2 = new();
end
end

// PROPERY ASSERTION
property p_paddr_no_x; //在PSEL为高时,PADDR总线不可以为X值
@(posedge clk) psel |-> !$isunknown(paddr);
endproperty: p_paddr_no_x
assert property(p_paddr_no_x) else `uvm_error("ASSERT", "PADDR is unknown when PSEL is high")

property p_psel_rose_next_cycle_penable_rise; //在PSEL拉高的下一个周期,PENABLE也应该拉高
@(posedge clk) $rose(psel) |=> $rose(penable);
endproperty: p_psel_rose_next_cycle_penable_rise
assert property(p_psel_rose_next_cycle_penable_rise) else `uvm_error("ASSERT", "PENABLE not rose after 1 cycle PSEL rose")

property p_penable_rose_next_cycle_fall; //在PENABLE和pready均为高的下一个周期,PENABLE应该拉低
@(posedge clk) penable && pready |=> $fell(penable);
endproperty: p_penable_rose_next_cycle_fall
assert property(p_penable_rose_next_cycle_fall) else `uvm_error("ASSERT", "PENABLE not fall after 1 cycle PENABLE rose")

property p_pwdata_stable_during_trans_phase; //在PSEL和PWRITE同时保持为高的阶段,PWDATA需要保持
@(posedge clk) ((psel && !penable) ##1 (psel && penable)) |-> $stable(pwdata);
endproperty: p_pwdata_stable_during_trans_phase
assert property(p_pwdata_stable_during_trans_phase) else `uvm_error("ASSERT", "PWDATA not stable during transaction phase")

property p_paddr_stable_until_next_trans; //下一次传输开始前(如何判断下一次传播啥时候开始),PADDR和PWRITE信号应该保持不变
logic[31:0] addr1, addr2;
@(posedge clk) first_match(($rose(penable),addr1=paddr) ##1 ((psel && !penable)[=1],addr2=$past(paddr))) |-> addr1 == addr2;
endproperty: p_paddr_stable_until_next_trans
assert property(p_paddr_stable_until_next_trans) else `uvm_error("ASSERT", "PADDR not stable until next transaction start")

property p_pwrite_stable_until_next_trans;
logic pwrite1, pwrite2;
@(posedge clk) first_match(($rose(penable),pwrite1=pwrite) ##1 ((psel && !penable)[=1],pwrite2=$past(pwrite))) |-> pwrite1 == pwrite2;
endproperty: p_pwrite_stable_until_next_trans
assert property(p_pwrite_stable_until_next_trans) else `uvm_error("ASSERT", "PWRITE not stable until next transaction start")

property p_prdata_available_once_penable_rose; //在PENABLE拉高的同一个周期,如果是读信号,PRDATA应该发生变化
@(posedge clk) penable && !pwrite && pready |-> !$stable(prdata);
endproperty: p_prdata_available_once_penable_rose
// disable the property below since it conflicts with long
// term hold PRDATA from the same address
//assert property(p_prdata_available_once_penable_rose) else `uvm_error("ASSERT", "PRDATA not available once PENABLE rose")

// PROPERTY COVERAGE
property p_write_during_nonburst_trans; //非连续写
@(posedge clk) $rose(penable) |-> pwrite throughout (##1 (!penable)[*2] ##1 penable[=1]);
endproperty: p_write_during_nonburst_trans
cover property(p_write_during_nonburst_trans);

property p_write_during_burst_trans; //连续写
@(posedge clk) $rose(penable) |-> pwrite throughout (##2 penable);
endproperty: p_write_during_burst_trans
cover property(p_write_during_burst_trans);

property p_write_read_burst_trans; //对同一个地址先做写操作,再不间隔做读操作
logic[31:0] addr;
@(posedge clk) ($rose(penable) && pwrite, addr=paddr) |-> (##2 ($rose(penable) && !pwrite && addr==paddr));
endproperty: p_write_read_burst_trans
cover property(p_write_read_burst_trans);

property p_write_twice_read_burst_trans; //对同一个地址做连续两次写操作,再从中读取数据
logic[31:0] addr;
@(posedge clk) ($rose(penable) && pwrite, addr=paddr) |-> (##2 ($rose(penable) && pwrite && addr==paddr) ##2 ($rose(penable) && !pwrite && addr==paddr) );
endproperty: p_write_twice_read_burst_trans
cover property(p_write_twice_read_burst_trans);

property p_read_during_nonburst_trans; //不连续读
@(posedge clk) $rose(penable) |-> !pwrite throughout (##1 (!penable)[*2] ##1 penable[=1]);
endproperty: p_read_during_nonburst_trans
cover property(p_read_during_nonburst_trans);

property p_read_during_burst_trans; //连续读
@(posedge clk) $rose(penable) |-> !pwrite throughout (##2 penable);
endproperty: p_read_during_burst_trans
cover property(p_read_during_burst_trans);

property p_read_write_read_burst_trans; //同一个地址连续的读,写,读操作
logic[31:0] addr;
@(posedge clk) ($rose(penable) && !pwrite, addr=paddr) |-> (##2 ($rose(penable) && pwrite && addr==paddr) ##2 ($rose(penable) && !pwrite && addr==paddr) );
endproperty: p_read_write_read_burst_trans
cover property(p_read_write_read_burst_trans);


initial begin: assertion_control
fork
forever begin
wait(rstn == 0);
$assertoff();
wait(rstn == 1);
$asserton();
end
join_none
end

endinterface : lvc_apb_if

apb_tb

  • 定义时钟周期
  • 例化接口+接口传入agent
  • run_test()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
`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