UVM的学习也告一段路,接下来进入验证的番外篇,也就是整儿八经的实战项目和对于行业的理解了,升华一下自己的眼界,同时也为以后的工作做些准备
1. MCDF设计更新
贴近工作场景,核心除了怎么更好的编,还有怎么更稳定的复用和迭代!!!
1.1 结构更新
- channel的概念由slave node替代
- slave node的接口发生变化
- register的接口发生变化
- formatter的接口发生变化
1.2 接口变化
slave node接口
DATA(31:0)
:通道数据输入。(input)DATA_PARITY
:data数据的奇偶校验位。(input)VALID
:数据有效标志信号。(input)WAIT
:暂停接收。(类似于之前的ready信号)(output)PARITY_ERR
:slave node侧发现数据或者数据校验位出错。(output)
输入数据和奇偶校验位,输出看数据和奇偶校验是否匹配
register接口(AMBA APB标准总线)
PADDR(7:0)
:地址。(input)PWR
:写标识。(input)PEN
:总线使能。(input)PSEL
:总线选择。(input)PWDATA(31:0)
:写入数据。(input)PRDATA(31:0)
:读出数据。(output)PREADY
:读写操作完成标识,可以用于延迟。(output)PSLVERR
:总线操作错误。(例如无效空间访问)(output)
formatter接口
PKG_VLD
:数据包有效标识。(output)PKG_FST
:数据包首个数据标识符。(output)PKG_DATA
:数据输出端口。(output)PKG_LST
:数据包末尾数据标识符。(output)PKG_RDY
:接受侧准备就绪标识。(input)
1.3 时序更新
slave node接口时序
- slave node接收从上行发来的数据,包括32bit的DATA和1bit的Parity。数据在VALID的指示下接收。
- 数据能否被接收取决于三个条件:数据是否有效(VALID)、内部FIFO的状态(Not Full)、数据有效性的校验结果(No Parity Error)。如果后两个条件不满足,WAIT端口置起,阻挡上行数据发送。
- DATA_PARITY_ERR端口在第三个条件不满足时被置起(不像FIFO状态可以自动改变wait信号),这个信号也同时作为RO(ready only)状态位可以通过APB读出。而且它只能通过APB设置P_ERR_CLR寄存器来清除复位。
上图给出了6种传输情形:
- #1:正常传输。
- #2:VALID=0,上行数据未准备好,等待。
- #3:数据校验错误,接收端置起WAIT将数据接收和输入挂起。
- #4:错误标识位通过APB清除,数据准备重发。
- #5:FIFO满,WAIT置起,数据发送被延期。
- #6:FIFO恢复,重发数据成功。
问:为啥会出现输入的奇偶校验位DATA_PARITY在到达slaveNODE之后和数据对应不上的情况?
答:
如上图所示的1个4个bit的输入,每一位对应一个触发器,最低位由于延迟而导致建立时间不够从1变成了0,此时输入的奇偶校验和数据就对不上了
formatter数据包格式以及接口时序
之前的formatter:ch_id和length都是放在接口上的,现在都放在了头字里,且length不再是简单的4,8,16,32。而是1-256
注意这里的奇偶校验位parity是只检测所有的data还是包含了头字
寄存器更新
变化明显:
- 由一个32位寄存器决定对哪个slave进行操作,同样是一个寄存器决定所有通道的数据包长度
- free_slot对应之前的状态寄存器,读取avalid
- parity_err对应slave的奇偶校验检测的数据,通过寄存器parity_err_clr可以将其擦除
1.4 设计代码
slave_node
assign valid_o = !fifo_empty_s;
意味着只要slave的fifo不满,valid_o 信号就为1,
top_module中:assign req_vec_s = {slv3_valid_o_s,slv2_valid_o_s,slv1_valid_o_s,slv0_valid_o_s};
4个chnal的valid_o 信号构成了arbiter的4位输入,再结合arbiter的仲裁算法,从valid_o 信号为1的通道中选择1个和formater通信
端口:
1 | module slave_node ( |
奇偶校验的处理
1 | assign parity_err_s = valid_i && ^{data_i,data_p_i}; //data的异或结果和传入的奇偶校验位异或 |
fifo模块的定义
1 | module sync_dff_fifo ( |
arbiter
冲裁器的仲裁行为规定:
4个通道中,从左到右的优先级依次降低,但这样设计会导致后面的通道很难有机会发送数据,如何解决这一问题??
解决措施就是用cp_vec_r信号将请求信号进行左右拆分,只有左侧有请求信号或者只有右侧有的时候,就按照左侧的优先级更高;
如果左侧和右侧同时有请求的时候,选择右侧的最左边请求信号作为winner
注意:cp_vec_r信号拆分出的l_filter 和 r_filter(~l_filter)覆盖了4个bit,然后和slave_node过来的请求req信号做与运算, 保证了只要有一个req,都能够选出winer
如果是常量的cp_vec_r,则拆分方式太过僵硬,优先级仍然是锁死的,可以考虑进行动态调整cp_vec_r信号,让4个通道的覆盖率尽可能相同
将cp信号和win信号挂钩,此时优先级就按照轮询的方式发生变化
如果命中了通道4,上一次的win信号为0001,则cp为0010,left为1100,right为0011
此时的优先级变为:通道3,通道4,通道1,通道2
如果命中了通道3:win信号变为0010,则cp为0100,left为1000,right为0111;
此时的优先级为:通道2,通道3,通道4,通道1
如果命中了通道2:win信号变为0100,则cp为1000,left为0000,right为1111;
此时的优先级为:通道1,通道2,通道3,通道4
如果命中了通道1:win信号变为1000,cp为0001,left为1110,right为1;
此时的优先级为:通道4,通道1,通道2,通道3
仲裁器的最终输出:沟通formater的slave通道winner
只有formater的trigger_i信号为1时,win_vec_o才会更新,更新值为trigger_i信号到来前的win_vec_s值。
即在trigger_i高电平信号到来前,优先级 & req信号 得到win_vec_s;
在trigger_i高电平信号到来时,当前的win_vec_s就是沟通formater的通道,优先级会发生更改;
在下一次触发信号到来前,会不断按照新的优先级计算出新的winner。
1 | module RR_arbiter( |
formater
对外端口:
1 | module formater( |
3种输出(len+id;payload;parity):
状态转换:
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// 初始态
parameter [2:0] ST_RST = 3'b000;
// 给arbiter发送请求信号
parameter [2:0] ST_Run_RR = 3'b001;
// 收到reciver_ready信号后,发送header信号
parameter [2:0] ST_Header = 3'b010;
// 收到reciver_ready信号后,发送len长度的数据
parameter [2:0] ST_Payload = 3'b011;
// 最后一个字发送奇偶校验
parameter [2:0] ST_Parity = 3'b100;
reg [2:0] cur_st, nxt_st;
wire [3:0] win_req_vec_s ;
wire win_req_s ;
wire any_req_s ;
assign win_req_vec_s = win_vec_i & req_vec_i ; //4位slave发送的req和arbiter的仲裁win可能会错过,此时一样是4位0
assign win_req_s = |win_req_vec_s ;
assign any_req_s = |req_vec_i ;
always @(posedge clk_i or negedge rst_n_i)
begin
if (!rst_n_i) cur_st <= ST_RST ;
else cur_st <= nxt_st ;
end
always @(*) begin
nxt_st <= cur_st;
case (cur_st)
//slave 只要向formater发送4位req有1位为1,formater应向arbiter发送triger信号
ST_RST : if ( any_req_s) nxt_st <= ST_Run_RR ;
//只要4位仲裁win信号和4位req信号匹配,formater应进入header状态,将发送header信息(组合逻辑已经得到了id和len)
ST_Run_RR : if ( win_req_s) nxt_st <= ST_Header ;
//接收到recive信号,正式进入传输阶段
ST_Header : if ( rev_rdy_i && win_req_s) nxt_st <= ST_Payload ;
//
ST_Payload : if (pkg_lst_s && rev_rdy_i && win_req_s) nxt_st <= ST_Parity ;
ST_Parity : if ( rev_rdy_i ) nxt_st <= ST_Run_RR ;
endcase
end仲裁结果:(还可以通过case分支语句)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//Select winner for data/id/len
assign data_slv0_win_s = win_vec_i[0] ? data_slv0_i: 0 ;
assign data_slv1_win_s = win_vec_i[1] ? data_slv1_i: 0 ;
assign data_slv2_win_s = win_vec_i[2] ? data_slv2_i: 0 ;
assign data_slv3_win_s = win_vec_i[3] ? data_slv3_i: 0 ;
assign data_win_s = data_slv0_win_s | data_slv1_win_s | data_slv2_win_s | data_slv3_win_s ;
assign id_slv0_win_s = win_vec_i[0] ? id_slv0_i : 0 ;
assign id_slv1_win_s = win_vec_i[1] ? id_slv1_i : 0 ;
assign id_slv2_win_s = win_vec_i[2] ? id_slv2_i : 0 ;
assign id_slv3_win_s = win_vec_i[3] ? id_slv3_i : 0 ;
assign id_win_s = id_slv0_win_s | id_slv1_win_s | id_slv2_win_s | id_slv3_win_s ;
assign len_slv0_win_s = win_vec_i[0] ? len_slv0_i : 0 ;
assign len_slv1_win_s = win_vec_i[1] ? len_slv1_i : 0 ;
assign len_slv2_win_s = win_vec_i[2] ? len_slv2_i : 0 ;
assign len_slv3_win_s = win_vec_i[3] ? len_slv3_i : 0 ;
assign len_win_s = len_slv0_win_s | len_slv1_win_s | len_slv2_win_s | len_slv3_win_s ;当前状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22wire is_st_run_rr_s ;
wire is_st_header_s ;
wire is_st_payload_s ;
wire is_st_parity_s ;
wire send_header_s ;
wire send_payload_s ;
wire send_parity_s ;
wire pkg_lst_s ;
//驱动发送
assign is_st_run_rr_s = (cur_st==ST_Run_RR )? 1'b1 : 1'b0 ;
//头信息发送
assign is_st_header_s = (cur_st==ST_Header )? 1'b1 : 1'b0 ;
//数据信息发送
assign is_st_payload_s = (cur_st==ST_Payload)? 1'b1 : 1'b0 ;
//奇偶校验发送
assign is_st_parity_s = (cur_st==ST_Parity )? 1'b1 : 1'b0 ;
assign send_header_s = is_st_header_s && rev_rdy_i ;
assign send_payload_s = is_st_payload_s && rev_rdy_i && win_req_s ;
assign send_parity_s = is_st_parity_s && rev_rdy_i ;assign trigger_start_o = is_st_run_rr_s ;
assign pkg_lst_s = ~|(len_cnt_r) ;
对slave_node的输出:(fetch)
1 | wire [3:0] fetch_vec_s ; |
当前formater处于is_st_payload_s,且
1 | wire [31:0] data_slv0_win_s ; |
2. UVM验证环境更新策略
2.1 组件更新
- channel master agent的driver和monitor由于总线接口信号和时序的变化,需要更新。
- register master agent由于总线更新为APB,需要开发完整的APB master agent。
- formatter slave agent由于总线信号和时序的变化,也需要进行更新。
- 寄存器列表发生了变化,因此也需要进行寄存器模块的更新。
- 由于寄存器访问VIP发生变化,也需要对寄存器模型与总线VIP桥接转换的adapter进行更新。
2.2 环境更新
- 与MCDF连接的各个接口信号需要重新定义。
- 在顶层testbench,对于各个接口信号的连接也需要更新。
2.3 测试更新
- 在尽量保证验证环境复用和测试用例复用的情况下,需要考虑如何复用原有的测试。原有的测试部分可以分为:do_config(),即寄存器配置部分。do_formatter(),即formatter slave agent行为模型的配置。do_data(),即发送数据。
- 可以尽量保证do_formatter()和do_data()的测试代码部分保持不变, 而只修改上层对寄存器的配置部分。
- 寄存器的配置之所以需要修改,是因为寄存器模型本身发生了变化,而与访问寄存器总线IP发生变化没有直接关系。
总结:
更新:寄存器模型、总线、adapter、do_config()、环境。
2.4 环境复用和序列复用
环境复用:
- 保证顶层环境的稳定
- 尽量减少底层组件的更新
在更新底层组件的时候,也要考虑如何实施,从而保证顶层环境的稳定。
序列复用:
- 保证底层序列的稳定
- 尽量减少底层序列的更新
尽量让底层序列的更新尽可能少的影响顶层序列
3. VIP
3.1 APB的VIP
transcation 和 sequence
apb_transfer.sv
地址(一个PADDR信号),数据(1个PWDATA/PRDATA信号),和总线的三种操作状态(三个控制信号决定了状态:PSEL, PENABLE, PWRITE)
1 |
|
底层apb_master_sequence
apb_master_single_write_sequence
1
2
3
4
5
6virtual task body();
get_response(rsp);
endtask: bodyapb_master_single_read_sequence
1
2
3
4
5
6
7virtual task body();
get_response(rsp);
data = rsp.data;
endtask: bodyapb_master_write_read_sequence
写完就读,设置cycle为0,写和读用的地址也是同一个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17rand int idle_cycles;
constraint cstr{
idle_cycles == 0;
}
virtual task body();
addr == local::addr;
data == local::data;
idle_cycles == local::idle_cycles;
})
get_response(rsp);
get_response(rsp);
data = rsp.data;
endtask: bodyapb_master_burst_write_sequence
连续写:数据改为数组
随机化:给一个连增的地址写入连增的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20rand bit [31:0] data[];
constraint cstr{
soft data.size() inside {4, 8, 16, 32};
foreach(data[i]) soft data[i] == addr + (i << 2);
}
virtual task body();
foreach(data[i]) begin
addr == local::addr + (i<<2);
data == local::data[i];
idle_cycles == 0;
})
get_response(rsp);
end
get_response(rsp);
endtask: bodyapb_master_burst_read_sequence
连续读,将读回的结果放入data数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24rand bit [31:0] addr;
rand bit [31:0] data[];
constraint cstr{
soft data.size() inside {4, 8, 16, 32};
}
function new(string name="");
super.new(name);
endfunction : new
virtual task body();
foreach(data[i]) begin
addr == local::addr + (i<<2);
idle_cycles == 0;
})
get_response(rsp);
data[i] = rsp.data;
end
get_response(rsp);
endtask: body
apb_test
test主要就是在build_phase中进行创建,让环境准备好;在run_phase中进行挂载,让系统跑起来~~
test cases真正的核心在于sequences
apb_base_test_sequence
check_mem_data()方法原理:(参考上面的结构图)
关联数组mem:bit[31:0] 对应32位地址总线
用来master和slave之间的数据比对,test和slave中都有一个mem,master通过接口发送数据给slave,slave中的mem和test中的mem都会存储这个数据,等从slave读回数据时,就可以和test中mem里面的数据进行比较。
1 | class apb_base_test_sequence extends uvm_sequence #(apb_transfer); |
apb_single_transaction_sequence
类
可以看到base_test的sequence没有body()方法,也就是激励的挂载全部交由子类进行
1 | task body(); |
3.2 APB协议的断言检查和断言覆盖率
问:为啥这里的读、写都是用##2的,中间空的一拍呢?
APB总线的时序,中间空了一拍setup的状态。
以连续写为例:相当于PENABLE为1时,写入数据。下一拍:PSEL和PWRITE保持,PENABLE为0,此时为setup状态,也就是开启了一次新的传输,PWDATA也在此时建立。再下一拍:PENABLE拉高,数据写入。
即一次写或读需要 两拍时钟才能完成:见断言覆盖率-1
断言检查:
所有property都在interface中定义,先看一下interface中总线信号的定义
1 | interface apb_if (input clk, input rstn); |
1.在PSEL为高时,PADDR总线不可以为X值
注意系统boolean方法:$isunknown():信号为x则返回1
1 | property p_paddr_no_x; |
2.在PSEL拉高的下一个周期,PENABLE也应该拉高
注意系统boolean方法:$rose():上一拍的值为0,这一拍的值为1,则返回1
1 | property p_psel_rose_next_cycle_penable_rise; |
3.在PENABLE拉高的下一个周期,PENABLE应该拉低
注意系统boolean方法:$fell():上一拍的值为1,这一拍的值为0,则返回1
1 | property p_penable_rose_next_cycle_fall; |
4.在PSEL和PWRITE同时保持为高的阶段,PWDATA需要保持
注意系统boolean方法:$stable():信号在时钟上升沿保持不变(也就是第二拍的数据和第一拍保持一样)则返回1
1 | property p_pwdata_stable_during_trans_phase; |
5.下一次传输开始前(如何判断下一次传播啥时候开始),PADDR和PWRITE信号应该保持不变
当前者为某个区间取值,例如[1:5],如果没有|->或者|=>时,则匹配一次即可进入下一阶段或者断言成功,但是如果有|->或者|=>时,则必须保证所有情况都满足才能进入下一阶段,否则卡死。而first_match的作用就是在此时,使得只要出现一种满足情况即可进入下一阶段。
$rose(penable):第一次数据传输,记录此时PADDR和PWRITE
(psel && !penable)[=1]:第二次setup,记录上一拍的。。。看一下时序图,在setup的这一拍传入新的PADDR
1 | property p_paddr_stable_until_next_trans; //PADDR保持不变 |
6.在PENABLE拉高的同一个周期,如果是读信号,PRDATA应该发生变化
1 | property p_prdata_available_once_penable_rose; |
7.断言检查的控制
只有做过复位之后才将断言检查打开
1 | initial begin: assertion_control |
断言覆盖率
1.写操作时,分别发生连续写(只需要覆盖到PSEL连续4拍为高,同时PWRITE连续4拍为高才能保证,PENABLE和PSEL的关系在上面的断言检查中已经检查过了 ,不用重复定义)和非连续写(PSEL信号每两拍就得拉下来至少1拍,PWRITE无所谓)
注意:
操作符throughout必须连接一个表达式和一个序列即req throughout seq,含义是在seq匹配起始到结束期间,req都必须成立。
[*2]:连续两拍
[=1]:非连续的1拍,前面(0-n)拍为!penable,后面(0-n)拍为!penable
- 不连续的写了两次
- 连续的写了两次
1 | property p_write_during_nonburst_trans; //非连续写 |
2.对同一个地址先做写操作,再不间隔做读操作
要求读的地址和写地址保持相同,
1 | property p_write_read_burst_trans; |
3.对同一个地址做连续两次写操作,再从中读取数据
1 | property p_write_twice_read_burst_trans; |
4.读操作时,分别发生连续读和不连续读
1 | property p_read_during_nonburst_trans; //直接套用不连续写 |
5.发生对同一个地址的读操作再不间隔做写操作,再不间隔做读操作
1 | property p_read_write_read_burst_trans; |
仿真命令
1 | vsim -novopt -sv_seed random -assertdebug -assertcover -classdebug +UVM_TESTNAME=apb_single_transaction_test -l assertion.log work.apb_tb |
执行结果
assertion
可以看到定义的7个并行断言
- pass count:真正通过property的,而不是空成功的(前置和后置seq都满足)
- active count:当前后台有没有正在做检查的assertion
- failure count:失败的property
- peak memory:峰值的memory,当前a满足,就需要开个线程去评估b,如果下一拍还满足就再开一个线程,当覆盖多个周期时,线程的累计会消耗大量资源
日志
可以直观的看到错误的数量,这也是为什么选择用`uvm_error进行打印!!!
assertion波形
断言检查的第4项:property p_pwdata_stable_during_trans_phase;
蓝点代表start:进入assert;黄点代表pass:在第二拍验证通过
covergroup
功能覆盖率
cover directives
断言覆盖率
4. 项目验证代码
大概思路:
VIP提供了沟通寄存器的master_agent,此时只需要建立寄存器模型,由adapter给agent的sequencer发送item
要思考一下寄存器的激励发送方式
寄存器模型(python生成)
slv_en_reg(4位,对应每个寄存器的使能)
覆盖率的收集调用方法是固定搭配:sample()方法+sample_values()方法;
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
41package mcdf_rgm_pkg;
import uvm_pkg::*;
class slv_en_reg extends uvm_reg;
rand uvm_reg_field en;
rand uvm_reg_field reserved;
covergroup value_cg;
option.per_instance = 1;
en: coverpoint en.value[3:0];
reserved: coverpoint reserved.value[27:0];
endgroup
function new(string name = "slv_en_reg");
super.new(name, 32, UVM_CVR_ALL);
void'(set_coverage(UVM_CVR_FIELD_VALS));
if(has_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg = new();
end
endfunction
virtual function void build();
en = uvm_reg_field::type_id::create("en");
reserved = uvm_reg_field::type_id::create("reserved");
en.configure(this, 4, 0, "RW", 0, 'h0, 1, 1, 0);
reserved.configure(this, 28, 4, "RO", 0, 'h0, 1, 0, 0);
endfunction
function void sample(
uvm_reg_data_t data,
uvm_reg_data_t byte_en,
bit is_read,
uvm_reg_map map
);
super.sample(data, byte_en, is_read, map);
sample_values();
endfunction
function void sample_values();
super.sample_values();
if (get_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg.sample();
end
endfunction
endclassparity_err_clr_reg(4位,对应4个奇偶校验结果的状态寄存器,为1时擦除)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class parity_err_clr_reg extends uvm_reg;
rand uvm_reg_field err_clr;
rand uvm_reg_field reserved;
covergroup value_cg;
option.per_instance = 1;
err_clr: coverpoint err_clr.value[3:0];
reserved: coverpoint reserved.value[27:0];
endgroup
function new(string name = "parity_err_clr_reg");
super.new(name, 32, UVM_CVR_ALL);
void'(set_coverage(UVM_CVR_FIELD_VALS));
if(has_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg = new();
end
endfunction
virtual function void build();
err_clr = uvm_reg_field::type_id::create("err_clr");
reserved = uvm_reg_field::type_id::create("reserved");
err_clr.configure(this, 4, 0, "RW", 0, 'h0, 1, 1, 0);
reserved.configure(this, 28, 4, "RO", 0, 'h0, 1, 0, 0);
endfunction
endclassslv_id_reg(32位,每个字节对应一个通道的ID,给到fomater)
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
31class slv_id_reg extends uvm_reg;
rand uvm_reg_field slv0_id;
rand uvm_reg_field slv1_id;
rand uvm_reg_field slv2_id;
rand uvm_reg_field slv3_id;
covergroup value_cg;
option.per_instance = 1;
slv0_id: coverpoint slv0_id.value[7:0];
slv1_id: coverpoint slv1_id.value[7:0];
slv2_id: coverpoint slv2_id.value[7:0];
slv3_id: coverpoint slv3_id.value[7:0];
endgroup
function new(string name = "slv_id_reg");
super.new(name, 32, UVM_CVR_ALL);
void'(set_coverage(UVM_CVR_FIELD_VALS));
if(has_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg = new();
end
endfunction
virtual function void build();
slv0_id = uvm_reg_field::type_id::create("slv0_id");
slv1_id = uvm_reg_field::type_id::create("slv1_id");
slv2_id = uvm_reg_field::type_id::create("slv2_id");
slv3_id = uvm_reg_field::type_id::create("slv3_id");
slv0_id.configure(this, 8, 0, "RW", 0, 'h0, 1, 1, 0);
slv1_id.configure(this, 8, 8, "RW", 0, 'h1, 1, 1, 0);
slv2_id.configure(this, 8, 16, "RW", 0, 'h2, 1, 1, 0);
slv3_id.configure(this, 8, 24, "RW", 0, 'h3, 1, 1, 0);
endfunction
endclassslv_len_reg(32位,每个字节对应一个通道的打包数据长度,给formater)
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
31class slv_len_reg extends uvm_reg;
rand uvm_reg_field slv0_len;
rand uvm_reg_field slv1_len;
rand uvm_reg_field slv2_len;
rand uvm_reg_field slv3_len;
covergroup value_cg;
option.per_instance = 1;
slv0_len: coverpoint slv0_len.value[7:0];
slv1_len: coverpoint slv1_len.value[7:0];
slv2_len: coverpoint slv2_len.value[7:0];
slv3_len: coverpoint slv3_len.value[7:0];
endgroup
function new(string name = "slv_len_reg");
super.new(name, 32, UVM_CVR_ALL);
void'(set_coverage(UVM_CVR_FIELD_VALS));
if(has_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg = new();
end
endfunction
virtual function void build();
slv0_len = uvm_reg_field::type_id::create("slv0_len");
slv1_len = uvm_reg_field::type_id::create("slv1_len");
slv2_len = uvm_reg_field::type_id::create("slv2_len");
slv3_len = uvm_reg_field::type_id::create("slv3_len");
slv0_len.configure(this, 8, 0, "RW", 0, 'h0, 1, 1, 0);
slv1_len.configure(this, 8, 8, "RW", 0, 'h0, 1, 1, 0);
slv2_len.configure(this, 8, 16, "RW", 0, 'h0, 1, 1, 0);
slv3_len.configure(this, 8, 24, "RW", 0, 'h0, 1, 1, 0);
endfunction
endclassslv_free_slot_reg(6位状态寄存器,复位值为32,记录slave_fifo的当前空余位)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class slv0_free_slot_reg extends uvm_reg;
rand uvm_reg_field free_slot;
rand uvm_reg_field reserved;
covergroup value_cg;
option.per_instance = 1;
free_slot: coverpoint free_slot.value[5:0];
reserved: coverpoint reserved.value[25:0];
endgroup
function new(string name = "slv0_free_slot_reg");
super.new(name, 32, UVM_CVR_ALL);
void'(set_coverage(UVM_CVR_FIELD_VALS));
if(has_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg = new();
end
endfunction
virtual function void build();
free_slot = uvm_reg_field::type_id::create("free_slot");
reserved = uvm_reg_field::type_id::create("reserved");
free_slot.configure(this, 6, 0, "RO", 0, 'h20, 1, 0, 0);
reserved.configure(this, 26, 6, "RO", 0, 'h0, 1, 0, 0);
endfunction
endclassslv_parity_err_reg(1位状态寄存器,slave_Node输入数据的奇偶校验结果和输入的奇偶校验位不匹配时置1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class slv0_parity_err_reg extends uvm_reg;
rand uvm_reg_field parity_err;
rand uvm_reg_field reserved;
covergroup value_cg;
option.per_instance = 1;
parity_err: coverpoint parity_err.value[0:0];
reserved: coverpoint reserved.value[30:0];
endgroup
function new(string name = "slv0_parity_err_reg");
super.new(name, 32, UVM_CVR_ALL);
void'(set_coverage(UVM_CVR_FIELD_VALS));
if(has_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg = new();
end
endfunction
virtual function void build();
parity_err = uvm_reg_field::type_id::create("parity_err");
reserved = uvm_reg_field::type_id::create("reserved");
parity_err.configure(this, 1, 0, "RO", 0, 'h0, 1, 0, 0);
reserved.configure(this, 31, 1, "RO", 0, 'h0, 1, 0, 0);
endfunction
endclass无论是6位的slv_freeslot寄存器,还是1位的slv_parity寄存器,都可以只声明一次类,然后通过例化创建多个寄存器对象
但是通过脚本创建寄存器的时候要将尽量统一化管理才行~
寄存器模型
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
104class mcdf_rgm extends uvm_reg_block;
rand slv_en_reg slv_en;
rand parity_err_clr_reg parity_err_clr;
rand slv_id_reg slv_id;
rand slv_len_reg slv_len;
rand slv0_free_slot_reg slv0_free_slot;
rand slv1_free_slot_reg slv1_free_slot;
rand slv2_free_slot_reg slv2_free_slot;
rand slv3_free_slot_reg slv3_free_slot;
rand slv0_parity_err_reg slv0_parity_err;
rand slv1_parity_err_reg slv1_parity_err;
rand slv2_parity_err_reg slv2_parity_err;
rand slv3_parity_err_reg slv3_parity_err;
uvm_reg_map map;
function new(string name = "mcdf_rgm");
super.new(name, UVM_NO_COVERAGE);
endfunction
virtual function void build();
slv_en = slv_en_reg::type_id::create("slv_en");
slv_en.configure(this);
slv_en.build();
parity_err_clr = parity_err_clr_reg::type_id::create("parity_err_clr");
parity_err_clr.configure(this);
parity_err_clr.build();
slv_id = slv_id_reg::type_id::create("slv_id");
slv_id.configure(this);
slv_id.build();
slv_len = slv_len_reg::type_id::create("slv_len");
slv_len.configure(this);
slv_len.build();
slv0_free_slot = slv0_free_slot_reg::type_id::create("slv0_free_slot");
slv0_free_slot.configure(this);
slv0_free_slot.build();
...//slv1,slv2,slv3
slv0_parity_err = slv0_parity_err_reg::type_id::create("slv0_parity_err");
slv0_parity_err.configure(this);
slv0_parity_err.build();
...//slv1,slv2,slv3
map = create_map("map", 'h0, 4, UVM_LITTLE_ENDIAN);
map.add_reg(slv_en, 32'h00, "RW");
map.add_reg(parity_err_clr, 32'h04, "RW");
map.add_reg(slv_id, 32'h08, "RW");
map.add_reg(slv_len, 32'h0C, "RW");
map.add_reg(slv0_free_slot, 32'h80, "RO");
map.add_reg(slv1_free_slot, 32'h84, "RO");
map.add_reg(slv2_free_slot, 32'h88, "RO");
map.add_reg(slv3_free_slot, 32'h8C, "RO");
map.add_reg(slv0_parity_err, 32'h90, "RO");
map.add_reg(slv1_parity_err, 32'h94, "RO");
map.add_reg(slv2_parity_err, 32'h98, "RO");
map.add_reg(slv3_parity_err, 32'h9C, "RO");
slv_en.add_hdl_path_slice("ctrl_mem[0]", 0, 32);
parity_err_clr.add_hdl_path_slice("ctrl_mem[1]", 0, 32);
slv_id.add_hdl_path_slice("ctrl_mem[2]", 0, 32);
slv_len.add_hdl_path_slice("ctrl_mem[3]", 0, 32);
slv0_free_slot.add_hdl_path_slice("ro_mem[0]", 0, 32);
slv1_free_slot.add_hdl_path_slice("ro_mem[1]", 0, 32);
slv2_free_slot.add_hdl_path_slice("ro_mem[2]", 0, 32);
slv3_free_slot.add_hdl_path_slice("ro_mem[3]", 0, 32);
slv0_parity_err.add_hdl_path_slice("ro_mem[4]", 0, 32);
slv1_parity_err.add_hdl_path_slice("ro_mem[5]", 0, 32);
slv2_parity_err.add_hdl_path_slice("ro_mem[6]", 0, 32);
slv3_parity_err.add_hdl_path_slice("ro_mem[7]", 0, 32);
add_hdl_path("tb.dut.inst_reg_if");
lock_model();
endfunction
//输入通道号,返回formater要打包的长度
function int get_reg_field_length(int ch);
int fd;
case(ch)
0: fd = slv_len.slv0_len.get();
1: fd = slv_len.slv1_len.get();
2: fd = slv_len.slv2_len.get();
3: fd = slv_len.slv3_len.get();
default:
endcase
return fd;
endfunction
//输入通道号,返回通道id
function int get_reg_field_id(int ch);
int fd;
case(ch)
0: fd = slv_id.slv0_id.get();
1: fd = slv_id.slv1_id.get();
2: fd = slv_id.slv2_id.get();
3: fd = slv_id.slv3_id.get();
default:
endcase
return fd;
endfunction
//根据id,判断是哪个通道
function int get_chnl_index(int ch_id);
int fd_id;
for(int i=0; i<4; i++) begin
fd_id = get_reg_field_id(i);
if(fd_id == ch_id)
return i;
end
return -1;
endfunction
endclass
endpackage
验证顶层
1. refmod
区别于上面没有使用寄存器模型的refmod,uvm中可以直接从寄存器模型中获取各寄存器域的值!!!
调用寄存器内部定义的任务获取数据进行打包的通道和数据包长度
rgm.get_reg_field_length(ch)
rgm.get_reg_field_id(ch)
此时只需要从
1 | class mcdf_refmod extends uvm_component; |
2. scordboard
在scordboard中例化参考模型,scoreboard,monitor,refmod之间使用tlm端口或tlm管道建立连接。
do_data_compare():发送到通道的激励
寄存器模型内部定义的get_chnl_index方法,可以根据fmt_monitor的transcation中的ch_id属性进行转换,得到这个fmt_trans是哪一个通道;
在该通道的out_tlm_fifo中取出打包好的fmt_trans和fmt_tlm_fifo中的trans进行对比
1 | class mcdf_checker extends uvm_scoreboard; |