我是靠谱客的博主 危机宝贝,最近开发中收集的这篇文章主要介绍电力电子转战数字IC——路科MCDF全览(持续更新)硬件RTL部分接口打包信号软件部分顶层的mcdf_rgm类mcdf_pkg,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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

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

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


目录

硬件RTL部分

slave_node

FIFO

arbiter

param_def

寄存器接口

整形器formatter

顶层MCDF

接口打包信号

APB接口

tb中的接口

tb后续

软件部分

chnl_pkg

fmt_pkg

mcdf_rgm_pkg

apb_pkg

apb_test

apb_slave


硬件RTL部分

slave_node

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

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

这里还有的疑问如下:

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

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

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

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

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

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

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

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

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

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

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

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

endmodule 

FIFO

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

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

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

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

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

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

wire full_s, empty_s; 
reg  overflow_r ;

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

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

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

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

endmodule 

arbiter

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

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

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

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

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

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

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

assign filter_R_s    = ~ filter_L_s ;

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

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

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

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

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

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

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

assign win_vec_o = win_vec_r;

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

    end 
end 


endmodule

param_def

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

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

`define  ADDR_WIDTH 8
`define  DATA_WIDTH 32

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

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

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

寄存器接口

APB4总线的序列图看这里。

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

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

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

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

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

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

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

);                        

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

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

reg [1:0] last_st, cur_st ;

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

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

wire                is_ctrl_rng_s;
wire                is_ro_rng_s;
wire                is_err_rng_s;

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

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

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

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

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

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

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

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

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

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

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

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

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


endmodule

整形器formatter

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

endmodule 
 

顶层MCDF

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

module mcdf(
input        clk_i              ,
input        rst_n_i            ,

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

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

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

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

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

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

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

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

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

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

endmodule

接口打包信号

APB接口

对应MCDF中的APB接口。

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


`ifndef APB_IF_SV
`define APB_IF_SV

interface apb_if (input clk, input rstn);

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

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

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

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

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

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

  // APB指令的覆盖组

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

endinterface : apb_if

`endif // APB_IF_SV

tb中的接口

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

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

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

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

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

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

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

tb后续

module tb;
  logic         clk;
  logic         rstn;

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


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

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

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

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

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

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

软件部分

chnl_pkg

 消息方法对应4个宏:

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

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

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

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

chnl_sequencer p_sequencer;

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

uvm_declare_p_sequencer_zilan23的博客-CSDN博客_uvm_declare_p_sequencer

 发送序列用的宏

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

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

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

在agent的build_phase中做:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    `uvm_component_utils(chnl_agent)

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

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

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

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

endpackage

fmt_pkg

复习mailbox

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

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

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

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

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

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

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

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

    `uvm_component_utils(fmt_driver)

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

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

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

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

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

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

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

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

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

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

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

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

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

    `uvm_component_utils(fmt_monitor)

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

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

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

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

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

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

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

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

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

endpackage

mcdf_rgm_pkg

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

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

关于set_coverage()

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

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

覆盖率选项设置

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

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

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

 关于uvm_reg_map

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

关于后门访问

  

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

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

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

顶层的mcdf_rgm类

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

UVM 中的寄存器模型

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

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

apb_pkg

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

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

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

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

package apb_pkg;

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

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

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

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

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

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

`ifndef APB_TRANSFER_SV
`define APB_TRANSFER_SV

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


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

  constraint cstr{
    soft idle_cycles == 1;
  };

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

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

endclass : apb_transfer

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

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

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

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

endclass : apb_master_driver
`endif

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

`ifndef APB_MASTER_DRIVER_SV
`define APB_MASTER_DRIVER_SV

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

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

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

`ifndef APB_MASTER_MONITOR_SVH
`define APB_MASTER_MONITOR_SVH

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

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

  event apb_master_cov_transaction;

  covergroup apb_master_cov_trans @apb_master_cov_transaction;

  endgroup : apb_master_cov_trans

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

`endif

//monitor正文

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

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

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

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

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

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

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

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

//sequencer正文

`ifndef APB_MASTER_SEQUENCER_SV
`define APB_MASTER_SEQUENCER_SV

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

`endif

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

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

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

`endif

//agent正文

`ifndef APB_MASTER_AGENT_SV
`define APB_MASTER_AGENT_SV

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


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

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

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

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

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

  `uvm_object_utils(apb_config)

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

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

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

endclass

`endif

apb_test

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

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

`ifndef APB_TESTS_SV
`define APB_TESTS_SV

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

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

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

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

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

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

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

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

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

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

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

    this.wait_cycles(10);
  endtask
endclass: apb_single_transaction_sequence

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

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

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

    this.wait_cycles(10);
  endtask
endclass: apb_burst_transaction_sequence

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

`endif

apb_slave

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

accept_tr, begin_tr, end_tr | Verification Academy

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

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

  `uvm_component_utils_begin(apb_slave_driver)
  `uvm_component_utils_end

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

`endif

//driver正文
`ifndef APB_SLAVE_DRIVER_SV
`define APB_SLAVE_DRIVER_SV

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

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

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

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

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

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

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

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

`endif

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

`ifndef APB_SLAVE_MONITOR_SVH
`define APB_SLAVE_MONITOR_SVH

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

  event apb_slave_cov_transaction;
  covergroup apb_slave_cov_trans @apb_slave_cov_transaction;
  endgroup : apb_slave_cov_trans

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

endclass : apb_slave_monitor

`endif 

//monitor正文

`ifndef APB_SLAVE_MONITOR_SV
`define APB_SLAVE_MONITOR_SV

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

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

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

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

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

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

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

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

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

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

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

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


mcdf_pkg

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

关于uvm_do的宏

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

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

package mcdf_pkg;

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

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

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

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

    `uvm_component_utils(mcdf_refmod)

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

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

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

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

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

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

    uvm_blocking_get_port #(fmt_trans) exp_bg_ports[4];

    `uvm_component_utils(mcdf_checker)

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

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

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

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

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

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

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

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

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

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

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

    `uvm_component_utils(mcdf_env)

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

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

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

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

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

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

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

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

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

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

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

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

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

    `uvm_component_utils(mcdf_base_test)

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

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

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

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

    virtual task run_top_virtual_sequence();
    endtask
  endclass: mcdf_base_test

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

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

    `uvm_component_utils(mcdf_data_consistence_basic_test)

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

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

关于set_timeout

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

最后

以上就是危机宝贝为你收集整理的电力电子转战数字IC——路科MCDF全览(持续更新)硬件RTL部分接口打包信号软件部分顶层的mcdf_rgm类mcdf_pkg的全部内容,希望文章能够帮你解决电力电子转战数字IC——路科MCDF全览(持续更新)硬件RTL部分接口打包信号软件部分顶层的mcdf_rgm类mcdf_pkg所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(50)

评论列表共有 0 条评论

立即
投稿
返回
顶部