项目1

UVM的学习也告一段路,接下来进入验证的番外篇,也就是整儿八经的实战项目和对于行业的理解了,升华一下自己的眼界,同时也为以后的工作做些准备

1. MCDF设计更新

贴近工作场景,核心除了怎么更好的编,还有怎么更稳定的复用和迭代!!!

1.1 结构更新

  • channel的概念由slave node替代
  • slave node的接口发生变化
  • register的接口发生变化
  • formatter的接口发生变化

image-20210801152901826

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寄存器来清除复位。

image-20210801154143147

上图给出了6种传输情形:

  • #1:正常传输。
  • #2:VALID=0,上行数据未准备好,等待。
  • #3:数据校验错误,接收端置起WAIT将数据接收和输入挂起。
  • #4:错误标识位通过APB清除,数据准备重发。
  • #5:FIFO满,WAIT置起,数据发送被延期。
  • #6:FIFO恢复,重发数据成功。

问:为啥会出现输入的奇偶校验位DATA_PARITY在到达slaveNODE之后和数据对应不上的情况?

答:image-20210801155527540

如上图所示的1个4个bit的输入,每一位对应一个触发器,最低位由于延迟而导致建立时间不够从1变成了0,此时输入的奇偶校验和数据就对不上了

formatter数据包格式以及接口时序

image-20210801155855670

image-20210801163149889

之前的formatter:ch_id和length都是放在接口上的,现在都放在了头字里,且length不再是简单的4,8,16,32。而是1-256

注意这里的奇偶校验位parity是只检测所有的data还是包含了头字

寄存器更新

image-20210801164307305

变化明显:

  • 由一个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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module slave_node (
input clk_i,
input rst_n_i,
// 和driver通信
input [31:0] data_i,
input data_p_i, // 输入的奇偶校验位
input valid_i, // 数据有效信号
input slv_en_i, // 使能信号
output wait_o,
output parity_err_o, // 内部奇偶校验和外部输入不匹配
// 和fifo通信
input fetch_i,
output [31:0] data_o,
output [ 5:0] freeslot_o, // 给到状态寄存器,表示fifo有多少空位
output valid_o, //不为空就可以向下传

// 和寄存器通信
input parity_err_clr_i //将奇偶校验的输出拉低

);
reg parity_err_r ;
wire parity_err_s, wait_s, fifo_full_s, fifo_wr_s, fifo_rd_s, fifo_empty_s;

奇偶校验的处理

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
assign parity_err_s = valid_i && ^{data_i,data_p_i}; //data的异或结果和传入的奇偶校验位异或

always @ (posedge clk_i or negedge rst_n_i) begin : Parity_Err
if (!rst_n_i) begin
parity_err_r <= 1'b0;
end
else begin
if (parity_err_s ) parity_err_r <= 1'b1 ;
if (parity_err_clr_i) parity_err_r <= 1'b0 ;
end
end

assign parity_err_o = parity_err_r;
assign parity_err_o = parity_err_r;

// slave的写,读,idle三种状态的条件
assign wait_s = fifo_full_s || parity_err_r;
assign fifo_wr_s = valid_i && !wait_s && slv_en_i ; //为1时fifo执行写入
assign fifo_rd_s = fetch_i && !fifo_empty_s; // 为1时从fifo读取

//决定写入时是否要等待
assign wait_o = !slv_en_i || wait_s;

sync_dff_fifo inst_fifo (
.clk_i(clk_i ),
.rst_n_i(rst_n_i ),
.data_i(data_i ),
.rd_i(fifo_rd_s ),
.wr_i(fifo_wr_s ),
.full_o(fifo_full_s ),
.empty_o(fifo_empty_s ),
.data_o(data_o ),
.freeslot_o(freeslot_o )
);
// fifo不为空时就表示下行数据有效
assign valid_o = !fifo_empty_s;

endmodule

fifo模块的定义

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
module sync_dff_fifo (
input clk_i,
input rst_n_i,
input [31:0] data_i, //宽度32
input rd_i,
input wr_i,
output full_o, //满时为1
output empty_o, //空时为1
output overflow_o, //溢出
output [31:0] data_o,
output [5:0] freeslot_o //给到状态寄存器,表示fifo有多少空位
);
parameter ADDR_W_C = 5; //fifo状态,对应状态寄存器
parameter DEPTH_C = 32; //fifo深度32
//写地址
reg [ADDR_W_C-1 :0] wr_p_r ;
//读地址
reg [ADDR_W_C-1 :0] rd_p_r ;
//定义32*32的数组,模拟fifo容器
reg [31:0] mem [DEPTH_C-1:0] ;
reg [ADDR_W_C :0] freeslot_r ;
wire full_s, empty_s;
reg overflow_r ;

always @(posedge clk_i or negedge rst_n_i)
begin
if (!rst_n_i) begin
freeslot_r <= DEPTH_C;
rd_p_r <= 0;
wr_p_r <= 0;
overflow_r <= 1'b0;
end else begin
//满足读写条件,且数据有效位 为1,slave模块将读、写置1
if(rd_i) rd_p_r <= rd_p_r +1 ;
if(wr_i) wr_p_r <= wr_p_r +1 ;
//数据读出,空槽+1
if ( rd_i && ~wr_i && !empty_s ) begin
freeslot_r <= freeslot_r+1 ;
end
//没满时数据写入,空槽-1,满了就设置
if (~rd_i && wr_i ) begin
if (~full_s) begin
freeslot_r <= freeslot_r-1 ; // cnt-- on only write
end else begin
overflow_r <= 1'b1;
end
end
if (wr_i) begin
mem[wr_p_r] <= data_i ;
end
end
end
assign full_s = freeslot_r == 0 ? 1 : 0 ;
assign empty_s = freeslot_r == DEPTH_C ? 1 : 0 ;
assign empty_o = empty_s ;
assign full_o = full_s ;
assign overflow_o = overflow_r;
assign data_o = mem[rd_p_r];
assign freeslot_o = freeslot_r ;
endmodule

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
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
module RR_arbiter(
input clk_i,
input rst_n_i,
input [3:0] req_vec_i,
output [3:0] win_vec_o,
input trigger_i
);
reg [3:0] cp_vec_r ;
wire [3:0] filter_L_s, filter_R_s, req_msb1_L_s, req_msb1_R_s, req_FL_L_s, req_FL_R_s, req_R_s, req_L_s;
wire [3:0] win_L_s , win_R_s ;
reg [3:0] win_vec_s;
reg [3:0] win_vec_r;
wire req_all0_L_s, req_all0_R_s ;
//--------------------------------------------------------------------------------------------------------
// Generate filter L/R from cp_vec_r
// e.g. cp_vec_r : 0100
// r_filter : 0111
// r3: c3
// r2: c3 | c2
// r1: c3 | c2 | c1
// r0: c3 | c2 | c1 | c0
// l_filter : 1000 (not r_filter)
//--------------------------------------------------------------------------------------------------------
assign filter_L_s[3] = cp_vec_r[3];
assign filter_L_s[2] = cp_vec_r[3] || cp_vec_r[2] ;
assign filter_L_s[1] = cp_vec_r[3] || cp_vec_r[2] || cp_vec_r[1] ;
assign filter_L_s[0] = cp_vec_r[3] || cp_vec_r[2] || cp_vec_r[1] || cp_vec_r[0];

assign filter_R_s = ~ filter_L_s ;
//--------------------------------------------------------------------------------------------------------
// generate L/R parts with filter
// e.g. req = 001100
// cp = 000100
// filter_L = 111000
// filter_R = 000111
// req_L = 001000
// req_R = 000100
//--------------------------------------------------------------------------------------------------------
assign req_L_s = req_vec_i & filter_R_s ;
assign req_R_s = req_vec_i & filter_L_s ;
//--------------------------------------------------------------------------------------------------------
// fill 1s into req lower part (right is lower)
// e.g. req = 001100
// cp = 000100
// filter_L = 111000
// filter_R = 000111
// req_L = 001000
// req_R = 000100
// req_FL_L = 001111
// req_FL_R = 000111
//--------------------------------------------------------------------------------------------------------
assign req_FL_L_s[3] = req_L_s[3];
assign req_FL_L_s[2] = req_L_s[3] || req_L_s[2] ;
assign req_FL_L_s[1] = req_L_s[3] || req_L_s[2] || req_L_s[1] ;
assign req_FL_L_s[0] = req_L_s[3] || req_L_s[2] || req_L_s[1] || req_L_s[0] ;

assign req_FL_R_s[3] = req_R_s[3];
assign req_FL_R_s[2] = req_R_s[3] || req_R_s[2] ;
assign req_FL_R_s[1] = req_R_s[3] || req_R_s[2] || req_R_s[1] ;
assign req_FL_R_s[0] = req_R_s[3] || req_R_s[2] || req_R_s[1] || req_R_s[0] ;
//--------------------------------------------------------------------------------------------------------
// Find the bit 1 from the Left most
// e.g. req = 001100
// cp = 000100
// filter_L = 111000
// filter_R = 000111
// req_L = 001000
// req_R = 000100
// req_FL_L = 001111
// req_FL_R = 000111
// req_MSB1_L = 001000
// req_MSB1_R = 000100
//--------------------------------------------------------------------------------------------------------
assign req_msb1_L_s[3] = req_FL_L_s[3] ;
assign req_msb1_L_s[2] = req_msb1_L_s[3] ? 1'b0 : req_FL_L_s[2] ;
assign req_msb1_L_s[1] = |{req_msb1_L_s[3],req_msb1_L_s[2]} ? 1'b0 : req_FL_L_s[1] ;
assign req_msb1_L_s[0] = |{req_msb1_L_s[3],req_msb1_L_s[2],req_msb1_L_s[1]} ? 1'b0 : req_FL_L_s[0] ;

assign req_msb1_R_s[3] = req_FL_R_s[3] ;
assign req_msb1_R_s[2] = req_msb1_R_s[3] ? 1'b0 : req_FL_R_s[2] ;
assign req_msb1_R_s[1] = |{req_msb1_R_s[3],req_msb1_R_s[2] }? 1'b0 : req_FL_R_s[1] ;
assign req_msb1_R_s[0] = |{req_msb1_R_s[3],req_msb1_R_s[2],req_msb1_R_s[1]}? 1'b0 : req_FL_R_s[0] ;
//最终的左右两边的最左侧信号
assign req_all0_L_s = ~(|req_msb1_L_s) ;
assign req_all0_R_s = ~(|req_msb1_R_s) ;

always @(req_all0_L_s or req_all0_R_s or req_msb1_R_s or req_msb1_L_s)
begin
case ({req_all0_L_s, req_all0_R_s})
2'b11: win_vec_s <= 4'b0000 ; // 没有请求进来
2'b10: win_vec_s <= req_msb1_R_s ; // 只有右侧有请求,选择最左边的那个
2'b01: win_vec_s <= req_msb1_L_s ; // 只有左侧有请求,选择最左边的那个
2'b00: win_vec_s <= req_msb1_R_s ; // 左侧和右侧都有请求,选择右侧的最左边那个
endcase
end

assign win_vec_o = win_vec_r;
always @(posedge clk_i or negedge rst_n_i)
begin
if (!rst_n_i) begin
cp_vec_r <= 4'b1000;
win_vec_r <= 4'b0000;
end else begin
if (trigger_i) begin
cp_vec_r[0] <= win_vec_s[1];
cp_vec_r[1] <= win_vec_s[2];
cp_vec_r[2] <= win_vec_s[3];
cp_vec_r[3] <= win_vec_s[0];
win_vec_r <= win_vec_s;
end

end
end
endmodule

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
31
module formater(
input clk_i,
input rst_n_i,
//4通道数据输入
input [31:0] data_slv0_i,
input [31:0] data_slv1_i,
input [31:0] data_slv2_i,
input [31:0] data_slv3_i,
//4通道配置寄存器输入 id+len(寄存器复位时输入默认值,可重新写入)
input [7:0] id_slv0_i,
input [7:0] id_slv1_i,
input [7:0] id_slv2_i,
input [7:0] id_slv3_i,

input [7:0] len_slv0_i,
input [7:0] len_slv1_i,
input [7:0] len_slv2_i,
input [7:0] len_slv3_i,
//起始通道输入(mcdf顶层可以看到,这里的每一位对应slave_node的valid_o)
input [3:0] req_vec_i ,
output [3:0] fetch_vec_o,
//arbiter通信
input [3:0] win_vec_i,
output trigger_start_o,
//下行数据,与formater的driver通信
input rev_rdy_i, // driver给的ready信号
output pkg_vld_o, // 打包好了信号
output [31:0] pkg_dat_o, // 单次数据传送
output pkg_fst_o, // 开始发送信号
output pkg_lst_o // 结束信号
);

3种输出(len+id;payload;parity):

  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
    // 初始态
    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
  2. 仲裁结果:(还可以通过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 ;
  3. 当前状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    wire 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
2
3
wire [3:0]  fetch_vec_s ;
assign fetch_vec_s = {4{rev_rdy_i && is_st_payload_s }} & win_vec_i ;
assign fetch_vec_o = fetch_vec_s ;

当前formater处于is_st_payload_s,且

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
wire [31:0] data_slv0_win_s ;
wire [31:0] data_slv1_win_s ;
wire [31:0] data_slv2_win_s ;
wire [31:0] data_slv3_win_s ;
wire [31:0] data_win_s ;

wire [7:0] id_slv0_win_s ;
wire [7:0] id_slv1_win_s ;
wire [7:0] id_slv2_win_s ;
wire [7:0] id_slv3_win_s ;
wire [7:0] id_win_s ;

wire [7:0] len_slv0_win_s ;
wire [7:0] len_slv1_win_s ;
wire [7:0] len_slv2_win_s ;
wire [7:0] len_slv3_win_s ;
wire [7:0] len_win_s ;



wire pkg_vld_s ;
reg [31:0] tx_d_s ;
reg [31:0] parity_r ;
reg [7:0] len_cnt_r ;

always @(*)
begin
tx_d_s <= 0;
// Header <= ID + LEN
if (is_st_header_s) begin
tx_d_s <= {id_win_s, len_win_s,16'h0000};
end
// Payload
if (is_st_payload_s) begin
tx_d_s <= data_win_s ;
end
// Parity
if (is_st_parity_s) begin
tx_d_s <= parity_r;
end

end


//--------------------------------------------------------------------------------------------------------
//Parity Gen
//--------------------------------------------------------------------------------------------------------
always @(posedge clk_i or negedge rst_n_i)
begin
if (~rst_n_i) begin
parity_r <= 0 ;
len_cnt_r <= 0 ;
end else begin
// Load the 1st data in buffer
if (send_header_s) begin
parity_r <= {id_win_s, len_win_s,16'h0000};
len_cnt_r <= len_win_s ;
end

// XOR payload
if (send_payload_s) begin
parity_r <= data_win_s ^ parity_r ;
len_cnt_r<= len_cnt_r - 1;
end

//
if (send_parity_s) begin
parity_r <= 0 ;
len_cnt_r <= 0 ;
end
end
end

// fetch asserted when
// 1. downlink's recieve_ready is asserted &
// 2. selected fifo data is valid


//--------------------------------------------------------------------------------------------------------
assign pkg_dat_o = tx_d_s ;
assign pkg_fst_o = send_header_s ;
assign pkg_lst_o = send_parity_s ;
//---------------------------------------
// pkg_vld_o asserted when
// a. header phase
// b. send payload phase and payload is valid
// c. parity phase
assign pkg_vld_s = is_st_header_s || (is_st_payload_s && win_req_s) || is_st_parity_s ;
assign pkg_vld_o = pkg_vld_s ;
endmodule

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

image-20210807102944216

transcation 和 sequence

apb_transfer.sv

地址(一个PADDR信号),数据(1个PWDATA/PRDATA信号),和总线的三种操作状态(三个控制信号决定了状态:PSEL, PENABLE, PWRITE)

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 APB_TRANSFER_SV
`define APB_TRANSFER_SV

typedef enum {IDLE, WRITE, READ } apb_trans_kind;

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;
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 // APB_TRANSFER_SV

底层apb_master_sequence

  • apb_master_single_write_sequence

    1
    2
    3
    4
    5
    6
    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
  • apb_master_single_read_sequence

    1
    2
    3
    4
    5
    6
    7
    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
  • apb_master_write_read_sequence

    写完就读,设置cycle为0,写和读用的地址也是同一个

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    rand int           idle_cycles; 
    constraint cstr{
    idle_cycles == 0;
    }
    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
  • apb_master_burst_write_sequence

    连续写:数据改为数组

    随机化:给一个连增的地址写入连增的数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    rand 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();
    `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
  • apb_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
    24
    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

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
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 != 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

apb_single_transaction_sequence

可以看到base_test的sequence没有body()方法,也就是激励的挂载全部交由子类进行

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

3.2 APB协议的断言检查和断言覆盖率

image-20210802184155414image-20210802225324329

问:为啥这里的读、写都是用##2的,中间空的一拍呢?

APB总线的时序,中间空了一拍setup的状态。

以连续写为例:相当于PENABLE为1时,写入数据。下一拍:PSEL和PWRITE保持,PENABLE为0,此时为setup状态,也就是开启了一次新的传输,PWDATA也在此时建立。再下一拍:PENABLE拉高,数据写入。

即一次写或读需要 两拍时钟才能完成:见断言覆盖率-1

断言检查:

所有property都在interface中定义,先看一下interface中总线信号的定义

1
2
3
4
5
6
7
8
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;

1.在PSEL为高时,PADDR总线不可以为X值

注意系统boolean方法:$isunknown():信号为x则返回1

1
2
3
4
property p_paddr_no_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")

2.在PSEL拉高的下一个周期,PENABLE也应该拉高

注意系统boolean方法:$rose():上一拍的值为0,这一拍的值为1,则返回1

1
2
3
4
property p_psel_rose_next_cycle_penable_rise;
@(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")

3.在PENABLE拉高的下一个周期,PENABLE应该拉低

注意系统boolean方法:$fell():上一拍的值为1,这一拍的值为0,则返回1

1
2
3
4
property p_penable_rose_next_cycle_fall;
@(posedge clk) $rose(penable) |=> $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")

4.在PSEL和PWRITE同时保持为高的阶段,PWDATA需要保持

注意系统boolean方法:$stable():信号在时钟上升沿保持不变(也就是第二拍的数据和第一拍保持一样)则返回1

1
2
3
4
property p_pwdata_stable_during_trans_phase;
@(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")

5.下一次传输开始前(如何判断下一次传播啥时候开始),PADDR和PWRITE信号应该保持不变

当前者为某个区间取值,例如[1:5],如果没有|->或者|=>时,则匹配一次即可进入下一阶段或者断言成功,但是如果有|->或者|=>时,则必须保证所有情况都满足才能进入下一阶段,否则卡死。而first_match的作用就是在此时,使得只要出现一种满足情况即可进入下一阶段。

$rose(penable):第一次数据传输,记录此时PADDR和PWRITE

(psel && !penable)[=1]:第二次setup,记录上一拍的。。。看一下时序图,在setup的这一拍传入新的PADDR

1
2
3
4
5
6
7
8
9
10
11
property p_paddr_stable_until_next_trans;       //PADDR保持不变
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; //PWRITE保持不变
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")

6.在PENABLE拉高的同一个周期,如果是读信号,PRDATA应该发生变化

1
2
3
4
property p_prdata_available_once_penable_rose;
@(posedge clk) $rose(penable) && !pwrite |-> !$stable(prdata);
endproperty: p_prdata_available_once_penable_rose
assert property(p_prdata_available_once_penable_rose) else `uvm_error("ASSERT", "PRDATA not available once PENABLE rose")

7.断言检查的控制

只有做过复位之后才将断言检查打开

1
2
3
4
5
6
7
8
9
10
initial begin: assertion_control
fork
forever begin
wait(rstn == 0);
$assertoff();
wait(rstn == 1);
$asserton();
end
join_none
end

断言覆盖率

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
2
3
4
5
6
7
8
9
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);

2.对同一个地址先做写操作,再不间隔做读操作

要求读的地址和写地址保持相同,

1
2
3
4
5
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);

3.对同一个地址做连续两次写操作,再从中读取数据

1
2
3
4
5
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);

4.读操作时,分别发生连续读和不连续读

1
2
3
4
5
6
7
8
9
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);

5.发生对同一个地址的读操作再不间隔做写操作,再不间隔做读操作

1
2
3
4
5
 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);

仿真命令

1
vsim -novopt -sv_seed random -assertdebug -assertcover -classdebug +UVM_TESTNAME=apb_single_transaction_test -l assertion.log work.apb_tb

执行结果

assertion

image-20210803004043117

可以看到定义的7个并行断言

  • pass count:真正通过property的,而不是空成功的(前置和后置seq都满足)
  • active count:当前后台有没有正在做检查的assertion
  • failure count:失败的property
  • peak memory:峰值的memory,当前a满足,就需要开个线程去评估b,如果下一拍还满足就再开一个线程,当覆盖多个周期时,线程的累计会消耗大量资源

日志

image-20210803005728575

可以直观的看到错误的数量,这也是为什么选择用`uvm_error进行打印!!!

assertion波形

image-20210803010000612

断言检查的第4项:property p_pwdata_stable_during_trans_phase;

蓝点代表start:进入assert;黄点代表pass:在第二拍验证通过

covergroup

功能覆盖率

image-20210803010732214

cover directives

断言覆盖率

image-20210803011005771

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
    41
    package mcdf_rgm_pkg;
    import uvm_pkg::*;
    `include "uvm_macros.svh"
    class slv_en_reg extends uvm_reg;
    `uvm_object_utils(slv_en_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
    endclass
  • parity_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
    23
    class parity_err_clr_reg extends uvm_reg;
    `uvm_object_utils(parity_err_clr_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
    endclass
  • slv_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
    31
    class slv_id_reg extends uvm_reg;
    `uvm_object_utils(slv_id_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
    endclass
  • slv_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
    31
    class slv_len_reg extends uvm_reg;
    `uvm_object_utils(slv_len_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
    endclass
  • slv_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
    23
    class slv0_free_slot_reg extends uvm_reg;
    `uvm_object_utils(slv0_free_slot_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
    endclass
  • slv_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
    23
    class slv0_parity_err_reg extends uvm_reg;
    `uvm_object_utils(slv0_parity_err_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
    104
    class mcdf_rgm extends uvm_reg_block;
    `uvm_object_utils(mcdf_rgm)
    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: `uvm_error("TYPERR", $sformatf("channel number should not be %0d", ch))
    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: `uvm_error("TYPERR", $sformatf("channel number should not be %0d", ch))
    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
    `uvm_error("CHIDERR", $sformatf("unrecognized channel ID and could not find the corresponding channel index", ch_id))
    return -1;
    endfunction
    endclass
    endpackage

验证顶层

1. refmod

image-20210727205241147

区别于上面没有使用寄存器模型的refmod,uvm中可以直接从寄存器模型中获取各寄存器域的值!!!

调用寄存器内部定义的任务获取数据进行打包的通道和数据包长度

rgm.get_reg_field_length(ch)

rgm.get_reg_field_id(ch)

此时只需要从

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
class mcdf_refmod extends uvm_component;
mcdf_rgm rgm;
uvm_blocking_get_peek_port #(chnl_mon_trans) in_bgpk_ports[4];
uvm_tlm_analysis_fifo #(fmt_trans) out_tlm_fifos[4];

`uvm_component_utils(mcdf_refmod)
function new (string name = "mcdf_refmod", uvm_component parent);
super.new(name, parent);
foreach(in_bgpk_ports[i]) in_bgpk_ports[i] = new($sformatf("in_bgpk_ports[%0d]", i), this);
foreach(out_tlm_fifos[i]) out_tlm_fifos[i] = new($sformatf("out_tlm_fifos[%0d]", i), this);
endfunction

function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(mcdf_rgm)::get(this,"","rgm", rgm)) begin
`uvm_fatal("GETRGM","cannot get RGM handle from config DB")
end
endfunction

task run_phase(uvm_phase phase);
fork
do_packet(0);
do_packet(1);
do_packet(2);
do_packet(3);
join
endtask

task do_packet(int ch);
fmt_trans ot;
chnl_mon_trans it;
forever begin
this.in_bgpk_ports[ch].peek(it);
ot = new();
ot.length = rgm.get_reg_field_length(ch);
ot.ch_id = rgm.get_reg_field_id(ch);
ot.data = new[ot.length+3];
foreach(ot.data[m]) begin
if(m == 0) begin
ot.data[m] = (ot.ch_id<<24) + (ot.length<<16);
ot.parity = ot.data[m];
end
else if(m == ot.data.size()-1) begin
ot.data[m] = ot.parity;
end
else begin
this.in_bgpk_ports[ch].get(it);
ot.data[m] = it.data;
ot.parity ^= it.data;
end
end
this.out_tlm_fifos[ch].put(ot);
end
endtask
endclass: mcdf_refmod

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
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
class mcdf_checker extends uvm_scoreboard;
local int err_count;
local int total_count;
local int chnl_count[4];
local virtual chnl_intf chnl_vifs[4];
local virtual mcdf_intf mcdf_vif;
local mcdf_refmod refmod;
mcdf_rgm rgm;

uvm_tlm_analysis_fifo #(chnl_mon_trans) chnl_tlm_fifos[4];
uvm_tlm_analysis_fifo #(fmt_trans) fmt_tlm_fifo;

uvm_blocking_get_port #(fmt_trans) exp_bg_ports[4];

`uvm_component_utils(mcdf_checker)

function new (string name = "mcdf_checker", uvm_component parent);
super.new(name, parent);
this.err_count = 0;
this.total_count = 0;
foreach(this.chnl_count[i]) this.chnl_count[i] = 0;
foreach(chnl_tlm_fifos[i]) chnl_tlm_fifos[i] = new($sformatf("chnl_tlm_fifos[%0d]", i), this);
fmt_tlm_fifo = new("fmt_tlm_fifo", this);
foreach(exp_bg_ports[i]) exp_bg_ports[i] = new($sformatf("exp_bg_ports[%0d]", i), this);
endfunction

function void build_phase(uvm_phase phase);
super.build_phase(phase);
// get virtual interface
if(!uvm_config_db#(virtual mcdf_intf)::get(this,"","mcdf_vif", mcdf_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
foreach(chnl_vifs[i]) begin
if(!uvm_config_db#(virtual chnl_intf)::get(this,"",$sformatf("chnl_vifs[%0d]",i), chnl_vifs[i])) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
end
if(!uvm_config_db#(mcdf_rgm)::get(this,"","rgm", rgm)) begin
`uvm_fatal("GETRGM","cannot get RGM handle from config DB")
end
this.refmod = mcdf_refmod::type_id::create("refmod", this);
endfunction

function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
foreach(refmod.in_bgpk_ports[i]) refmod.in_bgpk_ports[i].connect(chnl_tlm_fifos[i].blocking_get_peek_export);
foreach(exp_bg_ports[i]) begin
exp_bg_ports[i].connect(refmod.out_tlm_fifos[i].blocking_get_export);
end
endfunction

task run_phase(uvm_phase phase);
fork
this.do_channel_disable_check(0);
this.do_channel_disable_check(1);
this.do_channel_disable_check(2);
this.do_channel_disable_check(3);
this.do_data_compare();
join
endtask

task do_data_compare();
fmt_trans expt, mont;
bit cmp;
int ch_idx;
forever begin
this.fmt_tlm_fifo.get(mont);
ch_idx = this.rgm.get_chnl_index(mont.ch_id);
this.exp_bg_ports[ch_idx].get(expt);
cmp = mont.compare(expt);
this.total_count++;
this.chnl_count[ch_idx]++;
if(cmp == 0) begin
this.err_count++; #1ns;
`uvm_info("[CMPERR]", $sformatf("monitored formatter data packet:\n %s", mont.sprint()), UVM_MEDIUM)
`uvm_info("[CMPERR]", $sformatf("expected formatter data packet:\n %s", expt.sprint()), UVM_MEDIUM)
`uvm_error("[CMPERR]", $sformatf("%0dth times comparing but failed! MCDF monitored output packet is different with reference model output", this.total_count))
end
else begin
`uvm_info("[CMPSUC]",$sformatf("%0dth times comparing and succeeded! MCDF monitored output packet is the same with reference model output", this.total_count), UVM_LOW)
end
end
endtask

task do_channel_disable_check(int id);
forever begin
@(posedge this.mcdf_vif.clk iff (this.mcdf_vif.rstn && this.mcdf_vif.mon_ck.chnl_en[id]===0));
if(this.chnl_vifs[id].mon_ck.ch_valid===1 && this.chnl_vifs[id].mon_ck.ch_wait===0)
`uvm_error("[CHKERR]", "ERROR! when channel disabled, wait signal low when valid high")
end
endtask

function void report_phase(uvm_phase phase);
string s;
super.report_phase(phase);
s = "\n---------------------------------------------------------------\n";
s = {s, "CHECKER SUMMARY \n"};
s = {s, $sformatf("total comparison count: %0d \n", this.total_count)};
foreach(this.chnl_count[i]) s = {s, $sformatf(" channel[%0d] comparison count: %0d \n", i, this.chnl_count[i])};
s = {s, $sformatf("total error count: %0d \n", this.err_count)};
foreach(this.chnl_tlm_fifos[i]) begin
if(this.chnl_tlm_fifos[i].size() != 0)
s = {s, $sformatf("WARNING:: chnl_tlm_fifos[%0d] is not empty! size = %0d \n", i, this.chnl_tlm_fifos[i].size())};
end
if(this.fmt_tlm_fifo.size() != 0)
s = {s, $sformatf("WARNING:: fmt_tlm_fifo is not empty! size = %0d \n", this.fmt_tlm_fifo.size())};
s = {s, "---------------------------------------------------------------\n"};
`uvm_info(get_type_name(), s, UVM_LOW)
endfunction
endclass: mcdf_checker