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

经过两次面试后,对MCDF做一次全面的深入总结。

目前进度:硬件部分的node,fifo,寄存器,formatter,MCDF顶层,APB接口,TB接口

软件部分的chnl_pkg,fmt_pkg,apb_pkg,mcdf_rgm_pkg,mcdf_pkg


目录

硬件RTL部分

slave_node

FIFO

arbiter

param_def

寄存器接口

整形器formatter

顶层MCDF

接口打包信号

APB接口

tb中的接口

tb后续

软件部分

chnl_pkg

fmt_pkg

mcdf_rgm_pkg

apb_pkg

apb_test

apb_slave


硬件RTL部分

slave_node

首先是slave_node,就是一开始的channel,为了限制篇幅,具体的解析都写在注释里。

这里的功能是作为FIFO的上行端,按输入输出两端的信号拉高或拉低相应的信号,包括wait,奇偶校验位错误,freeslot,valid,fetch等。

这里还有的疑问如下:

fetch_i是哪里来的信号?由于是读信号的一个条件,且是上行端的数据,猜测是reg的信号

freeslot_o 是哪里的信号?输出端是fifo,在这里没有相应的编码。

奇偶校验位这里是放在最低位,详细看:

电力电子转战数字IC20220527day11(1)——奇偶校验_广工陈奕湘的博客-CSDN博客_缩位异或

module slave_node (
    clk_i          , // 
    rst_n_i        , // 
    // From uplink
    data_i         , // 
    data_p_i       , //奇偶校验位
    valid_i        , // 
    slv_en_i       , // 
    wait_o         , //
    parity_err_o   , //
    // 输出端
    data_o         , // 
    freeslot_o     , // 
    valid_o        , // 
    fetch_i        ,    
   
    parity_err_clr_i   //寄存器来的清除奇偶校验位错误信号    

);     
    input                                clk_i          ; // 
    input                                rst_n_i        ; // 
    // IO with driver
    input  [31:0]                        data_i         ; // 
    input                                data_p_i       ; // 
    input                                valid_i        ; // 
    input                                slv_en_i       ; // 
    output                               wait_o         ; //
    output                               parity_err_o   ; //
    // IO with Arbiter
    output  [31:0]                       data_o         ; // 
    output  [ 5:0]                       freeslot_o     ; // 
    output                               valid_o        ; // 
    output                               fetch_i        ;
    // IO with register
    input                                parity_err_clr_i      ;

reg             parity_err_r ;//定义一个reg来assign给parity_err_o   
wire            parity_err_s, wait_s, fifo_full_s, fifo_wr_s, fifo_rd_s, fifo_empty_s; 

assign parity_err_s = valid_i && ^{data_i,data_p_i}  ;
//奇偶校验,对数据进行缩位异或后,如果和奇偶校验位不同则出1,此时若valid还拉高就是出错了,error拉高

always @ (posedge clk_i or negedge rst_n_i)
begin : Parity_Err
    if (!rst_n_i) begin
       parity_err_r <= 1'b0;
    end else begin
       // 若err_s为1出现数据校验错误,err_r拉高,若clear信号拉高则清楚err信号,拉低
       if (parity_err_s    ) parity_err_r <= 1'b1 ; 
       if (parity_err_clr_i) parity_err_r <= 1'b0 ;
    end 
end
assign parity_err_o = parity_err_r;

assign wait_s       = fifo_full_s || parity_err_r ; 
//满了或者出现error则要wait
assign fifo_wr_s    = valid_i && !parity_err_r  && !wait_s && slv_en_i ;
//写信号,有效且校验位正确且不满且enable
assign fifo_rd_s    = fetch_i && !fifo_empty_s;
//读信号,不空且fetch则可读
assign wait_o = !slv_en_i || fifo_full_s || parity_err_r ;
//什么时候wait拉高?enable为低,fifo满了,奇偶校验错了

//硬件DUT信号的连接,左边是下一个代码段的fifo
//空满读写分别对应fifo的
sync_dff_fifo inst_fifo  (
    .clk_i(clk_i             ),
    .rst_n_i(rst_n_i         ),
    .data_i(data_i           ),
    .rd_i(fifo_rd_s          ),
    .wr_i(fifo_wr_s          ),
    .full_o(fifo_full_s      ),
    .empty_o(fifo_empty_s    ),
    .data_o(data_o           ),
    .freeslot_o(freeslot_o   )
);     

assign valid_o = !fifo_empty_s;//不空则valid

endmodule 

FIFO

node之后数据存放到fifo中, fifo复习

电力电子转战数字IC20220531day15——双端口RAM与异步FIFO_广工陈奕湘的博客-CSDN博客_fifo 双口ram

module sync_dff_fifo (
    clk_i        , 
    rst_n_i      , 
    
    data_i       ,             
    rd_i         ,             
    wr_i         ,              
    full_o       ,
    empty_o      ,
    overflow_o   ,//满了就会拉高
    data_o       ,            
    freeslot_o          
);     

    input                       clk_i        ; 
    input                       rst_n_i      ; 
    
    input  [31:0]               data_i       ;             
    input                       rd_i         ;             
    input                       wr_i         ;              
    output                      full_o       ;
    output                      empty_o      ;
    output                      overflow_o   ;
    
    output [31:0]               data_o       ;            
    output [ 5:0]               freeslot_o   ;       

parameter ADDR_W_C   = 5   ;
parameter DEPTH_C    = 32  ; 
//深度2^5的FIFO需要的地址位数就是5,要比深度多或者一样多就行
//所以ADDR_W_C是5
//深度2^5的FIFO需要的读写指针位宽5+1,多一位作为标志位
//所以freeslot是[5:0]

reg  [ADDR_W_C-1 :0]      wr_p_r ;//读写指针
reg  [ADDR_W_C-1 :0]      rd_p_r ;
reg  [31:0]               mem [DEPTH_C-1:0] ; 
//定义了mem型变量存放数据
reg  [ADDR_W_C   :0]      freeslot_r ;
//freeslot是说剩余的空间

wire full_s, empty_s; 
reg  overflow_r ;

always @(posedge clk_i or negedge rst_n_i)
begin
    if (!rst_n_i) begin//复位信号为0,指针为0,freeslot剩余空间为深度32
         freeslot_r <= DEPTH_C;
         rd_p_r     <= 0;
         wr_p_r     <= 0;
         overflow_r <= 1'b0;
     end else begin//读写信号时指针加1,表示读写了一次数据
         if(rd_i) rd_p_r     <= rd_p_r    +1 ;
         if(wr_i) wr_p_r     <= wr_p_r    +1 ;

//fifo同一时间可以被同时读写,也可以只读或者只写
         if ( rd_i && ~wr_i ) begin
            freeslot_r <= freeslot_r+1 ; 
         end //只读的话,剩余空间增加

         if (~rd_i && wr_i ) begin
            if (~full_s) begin//只写,剩余空间-1
                freeslot_r <= freeslot_r-1 ;
            end else begin//如果满了,overflow就会拉高
                overflow_r <= 1'b1;
            end
         end

         if (wr_i) begin
             mem[wr_p_r] <= data_i ;
         end//数据写入操作,直接把数据给mem[写指针]
     end
end
//空满标志的assign
assign full_s  = freeslot_r ==       0 ? 1 : 0  ; 
assign empty_o = freeslot_r == DEPTH_C ? 1 : 0  ; 
assign full_o  = full_s ;
assign overflow_o = overflow_r;
//读数据直接把对应mem中的assign给输出data就好,之前都是要写个always块来赋值
assign data_o  = mem[rd_p_r];
assign freeslot_o = freeslot_r ;

endmodule 

arbiter

fifo之后来到arbiter, 采用的机制是Round Robin轮询的一个仲裁机制。

简单来说就是4个通道会有请求,每一个clk都会设定一个最高优先级的通道,如果最高通道刚好有请求req,就判定这个channel胜出,拿他的数据,如果没有就往右查找看谁有req。操作完后这个通道的优先级就去到最低优先级,保证每个channel都可以读出数据。

遗留的问题是trigger是从哪里来的信号?

module RR_arbiter(
    clk_i,
    rst_n_i,
    req_vec_i,
    win_vec_o,
    trigger_i   
);
    input                    clk_i;
    input                    rst_n_i;
    input  [3:0]             req_vec_i;
//这个信号表示有多少个通道发起请求
    output [3:0]             win_vec_o;
    input                    trigger_i   ; //触发计算?

reg  [3:0] cp_vec_r ; //4个通道,表示哪一个通道优先级最高

wire [3:0] filter_L_s, filter_R_s, req_msb1_L_s, req_msb1_R_s, req_FL_L_s, req_FL_R_s, req_R_s, req_L_s;
wire [3:0] win_L_s   , win_R_s    ;
reg  [3:0] win_vec_s;
reg  [3:0] win_vec_r;
wire       req_all0_L_s, req_all0_R_s ;

//filter_L_s表示将cp_vec_r 中置1的那位的左边全部置0,然后取反得到filter_R_s    
/用以下四个assign来实现这个功能
assign filter_L_s[3] = cp_vec_r[3];
assign filter_L_s[2] = cp_vec_r[3] || cp_vec_r[2] ;
assign filter_L_s[1] = cp_vec_r[3] || cp_vec_r[2] || cp_vec_r[1] ;
assign filter_L_s[0] = cp_vec_r[3] || cp_vec_r[2] || cp_vec_r[1] || cp_vec_r[0];

assign filter_R_s    = ~ filter_L_s ;

//位操作符,每一位都进行与操作
//比如req_vec_i,也就是4个通道的请求;和根据最高优先级cp_vec_r选择的filter_R_s做与操作,都有1才为1,这样得到的req_L_s和req_R_s 是用来干什么的?
//表示结合slave自己的请求req_vec_i ,结合设定好的cp_vec_r ,最高优先级的左右分别有哪个通道允许做操作
assign req_L_s = req_vec_i & filter_R_s ;
assign req_R_s = req_vec_i & filter_L_s ;

//对刚才两个req的L和R做过滤操作,有1的位右边全部置1,为什么?
assign req_FL_L_s[3] = req_L_s[3];
assign req_FL_L_s[2] = req_L_s[3] || req_L_s[2] ;
assign req_FL_L_s[1] = req_L_s[3] || req_L_s[2] || req_L_s[1] ;
assign req_FL_L_s[0] = req_L_s[3] || req_L_s[2] || req_L_s[1] || req_L_s[0]  ;

assign req_FL_R_s[3] = req_R_s[3];
assign req_FL_R_s[2] = req_R_s[3] || req_R_s[2] ;
assign req_FL_R_s[1] = req_R_s[3] || req_R_s[2] || req_R_s[1] ;
assign req_FL_R_s[0] = req_R_s[3] || req_R_s[2] || req_R_s[1] || req_R_s[0]  ;

//对刚才得到的两个reg_FL的L和R,找置1的最高位,结果存放到req_msb1_L_s
//先把最高位赋值给最高位,然后次高位做判断,最高位为1时置0,否则保持不变;
//然后是次低位,合并高二位后做缩位或运算,如果是01,则出1,次低位置0,表示已经找到;如果是00,则出0,然后次低位保持不变,继续找。
assign req_msb1_L_s[3] = req_FL_L_s[3] ;
assign req_msb1_L_s[2] =   req_msb1_L_s[3]? 1'b0 : req_FL_L_s[2] ;
assign req_msb1_L_s[1] = |{req_msb1_L_s[3],req_msb1_L_s[2]}  ? 1'b0 : req_FL_L_s[1] ;
assign req_msb1_L_s[0] = |{req_msb1_L_s[3],req_msb1_L_s[2],req_msb1_L_s[1]} ? 1'b0 : req_FL_L_s[0] ;
assign req_msb1_R_s[3] = req_FL_R_s[3] ;
assign req_msb1_R_s[2] =   req_msb1_R_s[3] ? 1'b0 : req_FL_R_s[2] ;
assign req_msb1_R_s[1] = |{req_msb1_R_s[3],req_msb1_R_s[2]}? 1'b0 : req_FL_R_s[1] ;
assign req_msb1_R_s[0] = |{req_msb1_R_s[3],req_msb1_R_s[2],req_msb1_R_s[1]}? 1'b0 : req_FL_R_s[0] ;

//---------------------------------------------------------------------------------------
//也可以用这种异或的办法
//assign req_msb1_L_s[3] = req_FL_L_s[3] ;
//assign req_msb1_L_s[2] = req_FL_L_s[3] ^ req_FL_L_s[2] ;
//assign req_msb1_L_s[1] = req_FL_L_s[3] ^ req_FL_L_s[2] ^ req_FL_L_s[1] ;
//assign req_msb1_L_s[0] = req_FL_L_s[3] ^ req_FL_L_s[2] ^ req_FL_L_s[1] ^ req_FL_L_s[0] ;
//
//assign req_msb1_R_s[3] = req_FL_R_s[3] ;
//assign req_msb1_R_s[2] = req_FL_R_s[3] ^ req_FL_R_s[2] ;
//assign req_msb1_R_s[1] = req_FL_R_s[3] ^ req_FL_R_s[2] ^ req_FL_R_s[1] ;
//assign req_msb1_R_s[0] = req_FL_R_s[3] ^ req_FL_R_s[2] ^ req_FL_R_s[1] ^ req_FL_R_s[0] ;
//---------------------------------------------------------------------------------------

//如果req_FL_L_s全部为0,也就是没有满足优先级的通道,req_FL_L_s就拉高
assign req_all0_L_s = ~(|req_msb1_L_s) ;
assign req_all0_R_s = ~(|req_msb1_R_s) ;

//有4种情况:req_FL_L_s符合/不符合条件、req_FL_R_s符合/不符合条件
//这四种情况可以拼接两个全无位来表示{req_all0_L_s, req_all0_R_s}
//根据这个拼接位来决出最后的胜者win_vec_s ,也就是这个clk要给哪个通道授权
always @(req_all0_L_s or req_all0_R_s or req_msb1_R_s or req_msb1_L_s)
begin
    case ({req_all0_L_s, req_all0_R_s})
        2'b11: win_vec_s <= 4'b0000      ; //这是不可能出现的情况,只能是左或右
        2'b10: win_vec_s <= req_msb1_R_s ; 
//过滤后左边全0没有通道满足,则胜者为req_msb1_R_s 
        2'b01: win_vec_s <= req_msb1_L_s ; //同理 
        2'b00: win_vec_s <= req_msb1_R_s ; //两边都有通道满足要求,则优先是右边的通道,且高位优先级更高
    endcase
end

assign win_vec_o = win_vec_r;

always @(posedge clk_i or negedge rst_n_i)
begin
    if (!rst_n_i) begin//复位值
        cp_vec_r  <= 4'b1000;
        win_vec_r <= 4'b0000;
    end else begin
        if (trigger_i) begin
            cp_vec_r[0] <= win_vec_s[1];
            cp_vec_r[1] <= win_vec_s[2];
            cp_vec_r[2] <= win_vec_s[3];
            cp_vec_r[3] <= win_vec_s[0];
            //每个clk决出一个channel,然后优先级最高的channel就要去到优先级最低了,保证每个通道都有机会
            win_vec_r <= win_vec_s;
        end

    end 
end 


endmodule

param_def

在进入寄存器代码之前,先看参数定义param_def。定义了两种寄存器的地址

4个读写寄存器0x00到0x0C,4个地址为1个寄存器,1个地址存一个8位byte?

`define  ADDR_WIDTH 8
`define  DATA_WIDTH 32

`define SLV_EN_ADDR_C  8'h00    
`define ERR_CLR_ADDR_C 8'h04
`define SLV_ID_ADDR_C  8'h08
`define SLV_LEN_ADDR_C 8'h0C

`define SLV0_FSLOT_ADDR_C  8'h40
`define SLV1_FSLOT_ADDR_C  8'h44
`define SLV2_FSLOT_ADDR_C  8'h48
`define SLV3_FSLOT_ADDR_C  8'h4C

寄存器的代码开始,先看APB总线的状态图,等下会用到

寄存器接口

APB4总线的序列图看这里。

APB4总线介绍_脱密180天的博客-CSDN博客_apb4协议

`include "param_def.v"
module reg_if (
clk_i,
rst_n_i,
//寄存器的信号,其实这几个就是APB总线的信号了
paddr_i, pwr_i, pen_i, psel_i, pwdata_i, prdata_o, pready_o, 
pslverr_o, //这个不明,暂时记着

slv_en_o,  //对应0x00的使能信号
err_clr_o,

//对应0x08的id信号
slv0_id_o, slv1_id_o, slv2_id_o, slv3_id_o,

//对应0x0C的长度信号
slv0_len_o, slv1_len_o, slv2_len_o, slv3_len_o,

//对应0x04的奇偶校验位错误清除信号
slv0_parity_err_i, slv1_parity_err_i, slv2_parity_err_i, slv3_parity_err_i,

//对应只读寄存器0x80-0x8C的余量信号
slv0_free_slot_i, slv1_free_slot_i, slv2_free_slot_i, slv3_free_slot_i

);                        

input              clk_i;
input              rst_n_i;
input  [7:0]       paddr_i;
input              pwr_i;
input              pen_i;
input              psel_i;
input  [31:0]      pwdata_i;
output [31:0]      prdata_o;
output             pready_o;
output             pslverr_o; 
output [3:0]       slv_en_o;  
output [3:0]       err_clr_o;
output [7:0]       slv0_id_o;
output [7:0]       slv1_id_o;
output [7:0]       slv2_id_o;
output [7:0]       slv3_id_o;
output [7:0]       slv0_len_o;
output [7:0]       slv1_len_o;
output [7:0]       slv2_len_o;
output [7:0]       slv3_len_o;
input              slv0_parity_err_i;
input              slv1_parity_err_i;
input              slv2_parity_err_i;
input              slv3_parity_err_i;
input  [5:0]       slv0_free_slot_i;
input  [5:0]       slv1_free_slot_i;
input  [5:0]       slv2_free_slot_i;
input  [5:0]       slv3_free_slot_i;

//定义状态名,寄存器的状态有IDLE、SETUP、ACC
parameter [1:0]     st_IDLE  =2'b00 ;
parameter [1:0]     st_SETUP =2'b01 ;
parameter [1:0]     st_ACC   =2'b10 ;

reg [1:0] last_st, cur_st ;

//给两种寄存器开辟了两个mem,但是只读寄存器不是有8个吗?
reg     [31:0]      ctrl_mem [3:0]; 
reg     [31:0]      ro_mem   [3:0]; //?这里应该是[7:0]

wire                is_st_idle_s, is_st_setup_s, is_st_acc_s; 
//这三个是显示当前状态cur_st的,当前状态是哪个就哪个拉高
wire                is_addr_freeslot_3_s;
wire                is_addr_freeslot_2_s;
wire                is_addr_freeslot_1_s;
wire                is_addr_freeslot_0_s;
wire                is_addr_parity_err_3_s;
wire                is_addr_parity_err_2_s;
wire                is_addr_parity_err_1_s;
wire                is_addr_parity_err_0_s;
reg     [7:0]       addr_r;
reg     [31:0]      data_rd_r;

wire                is_ctrl_rng_s;
wire                is_ro_rng_s;
wire                is_err_rng_s;

wire                idx_0_s;
wire                idx_1_s;
wire                idx_2_s;
wire                idx_3_s;

wire                is_addr_slv_en_s;
wire                is_addr_err_clr_s;
wire                is_addr_slv_id_s;
wire                is_addr_slv_len_s;

//三段式状态机,之前手撕代码用的是next_state和state
//这里换成last_st和cur_st    
always @(posedge clk_i or negedge rst_n_i)
begin
    if (!rst_n_i) last_st <= st_IDLE   ;
    else          last_st <= cur_st    ;
end

//根据last_st是什么状态做对应的操作,结合APB总线的时序图来看
//IDLE时,sel拉高,就进入setup状态;
//SETUP时自动进入ACC状态,emmm……
//ACC状态时,sel仍然为高,enable拉高,只要sel和en有一个不是高,就回到IDLE状态
always @(*) begin
    case (last_st)
        st_IDLE    : if (psel_i) cur_st <= st_SETUP;
                     else cur_st <= st_IDLE;
        st_SETUP   : cur_st <= st_ACC  ; 
        st_ACC     : if (psel_i && pen_i) begin
                         cur_st <= st_ACC ;
                     end else begin
                         cur_st <= st_IDLE;
                     end 
    endcase
end 

//assign了寄存器三个状态的显示,状态机到哪个状态,对应的位就置高
assign is_st_idle_s  = (cur_st == st_IDLE ) ? 1'b1 : 1'b0 ;
assign is_st_setup_s = (cur_st == st_SETUP) ? 1'b1 : 1'b0 ;
assign is_st_acc_s   = (cur_st == st_ACC  ) ? 1'b1 : 1'b0 ;

//关于地址的,在setup状态时将APB总线的地址给到addr_r地址寄存器
always @(posedge clk_i or negedge rst_n_i)
begin
    if (!rst_n_i) begin
        addr_r <= 0 ;
    end else begin
        if (is_st_setup_s)  begin
            addr_r <= paddr_i ;end 
    end
end 

//定义了一个32位的数据存储data_rd_r
//在ACC状态时,读操作要write信号为低~pwr_i
//if条件中的12个信号分别表示用到哪个寄存器,哪个就为1,具体看后面代码
always @(*) begin
  data_rd_r <= 0; 
  if (is_st_acc_s) begin
      if (~pwr_i) begin 
//哪个寄存器拉高,就把对应mem中的数据给到读出数据的寄存器data_rd_r ,完成数据读出
          if (is_addr_slv_en_s )  data_rd_r <= ctrl_mem [0];
          if (is_addr_err_clr_s)  data_rd_r <= ctrl_mem [1];
          if (is_addr_slv_id_s )  data_rd_r <= ctrl_mem [2];
          if (is_addr_slv_len_s)  data_rd_r <= ctrl_mem [3];
          if (is_addr_freeslot_0_s)  data_rd_r <= ro_mem [0];
          if (is_addr_freeslot_1_s)  data_rd_r <= ro_mem [1];
          if (is_addr_freeslot_2_s)  data_rd_r <= ro_mem [2];
          if (is_addr_freeslot_3_s)  data_rd_r <= ro_mem [3];
          if (is_addr_parity_err_0_s)  data_rd_r <= ro_mem [4];
          if (is_addr_parity_err_1_s)  data_rd_r <= ro_mem [5];
          if (is_addr_parity_err_2_s)  data_rd_r <= ro_mem [6];
          if (is_addr_parity_err_3_s)  data_rd_r <= ro_mem [7];
      end  
  end  
end
//最后将读出数据寄存器的数据给到总线信号,完成读操作
assign   prdata_o  = data_rd_r ;
//这个信号表示在ACC状态(应该读写数据的状态)寄存器地址有误,拉高
assign   pslverr_o = is_st_acc_s && is_err_rng_s ; 
//在ACC状态时reday信号拉高表示准备好读写数据
assign   pready_o  = is_st_acc_s ;

//这三个信号,首先判断总线来的地址是不是控制寄存器的地址
//根据上面寄存器图,0x00-0x0C是读写寄存器,0x80-0x9C是只读寄存器
//也就是读写寄存器的地址最高为8C,二进制是00001000,对高四位做缩位或,四位都是0才是读写寄存器,所以取反表示当前的地址是读写寄存器的地址
assign is_ctrl_rng_s = ~|(addr_r[7:4]) ; //0h0*
//同理,只读寄存器的二进制地址是10000000到10011100,高三位必须满足100,
assign is_ro_rng_s   =  addr_r[7] && !addr_r[6] && !addr_r[5] ; //0h8* or 0h9*
//最后,其他地址都不对,就要报错,所以就有了以下信号,既不是读写又不是只读,取反出1
assign is_err_rng_s  =  ~(is_ctrl_rng_s | is_ro_rng_s);

//APB总线地址的[3:2]位表示的是寄存器的ID,读写寄存器有4个,只读寄存器中freeslot有4个,parity_err有四个,配合上面的表示寄存器的信号,可以得到下面这些信号,对应了12个寄存器,用到哪个就拉高哪个,想不明白的可以回去上面看寄存器的图。
assign idx_0_s       = (addr_r[3:2]==2'b00)? 1'b1 : 1'b0;
assign idx_1_s       = (addr_r[3:2]==2'b01)? 1'b1 : 1'b0;
assign idx_2_s       = (addr_r[3:2]==2'b10)? 1'b1 : 1'b0;
assign idx_3_s       = (addr_r[3:2]==2'b11)? 1'b1 : 1'b0;

//12个寄存器信号,地址是哪个就拉高哪个
assign is_addr_slv_en_s   = is_ctrl_rng_s & idx_0_s ;
assign is_addr_err_clr_s  = is_ctrl_rng_s & idx_1_s ;
assign is_addr_slv_id_s   = is_ctrl_rng_s & idx_2_s ;
assign is_addr_slv_len_s  = is_ctrl_rng_s & idx_3_s ;
assign is_addr_freeslot_0_s  = is_ro_rng_s && !addr_r[4] && idx_0_s ;
assign is_addr_freeslot_1_s  = is_ro_rng_s && !addr_r[4] && idx_1_s ;
assign is_addr_freeslot_2_s  = is_ro_rng_s && !addr_r[4] && idx_2_s ;
assign is_addr_freeslot_3_s  = is_ro_rng_s && !addr_r[4] && idx_3_s ;
assign is_addr_parity_err_0_s  = is_ro_rng_s && addr_r[4] && idx_0_s ;
assign is_addr_parity_err_1_s  = is_ro_rng_s && addr_r[4] && idx_1_s ;
assign is_addr_parity_err_2_s  = is_ro_rng_s && addr_r[4] && idx_2_s ;
assign is_addr_parity_err_3_s  = is_ro_rng_s && addr_r[4] && idx_3_s ;

//复位信号为0时,4个控制寄存器的初始值如下
//其中,0x08寄存器存放的是slv的ID,初始值为3210,32位寄存器每个id占8位,用32位十六进制表示为03020100,对应二进制32'b00000011|00000010|00000001|00000000(不是与)
always @ (posedge clk_i or negedge rst_n_i)
begin  : CONTROL_PROC
  if (!rst_n_i)
    begin
      ctrl_mem[0] <= 32'h00000000; // slv_en
      ctrl_mem[1] <= 32'h00000000; // parity_err_clr
      ctrl_mem[2] <= 32'h03020100; // slave ID
      ctrl_mem[3] <= 32'h00000000; // length
    end else begin
//ACC状态执行写操作,对slv_en和parity_err寄存器来说只有前4位,对slv_id和slv_len来说就有32位全写,所以pwdata分别写入这些位置
//pwdata包含了
      if (is_st_acc_s & pwr_i) begin
          if (is_addr_slv_en_s ) ctrl_mem [0][3:0] <= pwdata_i ;
          if (is_addr_err_clr_s) ctrl_mem [1][3:0] <= pwdata_i ;
          if (is_addr_slv_id_s ) ctrl_mem [2]      <= pwdata_i ;
          if (is_addr_slv_len_s) ctrl_mem [3]      <= pwdata_i ;
      end 
    end
end

//fifo余量和奇偶校验位报错的初始值是0
always @ (posedge clk_i or negedge rst_n_i)
begin  : RO_PROC
  if (!rst_n_i)
    begin
        ro_mem[0] <= 32'h00000000; 
        ro_mem[1] <= 32'h00000000; 
        ro_mem[2] <= 32'h00000000; 
        ro_mem[3] <= 32'h00000000; 
        ro_mem[4] <= 32'h00000000; 
        ro_mem[5] <= 32'h00000000; 
        ro_mem[6] <= 32'h00000000; 
        ro_mem[7] <= 32'h00000000;
    end else begin
        ro_mem[0][5:0] <= slv0_free_slot_i; //这是从fifo输出过来的余量信号
        ro_mem[1][5:0] <= slv1_free_slot_i; 
        ro_mem[2][5:0] <= slv2_free_slot_i; 
        ro_mem[3][5:0] <= slv3_free_slot_i;
        ro_mem[4][0] <= slv0_parity_err_i; //这是从node输出过来的奇偶校验位错误信号
        ro_mem[5][0] <= slv1_parity_err_i; 
        ro_mem[6][0] <= slv2_parity_err_i; 
        ro_mem[7][0] <= slv3_parity_err_i; 
    end
end

//对应控制寄存器的位assign给寄存器输出信号
assign slv_en_o   = ctrl_mem[0][3:0];  
assign err_clr_o  = ctrl_mem[1][3:0];
assign slv0_id_o  = ctrl_mem[2][1*8-1:  0];
assign slv1_id_o  = ctrl_mem[2][2*8-1:1*8];
assign slv2_id_o  = ctrl_mem[2][3*8-1:2*8];
assign slv3_id_o  = ctrl_mem[2][4*8-1:3*8];
assign slv0_len_o = ctrl_mem[3][1*8-1:  0];
assign slv1_len_o = ctrl_mem[3][2*8-1:1*8];
assign slv2_len_o = ctrl_mem[3][3*8-1:2*8];
assign slv3_len_o = ctrl_mem[3][4*8-1:3*8];


endmodule

整形器formatter

formatter的工作原理也是状态机,有任意的通道发起请求则进入RUN_RR启动仲裁器,然后进入发送第一个payload的状态header,接收端ready信号为高时开始发送数据,进入payload状态,最后一个payload发送完拉高pkg_lst表示发送完毕,进入parity,然后继续回到RUN_RR

module formater(
    input                      clk_i,
    input                      rst_n_i,
//和4个node的信号连接,包括32位数据,slv_id,slv_len,不包括读写寄存器[1]的奇偶校验报错
//但有些信号在node里面并没有,可能是寄存器来的?
    input     [31:0]           data_slv0_i,
    input     [31:0]           data_slv1_i,
    input     [31:0]           data_slv2_i,
    input     [31:0]           data_slv3_i,

    input     [7:0]            id_slv0_i,
    input     [7:0]            id_slv1_i,
    input     [7:0]            id_slv2_i,
    input     [7:0]            id_slv3_i,

    input     [7:0]            len_slv0_i,
    input     [7:0]            len_slv1_i,
    input     [7:0]            len_slv2_i,
    input     [7:0]            len_slv3_i,
//四个channel的请求信号
    input     [3:0]            req_vec_i ,
    output    [3:0]            fetch_vec_o,
    //仲裁器给到这里的是胜出channel,然后fmt就接收该channel的数据
    input     [3:0]            win_vec_i,
//arbiter中遗留的问题信号trigger应该是对应这里这个了
    output                     trigger_start_o,
//fmt输出端信号
    input                      rev_rdy_i, 
//接收端发出的ready信号表示准备好接收fmt的数据包
//如图,fmt的任务就是把各个数据打包成多个payload的形式
//len+1表示payload的数量,32位的payload存放的是id、len和16位data,最后一个payload存放的是奇偶校验位
    output                     pkg_vld_o,
    output   [31:0]            pkg_dat_o,
    output                     pkg_fst_o,
    output                     pkg_lst_o 
);
//状态定义
parameter [2:0] ST_RST     = 3'b000;
parameter [2:0] ST_Run_RR  = 3'b001;
parameter [2:0] ST_Header  = 3'b010;  
parameter [2:0] ST_Payload = 3'b011;
parameter [2:0] ST_Parity  = 3'b100;
reg [2:0] cur_st, nxt_st; 

//4个通道的胜者显示,包括数据、id、长度
wire [31:0] data_slv0_win_s ;
wire [31:0] data_slv1_win_s ;
wire [31:0] data_slv2_win_s ;
wire [31:0] data_slv3_win_s ;
wire [31:0] data_win_s      ;
wire [7:0] id_slv0_win_s   ;
wire [7:0] id_slv1_win_s   ;
wire [7:0] id_slv2_win_s   ;
wire [7:0] id_slv3_win_s   ;
wire [7:0] id_win_s        ;
wire [7:0] len_slv0_win_s  ;
wire [7:0] len_slv1_win_s  ;
wire [7:0] len_slv2_win_s  ;
wire [7:0] len_slv3_win_s  ;
wire [7:0] len_win_s       ;

//状态机当前状态显示
wire is_st_run_rr_s     ;
wire is_st_header_s     ;
wire is_st_payload_s    ;
wire is_st_parity_s     ;

//发送状态点亮
wire send_header_s      ;
wire send_payload_s     ;
wire send_parity_s      ;
wire pkg_lst_s          ;

//channel的请求和胜出显示
wire [3:0] win_req_vec_s;
wire win_req_s          ;
wire any_win_s          ;
wire any_req_s          ;
//数据包有效显示 
wire pkg_vld_s          ; 
reg  [31:0] tx_d_s      ;//输出数据寄存器
reg  [31:0] parity_r    ;//奇偶校验位寄存器
reg  [ 7:0] len_cnt_r   ;//长度len寄存器
wire [ 3:0] fetch_vec_s ;//fetch?

//通道胜出者显示
assign win_req_vec_s = win_vec_i & req_vec_i ;
//缩位与,有通道请求胜出就拉高
assign win_req_s     = |win_req_vec_s ;
//同理,有任意通道胜出就拉高,这两个好像差不多,要辨别一下
assign any_win_s     = |win_vec_i ;
//同理,有任意通道请求就拉高
assign any_req_s     = |req_vec_i ;

//fmt的工作模式也是状态机,只要有node发起请求,状态就从RST到RUN_RR再到Header,如果接收端准备好了就进入payload,如果最后一个payload发送完就进入parity,完成传输,回到RUN_RR
always @(posedge clk_i or negedge rst_n_i)
begin
    if (!rst_n_i) cur_st <= ST_RST    ; 
    else          cur_st <= nxt_st    ;
end 
always @(*) begin
    nxt_st <= cur_st;
    case (cur_st)
        ST_RST     : if (                          any_req_s) nxt_st <= ST_Run_RR   ;
        ST_Run_RR  : if (                          any_req_s) nxt_st <= ST_Header   ; 
        ST_Header  : if (             rev_rdy_i && any_req_s) nxt_st <= ST_Payload  ; 
        ST_Payload : if (pkg_lst_s && rev_rdy_i && win_req_s) nxt_st <= ST_Parity   ; 
        ST_Parity  : if (             rev_rdy_i             ) nxt_st <= ST_Run_RR   ;
    endcase
end

//根据仲裁器的胜者信号,拉高对应的表示data所在通道胜出的信号
assign data_slv0_win_s = win_vec_i[0] ? data_slv0_i: 0 ;
assign data_slv1_win_s = win_vec_i[1] ? data_slv1_i: 0 ;
assign data_slv2_win_s = win_vec_i[2] ? data_slv2_i: 0 ;
assign data_slv3_win_s = win_vec_i[3] ? data_slv3_i: 0 ;
//只要有一个胜出的node就拉高这个信号
assign data_win_s= data_slv0_win_s | data_slv1_win_s | data_slv2_win_s | data_slv3_win_s ; 
//同上,所在通道id的信号拉高
assign id_slv0_win_s   = win_vec_i[0] ? id_slv0_i  : 0 ;
assign id_slv1_win_s   = win_vec_i[1] ? id_slv1_i  : 0 ;
assign id_slv2_win_s   = win_vec_i[2] ? id_slv2_i  : 0 ;
assign id_slv3_win_s   = win_vec_i[3] ? id_slv3_i  : 0 ;
assign id_win_s        = id_slv0_win_s | id_slv1_win_s | id_slv2_win_s | id_slv3_win_s  ; 
//同上,所在通道数据长度的信号拉高
assign len_slv0_win_s  = win_vec_i[0] ? len_slv0_i : 0 ;
assign len_slv1_win_s  = win_vec_i[1] ? len_slv1_i : 0 ;
assign len_slv2_win_s  = win_vec_i[2] ? len_slv2_i : 0 ;
assign len_slv3_win_s  = win_vec_i[3] ? len_slv3_i : 0 ;
assign len_win_s       = len_slv0_win_s | len_slv1_win_s | len_slv2_win_s | len_slv3_win_s ;
//状态机的显示信号,在哪个状态就拉高哪个信号
assign is_st_run_rr_s     = (cur_st==ST_Run_RR )? 1'b1 : 1'b0 ; 
assign is_st_header_s     = (cur_st==ST_Header )? 1'b1 : 1'b0 ; 
assign is_st_payload_s    = (cur_st==ST_Payload)? 1'b1 : 1'b0 ; 
assign is_st_parity_s     = (cur_st==ST_Parity )? 1'b1 : 1'b0 ; 
//在header状态时,若有任意通道有req且接收端做好准备rev_req,则开始发送第一个payload,并进入payload状态,这个信号表示发送了第一个payload
assign send_header_s      = is_st_header_s   && rev_rdy_i ;
assign pkg_fst_o = send_header_s  ;
//同理,这个表示发送剩余payloads
assign send_payload_s     = is_st_payload_s  && rev_rdy_i && win_req_s ; 
//同理,这个表示发送最后的payload奇偶校验
assign send_parity_s      = is_st_parity_s   && rev_rdy_i ;
//send_parity_s标志着数据包传输结束,拉高last
assign pkg_lst_o = send_parity_s  ;

//trigger信号在启动仲裁器或开始发送header时拉高
//assign trigger_start_o    = send_header_s ;
assign trigger_start_o    = is_st_run_rr_s ;

//在传送payload的状态中将对应payload中的数据赋给输出端完成传输,结合fmt的图就知道了
always @(*)
begin
    tx_d_s   <= 0;
    if (is_st_header_s) begin
        tx_d_s   <= {id_win_s, len_win_s,16'h0000};
    end 
    if (is_st_payload_s) begin
        tx_d_s   <= data_win_s ;
    end 
    if (is_st_parity_s) begin
        tx_d_s   <= parity_r;
    end
end
assign pkg_dat_o = tx_d_s ;

//最后一个payload发送完后长度len就为0,缩位逻辑或出0,取反出1表示payload发送完了
assign pkg_lst_s          = ~|(len_cnt_r)    ;
//
always @(posedge clk_i or negedge rst_n_i)
begin
    if (~rst_n_i) begin
        parity_r <= 0 ;
        len_cnt_r <= 0 ;
    end else begin
        if (send_header_s) begin
            parity_r  <= {id_win_s, len_win_s,16'h0000};
            len_cnt_r <= len_win_s ;
        end 
        if (send_payload_s) begin
            parity_r <= data_win_s ^ parity_r ; 
            len_cnt_r<= len_cnt_r - 1;//第一个payload的len是[0],这里要减1
        end 
        if (send_parity_s) begin//发送完parity后清零结束一个pkg的传输
            parity_r  <= 0 ;
            len_cnt_r <= 0 ;
        end 
    end 
end

// fetch_vec是个4位信号,表示4个channel哪一个个完成了传输(ready且经过payload状态)
//4位和独热码win_vec按位与
assign fetch_vec_s = {4{rev_rdy_i && is_st_payload_s }} & win_vec_i ;
assign fetch_vec_o = fetch_vec_s ;

// pkg有效信号,在状态为header、payload且有req、parity时有效
assign pkg_vld_s = is_st_header_s || (is_st_payload_s && win_req_s) || is_st_parity_s ; 
assign pkg_vld_o = pkg_vld_s ;

endmodule 
 

顶层MCDF

结构不是之前的channel-arb-fmt,而是node_fifo直接到fmt,arb和reg和apb包围这两个结构,形成一个完整的MCDF结构

module mcdf(
input        clk_i              ,
input        rst_n_i            ,

//4个node的信号,分别有32位数据,1位奇偶校验,valid信号应该是fmt的输出pkg_vld_o,最后是奇偶校验报错给到APB总线
input  [31:0]    slv0_data_i         , // 
input            slv0_data_p_i       , //
input            slv0_valid_i        , // 
output           slv0_wait_o         , //fifo满了,slv_en为低,奇偶校验错误,则wait
output           slv0_parity_err_o   , //
input  [31:0]    slv1_data_i         , // 
input            slv1_data_p_i       , // 
input            slv1_valid_i        , // 
output           slv1_wait_o         , //
output           slv1_parity_err_o   , //
input  [31:0]    slv2_data_i         , // 
input            slv2_data_p_i       , // 
input            slv2_valid_i        , // 
output           slv2_wait_o         , //
output           slv2_parity_err_o   , //
input  [31:0]    slv3_data_i         , // 
input            slv3_data_p_i       , //
input            slv3_valid_i        , // 
output           slv3_wait_o         , //
output           slv3_parity_err_o   , //
    
// APB总线的接口信号,和后面的apb_if中的一样,在mcdf顶层结构中要完成接口的连接
input  [7:0]     paddr_i        ,
input            pwr_i          ,
input            pen_i          ,
input            psel_i         ,
input  [31:0]    pwdata_i       ,
output [31:0]    prdata_o       ,
output           pready_o       ,
output           pslverr_o      , 

//mcdf的输出端,其实就是fmt的输出端
input            rev_rdy_i      ,
output           pkg_vld_o      , 
output [31:0]    pkg_dat_o      , 
output           pkg_fst_o      , 
output           pkg_lst_o        
);
//数据显示信号
wire    [31:0] slv0_data_s,slv1_data_s, slv2_data_s, slv3_data_s ;
//余量显示信号,0x80-0x8C寄存器存储的
wire    [5 :0] slv0_freeslot_s, slv1_freeslot_s, slv2_freeslot_s, slv3_freeslot_s;
//0x08寄存器存储的ID信号,和寄存器相连
wire    [7 :0] slv0_id_s,slv1_id_s,slv2_id_s,slv3_id_s ;
//同理
wire    [7 :0] slv0_len_s,slv1_len_s,slv2_len_s,slv3_len_s ;

wire    [3 :0] err_clr_vec_s, fetch_vec_s, req_vec_s,  slv_en_vec_s, win_vec_s; 
wire           trigger_s,slv3_fetch_s ,slv2_fetch_s ,slv1_fetch_s ,slv0_fetch_s  ;

//MCDF接口连接到寄存器接口上,左边是reg_if的接口
reg_if  inst_reg_if (
    .clk_i               (clk_i                      ),
    .rst_n_i             (rst_n_i                    ),
    .paddr_i             (paddr_i                    ),
    .pwr_i               (pwr_i                      ),
    .pen_i               (pen_i                      ),
    .psel_i              (psel_i                     ),
    .pwdata_i            (pwdata_i                   ),
    .prdata_o            (prdata_o                   ),
    .pready_o            (pready_o                   ),
    .pslverr_o           (pslverr_o                  ), 
    .slv_en_o            (slv_en_vec_s               ),  
    .err_clr_o           (err_clr_vec_s              ),
    .slv0_id_o           (slv0_id_s                  ),
    .slv1_id_o           (slv1_id_s                  ),
    .slv2_id_o           (slv2_id_s                  ),
    .slv3_id_o           (slv3_id_s                  ),
    .slv0_len_o          (slv0_len_s                 ),
    .slv1_len_o          (slv1_len_s                 ),
    .slv2_len_o          (slv2_len_s                 ),
    .slv3_len_o          (slv3_len_s                 ),
    .slv0_parity_err_i   (slv0_parity_err_s          ),
    .slv1_parity_err_i   (slv1_parity_err_s          ),
    .slv2_parity_err_i   (slv2_parity_err_s          ),
    .slv3_parity_err_i   (slv3_parity_err_s          ),
    .slv0_free_slot_i    (slv0_freeslot_s            ),
    .slv1_free_slot_i    (slv1_freeslot_s            ),
    .slv2_free_slot_i    (slv2_freeslot_s            ),
    .slv3_free_slot_i    (slv3_freeslot_s            )
);                        

//MCDF接口连接到4个node上
slave_node inst_slave_node_0 (
    .clk_i               (clk_i                      ),
    .rst_n_i             (rst_n_i                    ),
    .data_i              (slv0_data_i                ),
    .data_p_i            (slv0_data_p_i              ),
    .valid_i             (slv0_valid_i               ),
    .slv_en_i            (slv_en_vec_s[0]            ),
    .wait_o              (slv0_wait_o                ),
    .parity_err_o        (slv0_parity_err_s          ),
    .data_o              (slv0_data_s                ),
    .freeslot_o          (slv0_freeslot_s            ),
    .valid_o             (slv0_valid_o_s             ),
    .fetch_i             (slv0_fetch_s               ),
    .parity_err_clr_i    (err_clr_vec_s[0]           )       
);     
assign  slv0_parity_err_o = slv0_parity_err_s;

slave_node inst_slave_node_1 (
    .clk_i               (clk_i                      ),
    .rst_n_i             (rst_n_i                    ),
    .data_i              (slv1_data_i                ),
    .data_p_i            (slv1_data_p_i              ),
    .valid_i             (slv1_valid_i               ),
    .slv_en_i            (slv_en_vec_s[1]            ),
    .wait_o              (slv1_wait_o                ),
    .parity_err_o        (slv1_parity_err_s          ),
    .data_o              (slv1_data_s                ),
    .freeslot_o          (slv1_freeslot_s            ),
    .valid_o             (slv1_valid_o_s             ),
    .fetch_i             (slv1_fetch_s               ),
    .parity_err_clr_i    (err_clr_vec_s[1]           )       
);     
assign  slv1_parity_err_o = slv1_parity_err_s;

slave_node inst_slave_node_2 (
    .clk_i               (clk_i                      ),
    .rst_n_i             (rst_n_i                    ),
    .data_i              (slv2_data_i                ),
    .data_p_i            (slv2_data_p_i              ),
    .valid_i             (slv2_valid_i               ),
    .slv_en_i            (slv_en_vec_s[2]            ),
    .wait_o              (slv2_wait_o                ),
    .parity_err_o        (slv2_parity_err_s          ),
    .data_o              (slv2_data_s                ),
    .freeslot_o          (slv2_freeslot_s            ),
    .valid_o             (slv2_valid_o_s             ),
    .fetch_i             (slv2_fetch_s               ),
    .parity_err_clr_i    (err_clr_vec_s[2]           )
);     
assign  slv2_parity_err_o = slv2_parity_err_s;

slave_node inst_slave_node_3 (
    .clk_i               (clk_i                      ),
    .rst_n_i             (rst_n_i                    ),
    .data_i              (slv3_data_i                ),
    .data_p_i            (slv3_data_p_i              ),
    .valid_i             (slv3_valid_i               ),
    .slv_en_i            (slv_en_vec_s[3]            ),
    .wait_o              (slv3_wait_o                ),
    .parity_err_o        (slv3_parity_err_s          ),
    .data_o              (slv3_data_s                ),
    .freeslot_o          (slv3_freeslot_s            ),
    .valid_o             (slv3_valid_o_s             ),
    .fetch_i             (slv3_fetch_s               ),
    .parity_err_clr_i    (err_clr_vec_s[3]           )       
);     
assign  slv3_parity_err_o = slv3_parity_err_s;

assign req_vec_s = {slv3_valid_o_s,slv2_valid_o_s,slv1_valid_o_s,slv0_valid_o_s};
//连接到仲裁器
RR_arbiter inst_arb (
    .clk_i               (clk_i                      ),
    .rst_n_i             (rst_n_i                    ),
    .req_vec_i           (req_vec_s                  ),
    .win_vec_o           (win_vec_s                  ),
    .trigger_i           (trigger_s                  )   
);
//连接到fmt
formater inst_formatter (
    .clk_i               (clk_i                      ),
    .rst_n_i             (rst_n_i                    ),
    .data_slv0_i         (slv0_data_s                ),
    .data_slv1_i         (slv1_data_s                ),
    .data_slv2_i         (slv2_data_s                ),
    .data_slv3_i         (slv3_data_s                ),
    .id_slv0_i           (slv0_id_s                  ),
    .id_slv1_i           (slv1_id_s                  ),
    .id_slv2_i           (slv2_id_s                  ),
    .id_slv3_i           (slv3_id_s                  ),
    .len_slv0_i          (slv0_len_s                 ),
    .len_slv1_i          (slv1_len_s                 ),
    .len_slv2_i          (slv2_len_s                 ),
    .len_slv3_i          (slv3_len_s                 ),
    .req_vec_i           (req_vec_s                  ),
    .fetch_vec_o         (fetch_vec_s                ),
    .win_vec_i           (win_vec_s                  ), 
    .trigger_start_o     (trigger_s                  ),
    .rev_rdy_i           (rev_rdy_i                  ),
    .pkg_vld_o           (pkg_vld_o                  ),
    .pkg_dat_o           (pkg_dat_o                  ),
    .pkg_fst_o           (pkg_fst_o                  ),
    .pkg_lst_o           (pkg_lst_o                  )
);

//fetch信号的显示
assign slv0_fetch_s = fetch_vec_s[0];
assign slv1_fetch_s = fetch_vec_s[1];
assign slv2_fetch_s = fetch_vec_s[2];
assign slv3_fetch_s = fetch_vec_s[3];

endmodule

接口打包信号

APB接口

对应MCDF中的APB接口。

这里的问题是,has_coverage和has_coverage有没有其他地方控制的?


`ifndef APB_IF_SV
`define APB_IF_SV

interface apb_if (input clk, input rstn);

  logic [31:0] paddr;
  logic        pwrite;
  logic        psel;
  logic        penable;
  logic [31:0] pwdata;
  logic [31:0] prdata;
  logic        pready;
//表示APB传输的过程中发生了错误,可能是读写;只在传输的最后一拍拉高
  logic        pslverr;

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

  import uvm_pkg::*;
  `include "uvm_macros.svh"

//这里信号的方向和MCDF中的完全相反,addr等信号是从总线给到MCDF的,所以这里是输出
  clocking cb_mst @(posedge clk);
    default input #1ps output #1ps;
    output paddr, pwrite, psel, penable, pwdata;
    input prdata, pready, pslverr;
  endclocking : cb_mst

//如果是总线的从设备(MCDF),则方向和MCDF的一样
  // clocking cb_slv @(posedge clk);
  //   default input #1ps output #1ps;
  //   input paddr, pwrite, psel, penable, pwdata;
  //   output prdata, pready, pslverr;
  // endclocking : cb_slv

//monitor的时钟块,所有信号都是input
  clocking cb_mon @(posedge clk);
    default input #1ps output #1ps;
    input paddr, pwrite, psel, penable, pwdata, prdata, pready, pslverr;
  endclocking : cb_mon

  // APB指令的覆盖组

//覆盖组 组名 @触发条件;然后是覆盖点 信号名{设置打印与收集 编写bins}
  covergroup cg_apb_command @(posedge clk iff rstn);
    pwrite: coverpoint pwrite{
      type_option.weight = 0;
      bins write = {1};
      bins read  = {0};
    }//pwrite信号有两种状态,拉高为write拉低为read,所以有两个bins,下面同理
    psel : coverpoint psel{
      type_option.weight = 0;
      bins sel   = {1};
      bins unsel = {0};
    }
//apb指令有三个,binsof指定覆盖点,指令为write时对应的覆盖点是sel拉高和write拉高
    cmd  : cross pwrite, psel{
      bins cmd_write = binsof(psel.sel) && binsof(pwrite.write);
      bins cmd_read  = binsof(psel.sel) && binsof(pwrite.read);
      bins cmd_idle  = binsof(psel.unsel);
    }
  endgroup: cg_apb_command

  // APB连续传输多个数据的覆盖组
  covergroup cg_apb_trans_timing_group @(posedge clk iff rstn);
    psel: coverpoint psel{//传输n个数据psel拉高2*n拍,
      bins single   = (0 => 1 => 1  => 0); 
      bins burst_2  = (0 => 1 [*4]  => 0); 
      bins burst_4  = (0 => 1 [*8]  => 0); 
      bins burst_8  = (0 => 1 [*16] => 0); 
      bins burst_16 = (0 => 1 [*32] => 0); 
      bins burst_32 = (0 => 1 [*64] => 0); 
    }
    penable: coverpoint penable {
//enable连续传输时每两拍拉高一拍,传输单个数据时记得是有idel_cycle的设置所以是2-10拍
      bins single = (0 => 1 => 0 [*2:10] => 1);
      bins burst  = (0 => 1 => 0         => 1);
    }
  endgroup: cg_apb_trans_timing_group

  // APB读写顺序检测的覆盖组
//触发条件多了个penable,只有enable拉高的读写才是有效读写
  covergroup cg_apb_write_read_order_group @(posedge clk iff (rstn && penable));
    write_read_order: coverpoint pwrite{
      bins write_write = (1 => 1);
      bins write_read  = (1 => 0);
      bins read_write  = (0 => 1);
      bins read_read   = (0 => 0);
    } 
  endgroup: cg_apb_write_read_order_group

//覆盖组需要例化,且声明为automatic型
  initial begin : coverage_control
    if(has_coverage) begin
      automatic cg_apb_command cg0 = new();
      automatic cg_apb_trans_timing_group cg1 = new();
      automatic cg_apb_write_read_order_group cg2 = new();
    end
  end

  //属性和断言的编写,描述具体的感兴趣的序列,首先是断言属性
//第一个是希望总线过来的地址不存在x,地址和sel为高时(交叠蕴含同一拍)用isunknown检查地址中是否存在x和z,若有则为1,取非没有出1,assert这个property要为1才不会报错
//断言是检查时序的,可以通过一个max_quit什么的设置error数量,到了就finish,具体忘了,等uvm的时候看看
  property p_paddr_no_x;
    @(posedge clk) psel |-> !$isunknown(paddr);
  endproperty: p_paddr_no_x
  assert property(p_paddr_no_x) else `uvm_error("ASSERT", "PADDR is unknown when PSEL is high")

  property p_psel_rose_next_cycle_penable_rise;
    @(posedge clk) $rose(psel) |=> $rose(penable);
  endproperty: p_psel_rose_next_cycle_penable_rise
  assert property(p_psel_rose_next_cycle_penable_rise) else `uvm_error("ASSERT", "PENABLE not rose after 1 cycle PSEL rose")

  property p_penable_rose_next_cycle_fall;
    @(posedge clk) penable && pready |=> $fell(penable);
  endproperty: p_penable_rose_next_cycle_fall
  assert property(p_penable_rose_next_cycle_fall) else `uvm_error("ASSERT", "PENABLE not fall after 1 cycle PENABLE rose")

  property p_pwdata_stable_during_trans_phase;
    @(posedge clk) ((psel && !penable) ##1 (psel && penable)) |-> $stable(pwdata);
  endproperty: p_pwdata_stable_during_trans_phase
  assert property(p_pwdata_stable_during_trans_phase) else `uvm_error("ASSERT", "PWDATA not stable during transaction phase")

  property p_paddr_stable_until_next_trans;
    logic[31:0] addr1, addr2;
    @(posedge clk) first_match(($rose(penable),addr1=paddr) ##1 ((psel && !penable)[=1],addr2=$past(paddr))) |-> addr1 == addr2;
  endproperty: p_paddr_stable_until_next_trans
  assert property(p_paddr_stable_until_next_trans) else `uvm_error("ASSERT", "PADDR not stable until next transaction start")

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

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

  //property覆盖率
//和上面的区别是这里是收集覆盖率
//第一个是非连续写操作
  property p_write_during_nonburst_trans;
    @(posedge clk) $rose(penable) |-> pwrite throughout (##1 (!penable)[*2] ##1 penable[=1]);
  endproperty: p_write_during_nonburst_trans
  cover property(p_write_during_nonburst_trans);

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

  property p_write_read_burst_trans;
    logic[31:0] addr;
    @(posedge clk) ($rose(penable) && pwrite, addr=paddr) |-> (##2 ($rose(penable) && !pwrite && addr==paddr)); 
  endproperty: p_write_read_burst_trans
  cover property(p_write_read_burst_trans);

  property p_write_twice_read_burst_trans;
    logic[31:0] addr;
    @(posedge clk) ($rose(penable) && pwrite, addr=paddr) |-> (##2 ($rose(penable) && pwrite && addr==paddr) ##2 ($rose(penable) && !pwrite && addr==paddr) );
  endproperty: p_write_twice_read_burst_trans
  cover property(p_write_twice_read_burst_trans);

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

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

  property p_read_write_read_burst_trans;
    logic[31:0] addr;
    @(posedge clk) ($rose(penable) && pwrite, addr=paddr) |-> ##2 ($rose(penable) && !pwrite && addr==paddr);  
  endproperty: p_read_write_read_burst_trans
  cover property(p_read_write_read_burst_trans);

//控制断言的开关
  initial begin: assertion_control
    fork
      forever begin
        wait(rstn == 0);
        $assertoff();
        wait(rstn == 1);
        if(has_checks) $asserton();
      end
    join_none
  end

endinterface : apb_if

`endif // APB_IF_SV

tb中的接口

bind_intf是什么接口?印象中好像是什么绑定接口

`timescale 1ns/1ps
`include "apb_if.sv"//上面的apb接口include了

//node的接口,不包含slv_en_i和给下线的信号,为什么?
interface chnl_intf(input clk, input rstn);
  logic [31:0] ch_data;
  logic        ch_data_p;
  logic        ch_valid;
  logic        ch_wait;
  logic        ch_parity_err;
  clocking drv_ck @(posedge clk);//驱动时钟块,channel是指哪个模块的?
    default input #1ps output #1ps;
    output ch_data, ch_valid, ch_data_p;
    input ch_wait, ch_parity_err;
  endclocking
  clocking mon_ck @(posedge clk);//给到monitor的信号全部为input
    default input #1ps output #1ps;
    input ch_data, ch_valid, ch_data_p, ch_wait, ch_parity_err;
  endclocking
endinterface

//fmt接口,只打包与外界的五个信号,fmt输出的这里作为输入
interface fmt_intf(input clk, input rstn);
  logic        fmt_ready;
  logic        fmt_valid;
  logic [31:0] fmt_data;
  logic        fmt_first;
  logic        fmt_last;
  clocking drv_ck @(posedge clk);
    default input #1ps output #1ps;
    input fmt_valid, fmt_data, fmt_first, fmt_last;
    output fmt_ready;
  endclocking
  clocking mon_ck @(posedge clk);
    default input #1ps output #1ps;
    input fmt_ready, fmt_valid, fmt_data, fmt_first, fmt_last;
  endclocking
endinterface

//mcdf的接口打包reg_if、chnl_if、fmt_if里面没有的信号
interface mcdf_intf(output logic clk, output logic rstn);
  logic [3:0] chnl_en;   //node中的slv_en_i
  clocking mon_ck @(posedge clk);
    default input #1ps output #1ps;
    input chnl_en;
  endclocking

  //产生clk
  initial begin 
    clk <= 0;
    forever begin
      #5 clk <= !clk;
    end
  end
  
  //复位信号
  initial begin 
    #10 rstn <= 0;
    repeat(10) @(posedge clk);
    rstn <= 1;
  end
endinterface

//这是什么接口?
interface bind_intf(
  input logic [5:0] slv0_freeslot_bind, 
  input logic [5:0] slv1_freeslot_bind, 
  input logic [5:0] slv2_freeslot_bind, 
  input logic [5:0] slv3_freeslot_bind
);
endinterface

tb后续

module tb;
  logic         clk;
  logic         rstn;

//顶层MCDF所有信号与刚才定义的接口进行连接
  mcdf dut(
    .clk_i               (clk                     ) ,
    .rst_n_i             (rstn                    ) ,
    .slv0_data_i         (chnl0_if.ch_data        ) ,  
    .slv0_data_p_i       (chnl0_if.ch_data_p      ) , 
    .slv0_valid_i        (chnl0_if.ch_valid       ) ,  
    .slv0_wait_o         (chnl0_if.ch_wait        ) , 
    .slv0_parity_err_o   (chnl0_if.ch_parity_err  ) , 
    .slv1_data_i         (chnl1_if.ch_data        ) ,  
    .slv1_data_p_i       (chnl1_if.ch_data_p      ) , 
    .slv1_valid_i        (chnl1_if.ch_valid       ) ,  
    .slv1_wait_o         (chnl1_if.ch_wait        ) , 
    .slv1_parity_err_o   (chnl1_if.ch_parity_err  ) , 
    .slv2_data_i         (chnl2_if.ch_data        ) ,  
    .slv2_data_p_i       (chnl2_if.ch_data_p      ) , 
    .slv2_valid_i        (chnl2_if.ch_valid       ) ,  
    .slv2_wait_o         (chnl2_if.ch_wait        ) , 
    .slv2_parity_err_o   (chnl2_if.ch_parity_err  ) , 
    .slv3_data_i         (chnl3_if.ch_data        ) ,  
    .slv3_data_p_i       (chnl3_if.ch_data_p      ) , 
    .slv3_valid_i        (chnl3_if.ch_valid       ) ,  
    .slv3_wait_o         (chnl3_if.ch_wait        ) , 
    .slv3_parity_err_o   (chnl3_if.ch_parity_err  ) , 
    .paddr_i             (reg_if.paddr[7:0]       ) ,
    .pwr_i               (reg_if.pwrite           ) ,
    .pen_i               (reg_if.penable          ) ,
    .psel_i              (reg_if.psel             ) ,
    .pwdata_i            (reg_if.pwdata           ) ,
    .prdata_o            (reg_if.prdata           ) ,
    .pready_o            (reg_if.pready           ) ,
    .pslverr_o           (reg_if.pslverr          ) , 
    .rev_rdy_i           (fmt_if.fmt_ready        ) , 
    .pkg_vld_o           (fmt_if.fmt_valid        ) , 
    .pkg_dat_o           (fmt_if.fmt_data         ) , 
    .pkg_fst_o           (fmt_if.fmt_first        ) , 
    .pkg_lst_o           (fmt_if.fmt_last         )   
  );


  import uvm_pkg::*;
  `include "uvm_macros.svh"
  import mcdf_pkg::*;

  apb_if    reg_if(.*);
  chnl_intf chnl0_if(.*);
  chnl_intf chnl1_if(.*);
  chnl_intf chnl2_if(.*);
  chnl_intf chnl3_if(.*);
  fmt_intf  fmt_if(.*);
  mcdf_intf mcdf_if(.*);

  // mcdf interface monitoring MCDF ports and signals
  assign mcdf_if.chnl_en = tb.dut.inst_reg_if.slv_en_o;
  
  initial begin 
    // do interface configuration from top tb (HW) to verification env (SW)
    uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.chnl_agts[0]",  "vif",          chnl0_if);
    uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.chnl_agts[1]",  "vif",          chnl1_if);
    uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.chnl_agts[2]",  "vif",          chnl2_if);
    uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.chnl_agts[3]",  "vif",          chnl3_if);
    uvm_config_db#(virtual apb_if   )::set(uvm_root::get(), "uvm_test_top.env.reg_agt",       "vif",          reg_if  );
    uvm_config_db#(virtual fmt_intf )::set(uvm_root::get(), "uvm_test_top.env.fmt_agt",       "vif",          fmt_if  );
    uvm_config_db#(virtual mcdf_intf)::set(uvm_root::get(), "uvm_test_top.env.*",             "mcdf_vif",     mcdf_if);
    uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.*",             "chnl_vifs[0]", chnl0_if);
    uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.*",             "chnl_vifs[1]", chnl1_if);
    uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.*",             "chnl_vifs[2]", chnl2_if);
    uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.*",             "chnl_vifs[3]", chnl3_if);
    uvm_config_db#(virtual apb_if   )::set(uvm_root::get(), "uvm_test_top.env.*",             "reg_vif",      reg_if  );
    uvm_config_db#(virtual fmt_intf )::set(uvm_root::get(), "uvm_test_top.env.*",             "fmt_vif",      fmt_if  );
    // If no external configured via +UVM_TESTNAME=my_test, the default test is
    // mcdf_data_consistence_basic_test
    run_test("mcdf_data_consistence_basic_test");
  end
  
  
  //--------------------------------------------------------
  // Example for how to probe signals
  //--------------------------------------------------------
  logic [5:0] slv0_freeslot_vlog, slv1_freeslot_vlog, slv2_freeslot_vlog, slv3_freeslot_vlog;
  logic [5:0] slv0_freeslot_mti, slv1_freeslot_mti, slv2_freeslot_mti, slv3_freeslot_mti;
  logic [5:0] slv0_freeslot_vcs, slv1_freeslot_vcs, slv2_freeslot_vcs, slv3_freeslot_vcs;
  // Verilog hierarchy probe
  assign slv0_freeslot_vlog = tb.dut.slv0_freeslot_s;
  assign slv1_freeslot_vlog = tb.dut.slv1_freeslot_s;
  assign slv2_freeslot_vlog = tb.dut.slv2_freeslot_s;
  assign slv3_freeslot_vlog = tb.dut.slv3_freeslot_s;

  // Questasim supplied probe 
  // initial begin
  //   $init_signal_spy("tb.dut.slv0_freeslot_s", "tb.slv0_freeslot_mti");
  //   $init_signal_spy("tb.dut.slv1_freeslot_s", "tb.slv1_freeslot_mti");
  //   $init_signal_spy("tb.dut.slv2_freeslot_s", "tb.slv2_freeslot_mti");
  //   $init_signal_spy("tb.dut.slv3_freeslot_s", "tb.slv3_freeslot_mti");
  // end

  // VCS supplied probe
  initial begin
    $hdl_xmr("tb.dut.slv0_freeslot_s", "tb.slv0_freeslot_vcs");
    $hdl_xmr("tb.dut.slv1_freeslot_s", "tb.slv1_freeslot_vcs");
    $hdl_xmr("tb.dut.slv2_freeslot_s", "tb.slv2_freeslot_vcs");
    $hdl_xmr("tb.dut.slv3_freeslot_s", "tb.slv3_freeslot_vcs");
  end

  //括号内是RTL的信号
  bind tb.dut bind_intf bind_if0(
     .slv0_freeslot_bind(slv0_freeslot_s)
    ,.slv1_freeslot_bind(slv1_freeslot_s)
    ,.slv2_freeslot_bind(slv2_freeslot_s)
    ,.slv3_freeslot_bind(slv3_freeslot_s)
  );
endmodule

软件部分

chnl_pkg

 消息方法对应4个宏:

`uvm_info(ID, 信息内容,过滤级别),warning和error和fatal没有过滤级别这个参数。

ID可以用get_type_name()获取,信息内容可以“”也可以是字符串s

过滤级别有4个:UVM_HIGH,UVM_MEDIUM,UVM_LOW,UVM_NONE

sequence中有个宏`uvm_declare_p_sequencer(chnl_sequencer),将chnl_sequencer转换成了p_sequencer,相当于?

chnl_sequencer p_sequencer;

也就是用了这个宏进行转换,不需要再声明,记住结论:在sequence中要用,参数是当前的sequencer

uvm_declare_p_sequencer_zilan23的博客-CSDN博客_uvm_declare_p_sequencer

 发送序列用的宏

 uvm_do_with对于item来说,创建了item,同步,做约束的随机化,发送

顶层环境中,根据uvm的config机制,配置接口:在tb的initial块中做

uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.chnl_agts[0]",  "vif",          chnl0_if);
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.chnl_agts[1]",  "vif",          chnl1_if);
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.chnl_agts[2]",  "vif",          chnl2_if);
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top.env.chnl_agts[3]",  "vif",          chnl3_if);

在agent的build_phase中做:

      if(!uvm_config_db#(virtual chnl_intf)::get(this,"","vif", vif)) begin
        `uvm_fatal("GETVIF","cannot get vif handle from config DB")
      end

我的第二个UVM代码——连接interface - 腾讯云开发者社区-腾讯云

//接下来进入软件验证环境的搭建,chnl_pkg对应node,先看结构
//首先由于用到UVM,开头的import和include少不了
//node有需要传输的数据,传输数据需要用到seq、sqeuencer、driver,检测数据的monitor,包含这几个结构的agent
package chnl_pkg;
  import uvm_pkg::*;
  `include "uvm_macros.svh"

  //sequence item继承于uvm_transaction——uvm_object
  //seq item的创建和随机化发生在seq中的body任务
  class chnl_trans extends uvm_sequence_item;
    rand bit[31:0] data[];//成员变量全部为随机变量rand,数据类型用动态数组
    rand int ch_id;
    rand int pkt_id;
    rand int data_nidles;
    rand int pkt_nidles;
    bit rsp;//单比特rsp在写入数据并转换句柄后点亮表示完成写入
//对成员变量做soft约束也就是初始化,具体在发包的时候会做随机化替代这里的初始化
    constraint cstr{
	//数据位宽在4到32位之间
      soft data.size inside {[4:32]};
	//数据之间按一定规律生成
      foreach(data[i]) soft data[i] == 'hC000_0000 + (this.ch_id<<24) + (this.pkt_id<<8) + i;
    //通道id和包的id默认是0  
	  soft ch_id == 0;
      soft pkt_id == 0;
	  //数据间隔和包的间隔在一定范围内,这跟idle_cycle有关?
      soft data_nidles inside {[0:2]};
      soft pkt_nidles inside {[1:10]};
    };
//注册,由于有成员变量,做域的自动化
    `uvm_object_utils_begin(chnl_trans)
      `uvm_field_array_int(data, UVM_ALL_ON)
      `uvm_field_int(ch_id, UVM_ALL_ON)
      `uvm_field_int(pkt_id, UVM_ALL_ON)
      `uvm_field_int(data_nidles, UVM_ALL_ON)
      `uvm_field_int(pkt_nidles, UVM_ALL_ON)
      `uvm_field_int(rsp, UVM_ALL_ON)
    `uvm_object_utils_end
//每个class必做的new函数,由于是object类,只有name
    function new (string name = "chnl_trans");
      super.new(name);
    endfunction
  endclass: chnl_trans
  
//driver的工作原理:通过seq_item_port从sequencer拿到一个seq_item,写入数据后返回rsp句柄,通过seq_item_port.item_done(rsp);结束发送
//注意是参数类#(chnl_trans)
  class chnl_driver extends uvm_driver #(chnl_trans);
  //注意这里就有接口了
    local virtual chnl_intf intf;
//每个class都要的注册,无成员变量,不需要域的自动化
    `uvm_component_utils(chnl_driver)
  //component的new函数有两个参数(和object区分)
    function new (string name = "chnl_driver", uvm_component parent);
      super.new(name, parent);
    endfunction
//由于有接口,要将接口连接到driver
    function void set_interface(virtual chnl_intf intf);
      if(intf == null)
        $error("interface handle is NULL, please check if target interface has been intantiated");
      else
        this.intf = intf;
    endfunction
//uvm_component的phase机制,只有run_phase是耗时的任务
//driver的run_phase同时执行了驱动和复位两个线程
    task run_phase(uvm_phase phase);
      fork
       this.do_drive();
       this.do_reset();
      join
    endtask

    task do_reset();
      forever begin//intf是这里定义的chnl_intf接口名,对应tb中的接口
        @(negedge intf.rstn);
        intf.ch_valid <= 0;//这三个是输出的三个信号。不空就valid
        intf.ch_data <= 0;
        intf.ch_data_p <= 0;
      end
    endtask
//驱动包的任务,声明两个item的句柄,通过seq_item_port调用get_next_item(req)方法
//拿到item,然后执行驱动任务chnl_write,通过接口中的时钟块点亮三个输出端数据
//等待:奇偶校验无错误、FIFO不满、slv_en_i为高,则wait拉低,包得到传送
//然后根据包中的data_nidles和pkt_nidles执行chnl_idle()
//完成以上驱动任务后将包的句柄进行克隆,由于item继承于object,克隆得到的句柄是父类句柄,需要将其转换成子类句柄
//点亮转换后的单比特rsp表示完成包的驱动,通过seq_item_port.item_done(rsp)返回rsp给sequence
    task do_drive();
      chnl_trans req, rsp;
      @(posedge intf.rstn);
      forever begin
        seq_item_port.get_next_item(req);
        this.chnl_write(req);
        void'($cast(rsp, req.clone()));
        rsp.rsp = 1;
//item中没有定义的函数,应该是自带的函数,从req获取seq id作为rsp的设置seq id的参数
        rsp.set_sequence_id(req.get_sequence_id());
        seq_item_port.item_done(rsp);
      end
    endtask

//driver和sequencer之间通信的tlm端口如下:采取get模式
//driver作为initiator,例化了两个端口,默认的REQ类型是uvm_sequence_item父类
//uvm_seq_item_pull_port #(REQ, RSP) seq_item_port
//uvm_analysis_port #(RSP) rsp_port
  
    task chnl_write(input chnl_trans t);
      foreach(t.data[i]) begin
        @(posedge intf.clk);
        intf.drv_ck.ch_valid <= 1;
        intf.drv_ck.ch_data <= t.data[i];
        intf.drv_ck.ch_data_p <= get_parity(t.data[i]);
        @(negedge intf.clk);
        wait(intf.ch_wait === 'b0);
//消息打印第一个参数ID用get_type_name获得类型名,第二个参数打印内容用
//系统函数sformatf再写,第三个参数过滤级别为HIGH
        `uvm_info(get_type_name(), $sformatf("sent data 'h%8x", t.data[i]), UVM_HIGH)
//get_type_name获取的可能是item的类型,对应seq,针对多个seq同时向sequencer发包
        repeat(t.data_nidles) chnl_idle();
      end
      repeat(t.pkt_nidles) chnl_idle();
    endtask
    
    task chnl_idle();
      @(posedge intf.clk);
      intf.drv_ck.ch_valid <= 0;
      intf.drv_ck.ch_data <= 0;
      intf.drv_ck.ch_data_p <= 0;
    endtask

    function get_parity(bit[31:0] data);
      return ^data;//对32位数据缩位异或可得奇偶校验位
    endfunction
  endclass: chnl_driver

//sequencer只需要注册和new函数,注意是参数类#(chnl_trans)
//sequencer继承于sequencer_base——component,所以也是两个参数
  class chnl_sequencer extends uvm_sequencer #(chnl_trans);
    `uvm_component_utils(chnl_sequencer)
    function new (string name = "chnl_sequencer", uvm_component parent);
      super.new(name, parent);
    endfunction
  endclass: chnl_sequencer

//sequence继承于item——transaction——object,item在这产生
  class chnl_data_sequence extends uvm_sequence #(chnl_trans);
    rand int pkt_id = 0;//定义的时候就赋值
    rand int ch_id = -1;
    rand int data_nidles = -1;
    rand int pkt_nidles = -1;
    rand int data_size = -1;
    rand int ntrans = 10;//包的数量?
    rand int data[];
    constraint cstr{//这里约束为什么是-1?
      soft pkt_id == 0;
      soft ch_id == -1;
      soft data_nidles == -1;
      soft pkt_nidles == -1;
      soft data_size == -1;
      soft ntrans == 10;
      soft data.size() == data_size;
      foreach(data[i]) soft data[i] == -1;
    };
    `uvm_object_utils_begin(chnl_data_sequence)
      `uvm_field_int(pkt_id, UVM_ALL_ON)
      `uvm_field_int(ch_id, UVM_ALL_ON)
      `uvm_field_int(data_nidles, UVM_ALL_ON)
      `uvm_field_int(pkt_nidles, UVM_ALL_ON)
      `uvm_field_int(data_size, UVM_ALL_ON)
      `uvm_field_int(ntrans, UVM_ALL_ON)
    `uvm_object_utils_end
	//将p_sequencer设置成chnl_sequencer
    `uvm_declare_p_sequencer(chnl_sequencer)
    function new (string name = "chnl_data_sequence");
      super.new(name);
    endfunction

    task body();
      repeat(ntrans) send_trans();
    endtask

    task send_trans();
      chnl_trans req, rsp;
	  //发送item的宏,第一个参数是发送的item的句柄,第二个参数是约束随机化
	  //约束块,这里是条件约束,在这个class中如果这五个成员变量大于等于0,就赋给
      `uvm_do_with(req, {local::ch_id >= 0 -> ch_id == local::ch_id; 
                         local::pkt_id >= 0 -> pkt_id == local::pkt_id;
                         local::data_nidles >= 0 -> data_nidles == local::data_nidles;
                         local::pkt_nidles >= 0 -> pkt_nidles == local::pkt_nidles;
                         local::data_size >0 -> data.size() == local::data_size; 
                         foreach(local::data[i]) local::data[i] >= 0 -> data[i] == local::data[i];
                         })//这里的随机化只针对一个包,然后重复ntrans次
      this.pkt_id++;//包的id加一
      `uvm_info(get_type_name(), req.sprint(), UVM_HIGH)
      get_response(rsp);//sequence从driver那获得rsp表示完成一次握手
      `uvm_info(get_type_name(), rsp.sprint(), UVM_HIGH)
      assert(rsp.rsp)//做个断言,若rsp不点亮则报错,why is 断言?
        else $error("[RSPERR] %0t error response received!", $time);
    endtask

    function void post_randomize();//随机化之后打印
      string s;
      s = {s, "AFTER RANDOMIZATION \n"};
      s = {s, "=======================================\n"};
      s = {s, "chnl_data_sequence object content is as below: \n"};
      s = {s, super.sprint()};
      s = {s, "=======================================\n"};
      `uvm_info(get_type_name(), s, UVM_HIGH)
    endfunction
  endclass: chnl_data_sequence

  typedef struct packed {//packed表示合并,结构体可以存放不同数据类型的变量
//typedef定义新的类型,32位的data和2位id合起来称为mon_data_t
    bit[31:0] data;
    bit[1:0] id;
  } mon_data_t;

  //monitor没有太多介绍,继承于comp,处理方法和其他comp类似
  class chnl_monitor extends uvm_monitor;
  //在接口中声明过monitor的时钟块和驱动的时钟块
  //由于要接入信号,也要用到接口,同样声明一个monitor中的local接口
    local virtual chnl_intf intf;
    uvm_analysis_port #(mon_data_t) mon_ana_port;
//analysis port是一initiator对多target的应用,push模式,从port调用各个target的write函数实现数据传输
//需要在顶层进行analysis port和imp的连接,在initiator调用write
    `uvm_component_utils(chnl_monitor)

    function new(string name="chnl_monitor", uvm_component parent);
      super.new(name, parent);
	  //ap不是组件自带的,要用户声明后在new函数例化
      mon_ana_port = new("mon_ana_port", this);
    endfunction

    function void set_interface(virtual chnl_intf intf);
      if(intf == null)//配置接口
        $error("interface handle is NULL, please check if target interface has been intantiated");
      else
        this.intf = intf;
    endfunction

    task run_phase(uvm_phase phase);
      this.mon_trans();//comp的子类,phase机制
    endtask

    task mon_trans();
      mon_data_t m;
      forever begin//在valid拉高wait拉低时才采集
        @(intf.mon_ck iff (intf.mon_ck.ch_valid==='b1 && intf.mon_ck.ch_wait==='b0));
        m.data = intf.mon_ck.ch_data;
        mon_ana_port.write(m);//initiator端调用write函数,参数是句柄
        `uvm_info(get_type_name(), $sformatf("monitored channel data 'h%8x", m.data), UVM_HIGH)
      end
    endtask
  endclass: chnl_monitor
  
  //agent作为chnl_pkg的顶层,build_phase获取接口,例化3个comp
  //例化用  组件名=类名::type_id::create(“组件名”,this)
  //connect_phase连接driver和sequencer,并把接口连上
  class chnl_agent extends uvm_agent;
    chnl_driver driver;
    chnl_monitor monitor;
    chnl_sequencer sequencer;
    local virtual chnl_intf vif;

    `uvm_component_utils(chnl_agent)

    function new(string name = "chnl_agent", uvm_component parent);
      super.new(name, parent);
    endfunction

    function void build_phase(uvm_phase phase);
      super.build_phase(phase);
//#(配置的类型),this,"","vif"为存储路径,vif为传递的接口(已声明)
      if(!uvm_config_db#(virtual chnl_intf)::get(this,"","vif", vif)) begin
        `uvm_fatal("GETVIF","cannot get vif handle from config DB")
      end
      driver = chnl_driver::type_id::create("driver", this);
      monitor = chnl_monitor::type_id::create("monitor", this);
      sequencer = chnl_sequencer::type_id::create("sequencer", this);
    endfunction

    function void connect_phase(uvm_phase phase);
      super.connect_phase(phase);
      driver.seq_item_port.connect(sequencer.seq_item_export);
      this.set_interface(vif);
    endfunction

    function void set_interface(virtual chnl_intf vif);
      driver.set_interface(vif);
      monitor.set_interface(vif);
    endfunction
  endclass: chnl_agent

endpackage

fmt_pkg

复习mailbox

SV--线程(mailbox)_ICer吼吼的博客-CSDN博客_sv中mailbox

//和硬件fmt对比了一下,感觉硬件和软件写的模型不太一样,这里fifo和带宽会变
package fmt_pkg;
  import uvm_pkg::*;
  `include "uvm_macros.svh"
//枚举类型:FIFO深度和数据位宽
  typedef enum {SHORT_FIFO, MED_FIFO, LONG_FIFO, ULTRA_FIFO} fmt_fifo_t;
  typedef enum {LOW_WIDTH, MED_WIDTH, HIGH_WIDTH, ULTRA_WIDTH} fmt_bandwidth_t;

//结构是一样的,从item开始
  //将fifo深度和带宽声明为随机变量
  class fmt_trans extends uvm_sequence_item;
    rand fmt_fifo_t fifo;
    rand fmt_bandwidth_t bandwidth;
    bit [7:0] length;
    bit [31:0] data[];
    bit [7:0] ch_id;
    bit [31:0] parity;
    bit rsp;

    constraint cstr{//默认为medium
      soft fifo == MED_FIFO;
      soft bandwidth == MED_WIDTH;
    };

    `uvm_object_utils_begin(fmt_trans)
      `uvm_field_enum(fmt_fifo_t, fifo, UVM_ALL_ON)
      `uvm_field_enum(fmt_bandwidth_t, bandwidth, UVM_ALL_ON)
      `uvm_field_int(length, UVM_ALL_ON)
      `uvm_field_array_int(data, UVM_ALL_ON)
      `uvm_field_int(ch_id, UVM_ALL_ON)
      `uvm_field_int(rsp, UVM_ALL_ON)
    `uvm_object_utils_end

    function new (string name = "fmt_trans");
      super.new(name);
    endfunction
  endclass

  //driver
  class fmt_driver extends uvm_driver #(fmt_trans);
    local virtual fmt_intf intf;

    local mailbox #(bit[31:0]) fifo;
    local int fifo_bound;
    local int data_consum_peroid;

    `uvm_component_utils(fmt_driver)

    function new (string name = "fmt_driver", uvm_component parent);
      super.new(name, parent);
      this.fifo = new();//mailbox需要例化
      this.fifo_bound = 4096;//fifo深度初始化
      this.data_consum_peroid = 1;//带宽初始化
    endfunction
  
    function void set_interface(virtual fmt_intf intf);
      if(intf == null)
        $error("interface handle is NULL, please check if target interface has been intantiated");
      else
        this.intf = intf;
    endfunction

    task run_phase(uvm_phase phase);
      fork
        this.do_receive();
        this.do_consume();
        this.do_config();
        this.do_reset();
      join
    endtask

    task do_config();
      fmt_trans req, rsp;
      forever begin//从seq拿到item
        seq_item_port.get_next_item(req);
        case(req.fifo)//看看里面的随机变量fifo深度是哪个
          SHORT_FIFO: this.fifo_bound = 64;
          MED_FIFO: this.fifo_bound = 256;
          LONG_FIFO: this.fifo_bound = 512;
          ULTRA_FIFO: this.fifo_bound = 2048;
        endcase
		//把得到的深度作为参数例化给当前类的邮箱fifo
        this.fifo = new(this.fifo_bound);
        case(req.bandwidth)//看看里面的随机变量带宽是哪个,带宽越大消化时间越短
          LOW_WIDTH: this.data_consum_peroid = 8;
          MED_WIDTH: this.data_consum_peroid = 4;
          HIGH_WIDTH: this.data_consum_peroid = 2;
          ULTRA_WIDTH: this.data_consum_peroid = 1;
        endcase
		//其他操作是一样的
        void'($cast(rsp, req.clone()));
        rsp.rsp = 1;
        rsp.set_sequence_id(req.get_sequence_id());
        seq_item_port.item_done(rsp);
      end
    endtask

    task do_reset();
      forever begin
        @(negedge intf.rstn) 
        intf.fmt_ready <= 0;//根据接口,output只有一个fmt_ready
      end
    endtask

    task do_receive();//fmt接收来自node的data,就是存进fifo
      forever begin
        @(intf.drv_ck); #10ps;
        if(intf.fmt_valid === 1'b1) begin//实验0里面的grant信号
          forever begin
            if((this.fifo_bound-this.fifo.num()) >= 1)//why?
              break;//如果fifo容量和实际存储的数量的差大于等于1就停止
            @(intf.drv_ck); #10ps;
          end
          this.fifo.put(intf.fmt_data);
          #1ps; intf.fmt_ready <= 1;
        end//接收完数据后拉高ready
        else begin
          #1ps; intf.fmt_ready <= 0;
        end
      end
    endtask

    task do_consume();//消化数据需要一定时间,但是为什么用随机范围?
      bit[31:0] data;
      forever begin
        void'(this.fifo.try_get(data));
        repeat($urandom_range(1, this.data_consum_peroid)) @(posedge intf.clk);
      end
    endtask
  endclass: fmt_driver

//sequencer依然是注册和new函数即可
  class fmt_sequencer extends uvm_sequencer #(fmt_trans);
    `uvm_component_utils(fmt_sequencer)
    function new (string name = "fmt_sequencer", uvm_component parent);
      super.new(name, parent);
    endfunction
  endclass: fmt_sequencer

//sequence做item的随机化和在body中发送,做p_sequencer的宏
  class fmt_config_sequence extends uvm_sequence #(fmt_trans);
    rand fmt_fifo_t fifo = MED_FIFO;
    rand fmt_bandwidth_t bandwidth = MED_WIDTH;
    constraint cstr{
      soft fifo == MED_FIFO;
      soft bandwidth == MED_WIDTH;
    }

    `uvm_object_utils_begin(fmt_config_sequence)
      `uvm_field_enum(fmt_fifo_t, fifo, UVM_ALL_ON)
      `uvm_field_enum(fmt_bandwidth_t, bandwidth, UVM_ALL_ON)
    `uvm_object_utils_end
    `uvm_declare_p_sequencer(fmt_sequencer)

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

    task body();//是body不是run_phase
      send_trans();
    endtask
    
    task send_trans();
      fmt_trans req, rsp;
      `uvm_do_with(req, {local::fifo != MED_FIFO -> fifo == local::fifo; 
                         local::bandwidth != MED_WIDTH -> bandwidth == local::bandwidth;
                        })//随机化只要和初始化的不一样就做更改,寄存器会配置
      `uvm_info(get_type_name(), req.sprint(), UVM_HIGH)
      get_response(rsp);
      `uvm_info(get_type_name(), rsp.sprint(), UVM_HIGH)
      assert(rsp.rsp)//其他的相同
        else $error("[RSPERR] %0t error response received!", $time);
    endtask

    function void post_randomize();
      string s;
      s = {s, "AFTER RANDOMIZATION \n"};
      s = {s, "=======================================\n"};
      s = {s, "fmt_config_sequence object content is as below: \n"};
      s = {s, super.sprint()};
      s = {s, "=======================================\n"};
      `uvm_info(get_type_name(), s, UVM_HIGH)
    endfunction
  endclass: fmt_config_sequence

  // formatter monitor
  class fmt_monitor extends uvm_monitor;
    local string name;
    local virtual fmt_intf intf;
    uvm_analysis_port #(fmt_trans) mon_ana_port;

    `uvm_component_utils(fmt_monitor)

    function new(string name="fmt_monitor", uvm_component parent);
      super.new(name, parent);
      mon_ana_port = new("mon_ana_port", this);
    endfunction

    function void set_interface(virtual fmt_intf intf);
      if(intf == null)
        $error("interface handle is NULL, please check if target interface has been intantiated");
      else
        this.intf = intf;
    endfunction

    task run_phase(uvm_phase phase);
      this.mon_trans();
    endtask

//chnl_pkg没有例化item,为什么这里就要例化?
    task mon_trans();
      fmt_trans m;
      string s;
      forever begin//first、valid、ready拉高才开始读数据
        @(intf.mon_ck iff intf.mon_ck.fmt_first && intf.mon_ck.fmt_valid && intf.mon_ck.fmt_ready);
        m = new();//为什么这里就要例化?
		//由fmt数据包格式的图可知id和length是高16位
        m.length = intf.mon_ck.fmt_data[23:16];
        m.ch_id = intf.mon_ck.fmt_data[31:24];
		//动态数组分配空间,length+1是payload的数量,加上包头包尾+2
		//所以这里需要length+3的空间。还是看数据包的图
        m.data = new[m.length + 3];
        foreach(m.data[i]) begin
          m.data[i] = intf.mon_ck.fmt_data;
		  //size-1表示数据最高位是奇偶校验位
          if(i == m.data.size()-1) m.parity = m.data[i];
          if(i < m.data.size()-1) @(intf.mon_ck iff intf.mon_ck.fmt_valid && intf.mon_ck.fmt_ready);
        end//奇偶校验位以外的是数据位
        mon_ana_port.write(m);
        s = $sformatf("=======================================\n");
        s = {s, $sformatf("%0t %s monitored a packet: \n", $time, this.m_name)};
        s = {s, $sformatf("length = %0d: \n", m.length)};
        s = {s, $sformatf("chid = %0d: \n", m.ch_id)};
        foreach(m.data[i]) s = {s, $sformatf("data[%0d] = %8x \n", i, m.data[i])};
        s = {s, $sformatf("=======================================\n")};
        `uvm_info(get_type_name(), s, UVM_HIGH)
      end
    endtask
  endclass: fmt_monitor

  //agent顶层环境做的事情完全一样
  class fmt_agent extends uvm_agent;
    fmt_driver driver;//声明三个组件和接口
    fmt_monitor monitor;
    fmt_sequencer sequencer;
    local virtual fmt_intf vif;

    `uvm_component_utils(fmt_agent) //注册和new函数

    function new(string name = "chnl_agent", uvm_component parent);
      super.new(name, parent);
    endfunction

    function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      //build_phase get接口,例化三个组件
      if(!uvm_config_db#(virtual fmt_intf)::get(this,"","vif", vif)) begin
        `uvm_fatal("GETVIF","cannot get vif handle from config DB")
      end
      driver = fmt_driver::type_id::create("driver", this);
      monitor = fmt_monitor::type_id::create("monitor", this);
      sequencer = fmt_sequencer::type_id::create("sequencer", this);
    endfunction
//connect phase连接driver和sequencer,调用set_interface连接接口
    function void connect_phase(uvm_phase phase);
      super.connect_phase(phase);
      driver.seq_item_port.connect(sequencer.seq_item_export);
      this.set_interface(vif);
    endfunction

    function void set_interface(virtual fmt_intf vif);
      driver.set_interface(vif);
      monitor.set_interface(vif);
    endfunction
  endclass

endpackage

mcdf_rgm_pkg

电力电子转战数字IC20220818day63——uvm入门实验5_广工陈奕湘的博客-CSDN博客

复习一下UVM的寄存器模型。寄存器自己操作的trans是uvm_reg_bus_op,通过adapter转化到mcdf的总线

关于set_coverage()

uvm设计分析——reg - _9_8 - 博客园

(30)UVM 寄存器模型的应用场景和功能覆盖率收集_数字IC小白的日常修炼的博客-CSDN博客_uvm_subscriber

覆盖率选项设置

Systemverilog(绿皮书)第九章——功能覆盖率(四)覆盖选项_胡九筒的博客-CSDN博客

rgm_pkg只提供两个reg的代码即可,因为12个reg类的代码结构完全一样,只有域和值不一样,注意默认值的设置。rgm可以用工具生成。

电力电子转战数字IC20220824day68——uvm实战3_广工陈奕湘的博客-CSDN博客

 关于uvm_reg_map

[CU]reg model构建篇-uvm_reg_map(与前门访问相关) - _见贤_思齐 - 博客园

关于后门访问

  

  //寄存器模型rgm,每一个寄存器分别做一个class,一共12个类,继承于uvm_reg
  class slv_en_reg extends uvm_reg;
  //每个class包含:注册+域的声明+覆盖组+new函数+build_phase+sample函数
  //相当于只是做了覆盖组和例化和采样
    `uvm_object_utils(slv_en_reg)
    rand uvm_reg_field en;//[3:0]前4位是slv_en,其他位是预留位reserved
    rand uvm_reg_field reserved;
	
    covergroup value_cg;//两个域写成两个覆盖点组成一个覆盖组
      option.per_instance = 1;
      en: coverpoint en.value[3:0];
      reserved: coverpoint reserved.value[31:4];
    endgroup

//new函数有3个参数,name,寄存器位数32,是否要加入覆盖率的支持
    function new(string name = "slv_en_reg");
      super.new(name, 32, UVM_CVR_ALL);//若UVM_NO_COVERAGE,则不支持
	  //如果有覆盖率收集的需求,在new函数要set_coverage,然后例化覆盖组
      void'(set_coverage(UVM_CVR_FIELD_VALS));
      if(has_coverage(UVM_CVR_FIELD_VALS)) begin
        value_cg = new();//覆盖组必须例化才会收集
      end
    endfunction
	//build_phase例化+配置寄存器域
    virtual function void build();
      en = uvm_reg_field::type_id::create("en");
      reserved = uvm_reg_field::type_id::create("reserved");
//配置域的参数:第一个是this,然后两个表示寄存器对应的位
//第四个参数表示寄存器属性,读写还是只读,后五个参数表示默认值
      en.configure(this, 4, 0, "RW", 0, 'h0, 1, 0, 0);
      reserved.configure(this, 28, 4, "RO", 0, 'h0, 1, 0, 0);
    endfunction
	
	//覆盖率采样函数,需要自定义
    function void sample(
      uvm_reg_data_t data,//寄存器操作的trans——uvm_reg_bus_op,见截图
      uvm_reg_data_t byte_en,
      bit            is_read,
      uvm_reg_map    map//为什么连map也要?
    );
	//调用父类sample函数,输入参数相同
      super.sample(data, byte_en, is_read, map);
      sample_values(); //调用自定义sample_values函数
    endfunction
    function void sample_values();
      super.sample_values();//也调用父类sample_values函数
	  //new函数set了,这里如果get就调用覆盖组的sample
      if (get_coverage(UVM_CVR_FIELD_VALS)) begin
        value_cg.sample();
      end
    endfunction
  endclass

  class parity_err_clr_reg extends uvm_reg;
    `uvm_object_utils(parity_err_clr_reg)//注册
    rand uvm_reg_field err_clr;//寄存器域声明
    rand uvm_reg_field reserved;
	
    covergroup value_cg;//覆盖组编写
      option.per_instance = 1;
      err_clr: coverpoint err_clr.value[3:0];
      reserved: coverpoint reserved.value[31:4];
    endgroup
	
    function new(string name = "parity_err_clr_reg");
      super.new(name, 32, UVM_CVR_ALL);
      void'(set_coverage(UVM_CVR_FIELD_VALS));
      if(has_coverage(UVM_CVR_FIELD_VALS)) begin
        value_cg = new();
      end
    endfunction
    virtual function void build();
      err_clr = uvm_reg_field::type_id::create("err_clr");
      reserved = uvm_reg_field::type_id::create("reserved");
      err_clr.configure(this, 4, 0, "RW", 0, 'h0, 1, 0, 0);
      reserved.configure(this, 28, 4, "RO", 0, 'h0, 1, 0, 0);
    endfunction
    function void sample(
      uvm_reg_data_t data,
      uvm_reg_data_t byte_en,
      bit            is_read,
      uvm_reg_map    map
    );
      super.sample(data, byte_en, is_read, map);
      sample_values(); 
    endfunction
    function void sample_values();
      super.sample_values();
      if (get_coverage(UVM_CVR_FIELD_VALS)) begin
        value_cg.sample();
      end
    endfunction
  endclass

顶层的mcdf_rgm类

关于配置寄存器的9个参数

UVM 中的寄存器模型

//最后的rgm就是寄存器的顶层环境
  class mcdf_rgm extends uvm_reg_block;
    `uvm_object_utils(mcdf_rgm)//注册
    rand slv_en_reg slv_en;//声明所有reg为随机变量
    rand parity_err_clr_reg parity_err_clr;
    rand slv_id_reg slv_id;
    rand slv_len_reg slv_len;
    rand slv0_free_slot_reg slv0_free_slot;
    rand slv1_free_slot_reg slv1_free_slot;
    rand slv2_free_slot_reg slv2_free_slot;
    rand slv3_free_slot_reg slv3_free_slot;
    rand slv0_parity_err_reg slv0_parity_err;
    rand slv1_parity_err_reg slv1_parity_err;
    rand slv2_parity_err_reg slv2_parity_err;
    rand slv3_parity_err_reg slv3_parity_err;
    uvm_reg_map map;//map不要漏
    function new(string name = "mcdf_rgm");
      super.new(name, UVM_NO_COVERAGE);//顶层不收集覆盖率
    endfunction
	
	//build_phase对每一个reg进行:例化+调用configure配置+调用build
	//对map进行例化
    virtual function void build();
      slv_en = slv_en_reg::type_id::create("slv_en");
      slv_en.configure(this);
      slv_en.build();
      parity_err_clr = parity_err_clr_reg::type_id::create("parity_err_clr");
      parity_err_clr.configure(this);
      parity_err_clr.build();
      slv_id = slv_id_reg::type_id::create("slv_id");
      slv_id.configure(this);
      slv_id.build();
      slv_len = slv_len_reg::type_id::create("slv_len");
      slv_len.configure(this);
      slv_len.build();
      slv0_free_slot = slv0_free_slot_reg::type_id::create("slv0_free_slot");
      slv0_free_slot.configure(this);
      slv0_free_slot.build();
      slv1_free_slot = slv1_free_slot_reg::type_id::create("slv1_free_slot");
      slv1_free_slot.configure(this);
      slv1_free_slot.build();
      slv2_free_slot = slv2_free_slot_reg::type_id::create("slv2_free_slot");
      slv2_free_slot.configure(this);
      slv2_free_slot.build();
      slv3_free_slot = slv3_free_slot_reg::type_id::create("slv3_free_slot");
      slv3_free_slot.configure(this);
      slv3_free_slot.build();
      slv0_parity_err = slv0_parity_err_reg::type_id::create("slv0_parity_err");
      slv0_parity_err.configure(this);
      slv0_parity_err.build();
      slv1_parity_err = slv1_parity_err_reg::type_id::create("slv1_parity_err");
      slv1_parity_err.configure(this);
      slv1_parity_err.build();
      slv2_parity_err = slv2_parity_err_reg::type_id::create("slv2_parity_err");
      slv2_parity_err.configure(this);
      slv2_parity_err.build();
      slv3_parity_err = slv3_parity_err_reg::type_id::create("slv3_parity_err");
      slv3_parity_err.configure(this);
      slv3_parity_err.build();
	  
	  //map的例化,名字-基地址-总线宽度,单位是byte,32位对应4byte-大小端不知道是什么意思
      map = create_map("map", 'h0, 4, UVM_LITTLE_ENDIAN);
	  //将寄存器添加到map:寄存器-偏移地址-访问模式(只读/读写)
      map.add_reg(slv_en, 32'h00, "RW");
      map.add_reg(parity_err_clr, 32'h04, "RW");
      map.add_reg(slv_id, 32'h08, "RW");
      map.add_reg(slv_len, 32'h0C, "RW");
      map.add_reg(slv0_free_slot, 32'h80, "RO");
      map.add_reg(slv1_free_slot, 32'h84, "RO");
      map.add_reg(slv2_free_slot, 32'h88, "RO");
      map.add_reg(slv3_free_slot, 32'h8C, "RO");
      map.add_reg(slv0_parity_err, 32'h90, "RO");
      map.add_reg(slv1_parity_err, 32'h94, "RO");
      map.add_reg(slv2_parity_err, 32'h98, "RO");
      map.add_reg(slv3_parity_err, 32'h9C, "RO");
	  //后门访问:reg.add_hdl_path_slice(“name”,首位,末位)
      slv_en.add_hdl_path_slice("???", 0, 32);
      parity_err_clr.add_hdl_path_slice("???", 0, 32);
      slv_id.add_hdl_path_slice("???", 0, 32);
      slv_len.add_hdl_path_slice("???", 0, 32);
      slv0_free_slot.add_hdl_path_slice("???", 0, 32);
      slv1_free_slot.add_hdl_path_slice("???", 0, 32);
      slv2_free_slot.add_hdl_path_slice("???", 0, 32);
      slv3_free_slot.add_hdl_path_slice("???", 0, 32);
      slv0_parity_err.add_hdl_path_slice("???", 0, 32);
      slv1_parity_err.add_hdl_path_slice("???", 0, 32);
      slv2_parity_err.add_hdl_path_slice("???", 0, 32);
      slv3_parity_err.add_hdl_path_slice("???", 0, 32);
      add_hdl_path("???");//为什么不是具体的名称,见图
      lock_model();//结束地址映射关系,保证model不会被其他用户修改
    endfunction
	
	//定义函数获取域的长度,暂时不知道会在哪里调用到
    function int get_reg_field_length(int ch);
      int fd;
      case(ch)//根据channel id将slv_len的4个域调用get()获得
        0: fd = slv_len.slv0_len.get();
        1: fd = slv_len.slv1_len.get();
        2: fd = slv_len.slv2_len.get();
        3: fd = slv_len.slv3_len.get();
        default: `uvm_error("TYPERR", $sformatf("channel number should not be %0d", ch))
      endcase
      return fd;
    endfunction

    function int get_reg_field_id(int ch);
      int fd;
      case(ch)
        0: fd = slv_id.slv0_id.get();
        1: fd = slv_id.slv1_id.get();
        2: fd = slv_id.slv2_id.get();
        3: fd = slv_id.slv3_id.get();
        default: `uvm_error("TYPERR", $sformatf("channel number should not be %0d", ch))
      endcase
      return fd;
    endfunction
  endclass

apb_pkg

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

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

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

//——————————————————————————apb_pkg————————————————————————————
//这两句话表示:如果没有定义过就定义,定义过了就不会执行
//由于多个文件都会调用到相同的文件,这样做避免头文件的重复编译
`ifndef APB_PKG_SV
`define APB_PKG_SV

package apb_pkg;

import uvm_pkg::*;//uvm的两句话
`include "uvm_macros.svh"
//定义了一个参数
parameter bit[31:0] DEFAULT_READ_VALUE = 32'hFFFF_FFFF;
`include "apb.svh"//uvm_pkg的内容写在其他文件中
endpackage : apb_pkg

`endif
//—————————————————————————apb.svh———————————————————————————
//那么就来看看apb.svh这个头文件有什么
//APB为一主多从结构,主端为AHB总线等发送数据过来,从端为给MCDF的信号
//所以分为master和slave两部分,分别搭建验证环境
`ifndef APB_SVH//同理
`define APB_SVH

`include "apb_transfer.sv"
`include "apb_config.sv"

`include "apb_master_driver.svh"
`include "apb_master_monitor.svh"
`include "apb_master_sequencer.svh"
`include "apb_master_agent.svh"
`include "apb_slave_driver.svh"
`include "apb_slave_monitor.svh"
`include "apb_slave_sequencer.svh"
`include "apb_slave_agent.svh"

`include "apb_master_driver.sv"       
`include "apb_master_monitor.sv"
`include "apb_master_sequencer.sv"
`include "apb_master_agent.sv"
`include "apb_master_seq_lib.sv"
`include "apb_slave_driver.sv"       
`include "apb_slave_monitor.sv"
`include "apb_slave_sequencer.sv"
`include "apb_slave_agent.sv"
`include "apb_slave_seq_lib.sv"

`endif
//——————————————————————apb_transfer——————————————————————————————
//按顺序,看apb_transfer,分为头文件和sv

`ifndef APB_TRANSFER_SV
`define APB_TRANSFER_SV

//枚举变量,APB工作的类型:idle还是读写
//APB传输状态,没问题or出错了
typedef enum {IDLE, WRITE, READ } apb_trans_kind;
typedef enum {OK, ERROR} apb_trans_status;


//apb_pkg本身就是个完整的验证结构,transfer就是apb传输的item
//item只有地址+数据+两个状态+间隔,没有ready信号这些具体的?
class apb_transfer extends uvm_sequence_item;
  rand bit [31:0]      addr;
  rand bit [31:0]      data;
  rand apb_trans_kind  trans_kind; 
  rand apb_trans_status trans_status;
  rand int idle_cycles;

  constraint cstr{
    soft idle_cycles == 1;
  };

   //注册和域的自动化
  `uvm_object_utils_begin(apb_transfer)
    `uvm_field_enum     (apb_trans_kind, trans_kind, UVM_ALL_ON)
    `uvm_field_int      (addr, UVM_ALL_ON)
    `uvm_field_int      (data, UVM_ALL_ON)
    `uvm_field_int      (idle_cycles, UVM_ALL_ON)
  `uvm_object_utils_end

  // new函数,object的子类只有一个参数
  function new (string name = "apb_transfer_inst");
    super.new(name);
  endfunction : new

endclass : apb_transfer

`endif
//——————————————————————主端部分——————————————————————————————
//——————————————————————apb_master_driver——————————————————————————————
//config是全局配置等下再看,分为svh和sv

//svh头文件就是一个骨架,不包含具体的成员变量和具体的方法,只有句柄和注册
//以及各个方法的名字,注意是extern
`ifndef APB_MASTER_DRIVER_SVH
`define APB_MASTER_DRIVER_SVH
class apb_master_driver extends uvm_driver #(apb_transfer);
  apb_config cfg;
  `uvm_component_utils_begin(apb_master_driver)
  `uvm_component_utils_end

  extern function new (string name, uvm_component parent);
  extern virtual task run();
  virtual apb_if vif;

  extern virtual protected task get_and_drive();
  extern virtual protected task drive_transfer(apb_transfer t);
  extern virtual protected task reset_listener();
  extern protected task do_idle();
  extern protected task do_write(apb_transfer t);
  extern protected task do_read(apb_transfer t);

endclass : apb_master_driver
`endif

//driver正文:extern virtual protected通通不用,只有方法
//每个方法名前都要加apb_master_driver::

`ifndef APB_MASTER_DRIVER_SV
`define APB_MASTER_DRIVER_SV

//new函数有两个参数
function apb_master_driver::new (string name, uvm_component parent);
  super.new(name, parent);
endfunction : new
//run函数就是run_phase,可以自定义名称?
task apb_master_driver::run();
   fork//同时运行两个线程,也同时运行后面的task(join_none)
     get_and_drive();
     reset_listener();
   join_none
endtask : run

//第一个线程就是driver的工作模式,通过seq_item_port调用get_next_item拿到item
//drive_transfer驱动item,克隆req后转换成rsp,设置seq和item的id
task apb_master_driver::get_and_drive();
  forever begin
    seq_item_port.get_next_item(req);
    `uvm_info(get_type_name(), "sequencer got next item", UVM_HIGH)
    drive_transfer(req);
    void'($cast(rsp, req.clone()));
    rsp.set_sequence_id(req.get_sequence_id());
    rsp.set_transaction_id(req.get_transaction_id());
    seq_item_port.item_done(rsp);
    `uvm_info(get_type_name(), "sequencer item_done_triggered", UVM_HIGH)
  end
endtask : get_and_drive
//APB驱动数据的方式,首先读取状态机看APB总线当前工作在idle还是读写
task apb_master_driver::drive_transfer (apb_transfer t);
  `uvm_info(get_type_name(), "drive_transfer", UVM_HIGH)
  case(t.trans_kind)
    IDLE    : this.do_idle();
    WRITE   : this.do_write(t);
    READ    : this.do_read(t);
    default : `uvm_error("ERRTYPE", "unrecognized transaction type")
  endcase
endtask : drive_transfer
//写操作,通过接口的时钟块采样和驱动各个信号
//第一个阶段:写入地址,write拉高,sel拉高,enable为低
task apb_master_driver::do_write(apb_transfer t);
  `uvm_info(get_type_name(), "do_write ...", UVM_HIGH)
  @(vif.cb_mst);
  vif.cb_mst.paddr <= t.addr;
  vif.cb_mst.pwrite <= 1;
  vif.cb_mst.psel <= 1;
  vif.cb_mst.penable <= 0;
  vif.cb_mst.pwdata <= t.data;//阻塞赋值,
  //第二个阶段,enable拉高,data成功写入
  @(vif.cb_mst);//触发事件为时钟块可以表示下一个clk
  //时钟块本身就包含了@(posedge clk),见apb_if
  vif.cb_mst.penable <= 1;
  #10ps;
  wait(vif.pready === 1);//ready为1时才继续往下进行
  #1ps;
  //如果pslverr拉高报错,状态更改为error,根据严重程度进行打印
  if(vif.pslverr === 1) begin
    t.trans_status = ERROR;
    if(cfg.master_pslverr_status_severity ==  UVM_ERROR)
      `uvm_error(get_type_name(), "PSLVERR asserted!")
    else
      `uvm_warning(get_type_name(), "PSLVERR asserted!")
  end
  else begin
    t.trans_status = OK;
  end
  repeat(t.idle_cycles) this.do_idle();
endtask: do_write
//读操作,和写操作的区别只有write信号拉低
task apb_master_driver::do_read(apb_transfer t);
  `uvm_info(get_type_name(), "do_write ...", UVM_HIGH)
  @(vif.cb_mst);
  vif.cb_mst.paddr <= t.addr;
  vif.cb_mst.pwrite <= 0;
  vif.cb_mst.psel <= 1;
  vif.cb_mst.penable <= 0;
  @(vif.cb_mst);
  vif.cb_mst.penable <= 1;
  #10ps;
  wait(vif.pready === 1);
  #1ps;
  if(vif.pslverr === 1) begin
    t.trans_status = ERROR;
    if(cfg.master_pslverr_status_severity ==  UVM_ERROR)
      `uvm_error(get_type_name(), "PSLVERR asserted!")
    else
      `uvm_warning(get_type_name(), "PSLVERR asserted!")
  end
  else begin
    t.trans_status = OK;
  end
  t.data = vif.prdata;
  repeat(t.idle_cycles) this.do_idle();
endtask: do_read
//idle时地址和write信号保持不变,省电,其他信号拉低
task apb_master_driver::do_idle();
  `uvm_info(get_type_name(), "do_idle ...", UVM_HIGH)
  @(vif.cb_mst);
  vif.cb_mst.psel <= 0;
  vif.cb_mst.penable <= 0;
  vif.cb_mst.pwdata <= 0;
endtask:do_idle
//第二个线程,在rstn复位信号下降沿时复位所有信号拉低置0
task apb_master_driver::reset_listener();
  `uvm_info(get_type_name(), "reset_listener ...", UVM_HIGH)
  fork
    forever begin
      @(negedge vif.rstn); // ASYNC reset
      vif.paddr <= 0;
      vif.pwrite <= 0;
      vif.psel <= 0;
      vif.penable <= 0;
      vif.pwdata <= 0;
    end
  join_none
endtask

`endif 
//——————————————————————apb_master_monitor——————————————————————————————
//和driver一样,也是svh和sv文件

`ifndef APB_MASTER_MONITOR_SVH
`define APB_MASTER_MONITOR_SVH

class apb_master_monitor extends uvm_monitor;
  apb_config cfg;
  bit checks_enable = 1;
  bit coverage_enable = 1;
  virtual apb_if vif;
  uvm_analysis_port #(apb_transfer) item_collected_port;

  `uvm_component_utils_begin(apb_master_monitor)
     `uvm_field_int(checks_enable, UVM_ALL_ON)
     `uvm_field_int(coverage_enable, UVM_ALL_ON)
  `uvm_component_utils_end
  
   extern function new(string name, uvm_component parent=null);
   extern virtual task run();

  event apb_master_cov_transaction;

  covergroup apb_master_cov_trans @apb_master_cov_transaction;

  endgroup : apb_master_cov_trans

  extern virtual protected task monitor_transactions();
  extern virtual protected task collect_transfer();
  extern protected function void perform_transfer_checks();
  extern protected function void perform_transfer_coverage();
endclass : apb_master_monitor

`endif

//monitor正文

`ifndef APB_MASTER_MONITOR_SV
`define APB_MASTER_MONITOR_SV
//和之前看的其他monitor一样,new函数例化port
//parent的参数具体为null了?
function apb_master_monitor::new(string name, uvm_component parent=null);
  super.new(name, parent);
  item_collected_port = new("item_collected_port",this);
endfunction:new
//run_phase,注意要用join_none不要阻塞
task apb_master_monitor::run();
  fork
    monitor_transactions();
  join_none
endtask

task apb_master_monitor::monitor_transactions();
   forever begin
      //从接口拿到item(transfer)
      collect_transfer();
      //检查item
      if (checks_enable)
 	      perform_transfer_checks();
      // Update coverage
      if (coverage_enable)
 	      perform_transfer_coverage();
      //ap一对多发给接收端
      item_collected_port.write(trans_collected);
   end
endtask

task apb_master_monitor::collect_transfer();
  //有条件例化item,sel和enable都拉高的时候从接口拿item
  @(vif.cb_mon iff (vif.cb_mon.psel === 1'b1 && vif.cb_mon.penable === 1'b0));
  //clk上升沿若sel为高,enable为低,标志着APB总线进入setup状态
  trans_collected = apb_transfer::type_id::create("trans_collected");
  case(vif.cb_mon.pwrite)
    1'b1    : begin//写操作,ready拉高则将数据写入
                @(vif.cb_mon iff vif.cb_mon.pready === 1'b1);
                trans_collected.addr = vif.cb_mon.paddr;
                trans_collected.data = vif.cb_mon.pwdata;
                trans_collected.trans_kind = WRITE;//item中声明的两个状态
                trans_collected.trans_status = vif.cb_mon.pslverr === 1'b0 ? OK : ERROR;
              end 
    1'b0    : begin
                @(vif.cb_mon iff vif.cb_mon.pready === 1'b1);
                trans_collected.addr = vif.cb_mon.paddr;
                trans_collected.data = vif.cb_mon.prdata;
                trans_collected.trans_kind = READ;
                trans_collected.trans_status = vif.cb_mon.pslverr === 1'b0 ? OK : ERROR;
              end
    default : `uvm_error(get_type_name(), "ERROR pwrite signal value")
  endcase
endtask: collect_transfer 

function void apb_master_monitor::perform_transfer_checks();
endfunction : perform_transfer_checks

function void apb_master_monitor::perform_transfer_coverage();
  -> apb_master_cov_transaction;	
endfunction : perform_transfer_coverage

`endif
//——————————————————————apb_master_sequencer——————————————————————————————
//一样是注册和new函数
`ifndef APB_MASTER_SEQUENCER_SVH
`define APB_MASTER_SEQUENCER_SVH

class apb_master_sequencer extends uvm_sequencer #(apb_transfer);
  apb_config cfg;
  `uvm_component_utils_begin(apb_master_sequencer)
  `uvm_component_utils_end

  extern function new (string name, uvm_component parent);
  virtual apb_if vif;
endclass : apb_master_sequencer
`endif

//sequencer正文

`ifndef APB_MASTER_SEQUENCER_SV
`define APB_MASTER_SEQUENCER_SV

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

`endif
//——————————————————————apb_master_sequence——————————————————————————————
`ifndef APB_MASTER_SEQ_LIB_SV
`define APB_MASTER_SEQ_LIB_SV

//sequence先写一个base seq,然后被5个seq继承
//分别是写1个、读1个、读写、连续读和连续写
//将item和sequencer用typedef定义为变量,为什么????????
typedef class apb_transfer;
typedef class apb_master_sequencer;
//base seq只需要注册和例化
class apb_master_base_sequence extends uvm_sequence #(apb_transfer);

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

endclass : apb_master_base_sequence 
//一次写,只需要一个uvm_do_with
class apb_master_single_write_sequence extends apb_master_base_sequence;
  rand bit [31:0]      addr;//item中要用到的成员变量就声明
  rand bit [31:0]      data;
  apb_trans_status     trans_status;
//注册和new函数,没有域的自动化
  `uvm_object_utils(apb_master_single_write_sequence)    
  function new(string name=""); 
    super.new(name);
  endfunction : new

  virtual task body();//注意这里加了virtual,为什么?
    `uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
	//随机化item并发送
	  `uvm_do_with(req, {trans_kind == WRITE; addr == local::addr; data == local::data;})
    get_response(rsp);
    trans_status = rsp.trans_status;//传输的状态要访问rsp的才知道
//$psprintf()返回一个格式化的临时字符串,只需要2个参数
    `uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
  endtask: body

endclass: apb_master_single_write_sequence
//和写1个的区别是:随机化不需要做data的,从rsp拿出data
class apb_master_single_read_sequence extends apb_master_base_sequence;
  rand bit [31:0]      addr;
  rand bit [31:0]      data;
  apb_trans_status     trans_status;

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

  virtual task body();
    `uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
	`uvm_do_with(req, {trans_kind == READ; addr == local::addr;})
    get_response(rsp);
    trans_status = rsp.trans_status;
    data = rsp.data;//读数据得将rsp中的data拿出来
    `uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
  endtask: body

endclass: apb_master_single_read_sequence
//先写后读,两次操作,两个do_with,每次都要拿到rsp
class apb_master_write_read_sequence extends apb_master_base_sequence;
  rand bit [31:0]    addr;
  rand bit [31:0]    data;
  rand int           idle_cycles; //多了个间隔
  apb_trans_status     trans_status;
  constraint cstr{
    idle_cycles == 0;//默认为0
  }

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

  virtual task body();
    `uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
	  `uvm_do_with(req,  {trans_kind == WRITE; 
                        addr == local::addr; 
                        data == local::data;
                        idle_cycles == local::idle_cycles;
                       })
    get_response(rsp);
    `uvm_do_with(req, {trans_kind == READ; addr == local::addr;})
    get_response(rsp);
    data = rsp.data;
    trans_status = rsp.trans_status;
    `uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
  endtask: body

endclass: apb_master_write_read_sequence
//连续的写,data的类型变成动态数组
class apb_master_burst_write_sequence extends apb_master_base_sequence;
  rand bit [31:0]      addr;
  rand bit [31:0]      data[];
  apb_trans_status     trans_status;
  constraint cstr{//连续写4、8、16、32个
    soft data.size() inside {4, 8, 16, 32};
    foreach(data[i]) soft data[i] == addr + (i << 2);
  }//data[i]的值是地址+i左移2位
  `uvm_object_utils(apb_master_burst_write_sequence)    
  function new(string name=""); 
    super.new(name);
  endfunction : new

  virtual task body();
    `uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
    trans_status = OK;//直接把状态设置为OK?
    foreach(data[i]) begin
	    `uvm_do_with(req, {trans_kind == WRITE; 
                         addr == local::addr + (i<<2); 
                         data == local::data[i];
                         idle_cycles == 0;
                        })
      get_response(rsp);
    end//写完把状态设置为idle
    `uvm_do_with(req, {trans_kind == IDLE;})
    get_response(rsp);
    trans_status = rsp.trans_status == ERROR ? ERROR : trans_status;
    `uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
  endtask: body
endclass: apb_master_burst_write_sequence
//连续读
class apb_master_burst_read_sequence extends apb_master_base_sequence;
  rand bit [31:0]      addr;
  rand bit [31:0]      data[];
  apb_trans_status     trans_status;
  constraint cstr{
    soft data.size() inside {4, 8, 16, 32};
  }
  `uvm_object_utils(apb_master_burst_read_sequence)
  function new(string name=""); 
    super.new(name);
  endfunction : new

  virtual task body();
    `uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
    trans_status = OK;
    foreach(data[i]) begin
	    `uvm_do_with(req, {trans_kind == READ; 
                         addr == local::addr + (i<<2); 
                         idle_cycles == 0;
                        })
      get_response(rsp);
      data[i] = rsp.data;
    end
    `uvm_do_with(req, {trans_kind == IDLE;})
    get_response(rsp);
    trans_status = rsp.trans_status == ERROR ? ERROR : trans_status;
    `uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
  endtask: body
endclass: apb_master_burst_read_sequence

`endif

//——————————————————————apb_master_agent——————————————————————————————
`ifndef APB_MASTER_AGENT_SVH
`define APB_MASTER_AGENT_SVH

//agent的结构基本相同
class apb_master_agent extends uvm_agent;
  apb_config cfg;
//多声明了个config
//声明三个组件和接口
  apb_master_driver driver;
  apb_master_sequencer sequencer;
  apb_master_monitor monitor;
  virtual apb_if vif;

  `uvm_component_utils_begin(apb_master_agent)
  `uvm_component_utils_end
//new函数,build_phase和connect_phase
  extern function new (string name, uvm_component parent);
  extern function void build();
  extern function void connect();
  extern function void assign_vi(virtual apb_if vif);
endclass : apb_master_agent

`endif

//agent正文

`ifndef APB_MASTER_AGENT_SV
`define APB_MASTER_AGENT_SV

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


function void apb_master_agent::build();
  super.build();
  //由于有config,这里要多个get config
  if( !uvm_config_db#(apb_config)::get(this,"","cfg", cfg)) begin
    `uvm_warning("GETCFG","cannot get config object from config DB")
     cfg = apb_config::type_id::create("cfg");
  end
  //拿接口
  if( !uvm_config_db#(virtual apb_if)::get(this,"","vif", vif)) begin
    `uvm_fatal("GETVIF","cannot get vif handle from config DB")
  end
  //例化monitor
  monitor = apb_master_monitor::type_id::create("monitor",this);
  monitor.cfg = cfg;
  //active时才例化和配置driver和sequencer
  if(cfg.is_active == UVM_ACTIVE) begin
    sequencer = apb_master_sequencer::type_id::create("sequencer",this);
    sequencer.cfg = cfg;
    driver = apb_master_driver::type_id::create("driver",this);
    driver.cfg = cfg;
  end
endfunction : build

//connect_phase在active时连接driver和sequencer对应的port,还有接口
function void apb_master_agent::connect();
  assign_vi(vif);
  if(is_active == UVM_ACTIVE) begin
    driver.seq_item_port.connect(sequencer.seq_item_export);       
  end
endfunction : connect
//在active时连接driver和sequencer的接口
function void apb_master_agent::assign_vi(virtual apb_if vif);
   monitor.vif = vif;
   if (is_active == UVM_ACTIVE) begin
      sequencer.vif = vif; 
      driver.vif = vif; 
    end
endfunction : assign_vi

`endif
//—————————————————————————apb_config———————————————————————————
//config机制中config object传递的应用
//整合每个组件中的变量放到这个apb_config中,对中心化的配置对象进行传递
//有利于整体环境的维护
`ifndef APB_CONFIG_SV
`define APB_CONFIG_SV

class apb_config extends uvm_object;
  //设置agent是active还是passive
  uvm_active_passive_enum  is_active = UVM_ACTIVE;
//既是声明也是初始化,master_pslverr_status_severity的初始化严重级别为warning
  uvm_severity master_pslverr_status_severity = UVM_WARNING;

  //声明了三个成员变量
  rand bit slave_pready_random  = 1;
  rand bit slave_pslverr_random = 0;
  rand bit slave_pready_default_value = 0;

  `uvm_object_utils(apb_config)

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

  virtual function get_pready_additional_cycles();
    if(slave_pready_random)//
      return $urandom_range(0, 2);
    else//从端准备好随机化了就返回ready的间隔在0-2之间随机
      return 0;
  endfunction

  virtual function get_pslverr_status();
    if(slave_pslverr_random && $urandom_range(0, 20) == 0)
      return 1;
    else //总的来说这个函数的作用就是error信号随机拉高
      return 0;
  endfunction

endclass

`endif

apb_test

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

//——————————————————————apb_test——————————————————————————————

`ifndef APB_TESTS_SV
`define APB_TESTS_SV

import apb_pkg::*;//import了apb_pkg所有的东西,就是上面所见的
//test包含了顶层env和各个test
//env做了:声明+注册+new函数+build_phase做例化
class apb_env extends uvm_env;
//env例化了两个agent
  apb_master_agent mst;
  apb_slave_agent slv;
  `uvm_component_utils(apb_env)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    mst = apb_master_agent::type_id::create("mst", this);
    slv = apb_slave_agent::type_id::create("slv", this);
  endfunction
endclass

//test需要两个东西:sequence和test,一样是写base后进行继承
//base test
class apb_base_test extends uvm_test;
  apb_env env;//
  apb_config cfg;
  `uvm_component_utils(apb_base_test)
  
  //new函数没有写parent具体的参数
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  //build_phase例化了config,可以手动设置或者随机化config声明的三个成员变量
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    cfg = apb_config::type_id::create("cfg");
    //手动设置
    cfg.slave_pready_default_value = 1;
    cfg.slave_pready_random = 1;
    cfg.slave_pslverr_random = 1;
    //或者对整个config随机化
    //void'(cfg.randomize());
    uvm_config_db#(apb_config)::set(this,"env.*","cfg", cfg);//用config机制set了cfg
    env = apb_env::type_id::create("env", this);//base test例化env
  endfunction
endclass

//base seq,注意sequence是参数类
class apb_base_test_sequence extends uvm_sequence #(apb_transfer);
//先声明个32*32的mem
  bit[31:0] mem[bit[31:0]];//经查和问,这是个32*32二维数组,忽视bit即可
  //注册
  `uvm_object_utils(apb_base_test_sequence)
  //new函数,seq继承于item继承于trans继承于object
  function new(string name=""); 
    super.new(name);
  endfunction : new
  //第一个函数:检查mem的数据和地址是否对应的上
  function bit check_mem_data(bit[31:0] addr, bit[31:0] data);
  //exist函数检查mem中是否存在addr,是则返回1,否则返回0(二值)或x(四值)
    if(mem.exists(addr)) begin
      if(data != mem[addr]) begin//对应地址不是对应数据则报错
        `uvm_error("CMPDATA", $sformatf("addr 32'h%8x, READ DATA expected 32'h%8x != actual 32'h%8x", addr, mem[addr], data))
        return 0;
      end
      else begin
        `uvm_info("CMPDATA", $sformatf("addr 32'h%8x, READ DATA 32'h%8x comparing success!", addr, data), UVM_LOW)
        return 1;//比较结果正确返回1,否则返回0
      end
    end
    else begin
//等式运算符!=表示不等于,昨天笔试好像用了!==,两者区别在于是否连x和z都相同
      if(data != DEFAULT_READ_VALUE) begin
        `uvm_error("CMPDATA", $sformatf("addr 32'h%8x, READ DATA expected 32'h%8x != actual 32'h%8x", addr, DEFAULT_READ_VALUE, data))
        return 0;//DEFAULT_READ_VALUE不知道是哪里跑出来的???
      end//从打印的信息看,DEFAULT_READ_VALUE是期望值
      else begin
        `uvm_info("CMPDATA", $sformatf("addr 32'h%8x, READ DATA 32'h%8x comparing success!", addr, data), UVM_LOW)
        return 1;
      end
    end
  endfunction: check_mem_data

//第二个任务:等待复位信号,用两个触发事件下降沿和上升沿
  task wait_reset_release();
    @(negedge apb_tb.rstn);
    @(posedge apb_tb.rstn);
  endtask

//第三个任务:等待n个clk,用repeat加上升沿实现
  task wait_cycles(int n);
    repeat(n) @(posedge apb_tb.clk);
  endtask

//第四个函数:随机化地址
  function bit[31:0] get_rand_addr();
    bit[31:0] addr;//addr在函数中声明,是临时变量,用std::rabdomize
	//但是addr为什么是这些值?这个问题应该要进一步问
	//哪个地方会调用到这个函数拿到这个addr?找到了应该就可以解决这两个问题了
    void'(std::randomize(addr) with {addr[31:12] == 0; addr[1:0] == 0;addr != 0;});
    return addr;//在下面的子类就调用到了,return给了addr赋值
  endfunction
endclass

//单个数据操作,包括写、读、写读、写了马上读、写2个读1个
class apb_single_transaction_sequence extends apb_base_test_sequence;
//这三个是写在apb_master_seq_lib中的sequence。声明
  apb_master_single_write_sequence single_write_seq;
  apb_master_single_read_sequence single_read_seq;
  apb_master_write_read_sequence write_read_seq;
  rand int test_num = 100;
  constraint cstr{
    soft test_num == 100;
  }
  `uvm_object_utils(apb_single_transaction_sequence)    
  function new(string name=""); 
    super.new(name);
  endfunction : new
  task body();
    bit[31:0] addr;//base test写的两个函数这里调用了
    this.wait_reset_release();
    this.wait_cycles(10);
	//写1个的测试
    `uvm_info(get_type_name(), "TEST continous write transaction...", UVM_LOW)
    repeat(test_num) begin
      addr = this.get_rand_addr();
	  //这里发送的是seq不是item,第一个参数是seq,把随机的地址赋给addr和data
      `uvm_do_with(single_write_seq, {addr == local::addr; data == local::addr;})
      mem[addr] = addr;//作为data写进mem
    end

    //读1个的测试
    `uvm_info(get_type_name(), "TEST continous read transaction...", UVM_LOW)
    repeat(test_num) begin
      addr = this.get_rand_addr();
      `uvm_do_with(single_read_seq, {addr == local::addr;})
      if(single_read_seq.trans_status == OK)//传输的状态没问题就检查数据对不对得上
        void'(this.check_mem_data(addr, single_read_seq.data));
    end

    //写完再读的测试
    `uvm_info(get_type_name(), "TEST read transaction after write transaction...", UVM_LOW)
    repeat(test_num) begin
      addr = this.get_rand_addr();
      `uvm_do_with(single_write_seq, {addr == local::addr; data == local::addr;})
      mem[addr] = addr;
      `uvm_do_with(single_read_seq, {addr == local::addr;})
      if(single_read_seq.trans_status == OK)
        void'(this.check_mem_data(addr, single_read_seq.data));
    end

    //写完马上读的测试
    `uvm_info(get_type_name(), "TEST read transaction immediately after write transaction", UVM_LOW)
    repeat(test_num) begin
      addr = this.get_rand_addr();//拿个地址,随机化并发送
      `uvm_do_with(write_read_seq, {addr == local::addr; data == local::addr;})
      mem[addr] = addr;//地址作为data写入mem,少了个uvm_do_with,包含在seq中了
      if(write_read_seq.trans_status == OK)
        void'(this.check_mem_data(addr, write_read_seq.data));
    end

    //连续写2次后马上读1次
    `uvm_info(get_type_name(), "TEST write twice and read immediately with burst transaction...", UVM_LOW)
    repeat(test_num) begin
      addr = this.get_rand_addr();
      //写入,这个宏是item
      `uvm_do_with(req,  {trans_kind == WRITE; 
                    addr == local::addr; 
                    data == local::addr;
                    idle_cycles == 0;
                   })
      mem[addr] = addr;
      get_response(rsp);
      //再写一次
      `uvm_do_with(req,  {trans_kind == WRITE; 
                    addr == local::addr; 
                    data == local::addr<<2;//不写入相同的,左移2位
                    idle_cycles == 0;
                   })
      mem[addr] = addr<<2;
      get_response(rsp);
      //马上读
      `uvm_do_with(req, {trans_kind == READ; addr == local::addr;})
      get_response(rsp);
      if(rsp.trans_status == OK)
      void'(this.check_mem_data(addr, rsp.data));
    end

    this.wait_cycles(10);
  endtask
endclass: apb_single_transaction_sequence

//对应的test只需要注册+new函数+run_phase
class apb_single_transaction_test extends apb_base_test;
  `uvm_component_utils(apb_single_transaction_test)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  //例化sequence,举手,调用父类的run_phase
  task run_phase(uvm_phase phase);
    apb_single_transaction_sequence seq = new();
    phase.raise_objection(this);
    super.run_phase(phase);
    seq.start(env.mst.sequencer);
    phase.drop_objection(this);
  endtask
endclass: apb_single_transaction_test

class apb_burst_transaction_sequence extends apb_base_test_sequence;
  apb_master_burst_write_sequence burst_write_seq;
  apb_master_burst_read_sequence burst_read_seq;
  rand int test_num = 100;
  constraint cstr{
    soft test_num == 100;
  }
  `uvm_object_utils(apb_burst_transaction_sequence)
  function new(string name=""); 
    super.new(name);
  endfunction : new
  task body();
    bit[31:0] addr;
    this.wait_reset_release();
    this.wait_cycles(10);

    //连续写
    repeat(test_num) begin
      addr = this.get_rand_addr();
	  //这里看sequence的内容,data已经在约束块中随机化了,和单个写不同
	  //发送的时候不需要再做随机化
      `uvm_do_with(burst_write_seq, {addr == local::addr;})//先随机化地址
      foreach(burst_write_seq.data[i]) begin
	  //地址+i左移2位就是约束块中的约束
	  //把seq中的data全部写进mem
        mem[addr+(i<<2)] = burst_write_seq.data[i];
      end
	  //连续读,读多少个按照seq中data的size决定
      `uvm_do_with(burst_read_seq, {addr == local::addr; data.size() == burst_write_seq.data.size();})
      foreach(burst_read_seq.data[i]) begin
        void'(this.check_mem_data(addr+(i<<2), burst_write_seq.data[i]));
      end
    end

    this.wait_cycles(10);
  endtask
endclass: apb_burst_transaction_sequence

//对应的test,一模一样
class apb_burst_transaction_test extends apb_base_test;
  `uvm_component_utils(apb_burst_transaction_test)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  task run_phase(uvm_phase phase);
    apb_burst_transaction_sequence seq = new();
    phase.raise_objection(this);
    super.run_phase(phase);
    seq.start(env.mst.sequencer);
    phase.drop_objection(this);
  endtask
endclass: apb_burst_transaction_test

`endif

apb_slave

上面从apb_pkg开始,完整的过完了整个master的验证结构(包含test),这里再看看slave端和master端的有什么不一样,结构上是一模一样的。

accept_tr, begin_tr, end_tr | Verification Academy

//—————————————————————————apb_slave_driver———————————————————————————
//先看头文件
`ifndef APB_SLAVE_DRIVER_SVH
`define APB_SLAVE_DRIVER_SVH

class apb_slave_driver extends uvm_driver #(apb_transfer);
  apb_config cfg;
  bit[31:0] mem [bit[31:0]];//区别1:从端driver有个mem

  `uvm_component_utils_begin(apb_slave_driver)
  `uvm_component_utils_end

  extern function new (string name, uvm_component parent);
  extern virtual task run();
  virtual apb_if vif;
  extern virtual protected task get_and_drive();
//区别3:驱动的不是item而是response
  extern virtual protected task drive_response();
  extern protected task do_idle();
  extern protected task do_write();
  extern protected task do_read();
  extern virtual protected task reset_listener();
  //区别2:任务没有item作为参数输入(驱动、读、写三个任务有参数输入)
endclass : apb_slave_driver

`endif

//driver正文
`ifndef APB_SLAVE_DRIVER_SV
`define APB_SLAVE_DRIVER_SV

function apb_slave_driver::new (string name, uvm_component parent);
  super.new(name, parent);
endfunction : new//new函数相同

task apb_slave_driver::run();
   fork//多了个驱动rsp的线程
     get_and_drive();
     reset_listener();
     drive_response();
   join_none
endtask : run

task apb_slave_driver::get_and_drive();
  forever begin//少了驱动item和设置rsp的trans的id
    seq_item_port.get_next_item(req);
    `uvm_info(get_type_name(), "sequencer got next item", UVM_HIGH)
    void'($cast(rsp, req.clone()));
    rsp.set_sequence_id(req.get_sequence_id());
    seq_item_port.item_done(rsp);
    `uvm_info(get_type_name(), "sequencer item_done_triggered", UVM_HIGH)
  end
endtask : get_and_drive

task apb_slave_driver::drive_response();
  `uvm_info(get_type_name(), "drive_response", UVM_HIGH)
  forever begin
    @(vif.cb_slv);//用时钟块当触发事件
	//进入setup状态时先判断write信号是读还是写
    if(vif.cb_slv.psel === 1'b1 && vif.cb_slv.penable === 1'b0) begin
      case(vif.cb_slv.pwrite)
        1'b1    : this.do_write();
        1'b0    : this.do_read();
        default : `uvm_error(get_type_name(), "ERROR pwrite signal value")
      endcase
    end
    else begin
      this.do_idle();
    end
  end
endtask : drive_response

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

task apb_slave_driver::do_write();//和master端完全不同
  bit[31:0] addr;//参数没有item,声明临时变量,配合seq
  bit[31:0] data;
  //调用config中的函数给两个新变量赋值
  int pready_add_cycles = cfg.get_pready_additional_cycles();
  bit pslverr_status =  cfg.get_pslverr_status();
  `uvm_info(get_type_name(), "do_write", UVM_HIGH)
  wait(vif.penable === 1'b1);//等enable信号拉高进入第二个状态
  addr = vif.cb_slv.paddr;//总线的地址和写入的数据给到这里,
  data = vif.cb_slv.pwdata;
  mem[addr] = data;//数据给到mem
  if(pready_add_cycles > 0) begin
    #1ps;
    vif.pready  <= 0;
    repeat(pready_add_cycles) @(vif.cb_slv);
  end//根据pready_add_cycles等待ready拉高,error信号给到apb总线输出的pslverr
  #1ps;
  vif.pready  <= 1;
  vif.pslverr <= pslverr_status;
  fork
    begin
      @(vif.cb_slv);//写完了,ready和error分别置为默认值和0
      vif.cb_slv.pready <= cfg.slave_pready_default_value;
      vif.cb_slv.pslverr <= 0;
    end
  join_none
endtask: do_write

task apb_slave_driver::do_read();//无item作为参数
  bit[31:0] addr;//同样要声明地址和数据,同样通过config拿两个变量
  bit[31:0] data;
  int pready_add_cycles = cfg.get_pready_additional_cycles();
  bit pslverr_status =  cfg.get_pslverr_status();
  `uvm_info(get_type_name(), "do_read", UVM_HIGH)
  wait(vif.penable === 1'b1);//enable拉高写入addr
  addr = vif.cb_slv.paddr;
  if(mem.exists(addr))//检查addr在mem中是否存在数据
    data = mem[addr];//是就把mem中的data读出来
  else
    data = DEFAULT_READ_VALUE;//在pkg一开始定义了,parameter bit[31:0] DEFAULT_READ_VALUE = 32'hFFFF_FFFF
  if(pready_add_cycles > 0) begin
    #1ps;
    vif.pready  <= 0;
    repeat(pready_add_cycles) @(vif.cb_slv);
  end
  #1ps;
  vif.pready  <= 1;
  vif.pslverr <= pslverr_status;
  vif.prdata  <= data;//读比写多了将data写到接口上的prdata
  fork
    begin
      @(vif.cb_slv);//读完了,ready和error分别置为默认值和0
      vif.cb_slv.pready <= cfg.slave_pready_default_value;
      vif.cb_slv.pslverr <= 0;
    end
  join_none
endtask: do_read

task apb_slave_driver::reset_listener();
  `uvm_info(get_type_name(), "reset_listener ...", UVM_HIGH)
  fork
    forever begin
      @(negedge vif.rstn); //rstn下降沿
      vif.prdata <= 0;
      vif.pslverr <= 0;//ready置为默认值
      vif.pready <= cfg.slave_pready_default_value;
      this.mem.delete(); //清空mem
    end
  join_none
endtask: reset_listener

`endif

//—————————————————————————apb_slave_monitor———————————————————————————
//monitor从框架上看比master多了个build_phase

`ifndef APB_SLAVE_MONITOR_SVH
`define APB_SLAVE_MONITOR_SVH

class apb_slave_monitor extends uvm_monitor;
  apb_config cfg;
  bit checks_enable = 1;
  bit coverage_enable = 1;
  virtual apb_if vif;
  uvm_analysis_port #(apb_transfer) item_collected_port;
  `uvm_component_utils_begin(apb_slave_monitor)
     `uvm_field_int(checks_enable, UVM_ALL_ON)
     `uvm_field_int(coverage_enable, UVM_ALL_ON)
  `uvm_component_utils_end  
  extern function new(string name, uvm_component parent=null);
  extern function void build();
  extern virtual task run();

  event apb_slave_cov_transaction;
  covergroup apb_slave_cov_trans @apb_slave_cov_transaction;
  endgroup : apb_slave_cov_trans

  protected apb_transfer trans_collected;
  extern virtual protected task monitor_transactions();
  extern virtual protected task collect_transfer();
  extern protected function void perform_transfer_checks();
  extern protected function void perform_transfer_coverage();

endclass : apb_slave_monitor

`endif 

//monitor正文

`ifndef APB_SLAVE_MONITOR_SV
`define APB_SLAVE_MONITOR_SV

function apb_slave_monitor::new(string name, uvm_component parent=null);
  super.new(name, parent);
  trans_collected = new();//这里例化了item,master在获取item时才例化
  item_collected_port = new("item_collected_port",this);
endfunction:new

// build phase调用父类的build即可?为什么这里就要build啊
function void apb_slave_monitor::build();
   super.build();
endfunction : build  

//run_phase调用monitor_transactions任务,与master相同
task apb_slave_monitor::run();
  fork
    monitor_transactions();
  join_none
endtask

task apb_slave_monitor::monitor_transactions();
   forever begin
      //从接口获取item
      collect_transfer();
      //检查item的内容
      if (checks_enable)
 	 perform_transfer_checks();
      //更新覆盖率?
      if (coverage_enable)
 	 perform_transfer_coverage();
      //ap一对多调用write方法
      item_collected_port.write(trans_collected);
   end
endtask 

  
task apb_slave_monitor::collect_transfer();
//begin_tr表示整个trans已经开始,且不是子trans
//查不到这个东西是什么,先放着
  void'(this.begin_tr(trans_collected));
  @(vif.cb_mon);
  void'(this.begin_tr(trans_collected));
  this.end_tr(trans_collected);
endtask

//检查item和覆盖率两个函数一样是空的
function void apb_slave_monitor::perform_transfer_checks();
endfunction : perform_transfer_checks

function void apb_slave_monitor::perform_transfer_coverage();
 -> apb_slave_cov_transaction;
endfunction : perform_transfer_coverage

`endif
//—————————————————————————apb_slave_sequencer——————————————————————————
//一样只有注册加new函数而已

//—————————————————————————apb_slave_sequence——————————————————————————
`ifndef APB_SLAVE_SEQ_LIB_SV
`define APB_SLAVE_SEQ_LIB_SV

//seq为空,应该是要自己写了,那从端的seq要怎么写?
class example_apb_slave_seq extends uvm_sequence #(apb_transfer);
    function new(string name=""); 
      super.new(name);
    endfunction : new
  
  `uvm_object_utils(example_apb_slave_seq)    

    apb_transfer this_transfer;
  
    virtual task body();
      `uvm_info(get_type_name(),"Starting example sequence", UVM_HIGH)
       `uvm_do(this_transfer) //uvm_do包括创建、随机化、发送
      `uvm_info(get_type_name(),$psprintf("Done example sequence: %s",this_transfer.convert2string()), UVM_HIGH)
    endtask
  
endclass : example_apb_slave_seq

`endif
//—————————————————————————apb_slave_agent——————————————————————————
agent一模一样,只有在build_phase的if条件中,is_active在master是指明了cfg.的,而slave这里没有,可能是笔误?


mcdf_pkg

以上所有的pkg组成了mcdf_pkg这个顶层环境,作为整个模块的顶层,从结构看,首先是mcdf_refomod模拟mcdf的数据整形;继承于scoreboard的checker做数据比较;路由器virtual sequencer负责调度sequencer;rgm数据映射到硬件寄存器的转换器adapter;顶层环境env和base vir seq;最后是一众test测试。

关于uvm_do的宏

uvm_do系列宏解析_Andy_ICer的博客-CSDN博客_uvm_do_with

 寄存器predictor这一块还要再了解学习。 

package mcdf_pkg;

  import uvm_pkg::*;
  `include "uvm_macros.svh"
  import apb_pkg::*;
  import chnl_pkg::*;
  import fmt_pkg::*;
  import mcdf_rgm_pkg::*;

  typedef enum {RW_LEN, RW_PRIO, RW_EN, RD_AVAIL} mcdf_field_t;

//—————————————————————————mcdf_refmod———————————————————————————
  // MCDF reference model注意是继承于comp
  class mcdf_refmod extends uvm_component;
    mcdf_rgm rgm;//声明寄存器模型rgm

//tlm通信的端口和fifo的声明
//根据是否可以等待延时选择阻塞b和非阻塞n的端口
//根据通信方式选择get、put、peek、get_peek
	//和apb总线通信的端口,阻塞类型可以有延时,数量只有1个
    uvm_blocking_get_port #(apb_transfer) reg_bg_port;
	//和monitor通信的端口,用get_peek不修改原来的item,数量4个分别连接到4个node!
    uvm_blocking_get_peek_port #(mon_data_t) in_bgpk_ports[4];
	//uvm_tlm_analysis_fifo是搭配有ap的tlm fifo,继承于uvm_tlm_fifo
    uvm_tlm_analysis_fifo #(fmt_trans) out_tlm_fifos[4];

    `uvm_component_utils(mcdf_refmod)

    function new (string name = "mcdf_refmod", uvm_component parent);
      super.new(name, parent);//new函数例化所有port和fifo
      reg_bg_port = new("reg_bg_port", this);//学习这种foreach的命名方法
      foreach(in_bgpk_ports[i]) in_bgpk_ports[i] = new($sformatf("in_bgpk_ports[%0d]", i), this);
      foreach(out_tlm_fifos[i]) out_tlm_fifos[i] = new($sformatf("out_tlm_fifos[%0d]", i), this);
    endfunction

    function void build_phase(uvm_phase phase);
      super.build_phase(phase);//config机制在这里get了rgm配置
      if(!uvm_config_db#(mcdf_rgm)::get(this,"","rgm", rgm)) begin
        `uvm_fatal("GETRGM","cannot get RGM handle from config DB")
      end
    endfunction

    task run_phase(uvm_phase phase);
      fork//run_phase并行执行4个通道的打包线程
        do_packet(0);
        do_packet(1);
        do_packet(2);
        do_packet(3);
      join
    endtask

    task do_packet(int ch);
      fmt_trans ot;
      mon_data_t it;
      forever begin
	  //通过和monitor通信的端口peek了monitor的句柄
        this.in_bgpk_ports[ch].peek(it);
        ot = new();//例化fmt中传输的包的句柄(rgm要访问)
		//mcdf_rgm定义的两个获取len和id函数在这里调用,赋值给包的对应信号
        ot.length = rgm.get_reg_field_length(ch);
        ot.ch_id = rgm.get_reg_field_id(ch);
        ot.data = new[ot.length+3];//payload数量=len+3,动态数组分配空间
		//每个payload都是用data[?]表示
        foreach(ot.data[m]) begin//m不用定义,就是平时用的i
          if(m == 0) begin//第一个payload是{8位id,8位len,16位0}
            ot.data[m] = (ot.ch_id<<24) + (ot.length<<16);//这就是第一个payload
            ot.parity = ot.data[m];//没data,直接把奇偶校验赋值成payload
          end //注意这里是阻塞赋值
          else if(m == ot.data.size()-1) begin
            ot.data[m] = ot.parity;//最后一个payload存放32位parity
          end
          else begin//中间的payload通过port获得monitor的句柄it然后
            this.in_bgpk_ports[ch].get(it);
            ot.data[m] = it.data;//赋值给data
			//按位异或后赋值,ot.parity = ot.parity^it.data
            ot.parity ^= it.data;
          end
        end
        this.out_tlm_fifos[ch].put(ot);//完成整形后再把句柄放到fifo中
      end
    endtask
  endclass: mcdf_refmod

//—————————————————————————mcdf_checker———————————————————————————
  class mcdf_checker extends uvm_scoreboard;
  //checker中的几个变量,error计数,总计数,channel的计数,具体记什么?
    local int err_count;//全部是local!只在checker中使用
    local int total_count;
    local int chnl_count[4];
    local virtual chnl_intf chnl_vifs[4]; //4个chnl的接口
    local virtual mcdf_intf mcdf_vif;//1个mcdf的接口
    local mcdf_refmod refmod;//参考模型声明
    mcdf_rgm rgm;//同样声明rgm

    uvm_tlm_analysis_fifo #(mon_data_t) chnl_tlm_fifos[4];
    uvm_tlm_analysis_fifo #(fmt_trans) fmt_tlm_fifo;
    uvm_tlm_analysis_fifo #(apb_transfer) reg_tlm_fifo;

    uvm_blocking_get_port #(fmt_trans) exp_bg_ports[4];

    `uvm_component_utils(mcdf_checker)

    function new (string name = "mcdf_checker", uvm_component parent);
      super.new(name, parent);//new函数对成员变量初始化,例化fifo和port
      this.err_count = 0;
      this.total_count = 0;
      foreach(this.chnl_count[i]) this.chnl_count[i] = 0;
      foreach(chnl_tlm_fifos[i]) chnl_tlm_fifos[i] = new($sformatf("chnl_tlm_fifos[%0d]", i), this);
      fmt_tlm_fifo = new("fmt_tlm_fifo", this);//fifo的new有两个参数
      reg_tlm_fifo = new("reg_tlm_fifo", this);//port的例化也是
      foreach(exp_bg_ports[i]) exp_bg_ports[i] = new($sformatf("exp_bg_ports[%0d]", i), this);
    endfunction

//build_phase拿config和接口,还有rgm!
    function void build_phase(uvm_phase phase);
      super.build_phase(phase);

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

//connect_phase将refmod和checker的端口和port进行连接
    function void connect_phase(uvm_phase phase);
      super.connect_phase(phase);
      foreach(refmod.in_bgpk_ports[i]) refmod.in_bgpk_ports[i].connect(chnl_tlm_fifos[i].blocking_get_peek_export);
      refmod.reg_bg_port.connect(reg_tlm_fifo.blocking_get_export);
      foreach(exp_bg_ports[i]) begin
        exp_bg_ports[i].connect(refmod.out_tlm_fifos[i].blocking_get_export);
      end
    endfunction

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

    task do_data_compare();
      fmt_trans expt, mont;
      bit cmp;//两个临时变量
      int ch_idx;//通道索引
      forever begin
        this.fmt_tlm_fifo.get(mont);//通过tlm_fifo调用get,参数为item的句柄
        ch_idx = this.get_chnl_index(mont.ch_id);//拿到通道索引值
        this.exp_bg_ports[ch_idx].get(expt);//索引值对应的exp_bg_port拿到期望包的句柄
        cmp = mont.compare(expt);   //将两个句柄进行比较,相同拉高cmp
        this.total_count++;//总数加1
        this.chnl_count[ch_idx]++;//对应通道计数加1
        if(cmp == 0) begin//两个句柄不一致error计数加1
          this.err_count++; #1ns;
          `uvm_info("[CMPERR]", $sformatf("monitored formatter data packet:\n %s", mont.sprint()), UVM_MEDIUM)
          `uvm_info("[CMPERR]", $sformatf("expected formatter data packet:\n %s", expt.sprint()), UVM_MEDIUM)
          `uvm_error("[CMPERR]", $sformatf("%0dth times comparing but failed! MCDF monitored output packet is different with reference model output", this.total_count))
        end
        else begin
          `uvm_info("[CMPSUC]",$sformatf("%0dth times comparing and succeeded! MCDF monitored output packet is the same with reference model output", this.total_count), UVM_LOW)
        end
      end
    endtask

//检查rgm寄存器的域的id,是不是等于fmt的item中的ch_id
    function int get_chnl_index(int ch_id);
      int fd_id;
      for(int i=0; i<4; i++) begin
        fd_id = rgm.get_reg_field_id(i);
        if(fd_id == ch_id)//ch_id是8位
          return i;
      end
      `uvm_error("CHIDERR", $sformatf("unrecognized channel ID and could not find the corresponding channel index", ch_id))
      return -1;//不一样返回-1
    endfunction

    task do_channel_disable_check(int id);
      forever begin//clk要带this和接口,且在复位信号拉高和channel使能信号为低,为什么?
        @(posedge this.mcdf_vif.clk iff (this.mcdf_vif.rstn && this.mcdf_vif.mon_ck.chnl_en[id]===0));
		//如果valid为高而wait为低,则报错
        if(this.chnl_vifs[id].mon_ck.ch_valid===1 && this.chnl_vifs[id].mon_ck.ch_wait===0)
          `uvm_error("[CHKERR]", "ERROR! when channel disabled, wait signal low when valid high") 
      end
    endtask

//checker中第一次出现了report_phase
    function void report_phase(uvm_phase phase);
      string s;//声明字符串
      super.report_phase(phase);//调用父类report_phase
      s = "\n---------------------------------------------------------------\n";
      s = {s, "CHECKER SUMMARY \n"}; //报告总结
      s = {s, $sformatf("total comparison count: %0d \n", this.total_count)};//打印总计数 
      foreach(this.chnl_count[i]) s = {s, $sformatf(" channel[%0d] comparison count: %0d \n", i, this.chnl_count[i])};
	  //打印通道各自计数
      s = {s, $sformatf("total error count: %0d \n", this.err_count)}; //打印error计数
      foreach(this.chnl_tlm_fifos[i]) begin//chnl和fmt的fifo如果不空就打印
        if(this.chnl_tlm_fifos[i].size() != 0)
          s = {s, $sformatf("WARNING:: chnl_tlm_fifos[%0d] is not empty! size = %0d \n", i, this.chnl_tlm_fifos[i].size())}; 
      end
      if(this.fmt_tlm_fifo.size() != 0)
          s = {s, $sformatf("WARNING:: fmt_tlm_fifo is not empty! size = %0d \n", this.fmt_tlm_fifo.size())}; 
      s = {s, "---------------------------------------------------------------\n"};
      `uvm_info(get_type_name(), s, UVM_LOW)
    endfunction
  endclass: mcdf_checker

//—————————————————————————mcdf_virtual_sequencer———————————————————————————
  class mcdf_virtual_sequencer extends uvm_sequencer #(uvm_sequence_item);
  //声明所有sequencer
    apb_master_sequencer reg_sqr;//apb声明的是master的sequencer
    fmt_sequencer fmt_sqr;//fmt的sequencer
    chnl_sequencer chnl_sqrs[4];//channel的sequencer
    mcdf_rgm rgm;//寄存器模型
    virtual mcdf_intf mcdf_vif;//接口
    `uvm_component_utils(mcdf_virtual_sequencer)//注册
	
    function new (string name = "mcdf_virtual_sequencer", uvm_component parent);
      super.new(name, parent);
    endfunction
	//rgm做接口和rgm的配置
    function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      if(!uvm_config_db#(virtual mcdf_intf)::get(this,"","mcdf_vif", mcdf_vif)) begin
        `uvm_fatal("GETVIF","cannot get vif handle from config DB")
      end
      if(!uvm_config_db#(mcdf_rgm)::get(this,"","rgm", rgm)) begin
        `uvm_fatal("GETRGM","cannot get RGM handle from config DB")
      end
    endfunction
  endclass

//—————————————————————————mcdf_env———————————————————————————
  // MCDF顶层环境env
  class mcdf_env extends uvm_env;
//包括chnl、apb、fmt的agent,checker,v-sequencer,rgm,adapter,predictor
    chnl_agent chnl_agts[4];
    apb_master_agent reg_agt;
    fmt_agent fmt_agt;
    mcdf_checker chker;
    mcdf_virtual_sequencer virt_sqr;
    mcdf_rgm rgm;
    reg2mcdf_adapter adapter;
    uvm_reg_predictor #(apb_transfer) predictor;

    `uvm_component_utils(mcdf_env)

    function new (string name = "mcdf_env", uvm_component parent);
      super.new(name, parent);
    endfunction

//build_phase例化上述所有单位
    function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      this.chker = mcdf_checker::type_id::create("chker", this);
      foreach(chnl_agts[i]) begin
        this.chnl_agts[i] = chnl_agent::type_id::create($sformatf("chnl_agts[%0d]",i), this);
      end
      this.reg_agt = apb_master_agent::type_id::create("reg_agt", this);
      this.fmt_agt = fmt_agent::type_id::create("fmt_agt", this);
      virt_sqr = mcdf_virtual_sequencer::type_id::create("virt_sqr", this);
	  //rgm例化后还要调用build和set配置
      rgm = mcdf_rgm::type_id::create("rgm", this);
      rgm.build();//rgm还要调用build函数,例化配置所有寄存器,并添加到map
      uvm_config_db#(mcdf_rgm)::set(this,"*","rgm", rgm);//在顶层set,在组件get
      adapter = reg2mcdf_adapter::type_id::create("adapter", this);
      predictor = uvm_reg_predictor#(apb_transfer)::type_id::create("predictor", this);//带参数类型例化
    endfunction

    function void connect_phase(uvm_phase phase);
      super.connect_phase(phase);
      foreach(chnl_agts[i]) chnl_agts[i].monitor.mon_ana_port.connect(chker.chnl_tlm_fifos[i].analysis_export);
      reg_agt.monitor.item_collected_port.connect(chker.reg_tlm_fifo.analysis_export);
      fmt_agt.monitor.mon_ana_port.connect(chker.fmt_tlm_fifo.analysis_export);
      virt_sqr.reg_sqr = reg_agt.sequencer;
      virt_sqr.fmt_sqr = fmt_agt.sequencer;
      foreach(virt_sqr.chnl_sqrs[i]) virt_sqr.chnl_sqrs[i] = chnl_agts[i].sequencer;
      rgm.map.set_sequencer(reg_agt.sequencer, adapter);
      reg_agt.monitor.item_collected_port.connect(predictor.bus_in);
      predictor.map = rgm.map;//predictor书里没有过多介绍啊
      predictor.adapter = adapter;
    endfunction
  endclass: mcdf_env

//—————————————————————————mcdf_base_sequence和test———————————————————————————
//base seq只做seq的框架,具体的等子seq去写
  class mcdf_base_virtual_sequence extends uvm_sequence #(uvm_sequence_item);
    chnl_data_sequence chnl_data_seq;//包含两个组件的seq
    fmt_config_sequence fmt_config_seq;
    mcdf_rgm rgm;

    `uvm_object_utils(mcdf_base_virtual_sequence)
	//把m_sequencer 转换成my_sequencer,相当于my_sequencer p_sequencer
    `uvm_declare_p_sequencer(mcdf_virtual_sequencer)

    function new (string name = "mcdf_base_virtual_sequence");
      super.new(name);//new函数咩都无
    endfunction

    virtual task body();
      `uvm_info(get_type_name(), "=====================STARTED=====================", UVM_LOW)
      rgm = p_sequencer.rgm;//为什么要把p_sequencer的rgm给到这里
      this.do_reg();
      this.do_formatter();
      this.do_data();
      `uvm_info(get_type_name(), "=====================FINISHED=====================", UVM_LOW)
    endtask

    virtual task do_reg();//做寄存器配置
    endtask

    //从外部对fmt下行端做配置
    virtual task do_formatter();
    endtask

	//4个node的数据转换
    virtual task do_data();
    endtask

    function bit diff_value(int val1, int val2, string id = "value_compare");
      if(val1 != val2) begin
        `uvm_error("[CMPERR]", $sformatf("ERROR! %s val1 %8x != val2 %8x", id, val1, val2)) 
        return 0;
      end
      else begin
        `uvm_info("[CMPSUC]", $sformatf("SUCCESS! %s val1 %8x == val2 %8x", id, val1, val2), UVM_LOW)
        return 1;
      end
    endfunction

    task wait_cycles(int n);
      repeat(n) @(posedge p_sequencer.mcdf_vif.clk);
    endtask
  endclass

  //base test例化env,在end_of_elaboration_phase做设置
  //run_phase运行空的顶层seq,等具体子seq继承
  class mcdf_base_test extends uvm_test;
    mcdf_env env;

    `uvm_component_utils(mcdf_base_test)

    function new(string name = "mcdf_base_test", uvm_component parent);
      super.new(name, parent);
    endfunction

    function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      env = mcdf_env::type_id::create("env", this);
    endfunction

//phase机制第一次见到end_of_elaboration_phase
    function void end_of_elaboration_phase(uvm_phase phase);
      super.end_of_elaboration_phase(phase);
	  //uvm_root验证平台中所有omp的隐含top-level和phase控制器。
	  //get()函数用于返回uvm_root的句柄。
      uvm_root::get().set_report_verbosity_level_hier(UVM_HIGH);
	  //冗余度阈值的设置可以放在build_phase之后run_phase之前的任意phase;
      uvm_root::get().set_report_max_quit_count(1);
	  //出现1次error就结束仿真
      uvm_root::get().set_timeout(10ms);
	  //超时10ms就结束仿真
    endfunction

//run_phase做举手落手,中间运行顶层seq即可
    task run_phase(uvm_phase phase);
      phase.raise_objection(this);
      this.run_top_virtual_sequence();
      phase.drop_objection(this);
    endtask

    virtual task run_top_virtual_sequence();
    endtask
  endclass: mcdf_base_test

//———————————mcdf_data_consistence_basic_virtual_sequence—————————————
//在sequence中使用寄存器模型, 通常通过p_sequencer的形式引用。
  class mcdf_data_consistence_basic_virtual_sequence extends mcdf_base_virtual_sequence;
  //注册和new函数少不了
    `uvm_object_utils(mcdf_data_consistence_basic_virtual_sequence)
    function new (string name = "mcdf_data_consistence_basic_virtual_sequence");
      super.new(name);
    endfunction
	
	//补充具体的任务内容了
    task do_reg();//做寄存器配置的任务
      bit[31:0] wr_val, rd_val;//读写值的两个临时变量
      uvm_status_e status;
      //复位寄存器
      @(negedge p_sequencer.mcdf_vif.rstn);
      rgm.reset();//所有接口前面都多了p_sequencer
      @(posedge p_sequencer.mcdf_vif.rstn);
      this.wait_cycles(10);
      // slv3 with len=64, en=1
      // slv2 with len=32, en=1
      // slv1 with len=16, en=1
      // slv0 with len=8,  en=1
	  
	  //slv_en是4位
      wr_val = ('b1<<3) + ('b1<<2) + ('b1<<1) + 1;
	  //看rgm那个链接
	  //uvm_status_e是一个输出, 用于表明写操作是否成功。
	  //先对slv_en
      rgm.slv_en.write(status, wr_val);
      rgm.slv_en.read(status, rd_val);//在refmod读寄存器
      void'(this.diff_value(wr_val, rd_val, "SLV_EN_REG"));
	  
	  //然后对slv_len
      wr_val = (63<<24) + (31<<16) + (15<<8) + 7;
      rgm.slv_len.write(status, wr_val);
      rgm.slv_len.read(status, rd_val);
	  //写进去读出来做对比
      void'(this.diff_value(wr_val, rd_val, "SLV_LEN_REG"));
    endtask
	
    task do_formatter();
      `uvm_do_on_with(fmt_config_seq, p_sequencer.fmt_sqr, {fifo == LONG_FIFO; bandwidth == HIGH_WIDTH;})
    endtask
    task do_data();
      fork
	  //参数:sequence或者item对应do,sequencer对应on,约束{}对应with
        `uvm_do_on_with(chnl_data_seq, p_sequencer.chnl_sqrs[0], {ntrans==100; ch_id==0; data_nidles==0; pkt_nidles==1; data_size==64;})
        `uvm_do_on_with(chnl_data_seq, p_sequencer.chnl_sqrs[1], {ntrans==100; ch_id==1; data_nidles==1; pkt_nidles==4; data_size==64;})
        `uvm_do_on_with(chnl_data_seq, p_sequencer.chnl_sqrs[2], {ntrans==100; ch_id==2; data_nidles==2; pkt_nidles==8; data_size==64;})
        `uvm_do_on_with(chnl_data_seq, p_sequencer.chnl_sqrs[3], {ntrans==100; ch_id==3; data_nidles==1; pkt_nidles==2; data_size==64;})
      join
      #10us; // wait until all data haven been transfered through MCDF
    endtask
  endclass: mcdf_data_consistence_basic_virtual_sequence

//———————————mcdf_data_consistence_basic_test—————————————
//对应的test只需要在任务中例化sequence并start即可,参数是env.virt_sqr
  class mcdf_data_consistence_basic_test extends mcdf_base_test;

    `uvm_component_utils(mcdf_data_consistence_basic_test)

    function new(string name = "mcdf_data_consistence_basic_test", uvm_component parent);
      super.new(name, parent);
    endfunction

    task run_top_virtual_sequence();
      mcdf_data_consistence_basic_virtual_sequence top_seq = new();
      top_seq.start(env.virt_sqr);
    endtask
  endclass: mcdf_data_consistence_basic_test

关于set_timeout

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

  • 13
    点赞
  • 78
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
SystemVerilog的听课学习笔记,包括讲义截取、知识点记录、注意事项等细节的标注。 目录如下: 第一章 SV环境构建常识 1 1.1 数据类型 1 四、二值逻辑 4 定宽数组 9 foreach 13 动态数组 16 队列 19 关联数组 21 枚举类型 23 字符串 25 1.2 过程块和方法 27 initial和always 30 function逻辑电路 33 task时序电路 35 动态 静态变量 39 1.3 设计例化和连接 45 第二章 验证的方法 393 动态仿真 395 静态检查 397 虚拟模型 403 硬件加速 405 效能验证 408 性能验证 410 第三章 SV组件实现 99 3.1 接口 100 什么是interface 101 接口的优势 108 3.2 采样和数据驱动 112 竞争问题 113 接口中的时序块clocking 123 利于clocking的驱动 133 3.3 测试的开始和结束 136 仿真开始 139 program隐式结束 143 program显式结束 145 软件域program 147 3.4 调试方法 150 第四章 验证的计划 166 4.1 计划概述 166 4.2 计划的内容 173 4.3 计划的实现 185 4.4 计划的进程评估 194 第五章 验证的管理 277 6.1 验证的周期检查 277 6.2 管理三要素 291 6.3 验证的收敛 303 6.4 问题追踪 314 6.5 团队建设 321 6.6 验证的专业化 330 第六章 验证平台的结构 48 2.1 测试平台 49 2.2 硬件设计描述 55 MCDF接口描述 58 MCDF接口时序 62 MCDF寄存器描述 65 2.3 激励发生器 67 channel initiator 72 register initiator 73 2.4 监测器 74 2.5 比较器 81 2.6 验证结构 95 第七章 激励发生封装:类 209 5.1 概述 209 5.2 类的成员 233 5.3 类的继承 245 三种类型权限 protected/local/public 247 this super 253 成员覆盖 257 5.4 句柄的使用 263 5.5 包的使用 269 第八章 激励发生的随机化 340 7.1 随机约束和分布 340 权重分布 353 条件约束 355 7.2 约束块控制 358 7.3 随机函数 366 7.4 数组约束 373 7.5 随机控制 388 第九章 线程与通信 432 9.1 线程的使用 432 9.2 线程的控制 441 三个fork...join 443 等待衍生线程 451 停止线程disable 451 9.3 线程的通信 458 第十章 进程评估:覆盖率 495 10.1 覆盖率类型 495 10.2 功能覆盖策略 510 10.3 覆盖组 516 10.4 数据采样 524 10.5 覆盖选项 544 10.6 数据分析 550 第十一章 SV语言核心进阶 552 11.1 类型转换 552 11.2 虚方法 564 11.3 对象拷贝 575 11.4 回调函数 584 11.5 参数化的类 590 第十二章 UVM简介 392 8.2 UVM简介 414 8.3 UVM组件 420 8.4 UVM环境 425

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值