赞
踩
目录
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
最最开始系统地学习芯片领域相关只是大概追溯到23年12月初,此前只是有一些Verilog和数电基础,做过两个小的设计项目。整个12月和1月几乎都在学习FPGA的相关课程,仍记得在跨年夜在宿舍敲UART串口通信那篇文章@UART串口通信_uart串口通讯-CSDN博客,但同时也感受到在国外没有开发板的情况下确实很难把这些东西学好。因此年后转而开始学习更偏向于软件的数字IC验证。期间也是断断续续,国内学校的事情太多了,因此总是不能全身心投入,加上自己软件基础并不好,C++,python这些语言学的都不好。大概花了3个月的时间基本学完了SV的语法,验证的基本流程和熟悉验证软件的使用,但也不敢说能够熟练掌握。目前算是进入了下一个时间节点,开始学习UVM验证环境。因此基于MCDF项目对此前学习进行一个总结。大佬们轻喷。
设计模块的全称叫做multi-channel data formatter,他可以将上行多个数据通道的数据通过内部的SLAVE, FIFO给到仲裁器Arbiter, Arbiter选择从不同的FIFO中读取数据,给到下端的Formatter, 对数据进行整形,以数据包的形式送给下行的数据接收端。整个设计通过Register接受外部命令,对MCDF的功能进行修改。
clk(0) :时钟信号
rstn(0):复位信号
CHx_DATA(31:0):通道数据输入。
CHx_VALID(0):通道数据有效标志信号,高位有效。
CHx_READY(0):通道数据接收信号,高位表示接收成功
当valid为高时,表示要写入数据。如果该时钟周期ready为高,则表示已经将数据写入;如果该时钟周期ready为低,则需要等到ready为高的时钟周期才可以将数据写入。
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):数据包结束标示。
CMD(1:0):寄存器读写命令。
CMD_ADDR(7:0):寄存器地址。
CMD_DATA_IN(31:0):寄存器写入数据。
CMD_DATA_OUT(31:0):寄存器读出数据。
当cmd为写指令时,需要把数据cmd_data_in写入到cmd_addr对应的寄存器中;当cmd为读指令时,即需要从cmd_addr对应的寄存器中读取数据,并在下一个周期,将数据驱动至cmd_data_out接口。
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。
bit(7:0):上行数据从端FIFO的可写余量,同FIFO的数据余量保持同步变化。复位值为FIFO的深度数。
bit(31:8):保留位,复位值为0。
一共分为chnl_pkg, reg_pkg, fmt_pkg和mcdf_pkg, 各个包之间通过interface进行连接
寄存器传输类,包含地址、命令、数据和响应字段
- class reg_trans;
- rand bit[7:0] addr;
- rand bit[1:0] cmd ;
- rand bit[31:0] data;
- bit rsp;
-
- constraint cstr{
- soft cmd inside{'WRITE, 'READ, 'IDLE};
- soft addr inside{'SLV0_RW_ADDR, 'SLV1_RW_ADDR, 'SLV2_RW_ADDR, 'SLV0_R_ADDR, 'SLV1_R_ADDR, 'SLV2_R_ADDR};
- addr[7:4] ==0 && cmd == 'WRITE -> soft data [31:6] == 0;//读写寄存器要求高26位为0
- soft addr [7:5] == 0;
- addr[4] == 1 -> soft cmd == 'READ;//地址为0x10,0x14,0x18的是通道1,2,3的只读存储器
- };
-
- function reg_trans clone() ;
- reg_trans c = new() ;
- c.addr = this.addr;
- c.cmd = this.cmd ;
- c.data = this.data;
- c.rsp = this.rsp ;
- return c ;
- endfunction
-
- function string sprint();
- string s ;
- s = {s, $sformatf("========================n")};
- s = {s, $sformatf("reg_trans object content is as below:\n")};
- s = {s, $sformatf("addr = %2x:\n", this.addr)};//两位16进制数
- s = {s, $sformatf("cmd = %2b:\n", this.cmd )};//2位2进制数
- s = {s, $sformatf("data = %8x:\n", this.data)};//8位16进制数
- s = {s, $sformatf("rsp = %0d:\n", this.rsp )};//10进制数,无字符宽度
- s = {s, $sformatf("=======================\n")};
- return s;
- endfunction
- endclass
寄存器驱动器,用于生成寄存器传输事务并将其发送到接口。
该类包括了一个 do_drive 任务来驱动事务的发送,一个 do_reset 任务来处理复位信号,以及一个 reg_write 任务来处理寄存器的读写操作。
- class reg_driver;
- local string name;
- local virtual reg_intf intf;
- mailbox #(reg_trans) reg_mb;
- mailbox #(reg_trans) rsp_mb;
-
- function new(string name = "reg_driver");
- this.name = name;
- endfunction
-
- function void set_interface (virtual reg_intf intf);
- if(intf == null)
- $error("interface is null");
- else
- this.intf == intf;
- endfunction
-
- task run();
- fork
- this.do_drive();
- this.do_reset();
- join
- endtask
-
- task do_reset();
- forever begin
- @(negedge intf.rstn);
- intf.cmd_addr <= 0 ;
- intf.cmd <= 'IDLE;
- intf.cmd_data_m2s <= 0 ;
- end
-
- task do_drive();
- reg_trans req, rsp;
- @(posedge intf.rstn);
- forever begin
- this.req_mb.get(req);
- this.reg_write(req);
- rsp = req.clone();
- rsp.rsp = 1;
- this.rsp_mb.put(rsp);
- end
- endtask
-
- task reg_write (reg_trans t);
- @posedge (intf.clk iff intf.rstn);//这是一个复合条件,iff 表示 "if and only if",即当且仅当。
- //所以整个条件表示:只有当 intf.rstn 为逻辑真时,才触发在 intf.clk 的上升沿执行操作。
- case(t.cmd)
- 'WRITE:begin
- intf.drv_ck.cmd_addr <= t.addr;
- intf.drv_ck.cmd <= t.cmd;
- intf.drv.cmd_data_m2s <= t.data;
- end
- 'READ :begin
- intf.drv_ck.cmd_addr <= t.addr;
- intf.drv_ck.cmd <= t.cmd;
- repeat(2) @(negedge intf.clk);//为何要等两个下降沿?
- t.data = intf.cmd_data_s2m;
- end
- 'IDLE :begin
- this.reg_idle();
- end
- default: $error("command %b is illegal", t.cmd);
- endcase
- $display("%0t reg driver [%s] sent addr %2x,cmd %2b,data %8x",$time,name,t.addr,t.cmd,t.data);
- endtask
-
- task reg_idle();
- @(posedge intf.clk);
- intf.drv_ck.cmd_addr <= 0 ;
- intf.drv_ck.cmd_addr <= 0 ;
- intf.drv_ck.cmd <= 'IDLE;
- endtask
- endclass
生成寄存器传输事务,并提供一个 send_trans 任务来发送事务并处理响应。
- class reg_generator;
- rand bit [7:0] addr = -1;
- rand bit [1:0] cmd = -1;
- rand bit [31:0]data = -1;
-
- mailbox #(reg_trans) req_mb;
- mailbox #(reg_trans) rsp_mb;
-
- reg_trans reg_req[$];
-
- constraint cstr{
- soft addr == -1;
- soft cmd == -1;
- soft data == -1;
- }
-
- function new();
- this.req_mb = new();
- this.rsp_mb = new();
- endfunction
-
- task start();
- send_trans();
- endtask
-
- //generate transaction and put into local mailbox
- task send_trans();
- reg_tans req,rsp;
- req = new();
- assert(req.randomize with{local::addr >= 0 -> addr == local::addr;
- local::cmd >= 0 -> cmd == local::cmd;
- local::data >= 0 -> data == local::data;
- })
- else
- $fatal("register packet randomization fail");
- $display(req.sprint());
- this.req_mb.put(req);
- this.rsp_mb.get(rsp);
- $display(rsp.sprint());
- if(req.cmd == 'READ)
- this.data = rsp.data;
- assert(rsp.rsp)//确保收到的响应中包含有效的响应标志。如果响应标志无效,则通过 $error 报错。
- else $error("[RSPERR] 0%t error response received!", $time);
- endtask
-
- function string sprint();
- string s ;
- s = {s, $sformatf("========================n")};
- s = {s, $sformatf("reg_trans object content is as below:\n")};
- s = {s, $sformatf("addr = %2x:\n", this.addr)};//两位16进制数
- s = {s, $sformatf("cmd = %2b:\n", this.cmd )};//2位2进制数
- s = {s, $sformatf("data = %8x:\n", this.data)};//8位16进制数
- s = {s, $sformatf("rsp = %0d:\n", this.rsp )};//10进制数,无字符宽度
- s = {s, $sformatf("=======================\n")};
- return s;
- endfunction
-
- //用于在随机化后显示对象的内容。
- function void post_randomize();
- string s;
- s = {"AFTER RANDOMIZE \n",this.sprint()};
- $display (s);
- endfunction
- endclass
寄存器监视器,用于监视寄存器传输事务并将其存储到邮箱中。
- class reg_monitor;
- local string name;
- local virtual reg_intf intf;
- mailbox #(reg_trans) mon_mb;
-
- function new(string name = "reg_monitor");
- this.name = name;
- endfunction
-
- function void set_interface(virtual reg_intf intf);
- if(intf == null)
- $error("interface handle is null");
- else
- this.intf = intf;
- endfunction
-
- task run();
- this.mon_trans();
- endtask
-
- //其功能是监视寄存器接口的传输事务,并将监视到的信息存储到邮箱 mon_mb 中
- task mon_trans();
- reg_trans m;
- forever begin
- @(posedge intf.clk iff(intf.rstn && intf.mon_ck.cmd != 'IDLE));
- m = new();
- m.addr = intf.mon_ck.cmd_addr;
- m.cmd = intf.mon_ck.cmd;
- if(intf.mon_ck.cmd == 'READ)begin
- @(posedge intf.clk);
- m.data = intf.mon_ck.cmd_data_s2m;
- end
- mon_mb.put(m);
- $display("%0t reg driver [%s] sent addr %2x,cmd %2b,data %8x",$time,this.name,m.addr,m.cmd,m.data);
- end
- endtask
- endclass
组合 reg_driver 和 reg_monitor,并提供了一个 run 任务来同时运行驱动器和监视器。
- class reg_agent;
- local string name;
- reg_driver driver;
- reg_monitor monitor;
- local virtual reg_intf vif;
- function new(string name = "reg_agent");
- this.name = name;
- this.driver = new({name,".driver"});
- this.monitor = new({name,".monitor"});
- endfunction
-
- function void set_interface(virtual reg_intf vif);
- this.vif = vif;
- driver.set_interface(vif);
- monitor.set_interface(vif);
- endfunction
-
- task run();
- fork
- driver.run();
- monitor.run();
- join
- endtask
- endclass
- import rpt_pkg::*;
-
- typedef enum {SHORT_FIFO, MED_FIFO, LONG_FIOF, ULTRA_FIFO} fmt_fifo_t;
- typedef enum {LOW_WIDTH, MED_WIDTH, HIGH_WIDTH, ULTRA_WIDTH} fmt_bandwidth_t;
- typedef enum bit[2:0]{
- FMT_REQ = 3'b000
- ,FMT_WIDTH_GRANT = 3'b001
- ,FMT_START = 3'b011
- ,FMT_SEND = 3'b010
- ,FMT_END = 3'b110
- ,FMT_IDLE = 3'b111
- } fmt_state_t;
模拟数据的比较和打印
- class fmt_trans;
- rand fmt_fifo_t fifo;
- rand fmt_bandwidth_t bandwidth;
- bit [9:0] length;
- bit [31:0]data[];
- bit [1:0] ch_id;
- bit rsp;
- constraint cstr{
- soft fifo == MED_FIFO ;
- soft bandwidth == MED_WIDTH;
- };
-
- function fmt_trans clone();
- fmt_trans c = new();
- c.fifo = this.fifo;
- c.bandwidth = this.bandwidth;
- c.length = this.length;
- c.data = this.data;
- c.ch_id = this.ch_id;
- c.rsp = this.rsp;
- return c;
- endfunction
-
- function string sprint();
- string s ;
- s = {s, $sformatf("========================\n")};
- s = {s, $sformatf("fmt_trans object content is as below:\n")};
- s = {s, $sformatf("fifo = %s:\n", this.fifo)};
- s = {s, $sformatf("bandwidth = %s:\n", this.bandwidth )};
- s = {s, $sformatf("length = %s:\n", this.length)};
- s = {s, $sformatf("rsp = %0d:\n", this.rsp )};
- foreach (data[i]) s = {s,$sformatf("data[%0d] = %8x \n", i, this.data[i])};
- s = {s, $sformatf("ch_id = %0d:\n", this.ch_id)};
- s = {s, $sformatf("rsp = %0d:\n", this.rsp )};
- s = {s, $sformatf("=======================\n")};
- return s;
- endfunction
-
-
- //??为啥要做这两个比较呢,fmt_trans例化后的内容不应该就和fmt_trans中的一样吗
- //当你创建一个类(如 fmt_trans)的实例(对象)时,每个实例通常拥有自己的一套成员变量。
- //这意味着,即使两个对象是相同类的不同实例,它们的成员变量也可能拥有不同的值。
- //检查它们是否具有相同的长度和通道ID,并且它们的数据数组是否相同
- function bit compare(fmt_trans t);
- string s;
- compare = 1;//假设对象最初是相等的。
- s = "\n============================\n";
- s = {s,$sformatf("COMPARING fmt_trans object at time %0d \n", $time)};
- if(this.length != t.length)begin//this.lengh指的是18行的length
- compare = 0;
- s = {s, $sformatf("sobj length %0d != tobj length %0d \n", this.length, t.length)};
- end
- if(thi.ch_id != t.ch_id)begin
- compare = 0;
- s = {s, $sformatf("sobj ch_id %0d != tobj ch_id %0d \n", this.ch_id, t.ch_id)};
- end
- foreach(this.data[i])begin
- if(this.data[i] != t.data[i])begin
- compare = 0;
- s = {s, $sformatf("sobj data[%0d] %8x != tobj data[%0d] %8x \n",i, this.data[i], i, t.data[i])};
- end
- end
- if(compare == 1) s = {s, "COMPARED SUCCESS!\n"};
- else s = {s, "COMPARED FAILURED! \n "};
- rpt_pkg::rpt_msg("[CMPOBJ]", s, rpt_pkg::INFO, rpt_pkg::MEDIUM);
- endfunction
- endclass
模拟数据发送和接受行为
- class fmt_driver;
- local string name;
- local virtual fmt_intf intf;
- mailbox #(fmt_trans) req_mb;
- mailbox #(fmt_trans) rsp_mb;//与generator通信
-
- local mailbox #(logical[31:0]) fifo;//模拟formatter的下行数据
- local int fifo_bound; //模拟的下行通道最多能接受多少数据
- local int data_consum_period;// 反应下行数据输出的快慢。反比
-
- function new(string name = "fmt_driver");
- this.name = name;
- this.fifo = new();
- this.fifo_bound = 4096;
- this.data_consum_period = 1;
- endfunction
-
- function void set_interface(virtual fmt_intf intf);
- if(intf == null)
- $error("interface handle is null");
- else
- this.intf = intf;
- endfunction
-
- task run();
- fork
- this.do_receive();
- this.do_consume();
- this.do_config();
- this.do_reset();
- join
-
- //配置driver
- task do_config();
- fmt_trans req, rsp;
- forever begin
- this.req_mb.get(req);
- case(req.fifo)
- SHORT_FIFO : this.fifo_bound = 64 ;
- MED_FIFO : this.fifo_bound = 256 ;
- LONG_FIOF : this.fifo_bound = 512 ;
- ULTRA_FIFO : this.fifo_bound = 2048;
- endcase
- this.fifo = new(this.fifo_bound);
- case(req.bandwidth) //为啥位宽低data_consum_period大呢?
- //可能是因为在低带宽条件下,数据传输速率较慢,
- //因此需要更长的周期来消费数据,以匹配数据的传输速率。
- LOW_WIDTH : this.data_consum_period = 8 ;
- MED_WIDTH : this.data_consum_period = 4 ;
- HIGH_WIDTH : this.data_consum_period = 2 ;
- ULTRA_WIDTH : this.data_consum_period = 1 ;
- endcase
- rsp = req.clone();
- rsp.rsp = 1;//这里的 rsp 成员变量是一个标志位,用于表示某种状态或响应的有效性。
- this.rsp_mb.put(rsp);
- end
- endtask
-
- task do_reset();
- forever begin
- @(negedge intf.rstn)
- intf.fmt_grant <= 0;
- end
- endtask
-
- //模拟具有随机延迟的数据消费场景。按照随机的时间间隔取出数据
- task do_consume();
- logic[31:0] data;
- forever begin
- void'(this.fifo.try_get(data));// 使用 try_get 方法尝试从FIFO中取出一个数据项并赋值给 data 变量
- //void'() 是SystemVerilog中的一个特性,它用来忽略 try_get 方法返回的状态值
- repeat($urandom_range(1, this.data_consum_period)) @(posedge intf.clk);//这里反应消耗数据的快慢
- //等待intf.clk的上升沿,repeat重复一次
- end
- endtask
- endclass
-
- //当数据接受的请求信号fmt_req拉高时,且FIFO深度足够时,fmt_grant拉高,表示接收方准备好接受数据了
- //
-
- task do_receive();//模拟接受数据
- forever begin
- @(posedge intf.fmt_req);
- forever begin
- @(posedge intf.clk);
- if((this.fifo_bound-this.fifo.num()) >= intf.fmt_length)
- //fifo_bound是选择的fifo的总深度
- //检查当前FIFO的剩余空间是否足够存放即将到来的数据。
- //如果剩余空间大于或等于 intf.fmt_length 指定的长度,则跳出内部循环。
- break;//只跳出内部的这个forever。当fifo余量足够时,拉高grant
- end
- intf.drv_ck.fmt_grant <= 1;//表示接收方准备好接受数据了
- @(posedge intf.fmt_start);//等待 intf.fmt_start 信号的下一个上升沿,
- //这表示发送方已经准备好发送数据。
- fork
- begin
- @(posedge intf.clk);
- intf.drv_ck.fmt_grant <= 0;//维持一拍后就拉低,不再授权数据传输
- end
- join_none
- repeat(intf.fmt_length)begin
- @(nedge intf.clk);
- this.fifo.put(intf.fmt_data);//连续放入数据
- end
- end
- endtask
创建、随机化、发送和接收 fmt_trans 类型的数据事务
- class fmt_generator;
- rand fmt_fifo_t fifo = MED_FIFO;
- rand fmt_bandwidth_t bandwidth = MED_WIDTH;
-
- mailbox #(fmt_trans) req_mb;//在 fmt_generator 类中,req_mb 用于发送 fmt_trans 类型的请求事务,
- //而 rsp_mb 用于接收响应事务。这种方式使得 fmt_generator 可以作为一个独立的组件,
- //模拟发送请求并等待响应,而不依赖于特定的通信协议实现细节。
- mailbox #(fmt_trans) rsp_mb;
-
- constraint cstr{
- soft fifo == MED_FIFO;
- soft bandwidth == MED_WIDTH;
- }
-
- function new();
- this.req.mb = new();
- this.rsp.mb = new();
- endfunction
-
- task start();
- send_trans();
- endtask
-
- //产生事务并放到本地信箱
- task send_trans();
- fmt_trans req, rsp;
- req = new();//为何这里是MED_FIFO呢,不等于就随机化失败?什么道理
- //因为192行就是这样给的
- assert (req.randomize with {local::fifo != MED_FIFO -> fifo == local::fifo
- local::bandwidth != MED_WIDTH -> bandwidth == local::bandwidth;
- })
- else $fatal("[RNDFALL] formatter packet randomize failure!");
- $display(req.sprint());
- this.req_mb.put(req);//放入请求
- this.rsp_mb.get(rsp);//取出响应
- $display(rsp.sprint());
- assert(rsp.rsp)//检查响应是否有效
- else $error("[RSPERR] %0t error response received!", $time);
- endtask
-
- function string sprint();
- string s;
- s = {s, $sformatf("========================\n")};
- s = {s, $sformatf("fmt_generator object content is as below:\n")};
- s = {s, $sformatf("fifo = %s:\n", this.fifo)};
- s = {s, $sformatf("bandwidth = %s:\n", this.bandwidth )};
- s = {s, $sformatf("=======================\n" )};
- return s;
- endfunction
-
- function void post_randomize();
- string s;
- s = {"AFTER RANDOMIZE \n", this.sprint()};
- $display(s);
- endfunction
-
- endclass
用于观察和记录通过接口 fmt_intf 传输的数据事务
- class fmt_monitor;
- local string name;
- local virtual fmt_intf intf;
- mailbox #(fmt_trans) mon_mb;//用于存储监视到的 fmt_trans 类型的事务。
- function new(string name = "fmt_generator");
- this.name = name;
- endfunction
-
- function void set_interface(virtual fmt_intf intf);
- if(intf == null)
- $error("interface handle is null")
- else
- this.intf = intf;
- endfunction//设置监视器要监视的接口
-
- task run();
- this.mon_trans();
- endtask
-
- task mon_trans();
- fmt_trans m;
- string s;
- forever begin
- @(posedge intf.mon_ck.fmt_start);//等待 intf.mon_ck.fmt_start 信号的上升沿,表示一个新事务的开始。
- m = new();
- m.length = intf.mon_ck.fmt_length;
- m.ch_id = intf.mon_ck.fmt_chid;
- m.data = new[m.length];//接口中获取事务的长度、通道ID和数据,并将它们赋值给 m 对象。
- foreach(m.data[i])begin
- @(posedge intf.clk);//在每个时钟上升沿从接口中获取数据,并将其存储在 m.data 数组中。
- m.data[i] = intf.mon_ck.fmt_data;
- end
- mon_mb.put(m);
- s = {s, $sformatf("========================\n")};
- s = {s, $sformatf("%0t %s monitored a packet:\n", $time, this.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" )};
- $display(s);
- end
- endtask
- endclass
-
组合驱动器(fmt_driver)和监视器(fmt_monitor)组件
- class fmt_agent;
- local string name;
- fmt_driver driver;
- fmt_monitor monitor;
- local virtual fmt_intf vif;
-
- function new(string name = "fmt_agent");
- this.name = name;
- this.driver = new({name, ".driver"});
- this.monitor= new({name, ".monitor"});
- endfunction
-
- function void set_interface(virtual fmt_intf vif);
- this.vif = vif;
- driver.set_interface(vif);
- monitor.set_interface(vif);
- endfunction
-
- task run();
- fork
- driver.run();
- monitor.run();
- join
- endtask
- endclass
- import chnl_pkg::*;
- import reg_pkg ::*;
- import arb_pkg ::*;
- import fmt_pkg ::*;
- import rpt_pkg ::*;
-
- typedef struct packed{
- bit[2:0] lenl ;//后面用来规定各寄存器通道的数据包长度,优先级,使能信号,可写余量
- bit[1:0] prio ;
- bit en ;
- bit[7:0] avail;
- }mcdf_reg_t
-
- typedef enum{RW_LEN, RW_PRIO, RW_EN, RD_AVAIL}mcdf_field_t;//寄存器的不同比特位代表不同的功能,详见蓝皮书376页
用于测试或模拟MCDF模块的寄存器行为,形成一个参考模型
- class mcdf_refmod;
- local virtual mcdf_intf intf;
- local string name;
- mcdf_reg_t regs[3];
- mailbox #(reg_trans) reg_mb;
- mailbox #(mon_data_t) in_mbs [3];
- mailbox #(fmt_trans) out_mbs[3];
-
- function new(string name = "mcdf_refmod");
- this.name = name;
- foreach (this.out_mbs[i]) this.out_mbs[i] = new();
- endfunction
-
- task run();
- fork
- do_reset();
- this.do_reg_update();
- do_packet(0);
- do_packet(1);
- do_packet(2);
- join
- endtask
-
- task do_reg_update();
- reg_trans t;
- forever begin
- this.reg_mb.get(t);
- if(t.addr[31 :4] == 0 && t.cmd == 'WRITE)begin
- this.regs[t.addr[3:2]].en = t.data[0] ;
- this.regs[t.addr[3:2]].prio = t.data[2:1] ;
- this.regs[t.addr[3:2]].len = t.data[5:3] ;
- end
- else if(t.addr[7:4] == 1 && t.cmd == 'READ)begin
- this.regs[t.addr[3:2]].avail = t.data[7:0];
- end
- end
- endtask
-
- //处理数据包
- task do_packet(int id);
- fmt_trans ot;
- mon_data_t it;
- bit[2:0] len;
- forever begin
- this.in_mbs[id].peek(it);
- ot = new();
- len = this.get_field_value(id, RW_LEN);
- ot.length = len > 3 ? 32:4 << len;//根据len的值计算数据包的长度。
- //为什么数据包长度这样定义 //如果len大于3,则长度为32,否则长度len左移4位。
- //因为第50行规定第4到6位存放数据包长度
- ot.data = new[ot.length];//根据计算出的长度,动态分配一个整型数组给ot.data,
- //用于存储数据包的数据。
- ot.ch_id = id;
- foreach(ot.data[i])begin
- this.in_mbs[id].get(it);//从信箱中取数据,给到it
- ot.data[i] = it.data;
- end
- this.out_mbs[id].put(ot);//将格式化后的事务ot放入到输出mailbox(out_mbs)中
- end
- endtask
-
- function int get_field_value(int id, mcdf_field_t f);//mcdf_field_t是18行的枚举类型
- case(f)
- RW_LEN : return regs[id].len ;//这四个case分别代表什么?
- RW_PRIO : return regs[id].prio ;
- RW_EN : return regs[id].en ;
- RD_AVAIL: return regs[id].avail;
- endcase
- endfunction
-
- task do_reset();
- forever begin
- @(negedge intf.rstn);
- foreach(regs[i])begin
- regs[i].len = 'h0;
- regs[i].prio = 'h3;
- regs[i].en = 'h1;//为什么复位信号要给这些值?
- regs[i].avail= 'h20;//应该还是和task do_reg_update有关
- end
- end
- endtask
-
- function void set_interface(virtual mcdf_intf intf);
- if(intf == null)
- $error("interface handle is null");
- else
- this.intf = intf;
- endfunction
-
- endclass
将refmod的数据与fmt的数据进行对比
- class mcdf_checker;
- local string name;
- local int err_count;
- local int total_count;
- local int chl_count[3];
- local virtual mcdf_intf intf;
- local refmod mcdf_refmod;
-
- mailbox #(mon_data_t) chnl_mbs[3];
- mailbox #(fmt_trans) fmt_mb;
- mailbox #(reg_trans) reg_mb;
- mailbox #(fmt_trans) exp_mbs[3];
-
- function new(string name = "mcdf_checker");
- this.name = name;
- foreach(this.chnl_mbs[i]) this.chnl_mbs[i] = new();
- this.fmt_mb = new();
- this.reg_mb = new();
- this.refmod = new();
- foreach (this.refomd.in_mbs[i])begin
- this.refmod.in_mbs[i] = this.chnl_mbs[i];
- this.exp_mbs[i] = this.refmod.out_mbs[i];
- end
- this.refmod.reg_mb = this.reg_mb;
- this.err_count = 0;
- this.total_count = 0;
- foreach(this.chnl_count[i]) this.chnl_count[i] = 0;
- endfunction
-
- function void set_interface (virtual mcdf_intf intf);
- if(intf == null)
- $error("interface handle is null");
- else
- this.intf = intf;
- this.refmod.set_interface(intf);
- endfunction
-
- task run();
- fork
- this.do_compare();
- this.refmod.run();
- join
- endtask
-
- //比较收集到的数据是否和参考数据一样
- task do_compare();
- fmt_trans expt, mont;
- bit cmp;
- forever begin
- this.fmt_mb.get(mont);//formatter中的数据
- this.exp_mbs[mont.ch_id].get(expt);//期望的数据
- cmp = mont.compare(expt);//调用的是fmt_pkg里面的compare函数
- this.total_count++;//是拿来计数什么的?
- this.chnl_count[mont.ch_id]++;//是拿来计数什么的?
- if(cmp == 0)begin//不相等
- this.err_count++;
- 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),
- rpt_pkg::ERROR,
- rpt_pkg::TOP,
- rpt_pkg::LOG);
- end
- else begin
- 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),
- rpt_pkg::INFO,
- rpt_pkg::HIGH,
- end
- end
- endtask
-
- function void do_report();
- string s;
- s = "\n--------------------------------------------\n";
- s = {s, " CHECKER SUMMARY \n"};
- s = {s, $sformatf("total comparison count: %0d \n", this.total_count)};
- foreach (this.chnl_count[i]) begin
- if(this.chnl_mbs[i].num() != 0 )//判断是否把数据比较完
- s = {s, $sformatf("WARNING:: chnl_mbs[%0d] is not empty! size = %0d \n", i, this.chnl_mbs[i].num())};
- end
- if(this.fmt_mb.num() != 0)
- s = {s, $sformatf("WARNING:: fmt_mb is not empty! size = %0d \n", this.fmt_mb.num())};
- s = "\n--------------------------------------------\n";
- rpt_pkg::rpt_msg($sformatf("[%s]",this.name), s, rpt_pkg::INFO, rpt_pkg::TOP);
- endfunction
- endclass
用于构建和运行MCDF模块的测试环境
- class mcdf_env;
- chnl_agent chnl_agts[3];
- reg_agent reg_agt;
- fmt_agent fmt_agt;
- mcdf_checker chker;
- mcdf_coverage cvrg;
- protected string name;
-
- function new(string name = "mcdf_env");
- this.name = name;
- this.chker = new();
- foreach(chnl_agts[i])begin
- this.chnl_agts[i] = new($sformatf("chnl_agts[%0d]", i));
- this.chnl_agts[i].monitor.mon_mb = this.chker.chnl_mbs[i];
- end
- this.reg_agt = new("reg_agt");
- this.reg_agt.monitor.mon_mb = this.chker_reg_mb;
- this.fmt_agt = new("fmt_agt");
- this.fmt_agt.monitor.mon_mb = this.chker.fmt.mb;
- this.cvrg = new();
- $display("%s instantiated and connected objects", this.name);
- endfunction
-
- virtual task run();
- $display($sformatf("*****************%s started***********", this.name));
- this.do_config();
- fork
- this.chnl_agts[0].run)();
- this.chnl_agts[1].run)();
- this.chnl_agts[2].run)();
- this.reg_agt.run)();
- this.fmt_agt.run)();
- this.chker.run();
- this.cvrg.run();
- join
- endtask
-
- virtual function void do_config();//之前式用do_config来配置generator。但是
- endfunction //现在将其移植到mcdf_base_test中来进行配置了
-
- virtual function void do_report();
- this.chker.do_report();
- this.cvrg.do_reporet();
- endfunction
- endclass
测试模板
- class mcdf_base_test;
- chnl_generator chnl_gens[3];
- reg_generator reg_gen;
- fmt_generator fmt_gen;
- mcdf_env env;
- protected string name;
-
- function new(string name = "mcdf_base_test");
- this.name = name;
- this.env = new(env);
-
- //将每个gens给到env进行代理
- foreach(this.chnl_gens[i])begin
- this.chnl_gens[i] = new();
- this.env.chnl_agts[i].driver.req_mb = this.chnl_gens[i].req_mb;
- this.env.chnl_agts[i].driver.rsq_mb = this.chnl_gens[i].rsq_mb;
- end
-
- this.reg_gen = new();
- this.env.reg_agt.driver.req_mb = this.reg_gen.req_mb;
- this.env.reg_agt.driver.rsp_mb = this.reg_gen.rsp_mb;
-
- this.fmt_gen = new();
- this.env.fmt_agt.driver.req_mb = this.fmt_gen.req_mb;
- this.env.fmt_agt.driver.rsp_mb = this.fmt_gen.rsp_mb;
-
- $display("%s instantiated and connected objects", this.name);
- endfunction
-
- virtual task run();
- fork
- env.run();
- join_none
- rpt_pkg::rpt_msg("[TEST]",
- $sformatf("===========%s AT TIME 0%t STARTED=======", this.name,$time),
- rpt_pkg::INFO;
- rpt_pkg::HIGH);
- this.do_reg();
- this.do_formatter();
- this.do_data();
- rpt_pkg::rpt_msg("[TEST]",
- $sformatf("===========%s AT TIME 0%t FINISHED=======", this.name,$time),
- rpt_pkg::INFO;
- rpt_pkg::HIGH);
- this.do_report();
- $finish();
- endtask
-
- //do register configuration
- virtual task do_reg();
- endtask
-
- //do external formatter down stream slave configuration
- virtual task do_formatter();
- endtask
-
- //do data transition from 3 channel slaves
- virtual task do_data();
- endtask
-
- //do simulation SUMMARY
- virtual function void do_report();
- this.env.do_report();
- rpt_pkg::do_report();
- endfunction
-
- virtual function void set_interface( virtual chnl_intf ch0_vif
- ,virtual chnl_agent ch1_vif
- ,virtual chnl_agent ch2_vif
- ,virtual reg_intf reg_vif
- ,virtual fmt_intf fmt_vif
- ,virtual mcdf_intf mcdf_vif);
- this.env.chnl_agts[0].set_interface(ch0_vif);
- this.env.chnl_agts[1].set_interface(ch1_vif);
- this.env.chnl_agts[2].set_interface(ch2_vif);
- this.env.reg_agt.set_interface(reg_vif);
- this.env.fmt_agt.set_interface(fmt_vif);
- this.env.chker.set_interface(mcdf_vif);
- this.env.cvrg.set_interface('{ch0_vif, ch1_vif, ch2_vif,}, reg_vif, arb_vif, fmt_vif, mcdf_vif);
- endfunction
-
- virtual function bit diff_value(int val1, int val2, string id ="value_compare");
- if(val1 != val2)begin
- rpt_pkg::rpt_msg("[CMP ERR]",
- $sformatf("ERROR! %s val1 %8x != val2 %8x", id, val1, val2),
- rpt_pkg::ERROR;
- rpt_pkg::TOP);
- return 0;
- end
- else begin
- rpt_pkg::rpt_msg("[CMP SUC]",
- $sformatf("SUCCESS! %s val1 %8x == val2 %8x", id, val1, val2),
- rpt_pkg::INFO;
- rpt_pkg::HIGH);
- return 1;
- end
- endfunction
-
- virtual task idle_reg();
- void'(reg_gen.randomize() with {cmd == 'IDLE; addr == 0; data == 0;});
- reg_gen.start();
- endtask
-
- virtual task write_reg(bit[7:0]addr, bit[31:0] data);
- void'(reg_gen.randomize() with {cmd == 'WRITE; addr == local::addr; data == local::data;});
- reg_gen.start();
- endtask
-
- virtual task read_reg(bit[7:0]addr, output bit[31:0] data);
- void'(reg_gen.randomize() with {cmd == 'READ; addr == local::addr;});
- reg_gen.start();
- data = reg_gen.data;
- endtask
- endclass
3.3.5 mcdf_data_consistence_basic_test
读写数据一致性检测,检查寄存器读进去和写出来的数据是否一致
- class mcdf_data_consistence_basic_test extends mcdf_base_test;
- function new(string name = "mcdf_data_consistence_basic_test");
- super.new(name);
- endfunction
-
- //检查读写是否一致
- task do_reg();
- bit[31:0] wr_val, rd_val;
- //slv0 with len=8,prior=0,en=1
- wr_val = (1<<3)+(0<<1)+1;//为什么要这样设置呢
- this.wrtie_reg('SLV0_RW_ADDR, wr_val);//写进去
- this.read_reg('SLV0_RW_ADDR, rd_val);//读回来
- void'(this.diff_value(wr_val, rd_val, "SLV0_WR_REG"));//把写进去的值和读回来的值做一个比较
- //看看写进去的值是否合法
- //slv1 with len=16,prior=1,en=1
- wr_val = (2<<3)+(1<<1)+1;
- this.wrtie_reg('SLV1_RW_ADDR, wr_val);
- this.read_reg('SLV1_RW_ADDR, rd_val);
- void'(this.diff_value(wr_val, rd_val, "SLV1_WR_REG"));
-
- //slv2 with len=32,prior=2,en=1
- wr_val = (3<<3)+(2<<1)+1;
- this.wrtie_reg('SLV2_RW_ADDR, wr_val);
- this.read_reg('SLV2_RW_ADDR, rd_val);
- void'(this.diff_value(wr_val, rd_val, "SLV2_WR_REG"));
- //send IDLE command
- this.idle_reg();
- endtask
-
- //配置格式化器(fmt_gen)的随机值,包括 FIFO 大小和带宽。
- task do_formatter();
- void'(fmt_gen.randomize()with{fifo == LONG_FIFO; bandwidth == HIGH_WIDTH;});
- fmt_gen.start();
- endtask
-
- //生成数据
- task do_data();
- void'(chnl_gens[0].randomize() with {ntrans == 100; ch_id == 0; data_nidles == 0; pkt_nidles == 1; data_size == 8;});
- void'(chnl_gens[1].randomize() with {ntrans == 100; ch_id == 1; data_nidles == 1; pkt_nidles == 1; data_size == 16;});
- void'(chnl_gens[2].randomize() with {ntrans == 100; ch_id == 2; data_nidles == 2; pkt_nidles == 1; data_size == 32;});
- fork
- chnl_gens[0].start();
- chnl_gens[1].start();
- chnl_gens[2].start();
- join
- #10us;//wait until all data haven been transfered through MCDF
- endtask
- endclass
寄存器读写测试,包括对控制读写寄存器和状态只读寄存器的测试
- class mcdf_reg_write_read_test extends mcdf_base_test;
-
-
- function new (string name = "mcdf_reg_write_read_test");
- super.new(name);
- endfunction
-
- virtual task do_reg();
- bit [31:0]wr_val, rd_val;
- bit [7:0] chnl_rw_addrs[] = '{'SLV0_RW_ADDR, 'SLV1_RW_ADDR, 'SLV2_RW_ADDR};//读写寄存器
- bit [7:0] chnl_ro_addrs[] = '{'SLV0_R_ADDR, 'SLV1_R_ADDR, 'SLV2_R_ADDR};//只读寄存器
- int pwidth = 'PAC_LEN_WIDTH + 'PRIO_WIDTH + 1;//PAC_LEN_WIDTH是定义在param_def中的常数
- bit[31:0] check_pattern[] = '{32'h0000_FFC0, 32'hFFFF_0000};//类似于地址检查库
-
- foreach (chnl_rw_addrs[i])begin
- foreach(check_pattern[j])begin
- wr_val = check_pattern[j];
- this.write_reg(chnl_rw_addrs[i], wr_val);
- this.read_reg(chnl_rw_addr[i], rd_val);
- void'((this.diff_value(wr_val & ((1<<pwidth)-1), rd_val));//为什么,(1<<pwidth)-1=31
- end//按位与操作。这种掩码结果的二进制形式会有pwidth个低位是1。即保留wr_val的低pwidth位,其余位清零
- end//如果wr_val是32'h0000_FFC0,且pwidth是5,那么结果是32'h0000_0010。
- //仅比较有效位(低pwidth位),忽略无关的高位。这在寄存器操作中非常重要,
- //因为某些寄存器的高位可能是未定义或保留位,比较时应避免这些位的影响。
-
- foreach(chnl_ro_addrs[i])begin
- wr_val = 32'hFFFF_FF00;//由于是只读寄存器,因此写进入的值是确定的
- this.write_reg(chnl_ro_addrs[i],wr_val);
- this.read_reg(chnl_ro_addrs[1], rd_val);
- void'(this.diff_value(0, rd_val & wr_val));//这用于将函数调用的返回值转换为 void,实际上是忽略函数的返回值。
- end//对于只读寄存器,我们期望写入操作不会改变寄存器的值。
- //因此,我们预期从只读寄存器读取到的值应该是0(这就是为什么第一个参数是0)。
-
-
- this.idle_reg();
- endtask
- endclass
寄存器稳定性测试。
- class mcdf_reg_illegal_access_tets extends mcdf_base_test;
- function new(string name = "mcdf_reg_illegal_access_tets");
- super.new(name);
- endfunction
-
- task do_reg();
- bit [31:0]wr_val, rd_val;
- bit [7:0] chnl_rw_addrs[] = '{'SLV0_RW_ADDR, 'SLV1_RW_ADDR, 'SLV2_RW_ADDR};
- bit [7:0] chnl_ro_addrs[] = '{'SLV0_R_ADDR, 'SLV1_R_ADDR, 'SLV2_R_ADDR};
- int pwidth = 'PAC_LEN_WIDTH + 'PRIO_WIDTH + 1;
- bit[31:0] check_pattern[] = '{((1<<pwidth)-1), 0, ((1<<pwidth)-1)};
-
- foreach (chnl_rw_addrs[i])begin
- foreach(check_pattern[j])begin
- wr_val = check_pattern[j];
- this.write_reg(chnl_rw_addrs[i],wr_val);
- this.read_reg(chnl_rw_addrs[i],rd_val);
- void'(this.diff_value(wr_val, rd_val));
- end
- end
-
- foreach(chnl_ro_addrs[i])begin
- this.read_reg(chnl_ro_addrs[i],rd_val);
- end
-
- this.idle_reg();
- endtask
- endclass
arbiter优先级测试
如果各数据通道优先级相同,那么arbiter应该采取轮询机制从各个通道接受数据,如果优先级不同,先从高优先级的通道接收数据
- class mcdf_arbiter_priority_test extends mcdf_base_test;
- function new (string name = "mcdf_arbiter_priority_test");
- super.new(name)
- endfunction
-
- task do_arbiter_priority_check();
- int id;
- forever begin
- @(posedge this.arb_vif.clk iff (this.arb_vif.rstn && this.arb_vif.mon_ck.f2a_id_req===1));
- id = this.get_slave_id_with_prio();//得到三个slave的最高优先级
- if(id >= 0) begin
- @(posedge this.arb_vif.clk);
- if(this.arb_vif.mon_ck.a2s_acks[id] !== 1)//acks【i】是设计的硬件信号的slvae通道选择
- rpt_pkg::rpt_msg("[CHKERR]",
- $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),
- rpt_pkg::ERROR,
- rpt_pkg::TOP);
- end
- end
- endtask
-
- //模拟arbiter仲裁逻辑
- function int get_slave_id_with_prio();
- int id=-1;
- int prio=999;
- //寻找最高优先级
- foreach(this.arb_vif.mon_ck.slv_prios[i]) begin
- if(this.arb_vif.mon_ck.slv_prios[i] < prio && this.arb_vif.mon_ck.slv_reqs[i]===1) begin
- id = i;
- prio = this.arb_vif.mon_ck.slv_prios[i];
- end
- end
- return id;
- endfunction
-
-
- endclass
数据通道开关检测
在数据通道关闭的情况下检测数据写入是否通过
- class mcdf_channel_disbale_test extends mcdf_base_tese;
- function new (string name = "mcdf_channel_disbale_test");
- super.new(name)
- endfunction
-
- task do_mcdf_channel_disable_test (int id)
- forever begin
- @(posedge this.mcdf_vif.clk iff(this.mcdf_vif.rstn && this.mcdf_vif.mon_ck.chnl_en[id] == 0));
- if (this.chnl_vifs[id].mon_ck.ch_valid == 1 && this.chnl_vifs[id].mon_ck.ch_ready == 1)
- rpt_pkg::rpt_msg("[CHECKER]",
- $sformatf("ERROR! %0t whern channel disabled, ready signal raised while valid signal is high", $time),
- rpt_pkg::ERROR,
- rpt_pkg::TOP);
- end
- endtask
-
- endclass
覆盖率收集
- class mcdf_coverage;
- local virtual chnl_intf chnl_vifs[3];
- local virtual arb_intf arb_vif;
- local virtual mcdf_intf mcdf_vif;
- local virtual reg_intf reg_vif;
- local virtual fmt_intf reg_vif;
- local string name;
- local int delay_req_to_grant;
-
- //对所有控制、状态寄存器的读写进行测试。
- covergroup cg_mcdf_reg_write_read;
- addr: coverpoint reg_vif.mon_ck.cmd_addr{
- type_option.weight = 0;//权重设置为0,表示不关心此coverpoint的覆盖率,否则cross以后会出现很多的bin
- bins slv0_rw_addr = {'SLV0_RW_ADDR};
- bins slv1_rw_addr = {'SLV1_RW_ADDR};
- bins slv2_rw_addr = {'SLV2_RW_ADDR};
- bins slv0_r_addr = {'SLV0_R_ADDR };
- bins slv1_r_addr = {'SLV1_R_ADDR };
- bins slv2_r_addr = {'SLV2_R_ADDR };
- }//8bit地址实际上会生成256个bin,256-6=250会被平均分配到这6个bin中,
- //但默认max为64个bin,所以剩下的250要分配到64-6=58个bin里面
- //建议如果要做cross,那需要被cross的这些功能点都weight=0,然后在cross中重新bin
- cmd: coverpoint reg_vif.mon_ck.cmd{
- type_option.weight = 0;
- bins write = {'WRITE};
- bins read = {'READ};
- bins idle = {'IDLE};
- }//cmd是2bit的,所以还有一个默认的bin
-
- cmdXaddr: cross cmd, addr{
- bins slv0_rw_addr = binsof (addr.slv0_rw_addr);
- bins slv1_rw_addr = binsof (addr.slv1_rw_addr);
- bins slv2_rw_addr = binsof (addr.slv2_rw_addr);
- bins slv0_r_addr = binsof (addr.slv0_r_addr );
- bins slv1_r_addr = binsof (addr.slv1_r_addr );
- bins slv2_r_addr = binsof (addr.slv2_r_addr );
- bins write = (cmd.write);
- bins read = (cmd.read );
- bins idle = (cmd.idle );
- //因为之前设置权重为0,所以需要再一次的声明,这样可以只关心所需要的bin即可,不会生成没用的bin
- bins write_slv0_rw_addr = binsof (cmd.write) && binsof (addr.slv0_rw_addr);
- bins write_slv1_rw_addr = binsof (cmd.write) && binsof (addr.slv1_rw_addr);
- bins write_slv2_rw_addr = binsof (cmd.write) && binsof (addr.slv2_rw_addr);
- bins read_slv0_rw_addr = binsof (cmd.read ) && binsof (addr.slv0_rw_addr);
- bins read_slv1_rw_addr = binsof (cmd.read ) && binsof (addr.slv1_rw_addr);
- bins read_slv2_rw_addr = binsof (cmd.read ) && binsof (addr.slv2_rw_addr);
- bins read_slv0_r_addr = binsof (cmd.read ) && binsof (addr.slv0_r_addr );
- bins read_slv1_r_addr = binsof (cmd.read ) && binsof (addr.slv1_r_addr );
- bins read_slv2_r_addr = binsof (cmd.read ) && binsof (addr.slv2_r_addr );
- }
- endgroup
- //对非法地址进行读写测试,对控制寄存器的保留域进行读写测试,对状态寄存器进行写操作测试。
- covergroup cg_mcdf_reg_illegal_access;
- addr: coverpoint reg_vif.mon_ck.cmd_addr {
- type_option.weight = 0;
- bins legal_rw = {`SLV0_RW_ADDR, `SLV1_RW_ADDR, `SLV2_RW_ADDR};//合法读写寄存器地址
- bins legal_r = {`SLV0_R_ADDR, `SLV1_R_ADDR, `SLV2_R_ADDR}; //合法读地址范围
- /*这里做了一些简化,这里对任意一个通道的读写寄存器进行了操作就满足覆盖率要求*/
- /**但是实际上如果要对每一个通道的寄存器都进行覆盖率检测,那应该写为数组形式*/
- /*bins legal_rw[] = {`SLV0_RW_ADDR, `SLV1_RW_ADDR, `SLV2_RW_ADDR};
- bins legal_r[] = {`SLV0_R_ADDR, `SLV1_R_ADDR, `SLV2_R_ADDR}; */
- bins illegal = {[8'h20:$], 8'hC, 8'h1C}; //非法地址范围
- }
- cmd: coverpoint reg_vif.mon_ck.cmd {
- type_option.weight = 0;
- bins write = {`WRITE};
- bins read = {`READ};
- }
- //写数据覆盖点
- wdata: coverpoint reg_vif.mon_ck.cmd_data_m2s {
- type_option.weight = 0;
- //数据合法范围,0~111111,即bit(0)使能信号、bit(2:1)优先级、bit(5:3)数据包长度都可以写
- bins legal = {[0:'h3F]};
- //数据非法范围,bit(31:6)保留位,无法写入
- bins illegal = {['h40:$]};
- }
- //读数据覆盖点
- rdata: coverpoint reg_vif.mon_ck.cmd_data_s2m {
- type_option.weight = 0;
- //读数据合法范围,0~11111111,即bit(7:0)上行数据从端FIFO的可写余量
- bins legal = {[0:'hFF]};
- illegal_bins illegal = default;
- }
- //交叉覆盖
- cmdXaddrXdata: cross cmd, addr, wdata, rdata {
- bins addr_legal_rw = binsof(addr.legal_rw);
- bins addr_legal_r = binsof(addr.legal_r);
- bins addr_illegal = binsof(addr.illegal);
- bins cmd_write = binsof(cmd.write);
- bins cmd_read = binsof(cmd.read);
- bins wdata_legal = binsof(wdata.legal);
- bins wdata_illegal = binsof(wdata.illegal);
- bins rdata_legal = binsof(rdata.legal);
- bins write_illegal_addr = binsof(cmd.write) && binsof(addr.illegal);//对非法地址进行写操作
- bins read_illegal_addr = binsof(cmd.read) && binsof(addr.illegal);//对非法地址进行写操作
- bins write_illegal_rw_data = binsof(cmd.write) && binsof(addr.legal_rw) && binsof(wdata.illegal);//对读写寄存器合法地址写入非法数据
- bins write_illegal_r_data = binsof(cmd.write) && binsof(addr.legal_r) && binsof(wdata.illegal);//对只读寄存器合法地址写入非法数据
- }
- endgroup
-
- //对每一个数据通道对应的控制寄存器域en配置为0,在关闭状态下测试数据写入是否通过。
- covergroup cg_channel_disable;
- ch0_en: coverpoint mcdf_vif.mon_ck.chnl_en[0] {
- type_option.weight = 0;
- wildcard bins en = {1'b1};//这里为什么要用wildcard来修饰
- wildcard bins dis = {1'b0};
- //wildcard修饰符用于位宽不定的信号,它使得值的匹配忽略位数上的差异。
- //具体来说,wildcard可以用来简化匹配条件,特别是在信号中有不关心的位时
- //wildcard可以让表达式中的任何x、z或?被当作0或1的通配符
- }
- ch1_en: coverpoint mcdf_vif.mon_ck.chnl_en[1] {
- type_option.weight = 0;
- wildcard bins en = {1'b1};//对应使能
- wildcard bins dis = {1'b0};//对于禁用
- }
- ch2_en: coverpoint mcdf_vif.mon_ck.chnl_en[2] {
- type_option.weight = 0;
- wildcard bins en = {1'b1};
- wildcard bins dis = {1'b0};
- }
- ch0_vld: coverpoint chnl_vifs[0].mon_ck.ch_valid {
- type_option.weight = 0;
- bins hi = {1'b1};
- bins lo = {1'b0};
- }
- ch1_vld: coverpoint chnl_vifs[1].mon_ck.ch_valid {
- type_option.weight = 0;
- bins hi = {1'b1};
- bins lo = {1'b0};
- }
- ch2_vld: coverpoint chnl_vifs[2].mon_ck.ch_valid {
- type_option.weight = 0;
- bins hi = {1'b1};
- bins lo = {1'b0};
- }
- //采集当valid拉高时,代表要进行写操作,en拉低代表数据通道关闭
- chenXchvld: cross ch0_en, ch1_en, ch2_en, ch0_vld, ch1_vld, ch2_vld {
- bins ch0_en = binsof(ch0_en.en );
- bins ch0_dis = binsof(ch0_en.dis);
- bins ch1_en = binsof(ch1_en.en );
- bins ch1_dis = binsof(ch1_en.dis);
- bins ch2_en = binsof(ch2_en.en );
- bins ch2_dis = binsof(ch2_en.dis);
- bins ch0_hi = binsof(ch0_vld.hi);
- bins ch0_lo = binsof(ch0_vld.lo);
- bins ch1_hi = binsof(ch1_vld.hi);
- bins ch1_lo = binsof(ch1_vld.lo);
- bins ch2_hi = binsof(ch2_vld.hi);
- bins ch2_lo = binsof(ch2_vld.lo);
- bins ch0_en_vld = binsof(ch0_en.en ) && binsof(ch0_vld.hi);
- bins ch0_dis_vld = binsof(ch0_en.dis) && binsof(ch0_vld.hi);
- bins ch1_en_vld = binsof(ch1_en.en ) && binsof(ch1_vld.hi);
- bins ch1_dis_vld = binsof(ch1_en.dis) && binsof(ch1_vld.hi);
- bins ch2_en_vld = binsof(ch2_en.en ) && binsof(ch2_vld.hi);
- bins ch2_dis_vld = binsof(ch2_en.dis) && binsof(ch2_vld.hi);
- }
- endgroup
-
- //将不同数据通道配置为相同或者不同的优先级,在数据通道使能的情况下进行测试。
- covergroup cg_arbiter_priority;
- ch0_prio: coverpoint arb_vif.mon_ck.slv_prios[0] {
- bins ch_prio0 = {0};
- bins ch_prio1 = {1};
- bins ch_prio2 = {2};
- bins ch_prio3 = {3}; //为什么会有4个优先级呢
- }
- ch1_prio: coverpoint arb_vif.mon_ck.slv_prios[1] {
- bins ch_prio0 = {0};
- bins ch_prio1 = {1};
- bins ch_prio2 = {2};
- bins ch_prio3 = {3};
- }
- ch2_prio: coverpoint arb_vif.mon_ck.slv_prios[2] {
- bins ch_prio0 = {0};
- bins ch_prio1 = {1};
- bins ch_prio2 = {2};
- bins ch_prio3 = {3};
- }
- endgroup
-
- //测试从formatter发送出来的数据包长度是否同对应通道寄存器的配置一致
- covergroup cg_formatter_length;
- id: coverpoint fmt_vif.mon_ck.fmt_chid {
- bins ch0 = {0};
- bins ch1 = {1};
- bins ch2 = {2};
- illegal_bins illegal = default;
- }
- length: coverpoint fmt_vif.mon_ck.fmt_length {
- bins len4 = {4};
- bins len8 = {8};
- bins len16 = {16};
- bins len32 = {32};
- illegal_bins illegal = default;
- }
- endgroup
-
- //在req拉高以后,grant至少应该在2个时钟周期后拉高,以此来模拟下行从端数据余量不足的情况
- covergroup cg_formatter_grant();
- delay_req_to_grant: coverpoint this.delay_req_to_grant {
- bins delay1 = {1};// delay_req_to_grant是一个自定义的软件信号,在task do_formater_sample()中被定义
- bins delay2 = {2};//数字代表req与grant信号之间延迟的拍数字
- bins delay3_or_more = {[3:10]};
- illegal_bins illegal = {0};//req与grant信号之间没有延迟则非法
- }
- endgroup
-
- function new(string name="mcdf_coverage");
- this.name = name;
- this.cg_mcdf_reg_write_read = new();
- this.cg_mcdf_reg_illegal_access = new();
- this.cg_channel_disable = new();
- this.cg_arbiter_priority = new();
- this.cg_formatter_length = new();
- this.cg_formatter_grant = new();
- endfunction
-
- task run();
- fork
- this.do_reg_sample();
- this.do_channel_sample();
- this.do_arbiter_sample();
- this.do_formater_sample();
- join
- endtask
-
- //寄存器采样
- task do_reg_sample();
- forever begin
- @(posedge reg_vif.clk iff reg_vif.rstn);
- this.cg_mcdf_reg_write_read.sample();
- this.cg_mcdf_reg_illegal_access.sample();
- end
- endtask
-
- //数据通道采样
- task do_channel_sample();
- forever begin对于每一个时钟上升沿,至少有一个channel的valid信号为1时,即有数据发送进来时,才对disable做采样
- @(posedge mcdf_vif.clk iff mcdf_vif.rstn);
- if(chnl_vifs[0].mon_ck.ch_valid===1
- || chnl_vifs[1].mon_ck.ch_valid===1
- || chnl_vifs[2].mon_ck.ch_valid===1)
- this.cg_channel_disable.sample();
- end
- endtask
-
- //对优先级做采样
- task do_arbiter_sample();
- forever begin对于每一个时钟的上升沿,至少有一个channel的req为1,即至少有一个channel要发送数据了,才对其优先级做采样
- @(posedge arb_vif.clk iff arb_vif.rstn);
- if(arb_vif.slv_reqs[0]!==0 || arb_vif.slv_reqs[1]!==0 || arb_vif.slv_reqs[2]!==0)
- this.cg_arbiter_priority.sample();
- end
- endtask
-
- //对grant信号做采样
- task do_formater_sample();
- fork
- forever begin//对于每一个时钟上升沿,当req为1时,对数据的长度做采样
- @(posedge fmt_vif.clk iff fmt_vif.rstn);
- if(fmt_vif.mon_ck.fmt_req === 1)
- this.cg_formatter_length.sample();
- end
- forever begin
- //当req拉高时,要通过变量delay_req_to_grant来计算延迟,对grant做采样
- @(posedge fmt_vif.mon_ck.fmt_req);
- this.delay_req_to_grant = 0;/*req信号与grant信号之间的延迟*/
- forever begin//只有grant拉高时,才对grant做采样,进而通过delay_req_to_grant的值得到延迟
- if(fmt_vif.fmt_grant === 1) begin
- this.cg_formatter_grant.sample();
- break;/*当fmt_vif.mon_ck.fmt_req信号和fmt_vif.fmt_grant信号都被拉高时,delay_req_to_grant为0,此时为非法状态,被采样*/
- end
- else begin
- @(posedge fmt_vif.clk);
- this.delay_req_to_grant++;/*每经过一拍,delay_req_to_grant加1*/
- end
- end
- end
- join
- endtask
-
- function void do_report();
- string s;
- s = "\n---------------------------------------------------------------\n";
- s = {s, "COVERAGE SUMMARY \n"};
- s = {s, $sformatf(" total coverage: %.1f \n", $get_coverage())}; //$get_coverage可以得到总体的全局覆盖率
- s = {s, $sformatf(" cg_mcdf_reg_write_read coverage: %.1f \n" , this.cg_mcdf_reg_write_read.get_coverage())}; //get_coverage获取单个covergroup实例的覆盖率
- s = {s, $sformatf(" cg_mcdf_reg_illegal_access coverage: %.1f \n", this.cg_mcdf_reg_illegal_access.get_coverage())};
- s = {s, $sformatf(" cg_channel_disable_test coverage: %.1f \n" , this.cg_channel_disable.get_coverage())};
- s = {s, $sformatf(" cg_arbiter_priority_test coverage: %.1f \n" , this.cg_arbiter_priority.get_coverage())};
- s = {s, $sformatf(" cg_formatter_length_test coverage: %.1f \n" , this.cg_formatter_length.get_coverage())};
- s = {s, $sformatf(" cg_formatter_grant_test coverage: %.1f \n" , this.cg_formatter_grant.get_coverage())};
- s = {s, "---------------------------------------------------------------\n"};
- rpt_pkg::rpt_msg($sformatf("[%s]",this.name), s, rpt_pkg::INFO, rpt_pkg::TOP);
- endfunction
-
- virtual function void set_interface(virtual chnl_intf ch_vifs[3]
- ,virtual reg_intf reg_vif
- ,virtual arb_intf arb_vif
- ,virtual fmt_intf fmt_vif
- ,virtual mcdf_intf mcdf_vif
- );
- this.chnl_vifs = ch_vifs;
- this.arb_vif = arb_vif;
- this.reg_vif = reg_vif;
- this.fmt_vif = fmt_vif;
- this.mcdf_vif = mcdf_vif;
- if(chnl_vifs[0] == null || chnl_vifs[1] == null || chnl_vifs[2] == null)
- $error("chnl interface handle is NULL, please check if target interface has been intantiated");
- if(arb_vif == null)
- $error("arb interface handle is NULL, please check if target interface has been intantiated");
- if(reg_vif == null)
- $error("reg interface handle is NULL, please check if target interface has been intantiated");
- if(fmt_vif == null)
- $error("fmt interface handle is NULL, please check if target interface has been intantiated");
- if(mcdf_vif == null)
- $error("mcdf interface handle is NULL, please check if target interface has been intantiated");
- endfunction
- endclass
运行完这几个test之后,得到覆盖率收集表如下
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。