当前位置:   article > 正文

【IC验证】一文速通多通道数据整型器(MCDF)

【IC验证】一文速通多通道数据整型器(MCDF)

目录

01 README

02 MCDF设计结构

2.1 功能描述

2.2 设计结构

2.3 接口与时序

2.3.1 系统信号接口

2.3.2 通道从端接口

2.3.3 整形器接口

2.3.4 控制寄存器接口

2.3.4.1 接口时序图

2.3.4.2 各数据位信息

03 验证框图

3.1 reg_pkg

3.1.1 reg_trans

3.1.2 reg_driver

3.1.3 reg_generator

3.1.4 reg_monitor

3.1.5 reg_agent

3.2 fmt_pkg

3.2.1 fmt_trans

3.2.2 fmt_driver

3.2.3 fmt_generator

3.2.4 fmt_monitor

3.2.5 fmt_agent

3.3 mcdf_pkg

3.3.1 mcdf_refmod

3.3.2 mcdf_checker

3.3.3 mcdf_env

3.3.4 mcdf_base_test

3.3.5 mcdf_reg_write_read_test

3.3.6 mcdf_reg_illegal_access_tets

3.3.7 mcdf_arbiter_priority_test

3.3.8 mcdf_channel_disbale_test

3.3.9 mcdf_coverage

04 覆盖率收集


01 README

        最最开始系统地学习芯片领域相关只是大概追溯到23年12月初,此前只是有一些Verilog和数电基础,做过两个小的设计项目。整个12月和1月几乎都在学习FPGA的相关课程,仍记得在跨年夜在宿舍敲UART串口通信那篇文章@UART串口通信_uart串口通讯-CSDN博客,但同时也感受到在国外没有开发板的情况下确实很难把这些东西学好。因此年后转而开始学习更偏向于软件的数字IC验证。期间也是断断续续,国内学校的事情太多了,因此总是不能全身心投入,加上自己软件基础并不好,C++,python这些语言学的都不好。大概花了3个月的时间基本学完了SV的语法,验证的基本流程和熟悉验证软件的使用,但也不敢说能够熟练掌握。目前算是进入了下一个时间节点,开始学习UVM验证环境。因此基于MCDF项目对此前学习进行一个总结。大佬们轻喷。

02 MCDF设计结构

2.1 功能描述

        设计模块的全称叫做multi-channel data formatter,他可以将上行多个数据通道的数据通过内部的SLAVE, FIFO给到仲裁器Arbiter, Arbiter选择从不同的FIFO中读取数据,给到下端的Formatter, 对数据进行整形,以数据包的形式送给下行的数据接收端。整个设计通过Register接受外部命令,对MCDF的功能进行修改。

2.2 设计结构

2.3 接口与时序

2.3.1 系统信号接口

clk(0)  :时钟信号
rstn(0):复位信号

2.3.2 通道从端接口

CHx_DATA(31:0):通道数据输入。

CHx_VALID(0):通道数据有效标志信号,高位有效。

CHx_READY(0):通道数据接收信号,高位表示接收成功

当valid为高时,表示要写入数据。如果该时钟周期ready为高,则表示已经将数据写入;如果该时钟周期ready为低,则需要等到ready为高的时钟周期才可以将数据写入。

2.3.3 整形器接口

FMT_CHID(1:0):整形数据包的通道ID号。
FMT_LENGTH(4:0):整形数据包长度信号。
FMT_REQ(0):整形数据包发送请求。
FMT_GRANT(0):整形数据包被允许发送的接受标示。
FMT_DATA(31:0):数据输出端口。
FMT_START(0):数据包起始标示。
FMT_END(0):数据包结束标示。

  • 整形器的作用是对数据进行打包,以数据包的形式向下行数据接收端传递数据,可以选择的数据包长度为4,8,16,32,这是由FMT_LENGTH信号来进行控制。
  • 在发送数据包时,首先应该将FMT_REQ置为高,同时等待接收端的FMT_GRANT拉高,此时表示接收端可以进行数据接受。当FMT_GRANT变为高时,应该在下一个周期将FMT_REQ置为低。FMT_START也必须在接收到FMT_GRANT高有效的下一个时钟被置为高,且需要维持一个时钟周期。在FMT_START被置为高有效的同一个周期,数据也开始传送,数据之间不允许有空闲周期,即应该连续发送数据,直到发送完最后一个数据时,FMT_END也应当被置为高并保持一个时钟周期。在数据包传输过程中FMT_CHID和FMT_LENGTH应该保持不变,直到数据包发送完毕。
  • 相邻的数据包之间应该至少有一个时钟周期的空闲,即FMT_END从高位被拉低以后,至少需要经过一个时钟周期,FMT_REQ才可以被再次置为高。

2.3.4 控制寄存器接口

CMD(1:0):寄存器读写命令。

CMD_ADDR(7:0):寄存器地址。

CMD_DATA_IN(31:0):寄存器写入数据。

CMD_DATA_OUT(31:0):寄存器读出数据。

2.3.4.1 接口时序图

当cmd为写指令时,需要把数据cmd_data_in写入到cmd_addr对应的寄存器中;当cmd为读指令时,即需要从cmd_addr对应的寄存器中读取数据,并在下一个周期,将数据驱动至cmd_data_out接口。

2.3.4.2 各数据位信息
  • 地址0x00 通道控制寄存器 32bits 读写寄存器

bit(0):通道使能信号。1为打开,0位关闭。复位值为1。

bit(2:1):优先级。0为最高,3为最低。复位值为3。
bit(5:3):数据包长度,解码对应表为, 0对应长度4,1对应长度8,2对应长度16,3对应长度32,其它数值(4-7)均暂时对应长度32。复位值为0。
bit(31:6):保留位,无法写入。复位值为0。

  • 地址0x10 通道状态寄存器 32bits 只读寄存器

bit(7:0):上行数据从端FIFO的可写余量,同FIFO的数据余量保持同步变化。复位值为FIFO的深度数。
bit(31:8):保留位,复位值为0。

03 验证框图

一共分为chnl_pkg, reg_pkg, fmt_pkg和mcdf_pkg, 各个包之间通过interface进行连接

3.1 reg_pkg

3.1.1 reg_trans

寄存器传输类,包含地址、命令、数据和响应字段

  1. class reg_trans;
  2. rand bit[7:0] addr;
  3. rand bit[1:0] cmd ;
  4. rand bit[31:0] data;
  5. bit rsp;
  6. constraint cstr{
  7. soft cmd inside{'WRITE, 'READ, 'IDLE};
  8. soft addr inside{'SLV0_RW_ADDR, 'SLV1_RW_ADDR, 'SLV2_RW_ADDR, 'SLV0_R_ADDR, 'SLV1_R_ADDR, 'SLV2_R_ADDR};
  9. addr[7:4] ==0 && cmd == 'WRITE -> soft data [31:6] == 0;//读写寄存器要求高26位为0
  10. soft addr [7:5] == 0;
  11. addr[4] == 1 -> soft cmd == 'READ;//地址为0x10,0x14,0x18的是通道1,2,3的只读存储器
  12. };
  13. function reg_trans clone() ;
  14. reg_trans c = new() ;
  15. c.addr = this.addr;
  16. c.cmd = this.cmd ;
  17. c.data = this.data;
  18. c.rsp = this.rsp ;
  19. return c ;
  20. endfunction
  21. function string sprint();
  22. string s ;
  23. s = {s, $sformatf("========================n")};
  24. s = {s, $sformatf("reg_trans object content is as below:\n")};
  25. s = {s, $sformatf("addr = %2x:\n", this.addr)};//两位16进制数
  26. s = {s, $sformatf("cmd = %2b:\n", this.cmd )};//2位2进制数
  27. s = {s, $sformatf("data = %8x:\n", this.data)};//8位16进制数
  28. s = {s, $sformatf("rsp = %0d:\n", this.rsp )};//10进制数,无字符宽度
  29. s = {s, $sformatf("=======================\n")};
  30. return s;
  31. endfunction
  32. endclass

3.1.2 reg_driver

寄存器驱动器,用于生成寄存器传输事务并将其发送到接口。
该类包括了一个 do_drive 任务来驱动事务的发送,一个 do_reset 任务来处理复位信号,以及一个 reg_write 任务来处理寄存器的读写操作。

  1. class reg_driver;
  2. local string name;
  3. local virtual reg_intf intf;
  4. mailbox #(reg_trans) reg_mb;
  5. mailbox #(reg_trans) rsp_mb;
  6. function new(string name = "reg_driver");
  7. this.name = name;
  8. endfunction
  9. function void set_interface (virtual reg_intf intf);
  10. if(intf == null)
  11. $error("interface is null");
  12. else
  13. this.intf == intf;
  14. endfunction
  15. task run();
  16. fork
  17. this.do_drive();
  18. this.do_reset();
  19. join
  20. endtask
  21. task do_reset();
  22. forever begin
  23. @(negedge intf.rstn);
  24. intf.cmd_addr <= 0 ;
  25. intf.cmd <= 'IDLE;
  26. intf.cmd_data_m2s <= 0 ;
  27. end
  28. task do_drive();
  29. reg_trans req, rsp;
  30. @(posedge intf.rstn);
  31. forever begin
  32. this.req_mb.get(req);
  33. this.reg_write(req);
  34. rsp = req.clone();
  35. rsp.rsp = 1;
  36. this.rsp_mb.put(rsp);
  37. end
  38. endtask
  39. task reg_write (reg_trans t);
  40. @posedge (intf.clk iff intf.rstn);//这是一个复合条件,iff 表示 "if and only if",即当且仅当。
  41. //所以整个条件表示:只有当 intf.rstn 为逻辑真时,才触发在 intf.clk 的上升沿执行操作。
  42. case(t.cmd)
  43. 'WRITE:begin
  44. intf.drv_ck.cmd_addr <= t.addr;
  45. intf.drv_ck.cmd <= t.cmd;
  46. intf.drv.cmd_data_m2s <= t.data;
  47. end
  48. 'READ :begin
  49. intf.drv_ck.cmd_addr <= t.addr;
  50. intf.drv_ck.cmd <= t.cmd;
  51. repeat(2) @(negedge intf.clk);//为何要等两个下降沿?
  52. t.data = intf.cmd_data_s2m;
  53. end
  54. 'IDLE :begin
  55. this.reg_idle();
  56. end
  57. default: $error("command %b is illegal", t.cmd);
  58. endcase
  59. $display("%0t reg driver [%s] sent addr %2x,cmd %2b,data %8x",$time,name,t.addr,t.cmd,t.data);
  60. endtask
  61. task reg_idle();
  62. @(posedge intf.clk);
  63. intf.drv_ck.cmd_addr <= 0 ;
  64. intf.drv_ck.cmd_addr <= 0 ;
  65. intf.drv_ck.cmd <= 'IDLE;
  66. endtask
  67. endclass

3.1.3 reg_generator

生成寄存器传输事务,并提供一个 send_trans 任务来发送事务并处理响应。

  1. class reg_generator;
  2. rand bit [7:0] addr = -1;
  3. rand bit [1:0] cmd = -1;
  4. rand bit [31:0]data = -1;
  5. mailbox #(reg_trans) req_mb;
  6. mailbox #(reg_trans) rsp_mb;
  7. reg_trans reg_req[$];
  8. constraint cstr{
  9. soft addr == -1;
  10. soft cmd == -1;
  11. soft data == -1;
  12. }
  13. function new();
  14. this.req_mb = new();
  15. this.rsp_mb = new();
  16. endfunction
  17. task start();
  18. send_trans();
  19. endtask
  20. //generate transaction and put into local mailbox
  21. task send_trans();
  22. reg_tans req,rsp;
  23. req = new();
  24. assert(req.randomize with{local::addr >= 0 -> addr == local::addr;
  25. local::cmd >= 0 -> cmd == local::cmd;
  26. local::data >= 0 -> data == local::data;
  27. })
  28. else
  29. $fatal("register packet randomization fail");
  30. $display(req.sprint());
  31. this.req_mb.put(req);
  32. this.rsp_mb.get(rsp);
  33. $display(rsp.sprint());
  34. if(req.cmd == 'READ)
  35. this.data = rsp.data;
  36. assert(rsp.rsp)//确保收到的响应中包含有效的响应标志。如果响应标志无效,则通过 $error 报错。
  37. else $error("[RSPERR] 0%t error response received!", $time);
  38. endtask
  39. function string sprint();
  40. string s ;
  41. s = {s, $sformatf("========================n")};
  42. s = {s, $sformatf("reg_trans object content is as below:\n")};
  43. s = {s, $sformatf("addr = %2x:\n", this.addr)};//两位16进制数
  44. s = {s, $sformatf("cmd = %2b:\n", this.cmd )};//2位2进制数
  45. s = {s, $sformatf("data = %8x:\n", this.data)};//8位16进制数
  46. s = {s, $sformatf("rsp = %0d:\n", this.rsp )};//10进制数,无字符宽度
  47. s = {s, $sformatf("=======================\n")};
  48. return s;
  49. endfunction
  50. //用于在随机化后显示对象的内容。
  51. function void post_randomize();
  52. string s;
  53. s = {"AFTER RANDOMIZE \n",this.sprint()};
  54. $display (s);
  55. endfunction
  56. endclass

3.1.4 reg_monitor

寄存器监视器,用于监视寄存器传输事务并将其存储到邮箱中。

  1. class reg_monitor;
  2. local string name;
  3. local virtual reg_intf intf;
  4. mailbox #(reg_trans) mon_mb;
  5. function new(string name = "reg_monitor");
  6. this.name = name;
  7. endfunction
  8. function void set_interface(virtual reg_intf intf);
  9. if(intf == null)
  10. $error("interface handle is null");
  11. else
  12. this.intf = intf;
  13. endfunction
  14. task run();
  15. this.mon_trans();
  16. endtask
  17. //其功能是监视寄存器接口的传输事务,并将监视到的信息存储到邮箱 mon_mb 中
  18. task mon_trans();
  19. reg_trans m;
  20. forever begin
  21. @(posedge intf.clk iff(intf.rstn && intf.mon_ck.cmd != 'IDLE));
  22. m = new();
  23. m.addr = intf.mon_ck.cmd_addr;
  24. m.cmd = intf.mon_ck.cmd;
  25. if(intf.mon_ck.cmd == 'READ)begin
  26. @(posedge intf.clk);
  27. m.data = intf.mon_ck.cmd_data_s2m;
  28. end
  29. mon_mb.put(m);
  30. $display("%0t reg driver [%s] sent addr %2x,cmd %2b,data %8x",$time,this.name,m.addr,m.cmd,m.data);
  31. end
  32. endtask
  33. endclass

3.1.5 reg_agent

组合 reg_driver 和 reg_monitor,并提供了一个 run 任务来同时运行驱动器和监视器。

  1. class reg_agent;
  2. local string name;
  3. reg_driver driver;
  4. reg_monitor monitor;
  5. local virtual reg_intf vif;
  6. function new(string name = "reg_agent");
  7. this.name = name;
  8. this.driver = new({name,".driver"});
  9. this.monitor = new({name,".monitor"});
  10. endfunction
  11. function void set_interface(virtual reg_intf vif);
  12. this.vif = vif;
  13. driver.set_interface(vif);
  14. monitor.set_interface(vif);
  15. endfunction
  16. task run();
  17. fork
  18. driver.run();
  19. monitor.run();
  20. join
  21. endtask
  22. endclass

3.2 fmt_pkg

  1. import rpt_pkg::*;
  2. typedef enum {SHORT_FIFO, MED_FIFO, LONG_FIOF, ULTRA_FIFO} fmt_fifo_t;
  3. typedef enum {LOW_WIDTH, MED_WIDTH, HIGH_WIDTH, ULTRA_WIDTH} fmt_bandwidth_t;
  4. typedef enum bit[2:0]{
  5. FMT_REQ = 3'b000
  6. ,FMT_WIDTH_GRANT = 3'b001
  7. ,FMT_START = 3'b011
  8. ,FMT_SEND = 3'b010
  9. ,FMT_END = 3'b110
  10. ,FMT_IDLE = 3'b111
  11. } fmt_state_t;

3.2.1 fmt_trans

模拟数据的比较和打印

  1. class fmt_trans;
  2. rand fmt_fifo_t fifo;
  3. rand fmt_bandwidth_t bandwidth;
  4. bit [9:0] length;
  5. bit [31:0]data[];
  6. bit [1:0] ch_id;
  7. bit rsp;
  8. constraint cstr{
  9. soft fifo == MED_FIFO ;
  10. soft bandwidth == MED_WIDTH;
  11. };
  12. function fmt_trans clone();
  13. fmt_trans c = new();
  14. c.fifo = this.fifo;
  15. c.bandwidth = this.bandwidth;
  16. c.length = this.length;
  17. c.data = this.data;
  18. c.ch_id = this.ch_id;
  19. c.rsp = this.rsp;
  20. return c;
  21. endfunction
  22. function string sprint();
  23. string s ;
  24. s = {s, $sformatf("========================\n")};
  25. s = {s, $sformatf("fmt_trans object content is as below:\n")};
  26. s = {s, $sformatf("fifo = %s:\n", this.fifo)};
  27. s = {s, $sformatf("bandwidth = %s:\n", this.bandwidth )};
  28. s = {s, $sformatf("length = %s:\n", this.length)};
  29. s = {s, $sformatf("rsp = %0d:\n", this.rsp )};
  30. foreach (data[i]) s = {s,$sformatf("data[%0d] = %8x \n", i, this.data[i])};
  31. s = {s, $sformatf("ch_id = %0d:\n", this.ch_id)};
  32. s = {s, $sformatf("rsp = %0d:\n", this.rsp )};
  33. s = {s, $sformatf("=======================\n")};
  34. return s;
  35. endfunction
  36. //??为啥要做这两个比较呢,fmt_trans例化后的内容不应该就和fmt_trans中的一样吗
  37. //当你创建一个类(如 fmt_trans)的实例(对象)时,每个实例通常拥有自己的一套成员变量。
  38. //这意味着,即使两个对象是相同类的不同实例,它们的成员变量也可能拥有不同的值。
  39. //检查它们是否具有相同的长度和通道ID,并且它们的数据数组是否相同
  40. function bit compare(fmt_trans t);
  41. string s;
  42. compare = 1;//假设对象最初是相等的。
  43. s = "\n============================\n";
  44. s = {s,$sformatf("COMPARING fmt_trans object at time %0d \n", $time)};
  45. if(this.length != t.length)begin//this.lengh指的是18行的length
  46. compare = 0;
  47. s = {s, $sformatf("sobj length %0d != tobj length %0d \n", this.length, t.length)};
  48. end
  49. if(thi.ch_id != t.ch_id)begin
  50. compare = 0;
  51. s = {s, $sformatf("sobj ch_id %0d != tobj ch_id %0d \n", this.ch_id, t.ch_id)};
  52. end
  53. foreach(this.data[i])begin
  54. if(this.data[i] != t.data[i])begin
  55. compare = 0;
  56. s = {s, $sformatf("sobj data[%0d] %8x != tobj data[%0d] %8x \n",i, this.data[i], i, t.data[i])};
  57. end
  58. end
  59. if(compare == 1) s = {s, "COMPARED SUCCESS!\n"};
  60. else s = {s, "COMPARED FAILURED! \n "};
  61. rpt_pkg::rpt_msg("[CMPOBJ]", s, rpt_pkg::INFO, rpt_pkg::MEDIUM);
  62. endfunction
  63. endclass

3.2.2 fmt_driver

模拟数据发送和接受行为

  1. class fmt_driver;
  2. local string name;
  3. local virtual fmt_intf intf;
  4. mailbox #(fmt_trans) req_mb;
  5. mailbox #(fmt_trans) rsp_mb;//与generator通信
  6. local mailbox #(logical[31:0]) fifo;//模拟formatter的下行数据
  7. local int fifo_bound; //模拟的下行通道最多能接受多少数据
  8. local int data_consum_period;// 反应下行数据输出的快慢。反比
  9. function new(string name = "fmt_driver");
  10. this.name = name;
  11. this.fifo = new();
  12. this.fifo_bound = 4096;
  13. this.data_consum_period = 1;
  14. endfunction
  15. function void set_interface(virtual fmt_intf intf);
  16. if(intf == null)
  17. $error("interface handle is null");
  18. else
  19. this.intf = intf;
  20. endfunction
  21. task run();
  22. fork
  23. this.do_receive();
  24. this.do_consume();
  25. this.do_config();
  26. this.do_reset();
  27. join
  28. //配置driver
  29. task do_config();
  30. fmt_trans req, rsp;
  31. forever begin
  32. this.req_mb.get(req);
  33. case(req.fifo)
  34. SHORT_FIFO : this.fifo_bound = 64 ;
  35. MED_FIFO : this.fifo_bound = 256 ;
  36. LONG_FIOF : this.fifo_bound = 512 ;
  37. ULTRA_FIFO : this.fifo_bound = 2048;
  38. endcase
  39. this.fifo = new(this.fifo_bound);
  40. case(req.bandwidth) //为啥位宽低data_consum_period大呢?
  41. //可能是因为在低带宽条件下,数据传输速率较慢,
  42. //因此需要更长的周期来消费数据,以匹配数据的传输速率。
  43. LOW_WIDTH : this.data_consum_period = 8 ;
  44. MED_WIDTH : this.data_consum_period = 4 ;
  45. HIGH_WIDTH : this.data_consum_period = 2 ;
  46. ULTRA_WIDTH : this.data_consum_period = 1 ;
  47. endcase
  48. rsp = req.clone();
  49. rsp.rsp = 1;//这里的 rsp 成员变量是一个标志位,用于表示某种状态或响应的有效性。
  50. this.rsp_mb.put(rsp);
  51. end
  52. endtask
  53. task do_reset();
  54. forever begin
  55. @(negedge intf.rstn)
  56. intf.fmt_grant <= 0;
  57. end
  58. endtask
  59. //模拟具有随机延迟的数据消费场景。按照随机的时间间隔取出数据
  60. task do_consume();
  61. logic[31:0] data;
  62. forever begin
  63. void'(this.fifo.try_get(data));// 使用 try_get 方法尝试从FIFO中取出一个数据项并赋值给 data 变量
  64. //void'() 是SystemVerilog中的一个特性,它用来忽略 try_get 方法返回的状态值
  65. repeat($urandom_range(1, this.data_consum_period)) @(posedge intf.clk);//这里反应消耗数据的快慢
  66. //等待intf.clk的上升沿,repeat重复一次
  67. end
  68. endtask
  69. endclass
  70. //当数据接受的请求信号fmt_req拉高时,且FIFO深度足够时,fmt_grant拉高,表示接收方准备好接受数据了
  71. //
  72. task do_receive();//模拟接受数据
  73. forever begin
  74. @(posedge intf.fmt_req);
  75. forever begin
  76. @(posedge intf.clk);
  77. if((this.fifo_bound-this.fifo.num()) >= intf.fmt_length)
  78. //fifo_bound是选择的fifo的总深度
  79. //检查当前FIFO的剩余空间是否足够存放即将到来的数据。
  80. //如果剩余空间大于或等于 intf.fmt_length 指定的长度,则跳出内部循环。
  81. break;//只跳出内部的这个forever。当fifo余量足够时,拉高grant
  82. end
  83. intf.drv_ck.fmt_grant <= 1;//表示接收方准备好接受数据了
  84. @(posedge intf.fmt_start);//等待 intf.fmt_start 信号的下一个上升沿,
  85. //这表示发送方已经准备好发送数据。
  86. fork
  87. begin
  88. @(posedge intf.clk);
  89. intf.drv_ck.fmt_grant <= 0;//维持一拍后就拉低,不再授权数据传输
  90. end
  91. join_none
  92. repeat(intf.fmt_length)begin
  93. @(nedge intf.clk);
  94. this.fifo.put(intf.fmt_data);//连续放入数据
  95. end
  96. end
  97. endtask

3.2.3 fmt_generator

创建、随机化、发送和接收 fmt_trans 类型的数据事务

  1. class fmt_generator;
  2. rand fmt_fifo_t fifo = MED_FIFO;
  3. rand fmt_bandwidth_t bandwidth = MED_WIDTH;
  4. mailbox #(fmt_trans) req_mb;//在 fmt_generator 类中,req_mb 用于发送 fmt_trans 类型的请求事务,
  5. //而 rsp_mb 用于接收响应事务。这种方式使得 fmt_generator 可以作为一个独立的组件,
  6. //模拟发送请求并等待响应,而不依赖于特定的通信协议实现细节。
  7. mailbox #(fmt_trans) rsp_mb;
  8. constraint cstr{
  9. soft fifo == MED_FIFO;
  10. soft bandwidth == MED_WIDTH;
  11. }
  12. function new();
  13. this.req.mb = new();
  14. this.rsp.mb = new();
  15. endfunction
  16. task start();
  17. send_trans();
  18. endtask
  19. //产生事务并放到本地信箱
  20. task send_trans();
  21. fmt_trans req, rsp;
  22. req = new();//为何这里是MED_FIFO呢,不等于就随机化失败?什么道理
  23. //因为192行就是这样给的
  24. assert (req.randomize with {local::fifo != MED_FIFO -> fifo == local::fifo
  25. local::bandwidth != MED_WIDTH -> bandwidth == local::bandwidth;
  26. })
  27. else $fatal("[RNDFALL] formatter packet randomize failure!");
  28. $display(req.sprint());
  29. this.req_mb.put(req);//放入请求
  30. this.rsp_mb.get(rsp);//取出响应
  31. $display(rsp.sprint());
  32. assert(rsp.rsp)//检查响应是否有效
  33. else $error("[RSPERR] %0t error response received!", $time);
  34. endtask
  35. function string sprint();
  36. string s;
  37. s = {s, $sformatf("========================\n")};
  38. s = {s, $sformatf("fmt_generator object content is as below:\n")};
  39. s = {s, $sformatf("fifo = %s:\n", this.fifo)};
  40. s = {s, $sformatf("bandwidth = %s:\n", this.bandwidth )};
  41. s = {s, $sformatf("=======================\n" )};
  42. return s;
  43. endfunction
  44. function void post_randomize();
  45. string s;
  46. s = {"AFTER RANDOMIZE \n", this.sprint()};
  47. $display(s);
  48. endfunction
  49. endclass

3.2.4 fmt_monitor

用于观察和记录通过接口 fmt_intf 传输的数据事务

  1. class fmt_monitor;
  2. local string name;
  3. local virtual fmt_intf intf;
  4. mailbox #(fmt_trans) mon_mb;//用于存储监视到的 fmt_trans 类型的事务。
  5. function new(string name = "fmt_generator");
  6. this.name = name;
  7. endfunction
  8. function void set_interface(virtual fmt_intf intf);
  9. if(intf == null)
  10. $error("interface handle is null")
  11. else
  12. this.intf = intf;
  13. endfunction//设置监视器要监视的接口
  14. task run();
  15. this.mon_trans();
  16. endtask
  17. task mon_trans();
  18. fmt_trans m;
  19. string s;
  20. forever begin
  21. @(posedge intf.mon_ck.fmt_start);//等待 intf.mon_ck.fmt_start 信号的上升沿,表示一个新事务的开始。
  22. m = new();
  23. m.length = intf.mon_ck.fmt_length;
  24. m.ch_id = intf.mon_ck.fmt_chid;
  25. m.data = new[m.length];//接口中获取事务的长度、通道ID和数据,并将它们赋值给 m 对象。
  26. foreach(m.data[i])begin
  27. @(posedge intf.clk);//在每个时钟上升沿从接口中获取数据,并将其存储在 m.data 数组中。
  28. m.data[i] = intf.mon_ck.fmt_data;
  29. end
  30. mon_mb.put(m);
  31. s = {s, $sformatf("========================\n")};
  32. s = {s, $sformatf("%0t %s monitored a packet:\n", $time, this.name)};
  33. s = {s, $sformatf("length = %0d:\n", m.length)};
  34. s = {s, $sformatf("chid = %0d:\n", m.ch_id )};
  35. foreach (m.data[i] s = {s,$sformatf("data[%0d] = %8x \n", i, m.data[i])};
  36. s = {s, $sformatf("=======================\n" )};
  37. $display(s);
  38. end
  39. endtask
  40. endclass

3.2.5 fmt_agent

组合驱动器(fmt_driver)和监视器(fmt_monitor)组件

  1. class fmt_agent;
  2. local string name;
  3. fmt_driver driver;
  4. fmt_monitor monitor;
  5. local virtual fmt_intf vif;
  6. function new(string name = "fmt_agent");
  7. this.name = name;
  8. this.driver = new({name, ".driver"});
  9. this.monitor= new({name, ".monitor"});
  10. endfunction
  11. function void set_interface(virtual fmt_intf vif);
  12. this.vif = vif;
  13. driver.set_interface(vif);
  14. monitor.set_interface(vif);
  15. endfunction
  16. task run();
  17. fork
  18. driver.run();
  19. monitor.run();
  20. join
  21. endtask
  22. endclass

3.3 mcdf_pkg

  1. import chnl_pkg::*;
  2. import reg_pkg ::*;
  3. import arb_pkg ::*;
  4. import fmt_pkg ::*;
  5. import rpt_pkg ::*;
  6. typedef struct packed{
  7. bit[2:0] lenl ;//后面用来规定各寄存器通道的数据包长度,优先级,使能信号,可写余量
  8. bit[1:0] prio ;
  9. bit en ;
  10. bit[7:0] avail;
  11. }mcdf_reg_t
  12. typedef enum{RW_LEN, RW_PRIO, RW_EN, RD_AVAIL}mcdf_field_t;//寄存器的不同比特位代表不同的功能,详见蓝皮书376

3.3.1 mcdf_refmod

用于测试或模拟MCDF模块的寄存器行为,形成一个参考模型

  1. class mcdf_refmod;
  2. local virtual mcdf_intf intf;
  3. local string name;
  4. mcdf_reg_t regs[3];
  5. mailbox #(reg_trans) reg_mb;
  6. mailbox #(mon_data_t) in_mbs [3];
  7. mailbox #(fmt_trans) out_mbs[3];
  8. function new(string name = "mcdf_refmod");
  9. this.name = name;
  10. foreach (this.out_mbs[i]) this.out_mbs[i] = new();
  11. endfunction
  12. task run();
  13. fork
  14. do_reset();
  15. this.do_reg_update();
  16. do_packet(0);
  17. do_packet(1);
  18. do_packet(2);
  19. join
  20. endtask
  21. task do_reg_update();
  22. reg_trans t;
  23. forever begin
  24. this.reg_mb.get(t);
  25. if(t.addr[31 :4] == 0 && t.cmd == 'WRITE)begin
  26. this.regs[t.addr[3:2]].en = t.data[0] ;
  27. this.regs[t.addr[3:2]].prio = t.data[2:1] ;
  28. this.regs[t.addr[3:2]].len = t.data[5:3] ;
  29. end
  30. else if(t.addr[7:4] == 1 && t.cmd == 'READ)begin
  31. this.regs[t.addr[3:2]].avail = t.data[7:0];
  32. end
  33. end
  34. endtask
  35. //处理数据包
  36. task do_packet(int id);
  37. fmt_trans ot;
  38. mon_data_t it;
  39. bit[2:0] len;
  40. forever begin
  41. this.in_mbs[id].peek(it);
  42. ot = new();
  43. len = this.get_field_value(id, RW_LEN);
  44. ot.length = len > 3 ? 32:4 << len;//根据len的值计算数据包的长度。
  45. //为什么数据包长度这样定义 //如果len大于3,则长度为32,否则长度len左移4位。
  46. //因为第50行规定第46位存放数据包长度
  47. ot.data = new[ot.length];//根据计算出的长度,动态分配一个整型数组给ot.data
  48. //用于存储数据包的数据。
  49. ot.ch_id = id;
  50. foreach(ot.data[i])begin
  51. this.in_mbs[id].get(it);//从信箱中取数据,给到it
  52. ot.data[i] = it.data;
  53. end
  54. this.out_mbs[id].put(ot);//将格式化后的事务ot放入到输出mailbox(out_mbs)中
  55. end
  56. endtask
  57. function int get_field_value(int id, mcdf_field_t f);//mcdf_field_t是18行的枚举类型
  58. case(f)
  59. RW_LEN : return regs[id].len ;//这四个case分别代表什么?
  60. RW_PRIO : return regs[id].prio ;
  61. RW_EN : return regs[id].en ;
  62. RD_AVAIL: return regs[id].avail;
  63. endcase
  64. endfunction
  65. task do_reset();
  66. forever begin
  67. @(negedge intf.rstn);
  68. foreach(regs[i])begin
  69. regs[i].len = 'h0;
  70. regs[i].prio = 'h3;
  71. regs[i].en = 'h1;//为什么复位信号要给这些值?
  72. regs[i].avail= 'h20;//应该还是和task do_reg_update有关
  73. end
  74. end
  75. endtask
  76. function void set_interface(virtual mcdf_intf intf);
  77. if(intf == null)
  78. $error("interface handle is null");
  79. else
  80. this.intf = intf;
  81. endfunction
  82. endclass

3.3.2 mcdf_checker

将refmod的数据与fmt的数据进行对比

  1. class mcdf_checker;
  2. local string name;
  3. local int err_count;
  4. local int total_count;
  5. local int chl_count[3];
  6. local virtual mcdf_intf intf;
  7. local refmod mcdf_refmod;
  8. mailbox #(mon_data_t) chnl_mbs[3];
  9. mailbox #(fmt_trans) fmt_mb;
  10. mailbox #(reg_trans) reg_mb;
  11. mailbox #(fmt_trans) exp_mbs[3];
  12. function new(string name = "mcdf_checker");
  13. this.name = name;
  14. foreach(this.chnl_mbs[i]) this.chnl_mbs[i] = new();
  15. this.fmt_mb = new();
  16. this.reg_mb = new();
  17. this.refmod = new();
  18. foreach (this.refomd.in_mbs[i])begin
  19. this.refmod.in_mbs[i] = this.chnl_mbs[i];
  20. this.exp_mbs[i] = this.refmod.out_mbs[i];
  21. end
  22. this.refmod.reg_mb = this.reg_mb;
  23. this.err_count = 0;
  24. this.total_count = 0;
  25. foreach(this.chnl_count[i]) this.chnl_count[i] = 0;
  26. endfunction
  27. function void set_interface (virtual mcdf_intf intf);
  28. if(intf == null)
  29. $error("interface handle is null");
  30. else
  31. this.intf = intf;
  32. this.refmod.set_interface(intf);
  33. endfunction
  34. task run();
  35. fork
  36. this.do_compare();
  37. this.refmod.run();
  38. join
  39. endtask
  40. //比较收集到的数据是否和参考数据一样
  41. task do_compare();
  42. fmt_trans expt, mont;
  43. bit cmp;
  44. forever begin
  45. this.fmt_mb.get(mont);//formatter中的数据
  46. this.exp_mbs[mont.ch_id].get(expt);//期望的数据
  47. cmp = mont.compare(expt);//调用的是fmt_pkg里面的compare函数
  48. this.total_count++;//是拿来计数什么的?
  49. this.chnl_count[mont.ch_id]++;//是拿来计数什么的?
  50. if(cmp == 0)begin//不相等
  51. this.err_count++;
  52. rpt_pkg::rpt_msg("[CMP FAIL]", $sformatf("%0t %0dth times comparing but failed!MCDF monitored output packet is different with reference model output", $time, this.total_count),
  53. rpt_pkg::ERROR,
  54. rpt_pkg::TOP,
  55. rpt_pkg::LOG);
  56. end
  57. else begin
  58. rpt_pkg::rpt_msg("[CMP SUCD]", $sformatf("%0t %0dth times comparing and succeeded! MCDF monitored output packet is different with reference model output", $time, this.total_count),
  59. rpt_pkg::INFO,
  60. rpt_pkg::HIGH,
  61. end
  62. end
  63. endtask
  64. function void do_report();
  65. string s;
  66. s = "\n--------------------------------------------\n";
  67. s = {s, " CHECKER SUMMARY \n"};
  68. s = {s, $sformatf("total comparison count: %0d \n", this.total_count)};
  69. foreach (this.chnl_count[i]) begin
  70. if(this.chnl_mbs[i].num() != 0 )//判断是否把数据比较完
  71. s = {s, $sformatf("WARNING:: chnl_mbs[%0d] is not empty! size = %0d \n", i, this.chnl_mbs[i].num())};
  72. end
  73. if(this.fmt_mb.num() != 0)
  74. s = {s, $sformatf("WARNING:: fmt_mb is not empty! size = %0d \n", this.fmt_mb.num())};
  75. s = "\n--------------------------------------------\n";
  76. rpt_pkg::rpt_msg($sformatf("[%s]",this.name), s, rpt_pkg::INFO, rpt_pkg::TOP);
  77. endfunction
  78. endclass

3.3.3 mcdf_env

用于构建和运行MCDF模块的测试环境

  1. class mcdf_env;
  2. chnl_agent chnl_agts[3];
  3. reg_agent reg_agt;
  4. fmt_agent fmt_agt;
  5. mcdf_checker chker;
  6. mcdf_coverage cvrg;
  7. protected string name;
  8. function new(string name = "mcdf_env");
  9. this.name = name;
  10. this.chker = new();
  11. foreach(chnl_agts[i])begin
  12. this.chnl_agts[i] = new($sformatf("chnl_agts[%0d]", i));
  13. this.chnl_agts[i].monitor.mon_mb = this.chker.chnl_mbs[i];
  14. end
  15. this.reg_agt = new("reg_agt");
  16. this.reg_agt.monitor.mon_mb = this.chker_reg_mb;
  17. this.fmt_agt = new("fmt_agt");
  18. this.fmt_agt.monitor.mon_mb = this.chker.fmt.mb;
  19. this.cvrg = new();
  20. $display("%s instantiated and connected objects", this.name);
  21. endfunction
  22. virtual task run();
  23. $display($sformatf("*****************%s started***********", this.name));
  24. this.do_config();
  25. fork
  26. this.chnl_agts[0].run)();
  27. this.chnl_agts[1].run)();
  28. this.chnl_agts[2].run)();
  29. this.reg_agt.run)();
  30. this.fmt_agt.run)();
  31. this.chker.run();
  32. this.cvrg.run();
  33. join
  34. endtask
  35. virtual function void do_config();//之前式用do_config来配置generator。但是
  36. endfunction //现在将其移植到mcdf_base_test中来进行配置了
  37. virtual function void do_report();
  38. this.chker.do_report();
  39. this.cvrg.do_reporet();
  40. endfunction
  41. endclass

3.3.4 mcdf_base_test

测试模板

  1. class mcdf_base_test;
  2. chnl_generator chnl_gens[3];
  3. reg_generator reg_gen;
  4. fmt_generator fmt_gen;
  5. mcdf_env env;
  6. protected string name;
  7. function new(string name = "mcdf_base_test");
  8. this.name = name;
  9. this.env = new(env);
  10. //将每个gens给到env进行代理
  11. foreach(this.chnl_gens[i])begin
  12. this.chnl_gens[i] = new();
  13. this.env.chnl_agts[i].driver.req_mb = this.chnl_gens[i].req_mb;
  14. this.env.chnl_agts[i].driver.rsq_mb = this.chnl_gens[i].rsq_mb;
  15. end
  16. this.reg_gen = new();
  17. this.env.reg_agt.driver.req_mb = this.reg_gen.req_mb;
  18. this.env.reg_agt.driver.rsp_mb = this.reg_gen.rsp_mb;
  19. this.fmt_gen = new();
  20. this.env.fmt_agt.driver.req_mb = this.fmt_gen.req_mb;
  21. this.env.fmt_agt.driver.rsp_mb = this.fmt_gen.rsp_mb;
  22. $display("%s instantiated and connected objects", this.name);
  23. endfunction
  24. virtual task run();
  25. fork
  26. env.run();
  27. join_none
  28. rpt_pkg::rpt_msg("[TEST]",
  29. $sformatf("===========%s AT TIME 0%t STARTED=======", this.name,$time),
  30. rpt_pkg::INFO;
  31. rpt_pkg::HIGH);
  32. this.do_reg();
  33. this.do_formatter();
  34. this.do_data();
  35. rpt_pkg::rpt_msg("[TEST]",
  36. $sformatf("===========%s AT TIME 0%t FINISHED=======", this.name,$time),
  37. rpt_pkg::INFO;
  38. rpt_pkg::HIGH);
  39. this.do_report();
  40. $finish();
  41. endtask
  42. //do register configuration
  43. virtual task do_reg();
  44. endtask
  45. //do external formatter down stream slave configuration
  46. virtual task do_formatter();
  47. endtask
  48. //do data transition from 3 channel slaves
  49. virtual task do_data();
  50. endtask
  51. //do simulation SUMMARY
  52. virtual function void do_report();
  53. this.env.do_report();
  54. rpt_pkg::do_report();
  55. endfunction
  56. virtual function void set_interface( virtual chnl_intf ch0_vif
  57. ,virtual chnl_agent ch1_vif
  58. ,virtual chnl_agent ch2_vif
  59. ,virtual reg_intf reg_vif
  60. ,virtual fmt_intf fmt_vif
  61. ,virtual mcdf_intf mcdf_vif);
  62. this.env.chnl_agts[0].set_interface(ch0_vif);
  63. this.env.chnl_agts[1].set_interface(ch1_vif);
  64. this.env.chnl_agts[2].set_interface(ch2_vif);
  65. this.env.reg_agt.set_interface(reg_vif);
  66. this.env.fmt_agt.set_interface(fmt_vif);
  67. this.env.chker.set_interface(mcdf_vif);
  68. this.env.cvrg.set_interface('{ch0_vif, ch1_vif, ch2_vif,}, reg_vif, arb_vif, fmt_vif, mcdf_vif);
  69. endfunction
  70. virtual function bit diff_value(int val1, int val2, string id ="value_compare");
  71. if(val1 != val2)begin
  72. rpt_pkg::rpt_msg("[CMP ERR]",
  73. $sformatf("ERROR! %s val1 %8x != val2 %8x", id, val1, val2),
  74. rpt_pkg::ERROR;
  75. rpt_pkg::TOP);
  76. return 0;
  77. end
  78. else begin
  79. rpt_pkg::rpt_msg("[CMP SUC]",
  80. $sformatf("SUCCESS! %s val1 %8x == val2 %8x", id, val1, val2),
  81. rpt_pkg::INFO;
  82. rpt_pkg::HIGH);
  83. return 1;
  84. end
  85. endfunction
  86. virtual task idle_reg();
  87. void'(reg_gen.randomize() with {cmd == 'IDLE; addr == 0; data == 0;});
  88. reg_gen.start();
  89. endtask
  90. virtual task write_reg(bit[7:0]addr, bit[31:0] data);
  91. void'(reg_gen.randomize() with {cmd == 'WRITE; addr == local::addr; data == local::data;});
  92. reg_gen.start();
  93. endtask
  94. virtual task read_reg(bit[7:0]addr, output bit[31:0] data);
  95. void'(reg_gen.randomize() with {cmd == 'READ; addr == local::addr;});
  96. reg_gen.start();
  97. data = reg_gen.data;
  98. endtask
  99. endclass

3.3.5 mcdf_data_consistence_basic_test

读写数据一致性检测,检查寄存器读进去和写出来的数据是否一致

  1. class mcdf_data_consistence_basic_test extends mcdf_base_test;
  2. function new(string name = "mcdf_data_consistence_basic_test");
  3. super.new(name);
  4. endfunction
  5. //检查读写是否一致
  6. task do_reg();
  7. bit[31:0] wr_val, rd_val;
  8. //slv0 with len=8,prior=0,en=1
  9. wr_val = (1<<3)+(0<<1)+1;//为什么要这样设置呢
  10. this.wrtie_reg('SLV0_RW_ADDR, wr_val);//写进去
  11. this.read_reg('SLV0_RW_ADDR, rd_val);//读回来
  12. void'(this.diff_value(wr_val, rd_val, "SLV0_WR_REG"));//把写进去的值和读回来的值做一个比较
  13. //看看写进去的值是否合法
  14. //slv1 with len=16,prior=1,en=1
  15. wr_val = (2<<3)+(1<<1)+1;
  16. this.wrtie_reg('SLV1_RW_ADDR, wr_val);
  17. this.read_reg('SLV1_RW_ADDR, rd_val);
  18. void'(this.diff_value(wr_val, rd_val, "SLV1_WR_REG"));
  19. //slv2 with len=32,prior=2,en=1
  20. wr_val = (3<<3)+(2<<1)+1;
  21. this.wrtie_reg('SLV2_RW_ADDR, wr_val);
  22. this.read_reg('SLV2_RW_ADDR, rd_val);
  23. void'(this.diff_value(wr_val, rd_val, "SLV2_WR_REG"));
  24. //send IDLE command
  25. this.idle_reg();
  26. endtask
  27. //配置格式化器(fmt_gen)的随机值,包括 FIFO 大小和带宽。
  28. task do_formatter();
  29. void'(fmt_gen.randomize()with{fifo == LONG_FIFO; bandwidth == HIGH_WIDTH;});
  30. fmt_gen.start();
  31. endtask
  32. //生成数据
  33. task do_data();
  34. void'(chnl_gens[0].randomize() with {ntrans == 100; ch_id == 0; data_nidles == 0; pkt_nidles == 1; data_size == 8;});
  35. void'(chnl_gens[1].randomize() with {ntrans == 100; ch_id == 1; data_nidles == 1; pkt_nidles == 1; data_size == 16;});
  36. void'(chnl_gens[2].randomize() with {ntrans == 100; ch_id == 2; data_nidles == 2; pkt_nidles == 1; data_size == 32;});
  37. fork
  38. chnl_gens[0].start();
  39. chnl_gens[1].start();
  40. chnl_gens[2].start();
  41. join
  42. #10us;//wait until all data haven been transfered through MCDF
  43. endtask
  44. endclass

3.3.5 mcdf_reg_write_read_test

寄存器读写测试,包括对控制读写寄存器和状态只读寄存器的测试

  1. class mcdf_reg_write_read_test extends mcdf_base_test;
  2. function new (string name = "mcdf_reg_write_read_test");
  3. super.new(name);
  4. endfunction
  5. virtual task do_reg();
  6. bit [31:0]wr_val, rd_val;
  7. bit [7:0] chnl_rw_addrs[] = '{'SLV0_RW_ADDR, 'SLV1_RW_ADDR, 'SLV2_RW_ADDR};//读写寄存器
  8. bit [7:0] chnl_ro_addrs[] = '{'SLV0_R_ADDR, 'SLV1_R_ADDR, 'SLV2_R_ADDR};//只读寄存器
  9. int pwidth = 'PAC_LEN_WIDTH + 'PRIO_WIDTH + 1;//PAC_LEN_WIDTH是定义在param_def中的常数
  10. bit[31:0] check_pattern[] = '{32'h0000_FFC0, 32'hFFFF_0000};//类似于地址检查库
  11. foreach (chnl_rw_addrs[i])begin
  12. foreach(check_pattern[j])begin
  13. wr_val = check_pattern[j];
  14. this.write_reg(chnl_rw_addrs[i], wr_val);
  15. this.read_reg(chnl_rw_addr[i], rd_val);
  16. void'((this.diff_value(wr_val & ((1<<pwidth)-1), rd_val));//为什么,(1<<pwidth)-1=31
  17. end//按位与操作。这种掩码结果的二进制形式会有pwidth个低位是1。即保留wr_val的低pwidth位,其余位清零
  18. end//如果wr_val是32'h0000_FFC0,且pwidth是5,那么结果是32'h0000_0010
  19. //仅比较有效位(低pwidth位),忽略无关的高位。这在寄存器操作中非常重要,
  20. //因为某些寄存器的高位可能是未定义或保留位,比较时应避免这些位的影响。
  21. foreach(chnl_ro_addrs[i])begin
  22. wr_val = 32'hFFFF_FF00;//由于是只读寄存器,因此写进入的值是确定的
  23. this.write_reg(chnl_ro_addrs[i],wr_val);
  24. this.read_reg(chnl_ro_addrs[1], rd_val);
  25. void'(this.diff_value(0, rd_val & wr_val));//这用于将函数调用的返回值转换为 void,实际上是忽略函数的返回值。
  26. end//对于只读寄存器,我们期望写入操作不会改变寄存器的值。
  27. //因此,我们预期从只读寄存器读取到的值应该是0(这就是为什么第一个参数是0)。
  28. this.idle_reg();
  29. endtask
  30. endclass

3.3.6 mcdf_reg_illegal_access_tets

寄存器稳定性测试。

  1. class mcdf_reg_illegal_access_tets extends mcdf_base_test;
  2. function new(string name = "mcdf_reg_illegal_access_tets");
  3. super.new(name);
  4. endfunction
  5. task do_reg();
  6. bit [31:0]wr_val, rd_val;
  7. bit [7:0] chnl_rw_addrs[] = '{'SLV0_RW_ADDR, 'SLV1_RW_ADDR, 'SLV2_RW_ADDR};
  8. bit [7:0] chnl_ro_addrs[] = '{'SLV0_R_ADDR, 'SLV1_R_ADDR, 'SLV2_R_ADDR};
  9. int pwidth = 'PAC_LEN_WIDTH + 'PRIO_WIDTH + 1;
  10. bit[31:0] check_pattern[] = '{((1<<pwidth)-1), 0, ((1<<pwidth)-1)};
  11. foreach (chnl_rw_addrs[i])begin
  12. foreach(check_pattern[j])begin
  13. wr_val = check_pattern[j];
  14. this.write_reg(chnl_rw_addrs[i],wr_val);
  15. this.read_reg(chnl_rw_addrs[i],rd_val);
  16. void'(this.diff_value(wr_val, rd_val));
  17. end
  18. end
  19. foreach(chnl_ro_addrs[i])begin
  20. this.read_reg(chnl_ro_addrs[i],rd_val);
  21. end
  22. this.idle_reg();
  23. endtask
  24. endclass

3.3.7 mcdf_arbiter_priority_test

arbiter优先级测试

如果各数据通道优先级相同,那么arbiter应该采取轮询机制从各个通道接受数据,如果优先级不同,先从高优先级的通道接收数据

  1. class mcdf_arbiter_priority_test extends mcdf_base_test;
  2. function new (string name = "mcdf_arbiter_priority_test");
  3. super.new(name)
  4. endfunction
  5. task do_arbiter_priority_check();
  6. int id;
  7. forever begin
  8. @(posedge this.arb_vif.clk iff (this.arb_vif.rstn && this.arb_vif.mon_ck.f2a_id_req===1));
  9. id = this.get_slave_id_with_prio();//得到三个slave的最高优先级
  10. if(id >= 0) begin
  11. @(posedge this.arb_vif.clk);
  12. if(this.arb_vif.mon_ck.a2s_acks[id] !== 1)//acks【i】是设计的硬件信号的slvae通道选择
  13. rpt_pkg::rpt_msg("[CHKERR]",
  14. $sformatf("ERROR! %0t arbiter received f2a_id_req===1 and channel[%0d] raising request with high priority, but is not granted by arbiter", $time, id),
  15. rpt_pkg::ERROR,
  16. rpt_pkg::TOP);
  17. end
  18. end
  19. endtask
  20. //模拟arbiter仲裁逻辑
  21. function int get_slave_id_with_prio();
  22. int id=-1;
  23. int prio=999;
  24. //寻找最高优先级
  25. foreach(this.arb_vif.mon_ck.slv_prios[i]) begin
  26. if(this.arb_vif.mon_ck.slv_prios[i] < prio && this.arb_vif.mon_ck.slv_reqs[i]===1) begin
  27. id = i;
  28. prio = this.arb_vif.mon_ck.slv_prios[i];
  29. end
  30. end
  31. return id;
  32. endfunction
  33. endclass

3.3.8 mcdf_channel_disbale_test

数据通道开关检测

在数据通道关闭的情况下检测数据写入是否通过

  1. class mcdf_channel_disbale_test extends mcdf_base_tese;
  2. function new (string name = "mcdf_channel_disbale_test");
  3. super.new(name)
  4. endfunction
  5. task do_mcdf_channel_disable_test (int id)
  6. forever begin
  7. @(posedge this.mcdf_vif.clk iff(this.mcdf_vif.rstn && this.mcdf_vif.mon_ck.chnl_en[id] == 0));
  8. if (this.chnl_vifs[id].mon_ck.ch_valid == 1 && this.chnl_vifs[id].mon_ck.ch_ready == 1)
  9. rpt_pkg::rpt_msg("[CHECKER]",
  10. $sformatf("ERROR! %0t whern channel disabled, ready signal raised while valid signal is high", $time),
  11. rpt_pkg::ERROR,
  12. rpt_pkg::TOP);
  13. end
  14. endtask
  15. endclass

3.3.9 mcdf_coverage

覆盖率收集

  1. class mcdf_coverage;
  2. local virtual chnl_intf chnl_vifs[3];
  3. local virtual arb_intf arb_vif;
  4. local virtual mcdf_intf mcdf_vif;
  5. local virtual reg_intf reg_vif;
  6. local virtual fmt_intf reg_vif;
  7. local string name;
  8. local int delay_req_to_grant;
  9. //对所有控制、状态寄存器的读写进行测试。
  10. covergroup cg_mcdf_reg_write_read;
  11. addr: coverpoint reg_vif.mon_ck.cmd_addr{
  12. type_option.weight = 0;//权重设置为0,表示不关心此coverpoint的覆盖率,否则cross以后会出现很多的bin
  13. bins slv0_rw_addr = {'SLV0_RW_ADDR};
  14. bins slv1_rw_addr = {'SLV1_RW_ADDR};
  15. bins slv2_rw_addr = {'SLV2_RW_ADDR};
  16. bins slv0_r_addr = {'SLV0_R_ADDR };
  17. bins slv1_r_addr = {'SLV1_R_ADDR };
  18. bins slv2_r_addr = {'SLV2_R_ADDR };
  19. }//8bit地址实际上会生成256个bin,256-6=250会被平均分配到这6个bin中,
  20. //但默认max为64个bin,所以剩下的250要分配到64-6=58个bin里面
  21. //建议如果要做cross,那需要被cross的这些功能点都weight=0,然后在cross中重新bin
  22. cmd: coverpoint reg_vif.mon_ck.cmd{
  23. type_option.weight = 0;
  24. bins write = {'WRITE};
  25. bins read = {'READ};
  26. bins idle = {'IDLE};
  27. }//cmd是2bit的,所以还有一个默认的bin
  28. cmdXaddr: cross cmd, addr{
  29. bins slv0_rw_addr = binsof (addr.slv0_rw_addr);
  30. bins slv1_rw_addr = binsof (addr.slv1_rw_addr);
  31. bins slv2_rw_addr = binsof (addr.slv2_rw_addr);
  32. bins slv0_r_addr = binsof (addr.slv0_r_addr );
  33. bins slv1_r_addr = binsof (addr.slv1_r_addr );
  34. bins slv2_r_addr = binsof (addr.slv2_r_addr );
  35. bins write = (cmd.write);
  36. bins read = (cmd.read );
  37. bins idle = (cmd.idle );
  38. //因为之前设置权重为0,所以需要再一次的声明,这样可以只关心所需要的bin即可,不会生成没用的bin
  39. bins write_slv0_rw_addr = binsof (cmd.write) && binsof (addr.slv0_rw_addr);
  40. bins write_slv1_rw_addr = binsof (cmd.write) && binsof (addr.slv1_rw_addr);
  41. bins write_slv2_rw_addr = binsof (cmd.write) && binsof (addr.slv2_rw_addr);
  42. bins read_slv0_rw_addr = binsof (cmd.read ) && binsof (addr.slv0_rw_addr);
  43. bins read_slv1_rw_addr = binsof (cmd.read ) && binsof (addr.slv1_rw_addr);
  44. bins read_slv2_rw_addr = binsof (cmd.read ) && binsof (addr.slv2_rw_addr);
  45. bins read_slv0_r_addr = binsof (cmd.read ) && binsof (addr.slv0_r_addr );
  46. bins read_slv1_r_addr = binsof (cmd.read ) && binsof (addr.slv1_r_addr );
  47. bins read_slv2_r_addr = binsof (cmd.read ) && binsof (addr.slv2_r_addr );
  48. }
  49. endgroup
  50. //对非法地址进行读写测试,对控制寄存器的保留域进行读写测试,对状态寄存器进行写操作测试。
  51. covergroup cg_mcdf_reg_illegal_access;
  52. addr: coverpoint reg_vif.mon_ck.cmd_addr {
  53. type_option.weight = 0;
  54. bins legal_rw = {`SLV0_RW_ADDR, `SLV1_RW_ADDR, `SLV2_RW_ADDR};//合法读写寄存器地址
  55. bins legal_r = {`SLV0_R_ADDR, `SLV1_R_ADDR, `SLV2_R_ADDR}; //合法读地址范围
  56. /*这里做了一些简化,这里对任意一个通道的读写寄存器进行了操作就满足覆盖率要求*/
  57. /**但是实际上如果要对每一个通道的寄存器都进行覆盖率检测,那应该写为数组形式*/
  58. /*bins legal_rw[] = {`SLV0_RW_ADDR, `SLV1_RW_ADDR, `SLV2_RW_ADDR};
  59. bins legal_r[] = {`SLV0_R_ADDR, `SLV1_R_ADDR, `SLV2_R_ADDR}; */
  60. bins illegal = {[8'h20:$], 8'hC, 8'h1C}; //非法地址范围
  61. }
  62. cmd: coverpoint reg_vif.mon_ck.cmd {
  63. type_option.weight = 0;
  64. bins write = {`WRITE};
  65. bins read = {`READ};
  66. }
  67. //写数据覆盖点
  68. wdata: coverpoint reg_vif.mon_ck.cmd_data_m2s {
  69. type_option.weight = 0;
  70. //数据合法范围,0~111111,即bit(0)使能信号、bit(2:1)优先级、bit(5:3)数据包长度都可以写
  71. bins legal = {[0:'h3F]};
  72. //数据非法范围,bit(31:6)保留位,无法写入
  73. bins illegal = {['h40:$]};
  74. }
  75. //读数据覆盖点
  76. rdata: coverpoint reg_vif.mon_ck.cmd_data_s2m {
  77. type_option.weight = 0;
  78. //读数据合法范围,0~11111111,即bit(7:0)上行数据从端FIFO的可写余量
  79. bins legal = {[0:'hFF]};
  80. illegal_bins illegal = default;
  81. }
  82. //交叉覆盖
  83. cmdXaddrXdata: cross cmd, addr, wdata, rdata {
  84. bins addr_legal_rw = binsof(addr.legal_rw);
  85. bins addr_legal_r = binsof(addr.legal_r);
  86. bins addr_illegal = binsof(addr.illegal);
  87. bins cmd_write = binsof(cmd.write);
  88. bins cmd_read = binsof(cmd.read);
  89. bins wdata_legal = binsof(wdata.legal);
  90. bins wdata_illegal = binsof(wdata.illegal);
  91. bins rdata_legal = binsof(rdata.legal);
  92. bins write_illegal_addr = binsof(cmd.write) && binsof(addr.illegal);//对非法地址进行写操作
  93. bins read_illegal_addr = binsof(cmd.read) && binsof(addr.illegal);//对非法地址进行写操作
  94. bins write_illegal_rw_data = binsof(cmd.write) && binsof(addr.legal_rw) && binsof(wdata.illegal);//对读写寄存器合法地址写入非法数据
  95. bins write_illegal_r_data = binsof(cmd.write) && binsof(addr.legal_r) && binsof(wdata.illegal);//对只读寄存器合法地址写入非法数据
  96. }
  97. endgroup
  98. //对每一个数据通道对应的控制寄存器域en配置为0,在关闭状态下测试数据写入是否通过。
  99. covergroup cg_channel_disable;
  100. ch0_en: coverpoint mcdf_vif.mon_ck.chnl_en[0] {
  101. type_option.weight = 0;
  102. wildcard bins en = {1'b1};//这里为什么要用wildcard来修饰
  103. wildcard bins dis = {1'b0};
  104. //wildcard修饰符用于位宽不定的信号,它使得值的匹配忽略位数上的差异。
  105. //具体来说,wildcard可以用来简化匹配条件,特别是在信号中有不关心的位时
  106. //wildcard可以让表达式中的任何x、z或?被当作0或1的通配符
  107. }
  108. ch1_en: coverpoint mcdf_vif.mon_ck.chnl_en[1] {
  109. type_option.weight = 0;
  110. wildcard bins en = {1'b1};//对应使能
  111. wildcard bins dis = {1'b0};//对于禁用
  112. }
  113. ch2_en: coverpoint mcdf_vif.mon_ck.chnl_en[2] {
  114. type_option.weight = 0;
  115. wildcard bins en = {1'b1};
  116. wildcard bins dis = {1'b0};
  117. }
  118. ch0_vld: coverpoint chnl_vifs[0].mon_ck.ch_valid {
  119. type_option.weight = 0;
  120. bins hi = {1'b1};
  121. bins lo = {1'b0};
  122. }
  123. ch1_vld: coverpoint chnl_vifs[1].mon_ck.ch_valid {
  124. type_option.weight = 0;
  125. bins hi = {1'b1};
  126. bins lo = {1'b0};
  127. }
  128. ch2_vld: coverpoint chnl_vifs[2].mon_ck.ch_valid {
  129. type_option.weight = 0;
  130. bins hi = {1'b1};
  131. bins lo = {1'b0};
  132. }
  133. //采集当valid拉高时,代表要进行写操作,en拉低代表数据通道关闭
  134. chenXchvld: cross ch0_en, ch1_en, ch2_en, ch0_vld, ch1_vld, ch2_vld {
  135. bins ch0_en = binsof(ch0_en.en );
  136. bins ch0_dis = binsof(ch0_en.dis);
  137. bins ch1_en = binsof(ch1_en.en );
  138. bins ch1_dis = binsof(ch1_en.dis);
  139. bins ch2_en = binsof(ch2_en.en );
  140. bins ch2_dis = binsof(ch2_en.dis);
  141. bins ch0_hi = binsof(ch0_vld.hi);
  142. bins ch0_lo = binsof(ch0_vld.lo);
  143. bins ch1_hi = binsof(ch1_vld.hi);
  144. bins ch1_lo = binsof(ch1_vld.lo);
  145. bins ch2_hi = binsof(ch2_vld.hi);
  146. bins ch2_lo = binsof(ch2_vld.lo);
  147. bins ch0_en_vld = binsof(ch0_en.en ) && binsof(ch0_vld.hi);
  148. bins ch0_dis_vld = binsof(ch0_en.dis) && binsof(ch0_vld.hi);
  149. bins ch1_en_vld = binsof(ch1_en.en ) && binsof(ch1_vld.hi);
  150. bins ch1_dis_vld = binsof(ch1_en.dis) && binsof(ch1_vld.hi);
  151. bins ch2_en_vld = binsof(ch2_en.en ) && binsof(ch2_vld.hi);
  152. bins ch2_dis_vld = binsof(ch2_en.dis) && binsof(ch2_vld.hi);
  153. }
  154. endgroup
  155. //将不同数据通道配置为相同或者不同的优先级,在数据通道使能的情况下进行测试。
  156. covergroup cg_arbiter_priority;
  157. ch0_prio: coverpoint arb_vif.mon_ck.slv_prios[0] {
  158. bins ch_prio0 = {0};
  159. bins ch_prio1 = {1};
  160. bins ch_prio2 = {2};
  161. bins ch_prio3 = {3}; //为什么会有4个优先级呢
  162. }
  163. ch1_prio: coverpoint arb_vif.mon_ck.slv_prios[1] {
  164. bins ch_prio0 = {0};
  165. bins ch_prio1 = {1};
  166. bins ch_prio2 = {2};
  167. bins ch_prio3 = {3};
  168. }
  169. ch2_prio: coverpoint arb_vif.mon_ck.slv_prios[2] {
  170. bins ch_prio0 = {0};
  171. bins ch_prio1 = {1};
  172. bins ch_prio2 = {2};
  173. bins ch_prio3 = {3};
  174. }
  175. endgroup
  176. //测试从formatter发送出来的数据包长度是否同对应通道寄存器的配置一致
  177. covergroup cg_formatter_length;
  178. id: coverpoint fmt_vif.mon_ck.fmt_chid {
  179. bins ch0 = {0};
  180. bins ch1 = {1};
  181. bins ch2 = {2};
  182. illegal_bins illegal = default;
  183. }
  184. length: coverpoint fmt_vif.mon_ck.fmt_length {
  185. bins len4 = {4};
  186. bins len8 = {8};
  187. bins len16 = {16};
  188. bins len32 = {32};
  189. illegal_bins illegal = default;
  190. }
  191. endgroup
  192. //在req拉高以后,grant至少应该在2个时钟周期后拉高,以此来模拟下行从端数据余量不足的情况
  193. covergroup cg_formatter_grant();
  194. delay_req_to_grant: coverpoint this.delay_req_to_grant {
  195. bins delay1 = {1};// delay_req_to_grant是一个自定义的软件信号,在task do_formater_sample()中被定义
  196. bins delay2 = {2};//数字代表req与grant信号之间延迟的拍数字
  197. bins delay3_or_more = {[3:10]};
  198. illegal_bins illegal = {0};//req与grant信号之间没有延迟则非法
  199. }
  200. endgroup
  201. function new(string name="mcdf_coverage");
  202. this.name = name;
  203. this.cg_mcdf_reg_write_read = new();
  204. this.cg_mcdf_reg_illegal_access = new();
  205. this.cg_channel_disable = new();
  206. this.cg_arbiter_priority = new();
  207. this.cg_formatter_length = new();
  208. this.cg_formatter_grant = new();
  209. endfunction
  210. task run();
  211. fork
  212. this.do_reg_sample();
  213. this.do_channel_sample();
  214. this.do_arbiter_sample();
  215. this.do_formater_sample();
  216. join
  217. endtask
  218. //寄存器采样
  219. task do_reg_sample();
  220. forever begin
  221. @(posedge reg_vif.clk iff reg_vif.rstn);
  222. this.cg_mcdf_reg_write_read.sample();
  223. this.cg_mcdf_reg_illegal_access.sample();
  224. end
  225. endtask
  226. //数据通道采样
  227. task do_channel_sample();
  228. forever begin对于每一个时钟上升沿,至少有一个channel的valid信号为1时,即有数据发送进来时,才对disable做采样
  229. @(posedge mcdf_vif.clk iff mcdf_vif.rstn);
  230. if(chnl_vifs[0].mon_ck.ch_valid===1
  231. || chnl_vifs[1].mon_ck.ch_valid===1
  232. || chnl_vifs[2].mon_ck.ch_valid===1)
  233. this.cg_channel_disable.sample();
  234. end
  235. endtask
  236. //对优先级做采样
  237. task do_arbiter_sample();
  238. forever begin对于每一个时钟的上升沿,至少有一个channel的req为1,即至少有一个channel要发送数据了,才对其优先级做采样
  239. @(posedge arb_vif.clk iff arb_vif.rstn);
  240. if(arb_vif.slv_reqs[0]!==0 || arb_vif.slv_reqs[1]!==0 || arb_vif.slv_reqs[2]!==0)
  241. this.cg_arbiter_priority.sample();
  242. end
  243. endtask
  244. //对grant信号做采样
  245. task do_formater_sample();
  246. fork
  247. forever begin//对于每一个时钟上升沿,当req为1时,对数据的长度做采样
  248. @(posedge fmt_vif.clk iff fmt_vif.rstn);
  249. if(fmt_vif.mon_ck.fmt_req === 1)
  250. this.cg_formatter_length.sample();
  251. end
  252. forever begin
  253. //当req拉高时,要通过变量delay_req_to_grant来计算延迟,对grant做采样
  254. @(posedge fmt_vif.mon_ck.fmt_req);
  255. this.delay_req_to_grant = 0;/*req信号与grant信号之间的延迟*/
  256. forever begin//只有grant拉高时,才对grant做采样,进而通过delay_req_to_grant的值得到延迟
  257. if(fmt_vif.fmt_grant === 1) begin
  258. this.cg_formatter_grant.sample();
  259. break;/*当fmt_vif.mon_ck.fmt_req信号和fmt_vif.fmt_grant信号都被拉高时,delay_req_to_grant为0,此时为非法状态,被采样*/
  260. end
  261. else begin
  262. @(posedge fmt_vif.clk);
  263. this.delay_req_to_grant++;/*每经过一拍,delay_req_to_grant加1*/
  264. end
  265. end
  266. end
  267. join
  268. endtask
  269. function void do_report();
  270. string s;
  271. s = "\n---------------------------------------------------------------\n";
  272. s = {s, "COVERAGE SUMMARY \n"};
  273. s = {s, $sformatf(" total coverage: %.1f \n", $get_coverage())}; //$get_coverage可以得到总体的全局覆盖率
  274. s = {s, $sformatf(" cg_mcdf_reg_write_read coverage: %.1f \n" , this.cg_mcdf_reg_write_read.get_coverage())}; //get_coverage获取单个covergroup实例的覆盖率
  275. s = {s, $sformatf(" cg_mcdf_reg_illegal_access coverage: %.1f \n", this.cg_mcdf_reg_illegal_access.get_coverage())};
  276. s = {s, $sformatf(" cg_channel_disable_test coverage: %.1f \n" , this.cg_channel_disable.get_coverage())};
  277. s = {s, $sformatf(" cg_arbiter_priority_test coverage: %.1f \n" , this.cg_arbiter_priority.get_coverage())};
  278. s = {s, $sformatf(" cg_formatter_length_test coverage: %.1f \n" , this.cg_formatter_length.get_coverage())};
  279. s = {s, $sformatf(" cg_formatter_grant_test coverage: %.1f \n" , this.cg_formatter_grant.get_coverage())};
  280. s = {s, "---------------------------------------------------------------\n"};
  281. rpt_pkg::rpt_msg($sformatf("[%s]",this.name), s, rpt_pkg::INFO, rpt_pkg::TOP);
  282. endfunction
  283. virtual function void set_interface(virtual chnl_intf ch_vifs[3]
  284. ,virtual reg_intf reg_vif
  285. ,virtual arb_intf arb_vif
  286. ,virtual fmt_intf fmt_vif
  287. ,virtual mcdf_intf mcdf_vif
  288. );
  289. this.chnl_vifs = ch_vifs;
  290. this.arb_vif = arb_vif;
  291. this.reg_vif = reg_vif;
  292. this.fmt_vif = fmt_vif;
  293. this.mcdf_vif = mcdf_vif;
  294. if(chnl_vifs[0] == null || chnl_vifs[1] == null || chnl_vifs[2] == null)
  295. $error("chnl interface handle is NULL, please check if target interface has been intantiated");
  296. if(arb_vif == null)
  297. $error("arb interface handle is NULL, please check if target interface has been intantiated");
  298. if(reg_vif == null)
  299. $error("reg interface handle is NULL, please check if target interface has been intantiated");
  300. if(fmt_vif == null)
  301. $error("fmt interface handle is NULL, please check if target interface has been intantiated");
  302. if(mcdf_vif == null)
  303. $error("mcdf interface handle is NULL, please check if target interface has been intantiated");
  304. endfunction
  305. endclass

04 覆盖率收集

运行完这几个test之后,得到覆盖率收集表如下

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/666548
推荐阅读
相关标签
  

闽ICP备14008679号