概述
经过两次面试后,对MCDF做一次全面的深入总结。
目前进度:硬件部分的node,fifo,寄存器,formatter,MCDF顶层,APB接口,TB接口
软件部分的chnl_pkg,fmt_pkg,apb_pkg,mcdf_rgm_pkg,mcdf_pkg
目录
硬件RTL部分
slave_node
FIFO
arbiter
param_def
寄存器接口
整形器formatter
顶层MCDF
接口打包信号
APB接口
tb中的接口
tb后续
软件部分
chnl_pkg
fmt_pkg
mcdf_rgm_pkg
apb_pkg
apb_test
apb_slave
硬件RTL部分
slave_node
首先是slave_node,就是一开始的channel,为了限制篇幅,具体的解析都写在注释里。
这里的功能是作为FIFO的上行端,按输入输出两端的信号拉高或拉低相应的信号,包括wait,奇偶校验位错误,freeslot,valid,fetch等。
这里还有的疑问如下:
fetch_i是哪里来的信号?由于是读信号的一个条件,且是上行端的数据,猜测是reg的信号
freeslot_o 是哪里的信号?输出端是fifo,在这里没有相应的编码。
奇偶校验位这里是放在最低位,详细看:
电力电子转战数字IC20220527day11(1)——奇偶校验_广工陈奕湘的博客-CSDN博客_缩位异或
module slave_node (
clk_i , //
rst_n_i , //
// From uplink
data_i , //
data_p_i , //奇偶校验位
valid_i , //
slv_en_i , //
wait_o , //
parity_err_o , //
// 输出端
data_o , //
freeslot_o , //
valid_o , //
fetch_i ,
parity_err_clr_i //寄存器来的清除奇偶校验位错误信号
);
input clk_i ; //
input rst_n_i ; //
// IO with driver
input [31:0] data_i ; //
input data_p_i ; //
input valid_i ; //
input slv_en_i ; //
output wait_o ; //
output parity_err_o ; //
// IO with Arbiter
output [31:0] data_o ; //
output [ 5:0] freeslot_o ; //
output valid_o ; //
output fetch_i ;
// IO with register
input parity_err_clr_i ;
reg parity_err_r ;//定义一个reg来assign给parity_err_o
wire parity_err_s, wait_s, fifo_full_s, fifo_wr_s, fifo_rd_s, fifo_empty_s;
assign parity_err_s = valid_i && ^{data_i,data_p_i} ;
//奇偶校验,对数据进行缩位异或后,如果和奇偶校验位不同则出1,此时若valid还拉高就是出错了,error拉高
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
// 若err_s为1出现数据校验错误,err_r拉高,若clear信号拉高则清楚err信号,拉低
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 wait_s = fifo_full_s || parity_err_r ;
//满了或者出现error则要wait
assign fifo_wr_s = valid_i && !parity_err_r && !wait_s && slv_en_i ;
//写信号,有效且校验位正确且不满且enable
assign fifo_rd_s = fetch_i && !fifo_empty_s;
//读信号,不空且fetch则可读
assign wait_o = !slv_en_i || fifo_full_s || parity_err_r ;
//什么时候wait拉高?enable为低,fifo满了,奇偶校验错了
//硬件DUT信号的连接,左边是下一个代码段的fifo
//空满读写分别对应fifo的
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 )
);
assign valid_o = !fifo_empty_s;//不空则valid
endmodule
FIFO
node之后数据存放到fifo中, fifo复习
电力电子转战数字IC20220531day15——双端口RAM与异步FIFO_广工陈奕湘的博客-CSDN博客_fifo 双口ram
module sync_dff_fifo (
clk_i ,
rst_n_i ,
data_i ,
rd_i ,
wr_i ,
full_o ,
empty_o ,
overflow_o ,//满了就会拉高
data_o ,
freeslot_o
);
input clk_i ;
input rst_n_i ;
input [31:0] data_i ;
input rd_i ;
input wr_i ;
output full_o ;
output empty_o ;
output overflow_o ;
output [31:0] data_o ;
output [ 5:0] freeslot_o ;
parameter ADDR_W_C = 5 ;
parameter DEPTH_C = 32 ;
//深度2^5的FIFO需要的地址位数就是5,要比深度多或者一样多就行
//所以ADDR_W_C是5
//深度2^5的FIFO需要的读写指针位宽5+1,多一位作为标志位
//所以freeslot是[5:0]
reg [ADDR_W_C-1 :0] wr_p_r ;//读写指针
reg [ADDR_W_C-1 :0] rd_p_r ;
reg [31:0] mem [DEPTH_C-1:0] ;
//定义了mem型变量存放数据
reg [ADDR_W_C :0] freeslot_r ;
//freeslot是说剩余的空间
wire full_s, empty_s;
reg overflow_r ;
always @(posedge clk_i or negedge rst_n_i)
begin
if (!rst_n_i) begin//复位信号为0,指针为0,freeslot剩余空间为深度32
freeslot_r <= DEPTH_C;
rd_p_r <= 0;
wr_p_r <= 0;
overflow_r <= 1'b0;
end else begin//读写信号时指针加1,表示读写了一次数据
if(rd_i) rd_p_r <= rd_p_r +1 ;
if(wr_i) wr_p_r <= wr_p_r +1 ;
//fifo同一时间可以被同时读写,也可以只读或者只写
if ( rd_i && ~wr_i ) begin
freeslot_r <= freeslot_r+1 ;
end //只读的话,剩余空间增加
if (~rd_i && wr_i ) begin
if (~full_s) begin//只写,剩余空间-1
freeslot_r <= freeslot_r-1 ;
end else begin//如果满了,overflow就会拉高
overflow_r <= 1'b1;
end
end
if (wr_i) begin
mem[wr_p_r] <= data_i ;
end//数据写入操作,直接把数据给mem[写指针]
end
end
//空满标志的assign
assign full_s = freeslot_r == 0 ? 1 : 0 ;
assign empty_o = freeslot_r == DEPTH_C ? 1 : 0 ;
assign full_o = full_s ;
assign overflow_o = overflow_r;
//读数据直接把对应mem中的assign给输出data就好,之前都是要写个always块来赋值
assign data_o = mem[rd_p_r];
assign freeslot_o = freeslot_r ;
endmodule
arbiter
fifo之后来到arbiter, 采用的机制是Round Robin轮询的一个仲裁机制。
简单来说就是4个通道会有请求,每一个clk都会设定一个最高优先级的通道,如果最高通道刚好有请求req,就判定这个channel胜出,拿他的数据,如果没有就往右查找看谁有req。操作完后这个通道的优先级就去到最低优先级,保证每个channel都可以读出数据。
遗留的问题是trigger是从哪里来的信号?
module RR_arbiter(
clk_i,
rst_n_i,
req_vec_i,
win_vec_o,
trigger_i
);
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 ; //4个通道,表示哪一个通道优先级最高
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 ;
//filter_L_s表示将cp_vec_r 中置1的那位的左边全部置0,然后取反得到filter_R_s
/用以下四个assign来实现这个功能
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 ;
//位操作符,每一位都进行与操作
//比如req_vec_i,也就是4个通道的请求;和根据最高优先级cp_vec_r选择的filter_R_s做与操作,都有1才为1,这样得到的req_L_s和req_R_s 是用来干什么的?
//表示结合slave自己的请求req_vec_i ,结合设定好的cp_vec_r ,最高优先级的左右分别有哪个通道允许做操作
assign req_L_s = req_vec_i & filter_R_s ;
assign req_R_s = req_vec_i & filter_L_s ;
//对刚才两个req的L和R做过滤操作,有1的位右边全部置1,为什么?
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] ;
//对刚才得到的两个reg_FL的L和R,找置1的最高位,结果存放到req_msb1_L_s
//先把最高位赋值给最高位,然后次高位做判断,最高位为1时置0,否则保持不变;
//然后是次低位,合并高二位后做缩位或运算,如果是01,则出1,次低位置0,表示已经找到;如果是00,则出0,然后次低位保持不变,继续找。
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_msb1_L_s[3] = req_FL_L_s[3] ;
//assign req_msb1_L_s[2] = req_FL_L_s[3] ^ req_FL_L_s[2] ;
//assign req_msb1_L_s[1] = req_FL_L_s[3] ^ req_FL_L_s[2] ^ req_FL_L_s[1] ;
//assign req_msb1_L_s[0] = req_FL_L_s[3] ^ req_FL_L_s[2] ^ req_FL_L_s[1] ^ req_FL_L_s[0] ;
//
//assign req_msb1_R_s[3] = req_FL_R_s[3] ;
//assign req_msb1_R_s[2] = req_FL_R_s[3] ^ req_FL_R_s[2] ;
//assign req_msb1_R_s[1] = req_FL_R_s[3] ^ req_FL_R_s[2] ^ req_FL_R_s[1] ;
//assign req_msb1_R_s[0] = req_FL_R_s[3] ^ req_FL_R_s[2] ^ req_FL_R_s[1] ^ req_FL_R_s[0] ;
//---------------------------------------------------------------------------------------
//如果req_FL_L_s全部为0,也就是没有满足优先级的通道,req_FL_L_s就拉高
assign req_all0_L_s = ~(|req_msb1_L_s) ;
assign req_all0_R_s = ~(|req_msb1_R_s) ;
//有4种情况:req_FL_L_s符合/不符合条件、req_FL_R_s符合/不符合条件
//这四种情况可以拼接两个全无位来表示{req_all0_L_s, req_all0_R_s}
//根据这个拼接位来决出最后的胜者win_vec_s ,也就是这个clk要给哪个通道授权
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 ;
//过滤后左边全0没有通道满足,则胜者为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];
//每个clk决出一个channel,然后优先级最高的channel就要去到优先级最低了,保证每个通道都有机会
win_vec_r <= win_vec_s;
end
end
end
endmodule
param_def
在进入寄存器代码之前,先看参数定义param_def。定义了两种寄存器的地址
4个读写寄存器0x00到0x0C,4个地址为1个寄存器,1个地址存一个8位byte?
`define ADDR_WIDTH 8
`define DATA_WIDTH 32
`define SLV_EN_ADDR_C 8'h00
`define ERR_CLR_ADDR_C 8'h04
`define SLV_ID_ADDR_C 8'h08
`define SLV_LEN_ADDR_C 8'h0C
`define SLV0_FSLOT_ADDR_C 8'h40
`define SLV1_FSLOT_ADDR_C 8'h44
`define SLV2_FSLOT_ADDR_C 8'h48
`define SLV3_FSLOT_ADDR_C 8'h4C
寄存器的代码开始,先看APB总线的状态图,等下会用到
寄存器接口
APB4总线的序列图看这里。
APB4总线介绍_脱密180天的博客-CSDN博客_apb4协议
`include "param_def.v"
module reg_if (
clk_i,
rst_n_i,
//寄存器的信号,其实这几个就是APB总线的信号了
paddr_i, pwr_i, pen_i, psel_i, pwdata_i, prdata_o, pready_o,
pslverr_o, //这个不明,暂时记着
slv_en_o, //对应0x00的使能信号
err_clr_o,
//对应0x08的id信号
slv0_id_o, slv1_id_o, slv2_id_o, slv3_id_o,
//对应0x0C的长度信号
slv0_len_o, slv1_len_o, slv2_len_o, slv3_len_o,
//对应0x04的奇偶校验位错误清除信号
slv0_parity_err_i, slv1_parity_err_i, slv2_parity_err_i, slv3_parity_err_i,
//对应只读寄存器0x80-0x8C的余量信号
slv0_free_slot_i, slv1_free_slot_i, slv2_free_slot_i, slv3_free_slot_i
);
input clk_i;
input rst_n_i;
input [7:0] paddr_i;
input pwr_i;
input pen_i;
input psel_i;
input [31:0] pwdata_i;
output [31:0] prdata_o;
output pready_o;
output pslverr_o;
output [3:0] slv_en_o;
output [3:0] err_clr_o;
output [7:0] slv0_id_o;
output [7:0] slv1_id_o;
output [7:0] slv2_id_o;
output [7:0] slv3_id_o;
output [7:0] slv0_len_o;
output [7:0] slv1_len_o;
output [7:0] slv2_len_o;
output [7:0] slv3_len_o;
input slv0_parity_err_i;
input slv1_parity_err_i;
input slv2_parity_err_i;
input slv3_parity_err_i;
input [5:0] slv0_free_slot_i;
input [5:0] slv1_free_slot_i;
input [5:0] slv2_free_slot_i;
input [5:0] slv3_free_slot_i;
//定义状态名,寄存器的状态有IDLE、SETUP、ACC
parameter [1:0] st_IDLE =2'b00 ;
parameter [1:0] st_SETUP =2'b01 ;
parameter [1:0] st_ACC =2'b10 ;
reg [1:0] last_st, cur_st ;
//给两种寄存器开辟了两个mem,但是只读寄存器不是有8个吗?
reg [31:0] ctrl_mem [3:0];
reg [31:0] ro_mem [3:0]; //?这里应该是[7:0]
wire is_st_idle_s, is_st_setup_s, is_st_acc_s;
//这三个是显示当前状态cur_st的,当前状态是哪个就哪个拉高
wire is_addr_freeslot_3_s;
wire is_addr_freeslot_2_s;
wire is_addr_freeslot_1_s;
wire is_addr_freeslot_0_s;
wire is_addr_parity_err_3_s;
wire is_addr_parity_err_2_s;
wire is_addr_parity_err_1_s;
wire is_addr_parity_err_0_s;
reg [7:0] addr_r;
reg [31:0] data_rd_r;
wire is_ctrl_rng_s;
wire is_ro_rng_s;
wire is_err_rng_s;
wire idx_0_s;
wire idx_1_s;
wire idx_2_s;
wire idx_3_s;
wire is_addr_slv_en_s;
wire is_addr_err_clr_s;
wire is_addr_slv_id_s;
wire is_addr_slv_len_s;
//三段式状态机,之前手撕代码用的是next_state和state
//这里换成last_st和cur_st
always @(posedge clk_i or negedge rst_n_i)
begin
if (!rst_n_i) last_st <= st_IDLE ;
else last_st <= cur_st ;
end
//根据last_st是什么状态做对应的操作,结合APB总线的时序图来看
//IDLE时,sel拉高,就进入setup状态;
//SETUP时自动进入ACC状态,emmm……
//ACC状态时,sel仍然为高,enable拉高,只要sel和en有一个不是高,就回到IDLE状态
always @(*) begin
case (last_st)
st_IDLE : if (psel_i) cur_st <= st_SETUP;
else cur_st <= st_IDLE;
st_SETUP : cur_st <= st_ACC ;
st_ACC : if (psel_i && pen_i) begin
cur_st <= st_ACC ;
end else begin
cur_st <= st_IDLE;
end
endcase
end
//assign了寄存器三个状态的显示,状态机到哪个状态,对应的位就置高
assign is_st_idle_s = (cur_st == st_IDLE ) ? 1'b1 : 1'b0 ;
assign is_st_setup_s = (cur_st == st_SETUP) ? 1'b1 : 1'b0 ;
assign is_st_acc_s = (cur_st == st_ACC ) ? 1'b1 : 1'b0 ;
//关于地址的,在setup状态时将APB总线的地址给到addr_r地址寄存器
always @(posedge clk_i or negedge rst_n_i)
begin
if (!rst_n_i) begin
addr_r <= 0 ;
end else begin
if (is_st_setup_s) begin
addr_r <= paddr_i ;end
end
end
//定义了一个32位的数据存储data_rd_r
//在ACC状态时,读操作要write信号为低~pwr_i
//if条件中的12个信号分别表示用到哪个寄存器,哪个就为1,具体看后面代码
always @(*) begin
data_rd_r <= 0;
if (is_st_acc_s) begin
if (~pwr_i) begin
//哪个寄存器拉高,就把对应mem中的数据给到读出数据的寄存器data_rd_r ,完成数据读出
if (is_addr_slv_en_s ) data_rd_r <= ctrl_mem [0];
if (is_addr_err_clr_s) data_rd_r <= ctrl_mem [1];
if (is_addr_slv_id_s ) data_rd_r <= ctrl_mem [2];
if (is_addr_slv_len_s) data_rd_r <= ctrl_mem [3];
if (is_addr_freeslot_0_s) data_rd_r <= ro_mem [0];
if (is_addr_freeslot_1_s) data_rd_r <= ro_mem [1];
if (is_addr_freeslot_2_s) data_rd_r <= ro_mem [2];
if (is_addr_freeslot_3_s) data_rd_r <= ro_mem [3];
if (is_addr_parity_err_0_s) data_rd_r <= ro_mem [4];
if (is_addr_parity_err_1_s) data_rd_r <= ro_mem [5];
if (is_addr_parity_err_2_s) data_rd_r <= ro_mem [6];
if (is_addr_parity_err_3_s) data_rd_r <= ro_mem [7];
end
end
end
//最后将读出数据寄存器的数据给到总线信号,完成读操作
assign prdata_o = data_rd_r ;
//这个信号表示在ACC状态(应该读写数据的状态)寄存器地址有误,拉高
assign pslverr_o = is_st_acc_s && is_err_rng_s ;
//在ACC状态时reday信号拉高表示准备好读写数据
assign pready_o = is_st_acc_s ;
//这三个信号,首先判断总线来的地址是不是控制寄存器的地址
//根据上面寄存器图,0x00-0x0C是读写寄存器,0x80-0x9C是只读寄存器
//也就是读写寄存器的地址最高为8C,二进制是00001000,对高四位做缩位或,四位都是0才是读写寄存器,所以取反表示当前的地址是读写寄存器的地址
assign is_ctrl_rng_s = ~|(addr_r[7:4]) ; //0h0*
//同理,只读寄存器的二进制地址是10000000到10011100,高三位必须满足100,
assign is_ro_rng_s = addr_r[7] && !addr_r[6] && !addr_r[5] ; //0h8* or 0h9*
//最后,其他地址都不对,就要报错,所以就有了以下信号,既不是读写又不是只读,取反出1
assign is_err_rng_s = ~(is_ctrl_rng_s | is_ro_rng_s);
//APB总线地址的[3:2]位表示的是寄存器的ID,读写寄存器有4个,只读寄存器中freeslot有4个,parity_err有四个,配合上面的表示寄存器的信号,可以得到下面这些信号,对应了12个寄存器,用到哪个就拉高哪个,想不明白的可以回去上面看寄存器的图。
assign idx_0_s = (addr_r[3:2]==2'b00)? 1'b1 : 1'b0;
assign idx_1_s = (addr_r[3:2]==2'b01)? 1'b1 : 1'b0;
assign idx_2_s = (addr_r[3:2]==2'b10)? 1'b1 : 1'b0;
assign idx_3_s = (addr_r[3:2]==2'b11)? 1'b1 : 1'b0;
//12个寄存器信号,地址是哪个就拉高哪个
assign is_addr_slv_en_s = is_ctrl_rng_s & idx_0_s ;
assign is_addr_err_clr_s = is_ctrl_rng_s & idx_1_s ;
assign is_addr_slv_id_s = is_ctrl_rng_s & idx_2_s ;
assign is_addr_slv_len_s = is_ctrl_rng_s & idx_3_s ;
assign is_addr_freeslot_0_s = is_ro_rng_s && !addr_r[4] && idx_0_s ;
assign is_addr_freeslot_1_s = is_ro_rng_s && !addr_r[4] && idx_1_s ;
assign is_addr_freeslot_2_s = is_ro_rng_s && !addr_r[4] && idx_2_s ;
assign is_addr_freeslot_3_s = is_ro_rng_s && !addr_r[4] && idx_3_s ;
assign is_addr_parity_err_0_s = is_ro_rng_s && addr_r[4] && idx_0_s ;
assign is_addr_parity_err_1_s = is_ro_rng_s && addr_r[4] && idx_1_s ;
assign is_addr_parity_err_2_s = is_ro_rng_s && addr_r[4] && idx_2_s ;
assign is_addr_parity_err_3_s = is_ro_rng_s && addr_r[4] && idx_3_s ;
//复位信号为0时,4个控制寄存器的初始值如下
//其中,0x08寄存器存放的是slv的ID,初始值为3210,32位寄存器每个id占8位,用32位十六进制表示为03020100,对应二进制32'b00000011|00000010|00000001|00000000(不是与)
always @ (posedge clk_i or negedge rst_n_i)
begin : CONTROL_PROC
if (!rst_n_i)
begin
ctrl_mem[0] <= 32'h00000000; // slv_en
ctrl_mem[1] <= 32'h00000000; // parity_err_clr
ctrl_mem[2] <= 32'h03020100; // slave ID
ctrl_mem[3] <= 32'h00000000; // length
end else begin
//ACC状态执行写操作,对slv_en和parity_err寄存器来说只有前4位,对slv_id和slv_len来说就有32位全写,所以pwdata分别写入这些位置
//pwdata包含了
if (is_st_acc_s & pwr_i) begin
if (is_addr_slv_en_s ) ctrl_mem [0][3:0] <= pwdata_i ;
if (is_addr_err_clr_s) ctrl_mem [1][3:0] <= pwdata_i ;
if (is_addr_slv_id_s ) ctrl_mem [2] <= pwdata_i ;
if (is_addr_slv_len_s) ctrl_mem [3] <= pwdata_i ;
end
end
end
//fifo余量和奇偶校验位报错的初始值是0
always @ (posedge clk_i or negedge rst_n_i)
begin : RO_PROC
if (!rst_n_i)
begin
ro_mem[0] <= 32'h00000000;
ro_mem[1] <= 32'h00000000;
ro_mem[2] <= 32'h00000000;
ro_mem[3] <= 32'h00000000;
ro_mem[4] <= 32'h00000000;
ro_mem[5] <= 32'h00000000;
ro_mem[6] <= 32'h00000000;
ro_mem[7] <= 32'h00000000;
end else begin
ro_mem[0][5:0] <= slv0_free_slot_i; //这是从fifo输出过来的余量信号
ro_mem[1][5:0] <= slv1_free_slot_i;
ro_mem[2][5:0] <= slv2_free_slot_i;
ro_mem[3][5:0] <= slv3_free_slot_i;
ro_mem[4][0] <= slv0_parity_err_i; //这是从node输出过来的奇偶校验位错误信号
ro_mem[5][0] <= slv1_parity_err_i;
ro_mem[6][0] <= slv2_parity_err_i;
ro_mem[7][0] <= slv3_parity_err_i;
end
end
//对应控制寄存器的位assign给寄存器输出信号
assign slv_en_o = ctrl_mem[0][3:0];
assign err_clr_o = ctrl_mem[1][3:0];
assign slv0_id_o = ctrl_mem[2][1*8-1: 0];
assign slv1_id_o = ctrl_mem[2][2*8-1:1*8];
assign slv2_id_o = ctrl_mem[2][3*8-1:2*8];
assign slv3_id_o = ctrl_mem[2][4*8-1:3*8];
assign slv0_len_o = ctrl_mem[3][1*8-1: 0];
assign slv1_len_o = ctrl_mem[3][2*8-1:1*8];
assign slv2_len_o = ctrl_mem[3][3*8-1:2*8];
assign slv3_len_o = ctrl_mem[3][4*8-1:3*8];
endmodule
整形器formatter
formatter的工作原理也是状态机,有任意的通道发起请求则进入RUN_RR启动仲裁器,然后进入发送第一个payload的状态header,接收端ready信号为高时开始发送数据,进入payload状态,最后一个payload发送完拉高pkg_lst表示发送完毕,进入parity,然后继续回到RUN_RR
module formater(
input clk_i,
input rst_n_i,
//和4个node的信号连接,包括32位数据,slv_id,slv_len,不包括读写寄存器[1]的奇偶校验报错
//但有些信号在node里面并没有,可能是寄存器来的?
input [31:0] data_slv0_i,
input [31:0] data_slv1_i,
input [31:0] data_slv2_i,
input [31:0] data_slv3_i,
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,
//四个channel的请求信号
input [3:0] req_vec_i ,
output [3:0] fetch_vec_o,
//仲裁器给到这里的是胜出channel,然后fmt就接收该channel的数据
input [3:0] win_vec_i,
//arbiter中遗留的问题信号trigger应该是对应这里这个了
output trigger_start_o,
//fmt输出端信号
input rev_rdy_i,
//接收端发出的ready信号表示准备好接收fmt的数据包
//如图,fmt的任务就是把各个数据打包成多个payload的形式
//len+1表示payload的数量,32位的payload存放的是id、len和16位data,最后一个payload存放的是奇偶校验位
output pkg_vld_o,
output [31:0] pkg_dat_o,
output pkg_fst_o,
output pkg_lst_o
);
//状态定义
parameter [2:0] ST_RST = 3'b000;
parameter [2:0] ST_Run_RR = 3'b001;
parameter [2:0] ST_Header = 3'b010;
parameter [2:0] ST_Payload = 3'b011;
parameter [2:0] ST_Parity = 3'b100;
reg [2:0] cur_st, nxt_st;
//4个通道的胜者显示,包括数据、id、长度
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 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 ;
//channel的请求和胜出显示
wire [3:0] win_req_vec_s;
wire win_req_s ;
wire any_win_s ;
wire any_req_s ;
//数据包有效显示
wire pkg_vld_s ;
reg [31:0] tx_d_s ;//输出数据寄存器
reg [31:0] parity_r ;//奇偶校验位寄存器
reg [ 7:0] len_cnt_r ;//长度len寄存器
wire [ 3:0] fetch_vec_s ;//fetch?
//通道胜出者显示
assign win_req_vec_s = win_vec_i & req_vec_i ;
//缩位与,有通道请求胜出就拉高
assign win_req_s = |win_req_vec_s ;
//同理,有任意通道胜出就拉高,这两个好像差不多,要辨别一下
assign any_win_s = |win_vec_i ;
//同理,有任意通道请求就拉高
assign any_req_s = |req_vec_i ;
//fmt的工作模式也是状态机,只要有node发起请求,状态就从RST到RUN_RR再到Header,如果接收端准备好了就进入payload,如果最后一个payload发送完就进入parity,完成传输,回到RUN_RR
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)
ST_RST : if ( any_req_s) nxt_st <= ST_Run_RR ;
ST_Run_RR : if ( any_req_s) nxt_st <= ST_Header ;
ST_Header : if ( rev_rdy_i && any_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
//根据仲裁器的胜者信号,拉高对应的表示data所在通道胜出的信号
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 ;
//只要有一个胜出的node就拉高这个信号
assign data_win_s= data_slv0_win_s | data_slv1_win_s | data_slv2_win_s | data_slv3_win_s ;
//同上,所在通道id的信号拉高
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 ;
//状态机的显示信号,在哪个状态就拉高哪个信号
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 ;
//在header状态时,若有任意通道有req且接收端做好准备rev_req,则开始发送第一个payload,并进入payload状态,这个信号表示发送了第一个payload
assign send_header_s = is_st_header_s && rev_rdy_i ;
assign pkg_fst_o = send_header_s ;
//同理,这个表示发送剩余payloads
assign send_payload_s = is_st_payload_s && rev_rdy_i && win_req_s ;
//同理,这个表示发送最后的payload奇偶校验
assign send_parity_s = is_st_parity_s && rev_rdy_i ;
//send_parity_s标志着数据包传输结束,拉高last
assign pkg_lst_o = send_parity_s ;
//trigger信号在启动仲裁器或开始发送header时拉高
//assign trigger_start_o = send_header_s ;
assign trigger_start_o = is_st_run_rr_s ;
//在传送payload的状态中将对应payload中的数据赋给输出端完成传输,结合fmt的图就知道了
always @(*)
begin
tx_d_s <= 0;
if (is_st_header_s) begin
tx_d_s <= {id_win_s, len_win_s,16'h0000};
end
if (is_st_payload_s) begin
tx_d_s <= data_win_s ;
end
if (is_st_parity_s) begin
tx_d_s <= parity_r;
end
end
assign pkg_dat_o = tx_d_s ;
//最后一个payload发送完后长度len就为0,缩位逻辑或出0,取反出1表示payload发送完了
assign pkg_lst_s = ~|(len_cnt_r) ;
//
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
if (send_header_s) begin
parity_r <= {id_win_s, len_win_s,16'h0000};
len_cnt_r <= len_win_s ;
end
if (send_payload_s) begin
parity_r <= data_win_s ^ parity_r ;
len_cnt_r<= len_cnt_r - 1;//第一个payload的len是[0],这里要减1
end
if (send_parity_s) begin//发送完parity后清零结束一个pkg的传输
parity_r <= 0 ;
len_cnt_r <= 0 ;
end
end
end
// fetch_vec是个4位信号,表示4个channel哪一个个完成了传输(ready且经过payload状态)
//4位和独热码win_vec按位与
assign fetch_vec_s = {4{rev_rdy_i && is_st_payload_s }} & win_vec_i ;
assign fetch_vec_o = fetch_vec_s ;
// pkg有效信号,在状态为header、payload且有req、parity时有效
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
顶层MCDF
结构不是之前的channel-arb-fmt,而是node_fifo直接到fmt,arb和reg和apb包围这两个结构,形成一个完整的MCDF结构
module mcdf(
input clk_i ,
input rst_n_i ,
//4个node的信号,分别有32位数据,1位奇偶校验,valid信号应该是fmt的输出pkg_vld_o,最后是奇偶校验报错给到APB总线
input [31:0] slv0_data_i , //
input slv0_data_p_i , //
input slv0_valid_i , //
output slv0_wait_o , //fifo满了,slv_en为低,奇偶校验错误,则wait
output slv0_parity_err_o , //
input [31:0] slv1_data_i , //
input slv1_data_p_i , //
input slv1_valid_i , //
output slv1_wait_o , //
output slv1_parity_err_o , //
input [31:0] slv2_data_i , //
input slv2_data_p_i , //
input slv2_valid_i , //
output slv2_wait_o , //
output slv2_parity_err_o , //
input [31:0] slv3_data_i , //
input slv3_data_p_i , //
input slv3_valid_i , //
output slv3_wait_o , //
output slv3_parity_err_o , //
// APB总线的接口信号,和后面的apb_if中的一样,在mcdf顶层结构中要完成接口的连接
input [7:0] paddr_i ,
input pwr_i ,
input pen_i ,
input psel_i ,
input [31:0] pwdata_i ,
output [31:0] prdata_o ,
output pready_o ,
output pslverr_o ,
//mcdf的输出端,其实就是fmt的输出端
input rev_rdy_i ,
output pkg_vld_o ,
output [31:0] pkg_dat_o ,
output pkg_fst_o ,
output pkg_lst_o
);
//数据显示信号
wire [31:0] slv0_data_s,slv1_data_s, slv2_data_s, slv3_data_s ;
//余量显示信号,0x80-0x8C寄存器存储的
wire [5 :0] slv0_freeslot_s, slv1_freeslot_s, slv2_freeslot_s, slv3_freeslot_s;
//0x08寄存器存储的ID信号,和寄存器相连
wire [7 :0] slv0_id_s,slv1_id_s,slv2_id_s,slv3_id_s ;
//同理
wire [7 :0] slv0_len_s,slv1_len_s,slv2_len_s,slv3_len_s ;
wire [3 :0] err_clr_vec_s, fetch_vec_s, req_vec_s, slv_en_vec_s, win_vec_s;
wire trigger_s,slv3_fetch_s ,slv2_fetch_s ,slv1_fetch_s ,slv0_fetch_s ;
//MCDF接口连接到寄存器接口上,左边是reg_if的接口
reg_if inst_reg_if (
.clk_i (clk_i ),
.rst_n_i (rst_n_i ),
.paddr_i (paddr_i ),
.pwr_i (pwr_i ),
.pen_i (pen_i ),
.psel_i (psel_i ),
.pwdata_i (pwdata_i ),
.prdata_o (prdata_o ),
.pready_o (pready_o ),
.pslverr_o (pslverr_o ),
.slv_en_o (slv_en_vec_s ),
.err_clr_o (err_clr_vec_s ),
.slv0_id_o (slv0_id_s ),
.slv1_id_o (slv1_id_s ),
.slv2_id_o (slv2_id_s ),
.slv3_id_o (slv3_id_s ),
.slv0_len_o (slv0_len_s ),
.slv1_len_o (slv1_len_s ),
.slv2_len_o (slv2_len_s ),
.slv3_len_o (slv3_len_s ),
.slv0_parity_err_i (slv0_parity_err_s ),
.slv1_parity_err_i (slv1_parity_err_s ),
.slv2_parity_err_i (slv2_parity_err_s ),
.slv3_parity_err_i (slv3_parity_err_s ),
.slv0_free_slot_i (slv0_freeslot_s ),
.slv1_free_slot_i (slv1_freeslot_s ),
.slv2_free_slot_i (slv2_freeslot_s ),
.slv3_free_slot_i (slv3_freeslot_s )
);
//MCDF接口连接到4个node上
slave_node inst_slave_node_0 (
.clk_i (clk_i ),
.rst_n_i (rst_n_i ),
.data_i (slv0_data_i ),
.data_p_i (slv0_data_p_i ),
.valid_i (slv0_valid_i ),
.slv_en_i (slv_en_vec_s[0] ),
.wait_o (slv0_wait_o ),
.parity_err_o (slv0_parity_err_s ),
.data_o (slv0_data_s ),
.freeslot_o (slv0_freeslot_s ),
.valid_o (slv0_valid_o_s ),
.fetch_i (slv0_fetch_s ),
.parity_err_clr_i (err_clr_vec_s[0] )
);
assign slv0_parity_err_o = slv0_parity_err_s;
slave_node inst_slave_node_1 (
.clk_i (clk_i ),
.rst_n_i (rst_n_i ),
.data_i (slv1_data_i ),
.data_p_i (slv1_data_p_i ),
.valid_i (slv1_valid_i ),
.slv_en_i (slv_en_vec_s[1] ),
.wait_o (slv1_wait_o ),
.parity_err_o (slv1_parity_err_s ),
.data_o (slv1_data_s ),
.freeslot_o (slv1_freeslot_s ),
.valid_o (slv1_valid_o_s ),
.fetch_i (slv1_fetch_s ),
.parity_err_clr_i (err_clr_vec_s[1] )
);
assign slv1_parity_err_o = slv1_parity_err_s;
slave_node inst_slave_node_2 (
.clk_i (clk_i ),
.rst_n_i (rst_n_i ),
.data_i (slv2_data_i ),
.data_p_i (slv2_data_p_i ),
.valid_i (slv2_valid_i ),
.slv_en_i (slv_en_vec_s[2] ),
.wait_o (slv2_wait_o ),
.parity_err_o (slv2_parity_err_s ),
.data_o (slv2_data_s ),
.freeslot_o (slv2_freeslot_s ),
.valid_o (slv2_valid_o_s ),
.fetch_i (slv2_fetch_s ),
.parity_err_clr_i (err_clr_vec_s[2] )
);
assign slv2_parity_err_o = slv2_parity_err_s;
slave_node inst_slave_node_3 (
.clk_i (clk_i ),
.rst_n_i (rst_n_i ),
.data_i (slv3_data_i ),
.data_p_i (slv3_data_p_i ),
.valid_i (slv3_valid_i ),
.slv_en_i (slv_en_vec_s[3] ),
.wait_o (slv3_wait_o ),
.parity_err_o (slv3_parity_err_s ),
.data_o (slv3_data_s ),
.freeslot_o (slv3_freeslot_s ),
.valid_o (slv3_valid_o_s ),
.fetch_i (slv3_fetch_s ),
.parity_err_clr_i (err_clr_vec_s[3] )
);
assign slv3_parity_err_o = slv3_parity_err_s;
assign req_vec_s = {slv3_valid_o_s,slv2_valid_o_s,slv1_valid_o_s,slv0_valid_o_s};
//连接到仲裁器
RR_arbiter inst_arb (
.clk_i (clk_i ),
.rst_n_i (rst_n_i ),
.req_vec_i (req_vec_s ),
.win_vec_o (win_vec_s ),
.trigger_i (trigger_s )
);
//连接到fmt
formater inst_formatter (
.clk_i (clk_i ),
.rst_n_i (rst_n_i ),
.data_slv0_i (slv0_data_s ),
.data_slv1_i (slv1_data_s ),
.data_slv2_i (slv2_data_s ),
.data_slv3_i (slv3_data_s ),
.id_slv0_i (slv0_id_s ),
.id_slv1_i (slv1_id_s ),
.id_slv2_i (slv2_id_s ),
.id_slv3_i (slv3_id_s ),
.len_slv0_i (slv0_len_s ),
.len_slv1_i (slv1_len_s ),
.len_slv2_i (slv2_len_s ),
.len_slv3_i (slv3_len_s ),
.req_vec_i (req_vec_s ),
.fetch_vec_o (fetch_vec_s ),
.win_vec_i (win_vec_s ),
.trigger_start_o (trigger_s ),
.rev_rdy_i (rev_rdy_i ),
.pkg_vld_o (pkg_vld_o ),
.pkg_dat_o (pkg_dat_o ),
.pkg_fst_o (pkg_fst_o ),
.pkg_lst_o (pkg_lst_o )
);
//fetch信号的显示
assign slv0_fetch_s = fetch_vec_s[0];
assign slv1_fetch_s = fetch_vec_s[1];
assign slv2_fetch_s = fetch_vec_s[2];
assign slv3_fetch_s = fetch_vec_s[3];
endmodule
接口打包信号
APB接口
对应MCDF中的APB接口。
这里的问题是,has_coverage和has_coverage有没有其他地方控制的?
`ifndef APB_IF_SV
`define APB_IF_SV
interface apb_if (input clk, input rstn);
logic [31:0] paddr;
logic pwrite;
logic psel;
logic penable;
logic [31:0] pwdata;
logic [31:0] prdata;
logic pready;
//表示APB传输的过程中发生了错误,可能是读写;只在传输的最后一拍拉高
logic pslverr;
// Control flags
bit has_checks = 1;
bit has_coverage = 1;
import uvm_pkg::*;
`include "uvm_macros.svh"
//这里信号的方向和MCDF中的完全相反,addr等信号是从总线给到MCDF的,所以这里是输出
clocking cb_mst @(posedge clk);
default input #1ps output #1ps;
output paddr, pwrite, psel, penable, pwdata;
input prdata, pready, pslverr;
endclocking : cb_mst
//如果是总线的从设备(MCDF),则方向和MCDF的一样
// clocking cb_slv @(posedge clk);
// default input #1ps output #1ps;
// input paddr, pwrite, psel, penable, pwdata;
// output prdata, pready, pslverr;
// endclocking : cb_slv
//monitor的时钟块,所有信号都是input
clocking cb_mon @(posedge clk);
default input #1ps output #1ps;
input paddr, pwrite, psel, penable, pwdata, prdata, pready, pslverr;
endclocking : cb_mon
// APB指令的覆盖组
//覆盖组 组名 @触发条件;然后是覆盖点 信号名{设置打印与收集 编写bins}
covergroup cg_apb_command @(posedge clk iff rstn);
pwrite: coverpoint pwrite{
type_option.weight = 0;
bins write = {1};
bins read = {0};
}//pwrite信号有两种状态,拉高为write拉低为read,所以有两个bins,下面同理
psel : coverpoint psel{
type_option.weight = 0;
bins sel = {1};
bins unsel = {0};
}
//apb指令有三个,binsof指定覆盖点,指令为write时对应的覆盖点是sel拉高和write拉高
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_apb_command
// APB连续传输多个数据的覆盖组
covergroup cg_apb_trans_timing_group @(posedge clk iff rstn);
psel: coverpoint psel{//传输n个数据psel拉高2*n拍,
bins single = (0 => 1 => 1 => 0);
bins burst_2 = (0 => 1 [*4] => 0);
bins burst_4 = (0 => 1 [*8] => 0);
bins burst_8 = (0 => 1 [*16] => 0);
bins burst_16 = (0 => 1 [*32] => 0);
bins burst_32 = (0 => 1 [*64] => 0);
}
penable: coverpoint penable {
//enable连续传输时每两拍拉高一拍,传输单个数据时记得是有idel_cycle的设置所以是2-10拍
bins single = (0 => 1 => 0 [*2:10] => 1);
bins burst = (0 => 1 => 0 => 1);
}
endgroup: cg_apb_trans_timing_group
// APB读写顺序检测的覆盖组
//触发条件多了个penable,只有enable拉高的读写才是有效读写
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: cg_apb_write_read_order_group
//覆盖组需要例化,且声明为automatic型
initial begin : coverage_control
if(has_coverage) begin
automatic cg_apb_command cg0 = new();
automatic cg_apb_trans_timing_group cg1 = new();
automatic cg_apb_write_read_order_group cg2 = new();
end
end
//属性和断言的编写,描述具体的感兴趣的序列,首先是断言属性
//第一个是希望总线过来的地址不存在x,地址和sel为高时(交叠蕴含同一拍)用isunknown检查地址中是否存在x和z,若有则为1,取非没有出1,assert这个property要为1才不会报错
//断言是检查时序的,可以通过一个max_quit什么的设置error数量,到了就finish,具体忘了,等uvm的时候看看
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")
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")
property p_penable_rose_next_cycle_fall;
@(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;
@(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;
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;
@(posedge clk) penable && !pwrite && pready |-> !$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")
//property覆盖率
//和上面的区别是这里是收集覆盖率
//第一个是非连续写操作
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);
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);
if(has_checks) $asserton();
end
join_none
end
endinterface : apb_if
`endif // APB_IF_SV
tb中的接口
bind_intf是什么接口?印象中好像是什么绑定接口
`timescale 1ns/1ps
`include "apb_if.sv"//上面的apb接口include了
//node的接口,不包含slv_en_i和给下线的信号,为什么?
interface chnl_intf(input clk, input rstn);
logic [31:0] ch_data;
logic ch_data_p;
logic ch_valid;
logic ch_wait;
logic ch_parity_err;
clocking drv_ck @(posedge clk);//驱动时钟块,channel是指哪个模块的?
default input #1ps output #1ps;
output ch_data, ch_valid, ch_data_p;
input ch_wait, ch_parity_err;
endclocking
clocking mon_ck @(posedge clk);//给到monitor的信号全部为input
default input #1ps output #1ps;
input ch_data, ch_valid, ch_data_p, ch_wait, ch_parity_err;
endclocking
endinterface
//fmt接口,只打包与外界的五个信号,fmt输出的这里作为输入
interface fmt_intf(input clk, input rstn);
logic fmt_ready;
logic fmt_valid;
logic [31:0] fmt_data;
logic fmt_first;
logic fmt_last;
clocking drv_ck @(posedge clk);
default input #1ps output #1ps;
input fmt_valid, fmt_data, fmt_first, fmt_last;
output fmt_ready;
endclocking
clocking mon_ck @(posedge clk);
default input #1ps output #1ps;
input fmt_ready, fmt_valid, fmt_data, fmt_first, fmt_last;
endclocking
endinterface
//mcdf的接口打包reg_if、chnl_if、fmt_if里面没有的信号
interface mcdf_intf(output logic clk, output logic rstn);
logic [3:0] chnl_en; //node中的slv_en_i
clocking mon_ck @(posedge clk);
default input #1ps output #1ps;
input chnl_en;
endclocking
//产生clk
initial begin
clk <= 0;
forever begin
#5 clk <= !clk;
end
end
//复位信号
initial begin
#10 rstn <= 0;
repeat(10) @(posedge clk);
rstn <= 1;
end
endinterface
//这是什么接口?
interface bind_intf(
input logic [5:0] slv0_freeslot_bind,
input logic [5:0] slv1_freeslot_bind,
input logic [5:0] slv2_freeslot_bind,
input logic [5:0] slv3_freeslot_bind
);
endinterface
tb后续
module tb;
logic clk;
logic rstn;
//顶层MCDF所有信号与刚才定义的接口进行连接
mcdf dut(
.clk_i (clk ) ,
.rst_n_i (rstn ) ,
.slv0_data_i (chnl0_if.ch_data ) ,
.slv0_data_p_i (chnl0_if.ch_data_p ) ,
.slv0_valid_i (chnl0_if.ch_valid ) ,
.slv0_wait_o (chnl0_if.ch_wait ) ,
.slv0_parity_err_o (chnl0_if.ch_parity_err ) ,
.slv1_data_i (chnl1_if.ch_data ) ,
.slv1_data_p_i (chnl1_if.ch_data_p ) ,
.slv1_valid_i (chnl1_if.ch_valid ) ,
.slv1_wait_o (chnl1_if.ch_wait ) ,
.slv1_parity_err_o (chnl1_if.ch_parity_err ) ,
.slv2_data_i (chnl2_if.ch_data ) ,
.slv2_data_p_i (chnl2_if.ch_data_p ) ,
.slv2_valid_i (chnl2_if.ch_valid ) ,
.slv2_wait_o (chnl2_if.ch_wait ) ,
.slv2_parity_err_o (chnl2_if.ch_parity_err ) ,
.slv3_data_i (chnl3_if.ch_data ) ,
.slv3_data_p_i (chnl3_if.ch_data_p ) ,
.slv3_valid_i (chnl3_if.ch_valid ) ,
.slv3_wait_o (chnl3_if.ch_wait ) ,
.slv3_parity_err_o (chnl3_if.ch_parity_err ) ,
.paddr_i (reg_if.paddr[7:0] ) ,
.pwr_i (reg_if.pwrite ) ,
.pen_i (reg_if.penable ) ,
.psel_i (reg_if.psel ) ,
.pwdata_i (reg_if.pwdata ) ,
.prdata_o (reg_if.prdata ) ,
.pready_o (reg_if.pready ) ,
.pslverr_o (reg_if.pslverr ) ,
.rev_rdy_i (fmt_if.fmt_ready ) ,
.pkg_vld_o (fmt_if.fmt_valid ) ,
.pkg_dat_o (fmt_if.fmt_data ) ,
.pkg_fst_o (fmt_if.fmt_first ) ,
.pkg_lst_o (fmt_if.fmt_last )
);
import uvm_pkg::*;
`include "uvm_macros.svh"
import mcdf_pkg::*;
apb_if reg_if(.*);
chnl_intf chnl0_if(.*);
chnl_intf chnl1_if(.*);
chnl_intf chnl2_if(.*);
chnl_intf chnl3_if(.*);
fmt_intf fmt_if(.*);
mcdf_intf mcdf_if(.*);
// mcdf interface monitoring MCDF ports and signals
assign mcdf_if.chnl_en = tb.dut.inst_reg_if.slv_en_o;
initial begin
// do interface configuration from top tb (HW) to verification env (SW)
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.chnl_agts[0]", "vif", chnl0_if);
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.chnl_agts[1]", "vif", chnl1_if);
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.chnl_agts[2]", "vif", chnl2_if);
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.chnl_agts[3]", "vif", chnl3_if);
uvm_config_db#(virtual apb_if )::set(uvm_root::get(), "uvm_test_top.env.reg_agt", "vif", reg_if );
uvm_config_db#(virtual fmt_intf )::set(uvm_root::get(), "uvm_test_top.env.fmt_agt", "vif", fmt_if );
uvm_config_db#(virtual mcdf_intf)::set(uvm_root::get(), "uvm_test_top.env.*", "mcdf_vif", mcdf_if);
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.*", "chnl_vifs[0]", chnl0_if);
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.*", "chnl_vifs[1]", chnl1_if);
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.*", "chnl_vifs[2]", chnl2_if);
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.*", "chnl_vifs[3]", chnl3_if);
uvm_config_db#(virtual apb_if )::set(uvm_root::get(), "uvm_test_top.env.*", "reg_vif", reg_if );
uvm_config_db#(virtual fmt_intf )::set(uvm_root::get(), "uvm_test_top.env.*", "fmt_vif", fmt_if );
// If no external configured via +UVM_TESTNAME=my_test, the default test is
// mcdf_data_consistence_basic_test
run_test("mcdf_data_consistence_basic_test");
end
//--------------------------------------------------------
// Example for how to probe signals
//--------------------------------------------------------
logic [5:0] slv0_freeslot_vlog, slv1_freeslot_vlog, slv2_freeslot_vlog, slv3_freeslot_vlog;
logic [5:0] slv0_freeslot_mti, slv1_freeslot_mti, slv2_freeslot_mti, slv3_freeslot_mti;
logic [5:0] slv0_freeslot_vcs, slv1_freeslot_vcs, slv2_freeslot_vcs, slv3_freeslot_vcs;
// Verilog hierarchy probe
assign slv0_freeslot_vlog = tb.dut.slv0_freeslot_s;
assign slv1_freeslot_vlog = tb.dut.slv1_freeslot_s;
assign slv2_freeslot_vlog = tb.dut.slv2_freeslot_s;
assign slv3_freeslot_vlog = tb.dut.slv3_freeslot_s;
// Questasim supplied probe
// initial begin
// $init_signal_spy("tb.dut.slv0_freeslot_s", "tb.slv0_freeslot_mti");
// $init_signal_spy("tb.dut.slv1_freeslot_s", "tb.slv1_freeslot_mti");
// $init_signal_spy("tb.dut.slv2_freeslot_s", "tb.slv2_freeslot_mti");
// $init_signal_spy("tb.dut.slv3_freeslot_s", "tb.slv3_freeslot_mti");
// end
// VCS supplied probe
initial begin
$hdl_xmr("tb.dut.slv0_freeslot_s", "tb.slv0_freeslot_vcs");
$hdl_xmr("tb.dut.slv1_freeslot_s", "tb.slv1_freeslot_vcs");
$hdl_xmr("tb.dut.slv2_freeslot_s", "tb.slv2_freeslot_vcs");
$hdl_xmr("tb.dut.slv3_freeslot_s", "tb.slv3_freeslot_vcs");
end
//括号内是RTL的信号
bind tb.dut bind_intf bind_if0(
.slv0_freeslot_bind(slv0_freeslot_s)
,.slv1_freeslot_bind(slv1_freeslot_s)
,.slv2_freeslot_bind(slv2_freeslot_s)
,.slv3_freeslot_bind(slv3_freeslot_s)
);
endmodule
软件部分
chnl_pkg
消息方法对应4个宏:
`uvm_info(ID, 信息内容,过滤级别),warning和error和fatal没有过滤级别这个参数。
ID可以用get_type_name()获取,信息内容可以“”也可以是字符串s
过滤级别有4个:UVM_HIGH,UVM_MEDIUM,UVM_LOW,UVM_NONE
sequence中有个宏`uvm_declare_p_sequencer(chnl_sequencer),将chnl_sequencer转换成了p_sequencer,相当于?
chnl_sequencer p_sequencer;
也就是用了这个宏进行转换,不需要再声明,记住结论:在sequence中要用,参数是当前的sequencer
uvm_declare_p_sequencer_zilan23的博客-CSDN博客_uvm_declare_p_sequencer
发送序列用的宏
uvm_do_with对于item来说,创建了item,同步,做约束的随机化,发送
顶层环境中,根据uvm的config机制,配置接口:在tb的initial块中做
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.chnl_agts[0]", "vif", chnl0_if);
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.chnl_agts[1]", "vif", chnl1_if);
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.chnl_agts[2]", "vif", chnl2_if);
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.chnl_agts[3]", "vif", chnl3_if);
在agent的build_phase中做:
if(!uvm_config_db#(virtual chnl_intf)::get(this,"","vif", vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
我的第二个UVM代码——连接interface - 腾讯云开发者社区-腾讯云
//接下来进入软件验证环境的搭建,chnl_pkg对应node,先看结构
//首先由于用到UVM,开头的import和include少不了
//node有需要传输的数据,传输数据需要用到seq、sqeuencer、driver,检测数据的monitor,包含这几个结构的agent
package chnl_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"
//sequence item继承于uvm_transaction——uvm_object
//seq item的创建和随机化发生在seq中的body任务
class chnl_trans extends uvm_sequence_item;
rand bit[31:0] data[];//成员变量全部为随机变量rand,数据类型用动态数组
rand int ch_id;
rand int pkt_id;
rand int data_nidles;
rand int pkt_nidles;
bit rsp;//单比特rsp在写入数据并转换句柄后点亮表示完成写入
//对成员变量做soft约束也就是初始化,具体在发包的时候会做随机化替代这里的初始化
constraint cstr{
//数据位宽在4到32位之间
soft data.size inside {[4:32]};
//数据之间按一定规律生成
foreach(data[i]) soft data[i] == 'hC000_0000 + (this.ch_id<<24) + (this.pkt_id<<8) + i;
//通道id和包的id默认是0
soft ch_id == 0;
soft pkt_id == 0;
//数据间隔和包的间隔在一定范围内,这跟idle_cycle有关?
soft data_nidles inside {[0:2]};
soft pkt_nidles inside {[1:10]};
};
//注册,由于有成员变量,做域的自动化
`uvm_object_utils_begin(chnl_trans)
`uvm_field_array_int(data, UVM_ALL_ON)
`uvm_field_int(ch_id, UVM_ALL_ON)
`uvm_field_int(pkt_id, UVM_ALL_ON)
`uvm_field_int(data_nidles, UVM_ALL_ON)
`uvm_field_int(pkt_nidles, UVM_ALL_ON)
`uvm_field_int(rsp, UVM_ALL_ON)
`uvm_object_utils_end
//每个class必做的new函数,由于是object类,只有name
function new (string name = "chnl_trans");
super.new(name);
endfunction
endclass: chnl_trans
//driver的工作原理:通过seq_item_port从sequencer拿到一个seq_item,写入数据后返回rsp句柄,通过seq_item_port.item_done(rsp);结束发送
//注意是参数类#(chnl_trans)
class chnl_driver extends uvm_driver #(chnl_trans);
//注意这里就有接口了
local virtual chnl_intf intf;
//每个class都要的注册,无成员变量,不需要域的自动化
`uvm_component_utils(chnl_driver)
//component的new函数有两个参数(和object区分)
function new (string name = "chnl_driver", uvm_component parent);
super.new(name, parent);
endfunction
//由于有接口,要将接口连接到driver
function void set_interface(virtual chnl_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
//uvm_component的phase机制,只有run_phase是耗时的任务
//driver的run_phase同时执行了驱动和复位两个线程
task run_phase(uvm_phase phase);
fork
this.do_drive();
this.do_reset();
join
endtask
task do_reset();
forever begin//intf是这里定义的chnl_intf接口名,对应tb中的接口
@(negedge intf.rstn);
intf.ch_valid <= 0;//这三个是输出的三个信号。不空就valid
intf.ch_data <= 0;
intf.ch_data_p <= 0;
end
endtask
//驱动包的任务,声明两个item的句柄,通过seq_item_port调用get_next_item(req)方法
//拿到item,然后执行驱动任务chnl_write,通过接口中的时钟块点亮三个输出端数据
//等待:奇偶校验无错误、FIFO不满、slv_en_i为高,则wait拉低,包得到传送
//然后根据包中的data_nidles和pkt_nidles执行chnl_idle()
//完成以上驱动任务后将包的句柄进行克隆,由于item继承于object,克隆得到的句柄是父类句柄,需要将其转换成子类句柄
//点亮转换后的单比特rsp表示完成包的驱动,通过seq_item_port.item_done(rsp)返回rsp给sequence
task do_drive();
chnl_trans req, rsp;
@(posedge intf.rstn);
forever begin
seq_item_port.get_next_item(req);
this.chnl_write(req);
void'($cast(rsp, req.clone()));
rsp.rsp = 1;
//item中没有定义的函数,应该是自带的函数,从req获取seq id作为rsp的设置seq id的参数
rsp.set_sequence_id(req.get_sequence_id());
seq_item_port.item_done(rsp);
end
endtask
//driver和sequencer之间通信的tlm端口如下:采取get模式
//driver作为initiator,例化了两个端口,默认的REQ类型是uvm_sequence_item父类
//uvm_seq_item_pull_port #(REQ, RSP) seq_item_port
//uvm_analysis_port #(RSP) rsp_port
task chnl_write(input chnl_trans t);
foreach(t.data[i]) begin
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 1;
intf.drv_ck.ch_data <= t.data[i];
intf.drv_ck.ch_data_p <= get_parity(t.data[i]);
@(negedge intf.clk);
wait(intf.ch_wait === 'b0);
//消息打印第一个参数ID用get_type_name获得类型名,第二个参数打印内容用
//系统函数sformatf再写,第三个参数过滤级别为HIGH
`uvm_info(get_type_name(), $sformatf("sent data 'h%8x", t.data[i]), UVM_HIGH)
//get_type_name获取的可能是item的类型,对应seq,针对多个seq同时向sequencer发包
repeat(t.data_nidles) chnl_idle();
end
repeat(t.pkt_nidles) chnl_idle();
endtask
task chnl_idle();
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 0;
intf.drv_ck.ch_data <= 0;
intf.drv_ck.ch_data_p <= 0;
endtask
function get_parity(bit[31:0] data);
return ^data;//对32位数据缩位异或可得奇偶校验位
endfunction
endclass: chnl_driver
//sequencer只需要注册和new函数,注意是参数类#(chnl_trans)
//sequencer继承于sequencer_base——component,所以也是两个参数
class chnl_sequencer extends uvm_sequencer #(chnl_trans);
`uvm_component_utils(chnl_sequencer)
function new (string name = "chnl_sequencer", uvm_component parent);
super.new(name, parent);
endfunction
endclass: chnl_sequencer
//sequence继承于item——transaction——object,item在这产生
class chnl_data_sequence extends uvm_sequence #(chnl_trans);
rand int pkt_id = 0;//定义的时候就赋值
rand int ch_id = -1;
rand int data_nidles = -1;
rand int pkt_nidles = -1;
rand int data_size = -1;
rand int ntrans = 10;//包的数量?
rand int data[];
constraint cstr{//这里约束为什么是-1?
soft pkt_id == 0;
soft ch_id == -1;
soft data_nidles == -1;
soft pkt_nidles == -1;
soft data_size == -1;
soft ntrans == 10;
soft data.size() == data_size;
foreach(data[i]) soft data[i] == -1;
};
`uvm_object_utils_begin(chnl_data_sequence)
`uvm_field_int(pkt_id, UVM_ALL_ON)
`uvm_field_int(ch_id, UVM_ALL_ON)
`uvm_field_int(data_nidles, UVM_ALL_ON)
`uvm_field_int(pkt_nidles, UVM_ALL_ON)
`uvm_field_int(data_size, UVM_ALL_ON)
`uvm_field_int(ntrans, UVM_ALL_ON)
`uvm_object_utils_end
//将p_sequencer设置成chnl_sequencer
`uvm_declare_p_sequencer(chnl_sequencer)
function new (string name = "chnl_data_sequence");
super.new(name);
endfunction
task body();
repeat(ntrans) send_trans();
endtask
task send_trans();
chnl_trans req, rsp;
//发送item的宏,第一个参数是发送的item的句柄,第二个参数是约束随机化
//约束块,这里是条件约束,在这个class中如果这五个成员变量大于等于0,就赋给
`uvm_do_with(req, {local::ch_id >= 0 -> ch_id == local::ch_id;
local::pkt_id >= 0 -> pkt_id == local::pkt_id;
local::data_nidles >= 0 -> data_nidles == local::data_nidles;
local::pkt_nidles >= 0 -> pkt_nidles == local::pkt_nidles;
local::data_size >0 -> data.size() == local::data_size;
foreach(local::data[i]) local::data[i] >= 0 -> data[i] == local::data[i];
})//这里的随机化只针对一个包,然后重复ntrans次
this.pkt_id++;//包的id加一
`uvm_info(get_type_name(), req.sprint(), UVM_HIGH)
get_response(rsp);//sequence从driver那获得rsp表示完成一次握手
`uvm_info(get_type_name(), rsp.sprint(), UVM_HIGH)
assert(rsp.rsp)//做个断言,若rsp不点亮则报错,why is 断言?
else $error("[RSPERR] %0t error response received!", $time);
endtask
function void post_randomize();//随机化之后打印
string s;
s = {s, "AFTER RANDOMIZATION n"};
s = {s, "=======================================n"};
s = {s, "chnl_data_sequence object content is as below: n"};
s = {s, super.sprint()};
s = {s, "=======================================n"};
`uvm_info(get_type_name(), s, UVM_HIGH)
endfunction
endclass: chnl_data_sequence
typedef struct packed {//packed表示合并,结构体可以存放不同数据类型的变量
//typedef定义新的类型,32位的data和2位id合起来称为mon_data_t
bit[31:0] data;
bit[1:0] id;
} mon_data_t;
//monitor没有太多介绍,继承于comp,处理方法和其他comp类似
class chnl_monitor extends uvm_monitor;
//在接口中声明过monitor的时钟块和驱动的时钟块
//由于要接入信号,也要用到接口,同样声明一个monitor中的local接口
local virtual chnl_intf intf;
uvm_analysis_port #(mon_data_t) mon_ana_port;
//analysis port是一initiator对多target的应用,push模式,从port调用各个target的write函数实现数据传输
//需要在顶层进行analysis port和imp的连接,在initiator调用write
`uvm_component_utils(chnl_monitor)
function new(string name="chnl_monitor", uvm_component parent);
super.new(name, parent);
//ap不是组件自带的,要用户声明后在new函数例化
mon_ana_port = new("mon_ana_port", this);
endfunction
function void set_interface(virtual chnl_intf intf);
if(intf == null)//配置接口
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
task run_phase(uvm_phase phase);
this.mon_trans();//comp的子类,phase机制
endtask
task mon_trans();
mon_data_t m;
forever begin//在valid拉高wait拉低时才采集
@(intf.mon_ck iff (intf.mon_ck.ch_valid==='b1 && intf.mon_ck.ch_wait==='b0));
m.data = intf.mon_ck.ch_data;
mon_ana_port.write(m);//initiator端调用write函数,参数是句柄
`uvm_info(get_type_name(), $sformatf("monitored channel data 'h%8x", m.data), UVM_HIGH)
end
endtask
endclass: chnl_monitor
//agent作为chnl_pkg的顶层,build_phase获取接口,例化3个comp
//例化用 组件名=类名::type_id::create(“组件名”,this)
//connect_phase连接driver和sequencer,并把接口连上
class chnl_agent extends uvm_agent;
chnl_driver driver;
chnl_monitor monitor;
chnl_sequencer sequencer;
local virtual chnl_intf vif;
`uvm_component_utils(chnl_agent)
function new(string name = "chnl_agent", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
//#(配置的类型),this,"","vif"为存储路径,vif为传递的接口(已声明)
if(!uvm_config_db#(virtual chnl_intf)::get(this,"","vif", vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
driver = chnl_driver::type_id::create("driver", this);
monitor = chnl_monitor::type_id::create("monitor", this);
sequencer = chnl_sequencer::type_id::create("sequencer", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
driver.seq_item_port.connect(sequencer.seq_item_export);
this.set_interface(vif);
endfunction
function void set_interface(virtual chnl_intf vif);
driver.set_interface(vif);
monitor.set_interface(vif);
endfunction
endclass: chnl_agent
endpackage
fmt_pkg
复习mailbox
SV--线程(mailbox)_ICer吼吼的博客-CSDN博客_sv中mailbox
//和硬件fmt对比了一下,感觉硬件和软件写的模型不太一样,这里fifo和带宽会变
package fmt_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"
//枚举类型:FIFO深度和数据位宽
typedef enum {SHORT_FIFO, MED_FIFO, LONG_FIFO, ULTRA_FIFO} fmt_fifo_t;
typedef enum {LOW_WIDTH, MED_WIDTH, HIGH_WIDTH, ULTRA_WIDTH} fmt_bandwidth_t;
//结构是一样的,从item开始
//将fifo深度和带宽声明为随机变量
class fmt_trans extends uvm_sequence_item;
rand fmt_fifo_t fifo;
rand fmt_bandwidth_t bandwidth;
bit [7:0] length;
bit [31:0] data[];
bit [7:0] ch_id;
bit [31:0] parity;
bit rsp;
constraint cstr{//默认为medium
soft fifo == MED_FIFO;
soft bandwidth == MED_WIDTH;
};
`uvm_object_utils_begin(fmt_trans)
`uvm_field_enum(fmt_fifo_t, fifo, UVM_ALL_ON)
`uvm_field_enum(fmt_bandwidth_t, bandwidth, UVM_ALL_ON)
`uvm_field_int(length, UVM_ALL_ON)
`uvm_field_array_int(data, UVM_ALL_ON)
`uvm_field_int(ch_id, UVM_ALL_ON)
`uvm_field_int(rsp, UVM_ALL_ON)
`uvm_object_utils_end
function new (string name = "fmt_trans");
super.new(name);
endfunction
endclass
//driver
class fmt_driver extends uvm_driver #(fmt_trans);
local virtual fmt_intf intf;
local mailbox #(bit[31:0]) fifo;
local int fifo_bound;
local int data_consum_peroid;
`uvm_component_utils(fmt_driver)
function new (string name = "fmt_driver", uvm_component parent);
super.new(name, parent);
this.fifo = new();//mailbox需要例化
this.fifo_bound = 4096;//fifo深度初始化
this.data_consum_peroid = 1;//带宽初始化
endfunction
function void set_interface(virtual fmt_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
task run_phase(uvm_phase phase);
fork
this.do_receive();
this.do_consume();
this.do_config();
this.do_reset();
join
endtask
task do_config();
fmt_trans req, rsp;
forever begin//从seq拿到item
seq_item_port.get_next_item(req);
case(req.fifo)//看看里面的随机变量fifo深度是哪个
SHORT_FIFO: this.fifo_bound = 64;
MED_FIFO: this.fifo_bound = 256;
LONG_FIFO: this.fifo_bound = 512;
ULTRA_FIFO: this.fifo_bound = 2048;
endcase
//把得到的深度作为参数例化给当前类的邮箱fifo
this.fifo = new(this.fifo_bound);
case(req.bandwidth)//看看里面的随机变量带宽是哪个,带宽越大消化时间越短
LOW_WIDTH: this.data_consum_peroid = 8;
MED_WIDTH: this.data_consum_peroid = 4;
HIGH_WIDTH: this.data_consum_peroid = 2;
ULTRA_WIDTH: this.data_consum_peroid = 1;
endcase
//其他操作是一样的
void'($cast(rsp, req.clone()));
rsp.rsp = 1;
rsp.set_sequence_id(req.get_sequence_id());
seq_item_port.item_done(rsp);
end
endtask
task do_reset();
forever begin
@(negedge intf.rstn)
intf.fmt_ready <= 0;//根据接口,output只有一个fmt_ready
end
endtask
task do_receive();//fmt接收来自node的data,就是存进fifo
forever begin
@(intf.drv_ck); #10ps;
if(intf.fmt_valid === 1'b1) begin//实验0里面的grant信号
forever begin
if((this.fifo_bound-this.fifo.num()) >= 1)//why?
break;//如果fifo容量和实际存储的数量的差大于等于1就停止
@(intf.drv_ck); #10ps;
end
this.fifo.put(intf.fmt_data);
#1ps; intf.fmt_ready <= 1;
end//接收完数据后拉高ready
else begin
#1ps; intf.fmt_ready <= 0;
end
end
endtask
task do_consume();//消化数据需要一定时间,但是为什么用随机范围?
bit[31:0] data;
forever begin
void'(this.fifo.try_get(data));
repeat($urandom_range(1, this.data_consum_peroid)) @(posedge intf.clk);
end
endtask
endclass: fmt_driver
//sequencer依然是注册和new函数即可
class fmt_sequencer extends uvm_sequencer #(fmt_trans);
`uvm_component_utils(fmt_sequencer)
function new (string name = "fmt_sequencer", uvm_component parent);
super.new(name, parent);
endfunction
endclass: fmt_sequencer
//sequence做item的随机化和在body中发送,做p_sequencer的宏
class fmt_config_sequence extends uvm_sequence #(fmt_trans);
rand fmt_fifo_t fifo = MED_FIFO;
rand fmt_bandwidth_t bandwidth = MED_WIDTH;
constraint cstr{
soft fifo == MED_FIFO;
soft bandwidth == MED_WIDTH;
}
`uvm_object_utils_begin(fmt_config_sequence)
`uvm_field_enum(fmt_fifo_t, fifo, UVM_ALL_ON)
`uvm_field_enum(fmt_bandwidth_t, bandwidth, UVM_ALL_ON)
`uvm_object_utils_end
`uvm_declare_p_sequencer(fmt_sequencer)
function new (string name = "fmt_config_sequence");
super.new(name);
endfunction
task body();//是body不是run_phase
send_trans();
endtask
task send_trans();
fmt_trans req, rsp;
`uvm_do_with(req, {local::fifo != MED_FIFO -> fifo == local::fifo;
local::bandwidth != MED_WIDTH -> bandwidth == local::bandwidth;
})//随机化只要和初始化的不一样就做更改,寄存器会配置
`uvm_info(get_type_name(), req.sprint(), UVM_HIGH)
get_response(rsp);
`uvm_info(get_type_name(), rsp.sprint(), UVM_HIGH)
assert(rsp.rsp)//其他的相同
else $error("[RSPERR] %0t error response received!", $time);
endtask
function void post_randomize();
string s;
s = {s, "AFTER RANDOMIZATION n"};
s = {s, "=======================================n"};
s = {s, "fmt_config_sequence object content is as below: n"};
s = {s, super.sprint()};
s = {s, "=======================================n"};
`uvm_info(get_type_name(), s, UVM_HIGH)
endfunction
endclass: fmt_config_sequence
// formatter monitor
class fmt_monitor extends uvm_monitor;
local string name;
local virtual fmt_intf intf;
uvm_analysis_port #(fmt_trans) mon_ana_port;
`uvm_component_utils(fmt_monitor)
function new(string name="fmt_monitor", uvm_component parent);
super.new(name, parent);
mon_ana_port = new("mon_ana_port", this);
endfunction
function void set_interface(virtual fmt_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
task run_phase(uvm_phase phase);
this.mon_trans();
endtask
//chnl_pkg没有例化item,为什么这里就要例化?
task mon_trans();
fmt_trans m;
string s;
forever begin//first、valid、ready拉高才开始读数据
@(intf.mon_ck iff intf.mon_ck.fmt_first && intf.mon_ck.fmt_valid && intf.mon_ck.fmt_ready);
m = new();//为什么这里就要例化?
//由fmt数据包格式的图可知id和length是高16位
m.length = intf.mon_ck.fmt_data[23:16];
m.ch_id = intf.mon_ck.fmt_data[31:24];
//动态数组分配空间,length+1是payload的数量,加上包头包尾+2
//所以这里需要length+3的空间。还是看数据包的图
m.data = new[m.length + 3];
foreach(m.data[i]) begin
m.data[i] = intf.mon_ck.fmt_data;
//size-1表示数据最高位是奇偶校验位
if(i == m.data.size()-1) m.parity = m.data[i];
if(i < m.data.size()-1) @(intf.mon_ck iff intf.mon_ck.fmt_valid && intf.mon_ck.fmt_ready);
end//奇偶校验位以外的是数据位
mon_ana_port.write(m);
s = $sformatf("=======================================n");
s = {s, $sformatf("%0t %s monitored a packet: n", $time, this.m_name)};
s = {s, $sformatf("length = %0d: n", m.length)};
s = {s, $sformatf("chid = %0d: n", m.ch_id)};
foreach(m.data[i]) s = {s, $sformatf("data[%0d] = %8x n", i, m.data[i])};
s = {s, $sformatf("=======================================n")};
`uvm_info(get_type_name(), s, UVM_HIGH)
end
endtask
endclass: fmt_monitor
//agent顶层环境做的事情完全一样
class fmt_agent extends uvm_agent;
fmt_driver driver;//声明三个组件和接口
fmt_monitor monitor;
fmt_sequencer sequencer;
local virtual fmt_intf vif;
`uvm_component_utils(fmt_agent) //注册和new函数
function new(string name = "chnl_agent", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
//build_phase get接口,例化三个组件
if(!uvm_config_db#(virtual fmt_intf)::get(this,"","vif", vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
driver = fmt_driver::type_id::create("driver", this);
monitor = fmt_monitor::type_id::create("monitor", this);
sequencer = fmt_sequencer::type_id::create("sequencer", this);
endfunction
//connect phase连接driver和sequencer,调用set_interface连接接口
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
driver.seq_item_port.connect(sequencer.seq_item_export);
this.set_interface(vif);
endfunction
function void set_interface(virtual fmt_intf vif);
driver.set_interface(vif);
monitor.set_interface(vif);
endfunction
endclass
endpackage
mcdf_rgm_pkg
电力电子转战数字IC20220818day63——uvm入门实验5_广工陈奕湘的博客-CSDN博客
复习一下UVM的寄存器模型。寄存器自己操作的trans是uvm_reg_bus_op,通过adapter转化到mcdf的总线
关于set_coverage()
uvm设计分析——reg - _9_8 - 博客园
(30)UVM 寄存器模型的应用场景和功能覆盖率收集_数字IC小白的日常修炼的博客-CSDN博客_uvm_subscriber
覆盖率选项设置
Systemverilog(绿皮书)第九章——功能覆盖率(四)覆盖选项_胡九筒的博客-CSDN博客
rgm_pkg只提供两个reg的代码即可,因为12个reg类的代码结构完全一样,只有域和值不一样,注意默认值的设置。rgm可以用工具生成。
电力电子转战数字IC20220824day68——uvm实战3_广工陈奕湘的博客-CSDN博客
关于uvm_reg_map
[CU]reg model构建篇-uvm_reg_map(与前门访问相关) - _见贤_思齐 - 博客园
关于后门访问
//寄存器模型rgm,每一个寄存器分别做一个class,一共12个类,继承于uvm_reg
class slv_en_reg extends uvm_reg;
//每个class包含:注册+域的声明+覆盖组+new函数+build_phase+sample函数
//相当于只是做了覆盖组和例化和采样
`uvm_object_utils(slv_en_reg)
rand uvm_reg_field en;//[3:0]前4位是slv_en,其他位是预留位reserved
rand uvm_reg_field reserved;
covergroup value_cg;//两个域写成两个覆盖点组成一个覆盖组
option.per_instance = 1;
en: coverpoint en.value[3:0];
reserved: coverpoint reserved.value[31:4];
endgroup
//new函数有3个参数,name,寄存器位数32,是否要加入覆盖率的支持
function new(string name = "slv_en_reg");
super.new(name, 32, UVM_CVR_ALL);//若UVM_NO_COVERAGE,则不支持
//如果有覆盖率收集的需求,在new函数要set_coverage,然后例化覆盖组
void'(set_coverage(UVM_CVR_FIELD_VALS));
if(has_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg = new();//覆盖组必须例化才会收集
end
endfunction
//build_phase例化+配置寄存器域
virtual function void build();
en = uvm_reg_field::type_id::create("en");
reserved = uvm_reg_field::type_id::create("reserved");
//配置域的参数:第一个是this,然后两个表示寄存器对应的位
//第四个参数表示寄存器属性,读写还是只读,后五个参数表示默认值
en.configure(this, 4, 0, "RW", 0, 'h0, 1, 0, 0);
reserved.configure(this, 28, 4, "RO", 0, 'h0, 1, 0, 0);
endfunction
//覆盖率采样函数,需要自定义
function void sample(
uvm_reg_data_t data,//寄存器操作的trans——uvm_reg_bus_op,见截图
uvm_reg_data_t byte_en,
bit is_read,
uvm_reg_map map//为什么连map也要?
);
//调用父类sample函数,输入参数相同
super.sample(data, byte_en, is_read, map);
sample_values(); //调用自定义sample_values函数
endfunction
function void sample_values();
super.sample_values();//也调用父类sample_values函数
//new函数set了,这里如果get就调用覆盖组的sample
if (get_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg.sample();
end
endfunction
endclass
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[31:4];
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, 0, 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
顶层的mcdf_rgm类
关于配置寄存器的9个参数
UVM 中的寄存器模型
//最后的rgm就是寄存器的顶层环境
class mcdf_rgm extends uvm_reg_block;
`uvm_object_utils(mcdf_rgm)//注册
rand slv_en_reg slv_en;//声明所有reg为随机变量
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;//map不要漏
function new(string name = "mcdf_rgm");
super.new(name, UVM_NO_COVERAGE);//顶层不收集覆盖率
endfunction
//build_phase对每一个reg进行:例化+调用configure配置+调用build
//对map进行例化
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_free_slot = slv1_free_slot_reg::type_id::create("slv1_free_slot");
slv1_free_slot.configure(this);
slv1_free_slot.build();
slv2_free_slot = slv2_free_slot_reg::type_id::create("slv2_free_slot");
slv2_free_slot.configure(this);
slv2_free_slot.build();
slv3_free_slot = slv3_free_slot_reg::type_id::create("slv3_free_slot");
slv3_free_slot.configure(this);
slv3_free_slot.build();
slv0_parity_err = slv0_parity_err_reg::type_id::create("slv0_parity_err");
slv0_parity_err.configure(this);
slv0_parity_err.build();
slv1_parity_err = slv1_parity_err_reg::type_id::create("slv1_parity_err");
slv1_parity_err.configure(this);
slv1_parity_err.build();
slv2_parity_err = slv2_parity_err_reg::type_id::create("slv2_parity_err");
slv2_parity_err.configure(this);
slv2_parity_err.build();
slv3_parity_err = slv3_parity_err_reg::type_id::create("slv3_parity_err");
slv3_parity_err.configure(this);
slv3_parity_err.build();
//map的例化,名字-基地址-总线宽度,单位是byte,32位对应4byte-大小端不知道是什么意思
map = create_map("map", 'h0, 4, UVM_LITTLE_ENDIAN);
//将寄存器添加到map:寄存器-偏移地址-访问模式(只读/读写)
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");
//后门访问:reg.add_hdl_path_slice(“name”,首位,末位)
slv_en.add_hdl_path_slice("???", 0, 32);
parity_err_clr.add_hdl_path_slice("???", 0, 32);
slv_id.add_hdl_path_slice("???", 0, 32);
slv_len.add_hdl_path_slice("???", 0, 32);
slv0_free_slot.add_hdl_path_slice("???", 0, 32);
slv1_free_slot.add_hdl_path_slice("???", 0, 32);
slv2_free_slot.add_hdl_path_slice("???", 0, 32);
slv3_free_slot.add_hdl_path_slice("???", 0, 32);
slv0_parity_err.add_hdl_path_slice("???", 0, 32);
slv1_parity_err.add_hdl_path_slice("???", 0, 32);
slv2_parity_err.add_hdl_path_slice("???", 0, 32);
slv3_parity_err.add_hdl_path_slice("???", 0, 32);
add_hdl_path("???");//为什么不是具体的名称,见图
lock_model();//结束地址映射关系,保证model不会被其他用户修改
endfunction
//定义函数获取域的长度,暂时不知道会在哪里调用到
function int get_reg_field_length(int ch);
int fd;
case(ch)//根据channel id将slv_len的4个域调用get()获得
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
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
endclass
apb_pkg
.sv与.svh以及`ifndef `else `endif - 知乎
SystemVerilog与Verilog中重定义问题解决方案 - 知乎
7-字符串与$sformatf,$sformat,$psprintf - _见贤_思齐 - 博客园
//——————————————————————————apb_pkg————————————————————————————
//这两句话表示:如果没有定义过就定义,定义过了就不会执行
//由于多个文件都会调用到相同的文件,这样做避免头文件的重复编译
`ifndef APB_PKG_SV
`define APB_PKG_SV
package apb_pkg;
import uvm_pkg::*;//uvm的两句话
`include "uvm_macros.svh"
//定义了一个参数
parameter bit[31:0] DEFAULT_READ_VALUE = 32'hFFFF_FFFF;
`include "apb.svh"//uvm_pkg的内容写在其他文件中
endpackage : apb_pkg
`endif
//—————————————————————————apb.svh———————————————————————————
//那么就来看看apb.svh这个头文件有什么
//APB为一主多从结构,主端为AHB总线等发送数据过来,从端为给MCDF的信号
//所以分为master和slave两部分,分别搭建验证环境
`ifndef APB_SVH//同理
`define APB_SVH
`include "apb_transfer.sv"
`include "apb_config.sv"
`include "apb_master_driver.svh"
`include "apb_master_monitor.svh"
`include "apb_master_sequencer.svh"
`include "apb_master_agent.svh"
`include "apb_slave_driver.svh"
`include "apb_slave_monitor.svh"
`include "apb_slave_sequencer.svh"
`include "apb_slave_agent.svh"
`include "apb_master_driver.sv"
`include "apb_master_monitor.sv"
`include "apb_master_sequencer.sv"
`include "apb_master_agent.sv"
`include "apb_master_seq_lib.sv"
`include "apb_slave_driver.sv"
`include "apb_slave_monitor.sv"
`include "apb_slave_sequencer.sv"
`include "apb_slave_agent.sv"
`include "apb_slave_seq_lib.sv"
`endif
//——————————————————————apb_transfer——————————————————————————————
//按顺序,看apb_transfer,分为头文件和sv
`ifndef APB_TRANSFER_SV
`define APB_TRANSFER_SV
//枚举变量,APB工作的类型:idle还是读写
//APB传输状态,没问题or出错了
typedef enum {IDLE, WRITE, READ } apb_trans_kind;
typedef enum {OK, ERROR} apb_trans_status;
//apb_pkg本身就是个完整的验证结构,transfer就是apb传输的item
//item只有地址+数据+两个状态+间隔,没有ready信号这些具体的?
class apb_transfer extends uvm_sequence_item;
rand bit [31:0] addr;
rand bit [31:0] data;
rand apb_trans_kind trans_kind;
rand apb_trans_status trans_status;
rand int idle_cycles;
constraint cstr{
soft idle_cycles == 1;
};
//注册和域的自动化
`uvm_object_utils_begin(apb_transfer)
`uvm_field_enum (apb_trans_kind, trans_kind, UVM_ALL_ON)
`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函数,object的子类只有一个参数
function new (string name = "apb_transfer_inst");
super.new(name);
endfunction : new
endclass : apb_transfer
`endif
//——————————————————————主端部分——————————————————————————————
//——————————————————————apb_master_driver——————————————————————————————
//config是全局配置等下再看,分为svh和sv
//svh头文件就是一个骨架,不包含具体的成员变量和具体的方法,只有句柄和注册
//以及各个方法的名字,注意是extern
`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
//driver正文:extern virtual protected通通不用,只有方法
//每个方法名前都要加apb_master_driver::
`ifndef APB_MASTER_DRIVER_SV
`define APB_MASTER_DRIVER_SV
//new函数有两个参数
function apb_master_driver::new (string name, uvm_component parent);
super.new(name, parent);
endfunction : new
//run函数就是run_phase,可以自定义名称?
task apb_master_driver::run();
fork//同时运行两个线程,也同时运行后面的task(join_none)
get_and_drive();
reset_listener();
join_none
endtask : run
//第一个线程就是driver的工作模式,通过seq_item_port调用get_next_item拿到item
//drive_transfer驱动item,克隆req后转换成rsp,设置seq和item的id
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
//APB驱动数据的方式,首先读取状态机看APB总线当前工作在idle还是读写
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
//写操作,通过接口的时钟块采样和驱动各个信号
//第一个阶段:写入地址,write拉高,sel拉高,enable为低
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;//阻塞赋值,
//第二个阶段,enable拉高,data成功写入
@(vif.cb_mst);//触发事件为时钟块可以表示下一个clk
//时钟块本身就包含了@(posedge clk),见apb_if
vif.cb_mst.penable <= 1;
#10ps;
wait(vif.pready === 1);//ready为1时才继续往下进行
#1ps;
//如果pslverr拉高报错,状态更改为error,根据严重程度进行打印
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
//读操作,和写操作的区别只有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
//idle时地址和write信号保持不变,省电,其他信号拉低
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
//第二个线程,在rstn复位信号下降沿时复位所有信号拉低置0
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_monitor——————————————————————————————
//和driver一样,也是svh和sv文件
`ifndef APB_MASTER_MONITOR_SVH
`define APB_MASTER_MONITOR_SVH
class apb_master_monitor extends uvm_monitor;
apb_config cfg;
bit checks_enable = 1;
bit coverage_enable = 1;
virtual apb_if vif;
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
extern function new(string name, uvm_component parent=null);
extern virtual task run();
event apb_master_cov_transaction;
covergroup apb_master_cov_trans @apb_master_cov_transaction;
endgroup : apb_master_cov_trans
extern virtual protected task monitor_transactions();
extern virtual protected task collect_transfer();
extern protected function void perform_transfer_checks();
extern protected function void perform_transfer_coverage();
endclass : apb_master_monitor
`endif
//monitor正文
`ifndef APB_MASTER_MONITOR_SV
`define APB_MASTER_MONITOR_SV
//和之前看的其他monitor一样,new函数例化port
//parent的参数具体为null了?
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
//run_phase,注意要用join_none不要阻塞
task apb_master_monitor::run();
fork
monitor_transactions();
join_none
endtask
task apb_master_monitor::monitor_transactions();
forever begin
//从接口拿到item(transfer)
collect_transfer();
//检查item
if (checks_enable)
perform_transfer_checks();
// Update coverage
if (coverage_enable)
perform_transfer_coverage();
//ap一对多发给接收端
item_collected_port.write(trans_collected);
end
endtask
task apb_master_monitor::collect_transfer();
//有条件例化item,sel和enable都拉高的时候从接口拿item
@(vif.cb_mon iff (vif.cb_mon.psel === 1'b1 && vif.cb_mon.penable === 1'b0));
//clk上升沿若sel为高,enable为低,标志着APB总线进入setup状态
trans_collected = apb_transfer::type_id::create("trans_collected");
case(vif.cb_mon.pwrite)
1'b1 : begin//写操作,ready拉高则将数据写入
@(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;//item中声明的两个状态
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
function void apb_master_monitor::perform_transfer_checks();
endfunction : perform_transfer_checks
function void apb_master_monitor::perform_transfer_coverage();
-> apb_master_cov_transaction;
endfunction : perform_transfer_coverage
`endif
//——————————————————————apb_master_sequencer——————————————————————————————
//一样是注册和new函数
`ifndef APB_MASTER_SEQUENCER_SVH
`define APB_MASTER_SEQUENCER_SVH
class apb_master_sequencer extends uvm_sequencer #(apb_transfer);
apb_config cfg;
`uvm_component_utils_begin(apb_master_sequencer)
`uvm_component_utils_end
extern function new (string name, uvm_component parent);
virtual apb_if vif;
endclass : apb_master_sequencer
`endif
//sequencer正文
`ifndef APB_MASTER_SEQUENCER_SV
`define APB_MASTER_SEQUENCER_SV
function apb_master_sequencer::new (string name, uvm_component parent);
super.new(name, parent);
endfunction : new
`endif
//——————————————————————apb_master_sequence——————————————————————————————
`ifndef APB_MASTER_SEQ_LIB_SV
`define APB_MASTER_SEQ_LIB_SV
//sequence先写一个base seq,然后被5个seq继承
//分别是写1个、读1个、读写、连续读和连续写
//将item和sequencer用typedef定义为变量,为什么????????
typedef class apb_transfer;
typedef class apb_master_sequencer;
//base seq只需要注册和例化
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
//一次写,只需要一个uvm_do_with
class apb_master_single_write_sequence extends apb_master_base_sequence;
rand bit [31:0] addr;//item中要用到的成员变量就声明
rand bit [31:0] data;
apb_trans_status trans_status;
//注册和new函数,没有域的自动化
`uvm_object_utils(apb_master_single_write_sequence)
function new(string name="");
super.new(name);
endfunction : new
virtual task body();//注意这里加了virtual,为什么?
`uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
//随机化item并发送
`uvm_do_with(req, {trans_kind == WRITE; addr == local::addr; data == local::data;})
get_response(rsp);
trans_status = rsp.trans_status;//传输的状态要访问rsp的才知道
//$psprintf()返回一个格式化的临时字符串,只需要2个参数
`uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
endtask: body
endclass: apb_master_single_write_sequence
//和写1个的区别是:随机化不需要做data的,从rsp拿出data
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;//读数据得将rsp中的data拿出来
`uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
endtask: body
endclass: apb_master_single_read_sequence
//先写后读,两次操作,两个do_with,每次都要拿到rsp
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;//默认为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
//连续的写,data的类型变成动态数组
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{//连续写4、8、16、32个
soft data.size() inside {4, 8, 16, 32};
foreach(data[i]) soft data[i] == addr + (i << 2);
}//data[i]的值是地址+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;//直接把状态设置为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//写完把状态设置为idle
`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_agent——————————————————————————————
`ifndef APB_MASTER_AGENT_SVH
`define APB_MASTER_AGENT_SVH
//agent的结构基本相同
class apb_master_agent extends uvm_agent;
apb_config cfg;
//多声明了个config
//声明三个组件和接口
apb_master_driver driver;
apb_master_sequencer sequencer;
apb_master_monitor monitor;
virtual apb_if vif;
`uvm_component_utils_begin(apb_master_agent)
`uvm_component_utils_end
//new函数,build_phase和connect_phase
extern function new (string name, uvm_component parent);
extern function void build();
extern function void connect();
extern function void assign_vi(virtual apb_if vif);
endclass : apb_master_agent
`endif
//agent正文
`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();
//由于有config,这里要多个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
//拿接口
if( !uvm_config_db#(virtual apb_if)::get(this,"","vif", vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
//例化monitor
monitor = apb_master_monitor::type_id::create("monitor",this);
monitor.cfg = cfg;
//active时才例化和配置driver和sequencer
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
//connect_phase在active时连接driver和sequencer对应的port,还有接口
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
//在active时连接driver和sequencer的接口
function void apb_master_agent::assign_vi(virtual apb_if vif);
monitor.vif = vif;
if (is_active == UVM_ACTIVE) begin
sequencer.vif = vif;
driver.vif = vif;
end
endfunction : assign_vi
`endif
//—————————————————————————apb_config———————————————————————————
//config机制中config object传递的应用
//整合每个组件中的变量放到这个apb_config中,对中心化的配置对象进行传递
//有利于整体环境的维护
`ifndef APB_CONFIG_SV
`define APB_CONFIG_SV
class apb_config extends uvm_object;
//设置agent是active还是passive
uvm_active_passive_enum is_active = UVM_ACTIVE;
//既是声明也是初始化,master_pslverr_status_severity的初始化严重级别为warning
uvm_severity master_pslverr_status_severity = UVM_WARNING;
//声明了三个成员变量
rand bit slave_pready_random = 1;
rand bit slave_pslverr_random = 0;
rand bit slave_pready_default_value = 0;
`uvm_object_utils(apb_config)
function new (string name = "apb_config");
super.new(name);
endfunction : new
virtual function get_pready_additional_cycles();
if(slave_pready_random)//
return $urandom_range(0, 2);
else//从端准备好随机化了就返回ready的间隔在0-2之间随机
return 0;
endfunction
virtual function get_pslverr_status();
if(slave_pslverr_random && $urandom_range(0, 20) == 0)
return 1;
else //总的来说这个函数的作用就是error信号随机拉高
return 0;
endfunction
endclass
`endif
apb_test
注意apb_pkg不包含test,test包含了apb_pkg
//——————————————————————apb_test——————————————————————————————
`ifndef APB_TESTS_SV
`define APB_TESTS_SV
import apb_pkg::*;//import了apb_pkg所有的东西,就是上面所见的
//test包含了顶层env和各个test
//env做了:声明+注册+new函数+build_phase做例化
class apb_env extends uvm_env;
//env例化了两个agent
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
//test需要两个东西:sequence和test,一样是写base后进行继承
//base test
class apb_base_test extends uvm_test;
apb_env env;//
apb_config cfg;
`uvm_component_utils(apb_base_test)
//new函数没有写parent具体的参数
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
//build_phase例化了config,可以手动设置或者随机化config声明的三个成员变量
function void build_phase(uvm_phase phase);
super.build_phase(phase);
cfg = apb_config::type_id::create("cfg");
//手动设置
cfg.slave_pready_default_value = 1;
cfg.slave_pready_random = 1;
cfg.slave_pslverr_random = 1;
//或者对整个config随机化
//void'(cfg.randomize());
uvm_config_db#(apb_config)::set(this,"env.*","cfg", cfg);//用config机制set了cfg
env = apb_env::type_id::create("env", this);//base test例化env
endfunction
endclass
//base seq,注意sequence是参数类
class apb_base_test_sequence extends uvm_sequence #(apb_transfer);
//先声明个32*32的mem
bit[31:0] mem[bit[31:0]];//经查和问,这是个32*32二维数组,忽视bit即可
//注册
`uvm_object_utils(apb_base_test_sequence)
//new函数,seq继承于item继承于trans继承于object
function new(string name="");
super.new(name);
endfunction : new
//第一个函数:检查mem的数据和地址是否对应的上
function bit check_mem_data(bit[31:0] addr, bit[31:0] data);
//exist函数检查mem中是否存在addr,是则返回1,否则返回0(二值)或x(四值)
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;//比较结果正确返回1,否则返回0
end
end
else begin
//等式运算符!=表示不等于,昨天笔试好像用了!==,两者区别在于是否连x和z都相同
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;//DEFAULT_READ_VALUE不知道是哪里跑出来的???
end//从打印的信息看,DEFAULT_READ_VALUE是期望值
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
//第三个任务:等待n个clk,用repeat加上升沿实现
task wait_cycles(int n);
repeat(n) @(posedge apb_tb.clk);
endtask
//第四个函数:随机化地址
function bit[31:0] get_rand_addr();
bit[31:0] addr;//addr在函数中声明,是临时变量,用std::rabdomize
//但是addr为什么是这些值?这个问题应该要进一步问
//哪个地方会调用到这个函数拿到这个addr?找到了应该就可以解决这两个问题了
void'(std::randomize(addr) with {addr[31:12] == 0; addr[1:0] == 0;addr != 0;});
return addr;//在下面的子类就调用到了,return给了addr赋值
endfunction
endclass
//单个数据操作,包括写、读、写读、写了马上读、写2个读1个
class apb_single_transaction_sequence extends apb_base_test_sequence;
//这三个是写在apb_master_seq_lib中的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;//base test写的两个函数这里调用了
this.wait_reset_release();
this.wait_cycles(10);
//写1个的测试
`uvm_info(get_type_name(), "TEST continous write transaction...", UVM_LOW)
repeat(test_num) begin
addr = this.get_rand_addr();
//这里发送的是seq不是item,第一个参数是seq,把随机的地址赋给addr和data
`uvm_do_with(single_write_seq, {addr == local::addr; data == local::addr;})
mem[addr] = addr;//作为data写进mem
end
//读1个的测试
`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
//写完再读的测试
`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
//写完马上读的测试
`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;//地址作为data写入mem,少了个uvm_do_with,包含在seq中了
if(write_read_seq.trans_status == OK)
void'(this.check_mem_data(addr, write_read_seq.data));
end
//连续写2次后马上读1次
`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();
//写入,这个宏是item
`uvm_do_with(req, {trans_kind == WRITE;
addr == local::addr;
data == local::addr;
idle_cycles == 0;
})
mem[addr] = addr;
get_response(rsp);
//再写一次
`uvm_do_with(req, {trans_kind == WRITE;
addr == local::addr;
data == local::addr<<2;//不写入相同的,左移2位
idle_cycles == 0;
})
mem[addr] = addr<<2;
get_response(rsp);
//马上读
`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
//对应的test只需要注册+new函数+run_phase
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
//例化sequence,举手,调用父类的run_phase
task run_phase(uvm_phase phase);
apb_single_transaction_sequence seq = new();
phase.raise_objection(this);
super.run_phase(phase);
seq.start(env.mst.sequencer);
phase.drop_objection(this);
endtask
endclass: apb_single_transaction_test
class apb_burst_transaction_sequence extends apb_base_test_sequence;
apb_master_burst_write_sequence burst_write_seq;
apb_master_burst_read_sequence burst_read_seq;
rand int test_num = 100;
constraint cstr{
soft test_num == 100;
}
`uvm_object_utils(apb_burst_transaction_sequence)
function new(string name="");
super.new(name);
endfunction : new
task body();
bit[31:0] addr;
this.wait_reset_release();
this.wait_cycles(10);
//连续写
repeat(test_num) begin
addr = this.get_rand_addr();
//这里看sequence的内容,data已经在约束块中随机化了,和单个写不同
//发送的时候不需要再做随机化
`uvm_do_with(burst_write_seq, {addr == local::addr;})//先随机化地址
foreach(burst_write_seq.data[i]) begin
//地址+i左移2位就是约束块中的约束
//把seq中的data全部写进mem
mem[addr+(i<<2)] = burst_write_seq.data[i];
end
//连续读,读多少个按照seq中data的size决定
`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
//对应的test,一模一样
class apb_burst_transaction_test extends apb_base_test;
`uvm_component_utils(apb_burst_transaction_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
task run_phase(uvm_phase phase);
apb_burst_transaction_sequence seq = new();
phase.raise_objection(this);
super.run_phase(phase);
seq.start(env.mst.sequencer);
phase.drop_objection(this);
endtask
endclass: apb_burst_transaction_test
`endif
apb_slave
上面从apb_pkg开始,完整的过完了整个master的验证结构(包含test),这里再看看slave端和master端的有什么不一样,结构上是一模一样的。
accept_tr, begin_tr, end_tr | Verification Academy
//—————————————————————————apb_slave_driver———————————————————————————
//先看头文件
`ifndef APB_SLAVE_DRIVER_SVH
`define APB_SLAVE_DRIVER_SVH
class apb_slave_driver extends uvm_driver #(apb_transfer);
apb_config cfg;
bit[31:0] mem [bit[31:0]];//区别1:从端driver有个mem
`uvm_component_utils_begin(apb_slave_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();
//区别3:驱动的不是item而是response
extern virtual protected task drive_response();
extern protected task do_idle();
extern protected task do_write();
extern protected task do_read();
extern virtual protected task reset_listener();
//区别2:任务没有item作为参数输入(驱动、读、写三个任务有参数输入)
endclass : apb_slave_driver
`endif
//driver正文
`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//new函数相同
task apb_slave_driver::run();
fork//多了个驱动rsp的线程
get_and_drive();
reset_listener();
drive_response();
join_none
endtask : run
task apb_slave_driver::get_and_drive();
forever begin//少了驱动item和设置rsp的trans的id
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);//用时钟块当触发事件
//进入setup状态时先判断write信号是读还是写
if(vif.cb_slv.psel === 1'b1 && vif.cb_slv.penable === 1'b0) begin
case(vif.cb_slv.pwrite)
1'b1 : this.do_write();
1'b0 : this.do_read();
default : `uvm_error(get_type_name(), "ERROR pwrite signal value")
endcase
end
else begin
this.do_idle();
end
end
endtask : drive_response
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();//和master端完全不同
bit[31:0] addr;//参数没有item,声明临时变量,配合seq
bit[31:0] data;
//调用config中的函数给两个新变量赋值
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);//等enable信号拉高进入第二个状态
addr = vif.cb_slv.paddr;//总线的地址和写入的数据给到这里,
data = vif.cb_slv.pwdata;
mem[addr] = data;//数据给到mem
if(pready_add_cycles > 0) begin
#1ps;
vif.pready <= 0;
repeat(pready_add_cycles) @(vif.cb_slv);
end//根据pready_add_cycles等待ready拉高,error信号给到apb总线输出的pslverr
#1ps;
vif.pready <= 1;
vif.pslverr <= pslverr_status;
fork
begin
@(vif.cb_slv);//写完了,ready和error分别置为默认值和0
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();//无item作为参数
bit[31:0] addr;//同样要声明地址和数据,同样通过config拿两个变量
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);//enable拉高写入addr
addr = vif.cb_slv.paddr;
if(mem.exists(addr))//检查addr在mem中是否存在数据
data = mem[addr];//是就把mem中的data读出来
else
data = DEFAULT_READ_VALUE;//在pkg一开始定义了,parameter bit[31:0] DEFAULT_READ_VALUE = 32'hFFFF_FFFF
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;//读比写多了将data写到接口上的prdata
fork
begin
@(vif.cb_slv);//读完了,ready和error分别置为默认值和0
vif.cb_slv.pready <= cfg.slave_pready_default_value;
vif.cb_slv.pslverr <= 0;
end
join_none
endtask: do_read
task apb_slave_driver::reset_listener();
`uvm_info(get_type_name(), "reset_listener ...", UVM_HIGH)
fork
forever begin
@(negedge vif.rstn); //rstn下降沿
vif.prdata <= 0;
vif.pslverr <= 0;//ready置为默认值
vif.pready <= cfg.slave_pready_default_value;
this.mem.delete(); //清空mem
end
join_none
endtask: reset_listener
`endif
//—————————————————————————apb_slave_monitor———————————————————————————
//monitor从框架上看比master多了个build_phase
`ifndef APB_SLAVE_MONITOR_SVH
`define APB_SLAVE_MONITOR_SVH
class apb_slave_monitor extends uvm_monitor;
apb_config cfg;
bit checks_enable = 1;
bit coverage_enable = 1;
virtual apb_if vif;
uvm_analysis_port #(apb_transfer) item_collected_port;
`uvm_component_utils_begin(apb_slave_monitor)
`uvm_field_int(checks_enable, UVM_ALL_ON)
`uvm_field_int(coverage_enable, UVM_ALL_ON)
`uvm_component_utils_end
extern function new(string name, uvm_component parent=null);
extern function void build();
extern virtual task run();
event apb_slave_cov_transaction;
covergroup apb_slave_cov_trans @apb_slave_cov_transaction;
endgroup : apb_slave_cov_trans
protected apb_transfer trans_collected;
extern virtual protected task monitor_transactions();
extern virtual protected task collect_transfer();
extern protected function void perform_transfer_checks();
extern protected function void perform_transfer_coverage();
endclass : apb_slave_monitor
`endif
//monitor正文
`ifndef APB_SLAVE_MONITOR_SV
`define APB_SLAVE_MONITOR_SV
function apb_slave_monitor::new(string name, uvm_component parent=null);
super.new(name, parent);
trans_collected = new();//这里例化了item,master在获取item时才例化
item_collected_port = new("item_collected_port",this);
endfunction:new
// build phase调用父类的build即可?为什么这里就要build啊
function void apb_slave_monitor::build();
super.build();
endfunction : build
//run_phase调用monitor_transactions任务,与master相同
task apb_slave_monitor::run();
fork
monitor_transactions();
join_none
endtask
task apb_slave_monitor::monitor_transactions();
forever begin
//从接口获取item
collect_transfer();
//检查item的内容
if (checks_enable)
perform_transfer_checks();
//更新覆盖率?
if (coverage_enable)
perform_transfer_coverage();
//ap一对多调用write方法
item_collected_port.write(trans_collected);
end
endtask
task apb_slave_monitor::collect_transfer();
//begin_tr表示整个trans已经开始,且不是子trans
//查不到这个东西是什么,先放着
void'(this.begin_tr(trans_collected));
@(vif.cb_mon);
void'(this.begin_tr(trans_collected));
this.end_tr(trans_collected);
endtask
//检查item和覆盖率两个函数一样是空的
function void apb_slave_monitor::perform_transfer_checks();
endfunction : perform_transfer_checks
function void apb_slave_monitor::perform_transfer_coverage();
-> apb_slave_cov_transaction;
endfunction : perform_transfer_coverage
`endif
//—————————————————————————apb_slave_sequencer——————————————————————————
//一样只有注册加new函数而已
//—————————————————————————apb_slave_sequence——————————————————————————
`ifndef APB_SLAVE_SEQ_LIB_SV
`define APB_SLAVE_SEQ_LIB_SV
//seq为空,应该是要自己写了,那从端的seq要怎么写?
class example_apb_slave_seq extends uvm_sequence #(apb_transfer);
function new(string name="");
super.new(name);
endfunction : new
`uvm_object_utils(example_apb_slave_seq)
apb_transfer this_transfer;
virtual task body();
`uvm_info(get_type_name(),"Starting example sequence", UVM_HIGH)
`uvm_do(this_transfer) //uvm_do包括创建、随机化、发送
`uvm_info(get_type_name(),$psprintf("Done example sequence: %s",this_transfer.convert2string()), UVM_HIGH)
endtask
endclass : example_apb_slave_seq
`endif
//—————————————————————————apb_slave_agent——————————————————————————
agent一模一样,只有在build_phase的if条件中,is_active在master是指明了cfg.的,而slave这里没有,可能是笔误?
mcdf_pkg
以上所有的pkg组成了mcdf_pkg这个顶层环境,作为整个模块的顶层,从结构看,首先是mcdf_refomod模拟mcdf的数据整形;继承于scoreboard的checker做数据比较;路由器virtual sequencer负责调度sequencer;rgm数据映射到硬件寄存器的转换器adapter;顶层环境env和base vir seq;最后是一众test测试。
关于uvm_do的宏
uvm_do系列宏解析_Andy_ICer的博客-CSDN博客_uvm_do_with
寄存器predictor这一块还要再了解学习。
package mcdf_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"
import apb_pkg::*;
import chnl_pkg::*;
import fmt_pkg::*;
import mcdf_rgm_pkg::*;
typedef enum {RW_LEN, RW_PRIO, RW_EN, RD_AVAIL} mcdf_field_t;
//—————————————————————————mcdf_refmod———————————————————————————
// MCDF reference model注意是继承于comp
class mcdf_refmod extends uvm_component;
mcdf_rgm rgm;//声明寄存器模型rgm
//tlm通信的端口和fifo的声明
//根据是否可以等待延时选择阻塞b和非阻塞n的端口
//根据通信方式选择get、put、peek、get_peek
//和apb总线通信的端口,阻塞类型可以有延时,数量只有1个
uvm_blocking_get_port #(apb_transfer) reg_bg_port;
//和monitor通信的端口,用get_peek不修改原来的item,数量4个分别连接到4个node!
uvm_blocking_get_peek_port #(mon_data_t) in_bgpk_ports[4];
//uvm_tlm_analysis_fifo是搭配有ap的tlm fifo,继承于uvm_tlm_fifo
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);//new函数例化所有port和fifo
reg_bg_port = new("reg_bg_port", this);//学习这种foreach的命名方法
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);//config机制在这里get了rgm配置
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//run_phase并行执行4个通道的打包线程
do_packet(0);
do_packet(1);
do_packet(2);
do_packet(3);
join
endtask
task do_packet(int ch);
fmt_trans ot;
mon_data_t it;
forever begin
//通过和monitor通信的端口peek了monitor的句柄
this.in_bgpk_ports[ch].peek(it);
ot = new();//例化fmt中传输的包的句柄(rgm要访问)
//mcdf_rgm定义的两个获取len和id函数在这里调用,赋值给包的对应信号
ot.length = rgm.get_reg_field_length(ch);
ot.ch_id = rgm.get_reg_field_id(ch);
ot.data = new[ot.length+3];//payload数量=len+3,动态数组分配空间
//每个payload都是用data[?]表示
foreach(ot.data[m]) begin//m不用定义,就是平时用的i
if(m == 0) begin//第一个payload是{8位id,8位len,16位0}
ot.data[m] = (ot.ch_id<<24) + (ot.length<<16);//这就是第一个payload
ot.parity = ot.data[m];//没data,直接把奇偶校验赋值成payload
end //注意这里是阻塞赋值
else if(m == ot.data.size()-1) begin
ot.data[m] = ot.parity;//最后一个payload存放32位parity
end
else begin//中间的payload通过port获得monitor的句柄it然后
this.in_bgpk_ports[ch].get(it);
ot.data[m] = it.data;//赋值给data
//按位异或后赋值,ot.parity = ot.parity^it.data
ot.parity ^= it.data;
end
end
this.out_tlm_fifos[ch].put(ot);//完成整形后再把句柄放到fifo中
end
endtask
endclass: mcdf_refmod
//—————————————————————————mcdf_checker———————————————————————————
class mcdf_checker extends uvm_scoreboard;
//checker中的几个变量,error计数,总计数,channel的计数,具体记什么?
local int err_count;//全部是local!只在checker中使用
local int total_count;
local int chnl_count[4];
local virtual chnl_intf chnl_vifs[4]; //4个chnl的接口
local virtual mcdf_intf mcdf_vif;//1个mcdf的接口
local mcdf_refmod refmod;//参考模型声明
mcdf_rgm rgm;//同样声明rgm
uvm_tlm_analysis_fifo #(mon_data_t) chnl_tlm_fifos[4];
uvm_tlm_analysis_fifo #(fmt_trans) fmt_tlm_fifo;
uvm_tlm_analysis_fifo #(apb_transfer) reg_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);//new函数对成员变量初始化,例化fifo和port
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);//fifo的new有两个参数
reg_tlm_fifo = new("reg_tlm_fifo", this);//port的例化也是
foreach(exp_bg_ports[i]) exp_bg_ports[i] = new($sformatf("exp_bg_ports[%0d]", i), this);
endfunction
//build_phase拿config和接口,还有rgm!
function void build_phase(uvm_phase phase);
super.build_phase(phase);
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
//connect_phase将refmod和checker的端口和port进行连接
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);
refmod.reg_bg_port.connect(reg_tlm_fifo.blocking_get_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);//通过tlm_fifo调用get,参数为item的句柄
ch_idx = this.get_chnl_index(mont.ch_id);//拿到通道索引值
this.exp_bg_ports[ch_idx].get(expt);//索引值对应的exp_bg_port拿到期望包的句柄
cmp = mont.compare(expt); //将两个句柄进行比较,相同拉高cmp
this.total_count++;//总数加1
this.chnl_count[ch_idx]++;//对应通道计数加1
if(cmp == 0) begin//两个句柄不一致error计数加1
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
//检查rgm寄存器的域的id,是不是等于fmt的item中的ch_id
function int get_chnl_index(int ch_id);
int fd_id;
for(int i=0; i<4; i++) begin
fd_id = rgm.get_reg_field_id(i);
if(fd_id == ch_id)//ch_id是8位
return i;
end
`uvm_error("CHIDERR", $sformatf("unrecognized channel ID and could not find the corresponding channel index", ch_id))
return -1;//不一样返回-1
endfunction
task do_channel_disable_check(int id);
forever begin//clk要带this和接口,且在复位信号拉高和channel使能信号为低,为什么?
@(posedge this.mcdf_vif.clk iff (this.mcdf_vif.rstn && this.mcdf_vif.mon_ck.chnl_en[id]===0));
//如果valid为高而wait为低,则报错
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
//checker中第一次出现了report_phase
function void report_phase(uvm_phase phase);
string s;//声明字符串
super.report_phase(phase);//调用父类report_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)}; //打印error计数
foreach(this.chnl_tlm_fifos[i]) begin//chnl和fmt的fifo如果不空就打印
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
//—————————————————————————mcdf_virtual_sequencer———————————————————————————
class mcdf_virtual_sequencer extends uvm_sequencer #(uvm_sequence_item);
//声明所有sequencer
apb_master_sequencer reg_sqr;//apb声明的是master的sequencer
fmt_sequencer fmt_sqr;//fmt的sequencer
chnl_sequencer chnl_sqrs[4];//channel的sequencer
mcdf_rgm rgm;//寄存器模型
virtual mcdf_intf mcdf_vif;//接口
`uvm_component_utils(mcdf_virtual_sequencer)//注册
function new (string name = "mcdf_virtual_sequencer", uvm_component parent);
super.new(name, parent);
endfunction
//rgm做接口和rgm的配置
function void build_phase(uvm_phase phase);
super.build_phase(phase);
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
if(!uvm_config_db#(mcdf_rgm)::get(this,"","rgm", rgm)) begin
`uvm_fatal("GETRGM","cannot get RGM handle from config DB")
end
endfunction
endclass
//—————————————————————————mcdf_env———————————————————————————
// MCDF顶层环境env
class mcdf_env extends uvm_env;
//包括chnl、apb、fmt的agent,checker,v-sequencer,rgm,adapter,predictor
chnl_agent chnl_agts[4];
apb_master_agent reg_agt;
fmt_agent fmt_agt;
mcdf_checker chker;
mcdf_virtual_sequencer virt_sqr;
mcdf_rgm rgm;
reg2mcdf_adapter adapter;
uvm_reg_predictor #(apb_transfer) predictor;
`uvm_component_utils(mcdf_env)
function new (string name = "mcdf_env", uvm_component parent);
super.new(name, parent);
endfunction
//build_phase例化上述所有单位
function void build_phase(uvm_phase phase);
super.build_phase(phase);
this.chker = mcdf_checker::type_id::create("chker", this);
foreach(chnl_agts[i]) begin
this.chnl_agts[i] = chnl_agent::type_id::create($sformatf("chnl_agts[%0d]",i), this);
end
this.reg_agt = apb_master_agent::type_id::create("reg_agt", this);
this.fmt_agt = fmt_agent::type_id::create("fmt_agt", this);
virt_sqr = mcdf_virtual_sequencer::type_id::create("virt_sqr", this);
//rgm例化后还要调用build和set配置
rgm = mcdf_rgm::type_id::create("rgm", this);
rgm.build();//rgm还要调用build函数,例化配置所有寄存器,并添加到map
uvm_config_db#(mcdf_rgm)::set(this,"*","rgm", rgm);//在顶层set,在组件get
adapter = reg2mcdf_adapter::type_id::create("adapter", this);
predictor = uvm_reg_predictor#(apb_transfer)::type_id::create("predictor", this);//带参数类型例化
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
foreach(chnl_agts[i]) chnl_agts[i].monitor.mon_ana_port.connect(chker.chnl_tlm_fifos[i].analysis_export);
reg_agt.monitor.item_collected_port.connect(chker.reg_tlm_fifo.analysis_export);
fmt_agt.monitor.mon_ana_port.connect(chker.fmt_tlm_fifo.analysis_export);
virt_sqr.reg_sqr = reg_agt.sequencer;
virt_sqr.fmt_sqr = fmt_agt.sequencer;
foreach(virt_sqr.chnl_sqrs[i]) virt_sqr.chnl_sqrs[i] = chnl_agts[i].sequencer;
rgm.map.set_sequencer(reg_agt.sequencer, adapter);
reg_agt.monitor.item_collected_port.connect(predictor.bus_in);
predictor.map = rgm.map;//predictor书里没有过多介绍啊
predictor.adapter = adapter;
endfunction
endclass: mcdf_env
//—————————————————————————mcdf_base_sequence和test———————————————————————————
//base seq只做seq的框架,具体的等子seq去写
class mcdf_base_virtual_sequence extends uvm_sequence #(uvm_sequence_item);
chnl_data_sequence chnl_data_seq;//包含两个组件的seq
fmt_config_sequence fmt_config_seq;
mcdf_rgm rgm;
`uvm_object_utils(mcdf_base_virtual_sequence)
//把m_sequencer 转换成my_sequencer,相当于my_sequencer p_sequencer
`uvm_declare_p_sequencer(mcdf_virtual_sequencer)
function new (string name = "mcdf_base_virtual_sequence");
super.new(name);//new函数咩都无
endfunction
virtual task body();
`uvm_info(get_type_name(), "=====================STARTED=====================", UVM_LOW)
rgm = p_sequencer.rgm;//为什么要把p_sequencer的rgm给到这里
this.do_reg();
this.do_formatter();
this.do_data();
`uvm_info(get_type_name(), "=====================FINISHED=====================", UVM_LOW)
endtask
virtual task do_reg();//做寄存器配置
endtask
//从外部对fmt下行端做配置
virtual task do_formatter();
endtask
//4个node的数据转换
virtual task do_data();
endtask
function bit diff_value(int val1, int val2, string id = "value_compare");
if(val1 != val2) begin
`uvm_error("[CMPERR]", $sformatf("ERROR! %s val1 %8x != val2 %8x", id, val1, val2))
return 0;
end
else begin
`uvm_info("[CMPSUC]", $sformatf("SUCCESS! %s val1 %8x == val2 %8x", id, val1, val2), UVM_LOW)
return 1;
end
endfunction
task wait_cycles(int n);
repeat(n) @(posedge p_sequencer.mcdf_vif.clk);
endtask
endclass
//base test例化env,在end_of_elaboration_phase做设置
//run_phase运行空的顶层seq,等具体子seq继承
class mcdf_base_test extends uvm_test;
mcdf_env env;
`uvm_component_utils(mcdf_base_test)
function new(string name = "mcdf_base_test", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = mcdf_env::type_id::create("env", this);
endfunction
//phase机制第一次见到end_of_elaboration_phase
function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
//uvm_root验证平台中所有omp的隐含top-level和phase控制器。
//get()函数用于返回uvm_root的句柄。
uvm_root::get().set_report_verbosity_level_hier(UVM_HIGH);
//冗余度阈值的设置可以放在build_phase之后run_phase之前的任意phase;
uvm_root::get().set_report_max_quit_count(1);
//出现1次error就结束仿真
uvm_root::get().set_timeout(10ms);
//超时10ms就结束仿真
endfunction
//run_phase做举手落手,中间运行顶层seq即可
task run_phase(uvm_phase phase);
phase.raise_objection(this);
this.run_top_virtual_sequence();
phase.drop_objection(this);
endtask
virtual task run_top_virtual_sequence();
endtask
endclass: mcdf_base_test
//———————————mcdf_data_consistence_basic_virtual_sequence—————————————
//在sequence中使用寄存器模型, 通常通过p_sequencer的形式引用。
class mcdf_data_consistence_basic_virtual_sequence extends mcdf_base_virtual_sequence;
//注册和new函数少不了
`uvm_object_utils(mcdf_data_consistence_basic_virtual_sequence)
function new (string name = "mcdf_data_consistence_basic_virtual_sequence");
super.new(name);
endfunction
//补充具体的任务内容了
task do_reg();//做寄存器配置的任务
bit[31:0] wr_val, rd_val;//读写值的两个临时变量
uvm_status_e status;
//复位寄存器
@(negedge p_sequencer.mcdf_vif.rstn);
rgm.reset();//所有接口前面都多了p_sequencer
@(posedge p_sequencer.mcdf_vif.rstn);
this.wait_cycles(10);
// slv3 with len=64, en=1
// slv2 with len=32, en=1
// slv1 with len=16, en=1
// slv0 with len=8, en=1
//slv_en是4位
wr_val = ('b1<<3) + ('b1<<2) + ('b1<<1) + 1;
//看rgm那个链接
//uvm_status_e是一个输出, 用于表明写操作是否成功。
//先对slv_en
rgm.slv_en.write(status, wr_val);
rgm.slv_en.read(status, rd_val);//在refmod读寄存器
void'(this.diff_value(wr_val, rd_val, "SLV_EN_REG"));
//然后对slv_len
wr_val = (63<<24) + (31<<16) + (15<<8) + 7;
rgm.slv_len.write(status, wr_val);
rgm.slv_len.read(status, rd_val);
//写进去读出来做对比
void'(this.diff_value(wr_val, rd_val, "SLV_LEN_REG"));
endtask
task do_formatter();
`uvm_do_on_with(fmt_config_seq, p_sequencer.fmt_sqr, {fifo == LONG_FIFO; bandwidth == HIGH_WIDTH;})
endtask
task do_data();
fork
//参数:sequence或者item对应do,sequencer对应on,约束{}对应with
`uvm_do_on_with(chnl_data_seq, p_sequencer.chnl_sqrs[0], {ntrans==100; ch_id==0; data_nidles==0; pkt_nidles==1; data_size==64;})
`uvm_do_on_with(chnl_data_seq, p_sequencer.chnl_sqrs[1], {ntrans==100; ch_id==1; data_nidles==1; pkt_nidles==4; data_size==64;})
`uvm_do_on_with(chnl_data_seq, p_sequencer.chnl_sqrs[2], {ntrans==100; ch_id==2; data_nidles==2; pkt_nidles==8; data_size==64;})
`uvm_do_on_with(chnl_data_seq, p_sequencer.chnl_sqrs[3], {ntrans==100; ch_id==3; data_nidles==1; pkt_nidles==2; data_size==64;})
join
#10us; // wait until all data haven been transfered through MCDF
endtask
endclass: mcdf_data_consistence_basic_virtual_sequence
//———————————mcdf_data_consistence_basic_test—————————————
//对应的test只需要在任务中例化sequence并start即可,参数是env.virt_sqr
class mcdf_data_consistence_basic_test extends mcdf_base_test;
`uvm_component_utils(mcdf_data_consistence_basic_test)
function new(string name = "mcdf_data_consistence_basic_test", uvm_component parent);
super.new(name, parent);
endfunction
task run_top_virtual_sequence();
mcdf_data_consistence_basic_virtual_sequence top_seq = new();
top_seq.start(env.virt_sqr);
endtask
endclass: mcdf_data_consistence_basic_test
关于set_timeout
UVM中超时退出set_timeout函数_Alfred.HOO的博客-CSDN博客_uvmtimeout
最后
以上就是危机宝贝为你收集整理的电力电子转战数字IC——路科MCDF全览(持续更新)硬件RTL部分接口打包信号软件部分顶层的mcdf_rgm类mcdf_pkg的全部内容,希望文章能够帮你解决电力电子转战数字IC——路科MCDF全览(持续更新)硬件RTL部分接口打包信号软件部分顶层的mcdf_rgm类mcdf_pkg所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复