经过两次面试后,对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 uplinkdata_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 driverinput  [31:0]                        data_i         ; // input                                data_p_i       ; // input                                valid_i        ; // input                                slv_en_i       ; // output                               wait_o         ; //output                               parity_err_o   ; //// IO with Arbiteroutput  [31:0]                       data_o         ; // output  [ 5:0]                       freeslot_o     ; // output                               valid_o        ; // output                               fetch_i        ;// IO with registerinput                                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_Errif (!rst_n_i) beginparity_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;//不空则validendmodule

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)
beginif (!rst_n_i) begin//复位信号为0,指针为0,freeslot剩余空间为深度32freeslot_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 ) beginfreeslot_r <= freeslot_r+1 ; end //只读的话,剩余空间增加if (~rd_i && wr_i ) beginif (~full_s) begin//只写,剩余空间-1freeslot_r <= freeslot_r-1 ;end else begin//如果满了,overflow就会拉高overflow_r <= 1'b1;endendif (wr_i) beginmem[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)
begincase ({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
endassign win_vec_o = win_vec_r;always @(posedge clk_i or negedge rst_n_i)
beginif (!rst_n_i) begin//复位值cp_vec_r  <= 4'b1000;win_vec_r <= 4'b0000;end else beginif (trigger_i) begincp_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;endend
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)
beginif (!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 @(*) begincase (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) begincur_st <= st_ACC ;end else begincur_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)
beginif (!rst_n_i) beginaddr_r <= 0 ;end else beginif (is_st_setup_s)  beginaddr_r <= paddr_i ;end end
end //定义了一个32位的数据存储data_rd_r
//在ACC状态时,读操作要write信号为低~pwr_i
//if条件中的12个信号分别表示用到哪个寄存器,哪个就为1,具体看后面代码
always @(*) begindata_rd_r <= 0; if (is_st_acc_s) beginif (~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_PROCif (!rst_n_i)beginctrl_mem[0] <= 32'h00000000; // slv_enctrl_mem[1] <= 32'h00000000; // parity_err_clrctrl_mem[2] <= 32'h03020100; // slave IDctrl_mem[3] <= 32'h00000000; // lengthend else begin
//ACC状态执行写操作,对slv_en和parity_err寄存器来说只有前4位,对slv_id和slv_len来说就有32位全写,所以pwdata分别写入这些位置
//pwdata包含了if (is_st_acc_s & pwr_i) beginif (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_PROCif (!rst_n_i)beginro_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 beginro_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)
beginif (!rst_n_i) cur_st <= ST_RST    ; else          cur_st <= nxt_st    ;
end
always @(*) beginnxt_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 @(*)
begintx_d_s   <= 0;if (is_st_header_s) begintx_d_s   <= {id_win_s, len_win_s,16'h0000};end if (is_st_payload_s) begintx_d_s   <= data_win_s ;end if (is_st_parity_s) begintx_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)
beginif (~rst_n_i) beginparity_r <= 0 ;len_cnt_r <= 0 ;end else beginif (send_header_s) beginparity_r  <= {id_win_s, len_win_s,16'h0000};len_cnt_r <= len_win_s ;end if (send_payload_s) beginparity_r <= data_win_s ^ parity_r ; len_cnt_r<= len_cnt_r - 1;//第一个payload的len是[0],这里要减1end 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_SVinterface 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 flagsbit                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的时钟块,所有信号都是inputclocking 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_controlif(has_coverage) beginautomatic cg_apb_command cg0 = new();automatic cg_apb_trans_timing_group cg1 = new();automatic cg_apb_write_read_order_group cg2 = new();endend//属性和断言的编写,描述具体的感兴趣的序列,首先是断言属性
//第一个是希望总线过来的地址不存在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_xassert 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_riseassert 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_fallassert 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_phaseassert 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_transassert 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_transassert 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_roseassert 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_transcover 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_transcover 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_transcover 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_transcover 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_transcover 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_transcover 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_transcover property(p_read_write_read_burst_trans);//控制断言的开关initial begin: assertion_controlforkforever beginwait(rstn == 0);$assertoff();wait(rstn == 1);if(has_checks) $asserton();endjoin_noneendendinterface : 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;endclockingclocking mon_ck @(posedge clk);//给到monitor的信号全部为inputdefault 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;endclockingclocking 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_iclocking mon_ck @(posedge clk);default input #1ps output #1ps;input chnl_en;endclocking//产生clkinitial begin clk <= 0;forever begin#5 clk <= !clk;endend//复位信号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 signalsassign 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_testrun_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 probeassign 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 probeinitial 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类,只有namefunction new (string name = "chnl_trans");super.new(name);endfunctionendclass: 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
//由于有接口,要将接口连接到driverfunction void set_interface(virtual chnl_intf intf);if(intf == null)$error("interface handle is NULL, please check if target interface has been intantiated");elsethis.intf = intf;endfunction
//uvm_component的phase机制,只有run_phase是耗时的任务
//driver的run_phase同时执行了驱动和复位两个线程task run_phase(uvm_phase phase);forkthis.do_drive();this.do_reset();joinendtasktask do_reset();forever begin//intf是这里定义的chnl_intf接口名,对应tb中的接口@(negedge intf.rstn);intf.ch_valid <= 0;//这三个是输出的三个信号。不空就validintf.ch_data <= 0;intf.ch_data_p <= 0;endendtask
//驱动包的任务,声明两个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给sequencetask do_drive();chnl_trans req, rsp;@(posedge intf.rstn);forever beginseq_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);endendtask//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_porttask 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();endrepeat(t.pkt_nidles) chnl_idle();endtasktask chnl_idle();@(posedge intf.clk);intf.drv_ck.ch_valid <= 0;intf.drv_ck.ch_data <= 0;intf.drv_ck.ch_data_p <= 0;endtaskfunction get_parity(bit[31:0] data);return ^data;//对32位数据缩位异或可得奇偶校验位endfunctionendclass: 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);endfunctionendclass: 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);endfunctiontask body();repeat(ntrans) send_trans();endtasktask 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);endtaskfunction 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)endfunctionendclass: chnl_data_sequencetypedef struct packed {//packed表示合并,结构体可以存放不同数据类型的变量
//typedef定义新的类型,32位的data和2位id合起来称为mon_data_tbit[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);endfunctionfunction void set_interface(virtual chnl_intf intf);if(intf == null)//配置接口$error("interface handle is NULL, please check if target interface has been intantiated");elsethis.intf = intf;endfunctiontask run_phase(uvm_phase phase);this.mon_trans();//comp的子类,phase机制endtasktask 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)endendtaskendclass: 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);endfunctionfunction 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")enddriver = chnl_driver::type_id::create("driver", this);monitor = chnl_monitor::type_id::create("monitor", this);sequencer = chnl_sequencer::type_id::create("sequencer", this);endfunctionfunction void connect_phase(uvm_phase phase);super.connect_phase(phase);driver.seq_item_port.connect(sequencer.seq_item_export);this.set_interface(vif);endfunctionfunction void set_interface(virtual chnl_intf vif);driver.set_interface(vif);monitor.set_interface(vif);endfunctionendclass: chnl_agentendpackage

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{//默认为mediumsoft 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_endfunction new (string name = "fmt_trans");super.new(name);endfunctionendclass//driverclass 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;//带宽初始化endfunctionfunction void set_interface(virtual fmt_intf intf);if(intf == null)$error("interface handle is NULL, please check if target interface has been intantiated");elsethis.intf = intf;endfunctiontask run_phase(uvm_phase phase);forkthis.do_receive();this.do_consume();this.do_config();this.do_reset();joinendtasktask do_config();fmt_trans req, rsp;forever begin//从seq拿到itemseq_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//把得到的深度作为参数例化给当前类的邮箱fifothis.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);endendtasktask do_reset();forever begin@(negedge intf.rstn) intf.fmt_ready <= 0;//根据接口,output只有一个fmt_readyendendtasktask do_receive();//fmt接收来自node的data,就是存进fifoforever begin@(intf.drv_ck); #10ps;if(intf.fmt_valid === 1'b1) begin//实验0里面的grant信号forever beginif((this.fifo_bound-this.fifo.num()) >= 1)//why?break;//如果fifo容量和实际存储的数量的差大于等于1就停止@(intf.drv_ck); #10ps;endthis.fifo.put(intf.fmt_data);#1ps; intf.fmt_ready <= 1;end//接收完数据后拉高readyelse begin#1ps; intf.fmt_ready <= 0;endendendtasktask do_consume();//消化数据需要一定时间,但是为什么用随机范围?bit[31:0] data;forever beginvoid'(this.fifo.try_get(data));repeat($urandom_range(1, this.data_consum_peroid)) @(posedge intf.clk);endendtaskendclass: 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);endfunctionendclass: 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);endfunctiontask body();//是body不是run_phasesend_trans();endtasktask 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);endtaskfunction 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)endfunctionendclass: fmt_config_sequence// formatter monitorclass 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);endfunctionfunction void set_interface(virtual fmt_intf intf);if(intf == null)$error("interface handle is NULL, please check if target interface has been intantiated");elsethis.intf = intf;endfunctiontask 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]) beginm.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)endendtaskendclass: 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);endfunctionfunction 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")enddriver = 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);endfunctionfunction void set_interface(virtual fmt_intf vif);driver.set_interface(vif);monitor.set_interface(vif);endfunctionendclassendpackage

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_regclass 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,其他位是预留位reservedrand 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)) beginvalue_cg = new();//覆盖组必须例化才会收集endendfunction//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函数endfunctionfunction void sample_values();super.sample_values();//也调用父类sample_values函数//new函数set了,这里如果get就调用覆盖组的sampleif (get_coverage(UVM_CVR_FIELD_VALS)) beginvalue_cg.sample();endendfunctionendclassclass 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];endgroupfunction 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)) beginvalue_cg = new();endendfunctionvirtual 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);endfunctionfunction 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(); endfunctionfunction void sample_values();super.sample_values();if (get_coverage(UVM_CVR_FIELD_VALS)) beginvalue_cg.sample();endendfunctionendclass

顶层的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))endcasereturn fd;endfunctionfunction 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))endcasereturn fd;endfunctionendclass

apb_pkg

.sv与.svh以及`ifndef `else `endif - 知乎

SystemVerilog与Verilog中重定义问题解决方案 - 知乎

7-字符串与$sformatf,$sformat,$psprintf - _见贤_思齐 - 博客园

//——————————————————————————apb_pkg————————————————————————————
//这两句话表示:如果没有定义过就定义,定义过了就不会执行
//由于多个文件都会调用到相同的文件,这样做避免头文件的重复编译
`ifndef APB_PKG_SV
`define APB_PKG_SVpackage 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 : newendclass : 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_endextern 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 beginseq_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_ifvif.cb_mst.penable <= 1;#10ps;wait(vif.pready === 1);//ready为1时才继续往下进行#1ps;//如果pslverr拉高报错,状态更改为error,根据严重程度进行打印if(vif.pslverr === 1) begint.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!")endelse begint.trans_status = OK;endrepeat(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) begint.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!")endelse begint.trans_status = OK;endt.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)forkforever begin@(negedge vif.rstn); // ASYNC resetvif.paddr <= 0;vif.pwrite <= 0;vif.psel <= 0;vif.penable <= 0;vif.pwdata <= 0;endjoin_none
endtask`endif
//——————————————————————apb_master_monitor——————————————————————————————
//和driver一样,也是svh和sv文件`ifndef APB_MASTER_MONITOR_SVH
`define APB_MASTER_MONITOR_SVHclass 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_endextern 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_transextern 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();forkmonitor_transactions();join_none
endtasktask apb_master_monitor::monitor_transactions();forever begin//从接口拿到item(transfer)collect_transfer();//检查itemif (checks_enable)perform_transfer_checks();// Update coverageif (coverage_enable)perform_transfer_coverage();//ap一对多发给接收端item_collected_port.write(trans_collected);end
endtasktask 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;enddefault : `uvm_error(get_type_name(), "ERROR pwrite signal value")endcase
endtask: collect_transfer function void apb_master_monitor::perform_transfer_checks();
endfunction : perform_transfer_checksfunction 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_SVHclass apb_master_sequencer extends uvm_sequencer #(apb_transfer);apb_config cfg;`uvm_component_utils_begin(apb_master_sequencer)`uvm_component_utils_endextern 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_SVfunction 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 : newendclass : 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 : newvirtual 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: bodyendclass: 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 : newvirtual 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: bodyendclass: 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 : newvirtual 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: bodyendclass: 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 : newvirtual 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 : newvirtual 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_phaseextern 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_SVfunction apb_master_agent::new(string name, uvm_component parent);super.new(name, parent);
endfunction : newfunction void apb_master_agent::build();super.build();//由于有config,这里要多个get configif( !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//例化monitormonitor = apb_master_monitor::type_id::create("monitor",this);monitor.cfg = cfg;//active时才例化和配置driver和sequencerif(cfg.is_active == UVM_ACTIVE) beginsequencer = 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) begindriver.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) beginsequencer.vif = vif; driver.vif = vif; end
endfunction : assign_vi`endif
//—————————————————————————apb_config———————————————————————————
//config机制中config object传递的应用
//整合每个组件中的变量放到这个apb_config中,对中心化的配置对象进行传递
//有利于整体环境的维护
`ifndef APB_CONFIG_SV
`define APB_CONFIG_SVclass apb_config extends uvm_object;//设置agent是active还是passiveuvm_active_passive_enum  is_active = UVM_ACTIVE;
//既是声明也是初始化,master_pslverr_status_severity的初始化严重级别为warninguvm_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 : newvirtual function get_pready_additional_cycles();if(slave_pready_random)//return $urandom_range(0, 2);else//从端准备好随机化了就返回ready的间隔在0-2之间随机return 0;endfunctionvirtual function get_pslverr_status();if(slave_pslverr_random && $urandom_range(0, 20) == 0)return 1;else //总的来说这个函数的作用就是error信号随机拉高return 0;endfunctionendclass`endif

apb_test

注意apb_pkg不包含test,test包含了apb_pkg

//——————————————————————apb_test——————————————————————————————`ifndef APB_TESTS_SV
`define APB_TESTS_SVimport apb_pkg::*;//import了apb_pkg所有的东西,就是上面所见的
//test包含了顶层env和各个test
//env做了:声明+注册+new函数+build_phase做例化
class apb_env extends uvm_env;
//env例化了两个agentapb_master_agent mst;apb_slave_agent slv;`uvm_component_utils(apb_env)function new(string name, uvm_component parent);super.new(name, parent);endfunctionfunction 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了cfgenv = apb_env::type_id::create("env", this);//base test例化envendfunction
endclass//base seq,注意sequence是参数类
class apb_base_test_sequence extends uvm_sequence #(apb_transfer);
//先声明个32*32的membit[31:0] mem[bit[31:0]];//经查和问,这是个32*32二维数组,忽视bit即可//注册`uvm_object_utils(apb_base_test_sequence)//new函数,seq继承于item继承于trans继承于objectfunction 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)) beginif(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;endelse begin`uvm_info("CMPDATA", $sformatf("addr 32'h%8x, READ DATA 32'h%8x comparing success!", addr, data), UVM_LOW)return 1;//比较结果正确返回1,否则返回0endendelse 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;endendendfunction: 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 : newtask 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) beginaddr = 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写进memend//读1个的测试`uvm_info(get_type_name(), "TEST continous read transaction...", UVM_LOW)repeat(test_num) beginaddr = 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) beginaddr = 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) beginaddr = 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) beginaddr = 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));endthis.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_phasetask 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_testclass 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 : newtask body();bit[31:0] addr;this.wait_reset_release();this.wait_cycles(10);//连续写repeat(test_num) beginaddr = 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全部写进memmem[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]) beginvoid'(this.check_mem_data(addr+(i<<2), burst_write_seq.data[i]));endendthis.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);endfunctiontask 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_SVHclass 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_endextern function new (string name, uvm_component parent);extern virtual task run();virtual apb_if vif;extern virtual protected task get_and_drive();
//区别3:驱动的不是item而是responseextern 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_SVfunction 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 : runtask apb_slave_driver::get_and_drive();forever begin//少了驱动item和设置rsp的trans的idseq_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_drivetask 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) begincase(vif.cb_slv.pwrite)1'b1    : this.do_write();1'b0    : this.do_read();default : `uvm_error(get_type_name(), "ERROR pwrite signal value")endcaseendelse beginthis.do_idle();endend
endtask : drive_responsetask 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_idletask apb_slave_driver::do_write();//和master端完全不同bit[31:0] addr;//参数没有item,声明临时变量,配合seqbit[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;//数据给到memif(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;forkbegin@(vif.cb_slv);//写完了,ready和error分别置为默认值和0vif.cb_slv.pready <= cfg.slave_pready_default_value;vif.cb_slv.pslverr <= 0;endjoin_none
endtask: do_writetask 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拉高写入addraddr = vif.cb_slv.paddr;if(mem.exists(addr))//检查addr在mem中是否存在数据data = mem[addr];//是就把mem中的data读出来elsedata = DEFAULT_READ_VALUE;//在pkg一开始定义了,parameter bit[31:0] DEFAULT_READ_VALUE = 32'hFFFF_FFFFif(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写到接口上的prdataforkbegin@(vif.cb_slv);//读完了,ready和error分别置为默认值和0vif.cb_slv.pready <= cfg.slave_pready_default_value;vif.cb_slv.pslverr <= 0;endjoin_none
endtask: do_readtask apb_slave_driver::reset_listener();`uvm_info(get_type_name(), "reset_listener ...", UVM_HIGH)forkforever begin@(negedge vif.rstn); //rstn下降沿vif.prdata <= 0;vif.pslverr <= 0;//ready置为默认值vif.pready <= cfg.slave_pready_default_value;this.mem.delete(); //清空memendjoin_none
endtask: reset_listener`endif//—————————————————————————apb_slave_monitor———————————————————————————
//monitor从框架上看比master多了个build_phase`ifndef APB_SLAVE_MONITOR_SVH
`define APB_SLAVE_MONITOR_SVHclass 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_transprotected 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_SVfunction 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();forkmonitor_transactions();join_none
endtasktask apb_slave_monitor::monitor_transactions();forever begin//从接口获取itemcollect_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_checksfunction 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)endtaskendclass : 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注意是继承于compclass 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_fifouvm_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和fiforeg_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);endfunctionfunction 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")endendfunctiontask run_phase(uvm_phase phase);fork//run_phase并行执行4个通道的打包线程do_packet(0);do_packet(1);do_packet(2);do_packet(3);joinendtasktask 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不用定义,就是平时用的iif(m == 0) begin//第一个payload是{8位id,8位len,16位0}ot.data[m] = (ot.ch_id<<24) + (ot.length<<16);//这就是第一个payloadot.parity = ot.data[m];//没data,直接把奇偶校验赋值成payloadend //注意这里是阻塞赋值else if(m == ot.data.size()-1) beginot.data[m] = ot.parity;//最后一个payload存放32位parityendelse begin//中间的payload通过port获得monitor的句柄it然后this.in_bgpk_ports[ch].get(it);ot.data[m] = it.data;//赋值给data//按位异或后赋值,ot.parity = ot.parity^it.dataot.parity ^= it.data;endendthis.out_tlm_fifos[ch].put(ot);//完成整形后再把句柄放到fifo中endendtaskendclass: 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;//同样声明rgmuvm_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和portthis.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")endforeach(chnl_vifs[i]) beginif(!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")endendif(!uvm_config_db#(mcdf_rgm)::get(this,"","rgm", rgm)) begin`uvm_fatal("GETRGM","cannot get RGM handle from config DB")endthis.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]) beginexp_bg_ports[i].connect(refmod.out_tlm_fifos[i].blocking_get_export);endendfunctiontask run_phase(uvm_phase phase);forkthis.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();joinendtasktask do_data_compare();fmt_trans expt, mont;bit cmp;//两个临时变量int ch_idx;//通道索引forever beginthis.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);   //将两个句柄进行比较,相同拉高cmpthis.total_count++;//总数加1this.chnl_count[ch_idx]++;//对应通道计数加1if(cmp == 0) begin//两个句柄不一致error计数加1this.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))endelse 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)endendendtask//检查rgm寄存器的域的id,是不是等于fmt的item中的ch_idfunction int get_chnl_index(int ch_id);int fd_id;for(int i=0; i<4; i++) beginfd_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;//不一样返回-1endfunctiontask 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") endendtask//checker中第一次出现了report_phasefunction void report_phase(uvm_phase phase);string s;//声明字符串super.report_phase(phase);//调用父类report_phases = "\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())}; endif(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)endfunctionendclass: mcdf_checker//—————————————————————————mcdf_virtual_sequencer———————————————————————————class mcdf_virtual_sequencer extends uvm_sequencer #(uvm_sequence_item);//声明所有sequencerapb_master_sequencer reg_sqr;//apb声明的是master的sequencerfmt_sequencer fmt_sqr;//fmt的sequencerchnl_sequencer chnl_sqrs[4];//channel的sequencermcdf_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")endif(!uvm_config_db#(mcdf_rgm)::get(this,"","rgm", rgm)) begin`uvm_fatal("GETRGM","cannot get RGM handle from config DB")endendfunctionendclass//—————————————————————————mcdf_env———————————————————————————// MCDF顶层环境envclass mcdf_env extends uvm_env;
//包括chnl、apb、fmt的agent,checker,v-sequencer,rgm,adapter,predictorchnl_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]) beginthis.chnl_agts[i] = chnl_agent::type_id::create($sformatf("chnl_agts[%0d]",i), this);endthis.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函数,例化配置所有寄存器,并添加到mapuvm_config_db#(mcdf_rgm)::set(this,"*","rgm", rgm);//在顶层set,在组件getadapter = reg2mcdf_adapter::type_id::create("adapter", this);predictor = uvm_reg_predictor#(apb_transfer)::type_id::create("predictor", this);//带参数类型例化endfunctionfunction 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;endfunctionendclass: 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;//包含两个组件的seqfmt_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函数咩都无endfunctionvirtual 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)endtaskvirtual task do_reg();//做寄存器配置endtask//从外部对fmt下行端做配置virtual task do_formatter();endtask//4个node的数据转换virtual task do_data();endtaskfunction 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;endelse begin`uvm_info("[CMPSUC]", $sformatf("SUCCESS! %s val1 %8x == val2 %8x", id, val1, val2), UVM_LOW)return 1;endendfunctiontask wait_cycles(int n);repeat(n) @(posedge p_sequencer.mcdf_vif.clk);endtaskendclass//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);endfunctionfunction void build_phase(uvm_phase phase);super.build_phase(phase);env = mcdf_env::type_id::create("env", this);endfunction//phase机制第一次见到end_of_elaboration_phasefunction 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);endtaskvirtual task run_top_virtual_sequence();endtaskendclass: 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_enrgm.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_lenwr_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"));endtasktask do_formatter();`uvm_do_on_with(fmt_config_seq, p_sequencer.fmt_sqr, {fifo == LONG_FIFO; bandwidth == HIGH_WIDTH;})endtasktask 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 MCDFendtaskendclass: mcdf_data_consistence_basic_virtual_sequence//———————————mcdf_data_consistence_basic_test—————————————
//对应的test只需要在任务中例化sequence并start即可,参数是env.virt_sqrclass 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);endfunctiontask run_top_virtual_sequence();mcdf_data_consistence_basic_virtual_sequence top_seq = new();top_seq.start(env.virt_sqr);endtaskendclass: mcdf_data_consistence_basic_test

关于set_timeout

UVM中超时退出set_timeout函数_Alfred.HOO的博客-CSDN博客_uvmtimeout

电力电子转战数字IC——路科MCDF全览(持续更新)相关推荐

  1. 电力电子转战数字IC20220629day35——路科实验2b

    目录 tb3代码改造 tb3代码 tb4改造思路: tb4结构示意图 tb4代码 basic_test burst_test full_test 首先复习一下类的知识点 CSDNhttps://mp. ...

  2. 电力电子转战数字IC——我的IC笔试(2022.10.14更新)

    IC笔试有:JL科技.TR半导体.HZW.MX半导体.RSKX.TCL 部分题目暂时还是做不出来,先好好复习一遍,会有柳暗花明的时候的. 目录 RY10.11 TCL10.9 位宽定义正确的是 逻辑与 ...

  3. 电力电子转战数字IC——我的IC面试(2022.10.14更新)

    目录 感谢信 HKWS10.14面试 25mins JXC10.13面试 30mins JDSK9.23面试 42mins 快速的自我介绍 介绍一下这个MCDF的项目 你这里写SV搭建的验证环境,和U ...

  4. 电力电子转战数字IC20220819day64——uvm实战1A

    目录 MCDF改造 APB接口 验证环境的改造 实战任务 apb_config apb_master_agent apb_master_driver 1.1实现apb_master_driver的dr ...

  5. 电力电子转战数字IC20220727day57——寄存器模型(续)

    rgm的常规方法 关于reg的三个值 mirrored value镜像值:由模型预测给出,即在前门访问时通过观察总线.在后门访问时通过自动预测等方式给出 desired value期望值:先利用rgm ...

  6. 电力电子转战数字IC20220613day23——江哥nb!

    江哥nb! 今天的头版必须给到江哥,华为树枝江哥,FPGA设计岗,两年工资翻倍,年薪突破50,月薪突破30,向江哥学习! 想做江哥的水花兄弟,差个11号球衣  -------------------- ...

  7. 电力电子转战数字IC20220711day45——SV终章

    至此,SV的学习结束,实验4和实验5等回头过来重做,一定要到完全理解为止.SV DAY30 类型转换 分类:静态转换,动态转换 静态转换:在需要转换的表达式前面加上单引号,不会对转换值检查,如果转换失 ...

  8. 数字IC设计流程(全),芯片设计流程,集成电路设计流程

    一 数字IC设计流程 前端: 1.规格制定 甲方提要求,确定芯片的功能,性能等方面. 2.架构设计 架构工程师制定方案,设计架构,划分模块功能,定义接口时序. 3.RTL编码 数字IC设计工程师编写R ...

  9. 【调剂】东北电力大学2022年硕士研究生招生调剂公告(持续更新)

    公众号[计算机与软件考研]每天都会发布最新的计算机考研调剂信息! 点击公众号界面左下角的调剂信息或者公众号回复"调剂"是计算机/软件等专业的所有调剂信息集合,会一直更新的. 根据我 ...

最新文章

  1. 大红灯笼高高挂专业影评_浅谈《大红灯笼高高挂》
  2. nginx重定向到其他url方法_高级开发必须掌握Nginx之四,if、set、return
  3. 动态规划算法解最长公共子序列LCS问题
  4. 信息系统项目管理师-信息系统项目整体管理核心知识点思维脑图
  5. MySQL基础课堂笔记
  6. ActiveMQ 持久化讯息数据库信息
  7. oracle日志文件大小规则,修改oracle日志文件大小
  8. PHP5比PHP4,php4和php5的配置异同比较
  9. oracle imp 包,oracle imp
  10. 信息学奥赛一本通 1068:与指定数字相同的数的个数 | OpenJudge NOI 1.5 12
  11. 微软私有云解决方案_毕马威 AI 工厂携手微软云技术 | 共创人工智能发展,共建创新解决方案...
  12. 业务初期野蛮生长阶段,微服务化比较麻烦
  13. Python - matplotlib 不显示中文 findfont: Font family [‘SimHei‘] not found - IOS
  14. 36.伪造目标不可达的ICMP数据包
  15. 计算机前沿技术科论文,计算机前沿技术论文
  16. DS1302时钟芯片(SPI协议)
  17. STM32的IO口有幺蛾子(bug)
  18. 主流报表开发工具有FastReport.NET V2022.3正式发布——支持SkiaSharp
  19. CentOS安装Oracle教程
  20. Python容器数据类型(字符串)

热门文章

  1. java去除中文括号小括号,或者英文括号
  2. Shell脚本之H3C网络设备批量巡检
  3. 【数据库学习笔记】03 MySQL数据库CURD整理大全
  4. 【知识图谱】实践篇——基于医疗知识图谱的问答系统实践(Part5-完结):信息检索与结果组装
  5. 张赐荣 | PHP 获取喜马拉雅音频直链地址
  6. linux打开xml文件,查看 XML 文件
  7. PostgreSQL 179个场景
  8. 计算机网路——163邮箱授权码
  9. Semi-Supervised Semantic Segmentation with Cross-Consistency Training论文笔记
  10. NAACL 2022 | FACTPEGASUS:抽象摘要的真实性感知预训练和微调