MCDF_SV_lab4

欢迎留言,大家一起讨论学习哦~~

目录

lab4简介

1 MCDF代码理解

1.1 slave_fifo.v

1.2 reg.v

1.3 arbiter.v

1.4 formatter.v

2 tb代码理解

2.1 chnl_pkg.sv

2.2 reg_pkg.sv

2.3 fmt_pkg.sv

2.4 rpt_pkg.sv

2.5 mcdf_pkg.sv

3 测试

3.1 寄存器读写测试

3.2 寄存器稳定性测试

3.3 数据通道开关测试

3.4 优先级测试

3.5 下行从端低宽带测试

lab4简介

  • 与 MCDT 相比, MCDF主要添加了寄存器控制(RW-regs)和状态显示功能(RD-regs),同时也添加了一个重要的数据整形功能(formatter)。
  • 验证环境的各个组件之间相互独立,功能清晰,不同的 package之间的功能是独立的,同一个 package 中的各个验证组件的功能也是独立的。不同组件之间的通信靠信箱mailbox实现
  • lab4主要以测试是否通过为原则
  • 验证计划的核心要素点:那就是围绕着设计的功能点罗列出需要展开测试的功能点,以及如何展开测试,并且指明测试的验收标准是什么。

1 MCDF代码理解

  • 相比较MCDT只有slave_fifo和arbiter,MCDF在其基础上增加了register和formatter

寄存器分为:三个读写寄存器和三个状态寄存器,写命令时,三个读写寄存器接收外部控制信号(对各通道接收数据的使能、发向arbiter的优先级和长度的控制),三个状态寄存器监测接收三个通道fifo的余量;读命令时,所有寄存器将内部数据发送到slave端。

formatter功能:fmt向arb发送读取确认信号f2a_ack_o,然后arb通过各个slv向arb的请求信号slvx_req_o,并结合reg发向arb的优先级信号slvx_prio_i进行判断,确定要对哪一个通道进行读取数据,再把f2a_ack_o值赋给arb向slv的读取确认信号a2sx_ack_o(在slave中,将信号a2sx_ack_i赋给自己内部的rd_en_s,等slave_fifo不为空时便向arb发送数据),待arb获取到该通道的各种数据(id、data、pkglen)后,再将这些数据发送给fmt中的fifo暂存,fmt在fmt_req信号拉高后,等待DUT外部输入的grant信号拉高,便开始向DUT外输出数据。

1.1 slave_fifo.v

fifo32bits宽和32bits深,故读写地址指针要6bits,bit(0)~bit(4)表示32位的地址,bit(5)为拓展标志位。

fifo写满和读空的判断:

当读写地址和拓展位(最高位)均相同的时候,表示读写数据数量是一致的,此时为empty读空状态;当读写地址相同、拓展位(最高位)互为相反数时,表示读写数据数量是不一致的,此时为full写满状态。

chx_ready_o信号的控制(=1时:告诉外部数据源,内部的fifo可以接受写入外部数据):

当fifo未满,即full_s=0,且寄存器对slave的通道使能控制信号slvx_en_i=1时(寄存器地址为0x00、0x04、0x08的bit(0)分别控制ch0、ch1、ch2的开和关,1-开,0-关),chx_ready_o拉高;否则为0。

wr_pointer_r写指针和rd_pointer_r读指针的控制:

在每个clk上升沿或rstn下降沿检测,若rstn=0,复位=0;若rstn=1且chx_valid_i=1且chx_ready_o=1,wr_pointer_r累加1;rstn=1且rd_en_s=1且empty_s=0(fifo未空),rd_pointer_r累加1。

向fifo中写入data和从fifo中读出data:

通道的数据chx_data_i写进mem,存储地址为wr_pointer_r[4:0];fifo中读出地址为rd_pointer_r[0:4]的数据,赋给slvx_data_o,然后传给arbiter仲裁器。

1.2 reg.v

地址0x00、0x04、0x08通道0、1、2控制寄存器 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、0x14、0x18通道0、1、2状态寄存器32bits只读寄存器

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

bit(31:8):保留位,复位值为0。

寄存器“reg [`CMD_DATA_WIDTH-1:0] mem [5:0];”:CMD_DATA_WIDTH(param_def.v中为32)bits宽,6bits深。

//Trace fifo's margin(状态寄存器监测slave_fifo余量):

将三个通道的fifo余量数值(32bits,第8位表示余量,高24位保留位:{24'b0,slvx_margin_i})分别赋给mem的高三位(第3、4、5位),对应地址分别是0x10、0x14、0x18的只读寄存器。

//write R&W register(对读写寄存器进行写操作):

将输入数据cmd_data_i的[`PAC_LEN_HIGH:0]位,分别赋给mem的低三位(第0、1、2位),对应地址分别是0x00、0x04、0x08的读写寄存器。

为什么复位值为32’h0000_0007?

——对应上述三个读写寄存器的三个功能位的复位值,分别是1、3、0,即{26'h0, 00_0111}。

为什么要截取cmd_data_i的包长位到第0位去赋值呢?

——对应上述三个读写寄存器的三个功能位(bit(0):通道使能信号;bit(2:1):优先级;bit(5:3):数据包长度)所以从包长位到第0位截取,保证了完成这三个功能的相应位都被完整传递进寄存器。

// read R&W, R register(对读写、状态寄存器进行读操作):

将mem的6个数据信息(高三位:三个fifo余量信息;低三位:包长、优先级、使能等三个控制信号)读出,先赋值给中间寄存器变量cmf_data_reg,再assgin给cmd_data_o(此信号发送到DUT外部)。

最后assgin语句将mem中包长、优先级、使能等三个控制信号值,分别赋值给三个控制信号slv0_pkglen_o、slv0_prio_o、slv0_en_o(前两个信号用于slv→arbiter的数据控制,第三个是控制fifo接收并写入外部数据)。

1.3 arbiter.v

arbiter进行通道选择和获取并存储length包长值的always块:

若formatter对arbiter的通道ID请求信号f2a_id_req_i=1,则利用case语句对三个通道向arbiter的请求信号slvx_req_i做判断选择(共有七种情况001、010、011、100、101、110、111),若相应bit值为1,便将该bit对应通道ID值赋给reg类型中间变量id_sel_r,并将该通道包长信号slvx_pkglen_i的值赋值给reg类型中间变量a2f_pkglen_sel_r,若有两个及以上bit为1,则通过通道优先级信号slvx_prio_i去判断哪个通道优先级最高(0最高,3最低,值大的,优先级小)。

arbiter获取并存储通道ID、和该通道data、valid值的always块:

利用case选择语句对存储通道ID的中间变量id_sel_r进行判断,并将对应通道的ID、data、valid(slvx_val_i=1,表示slave数据被成功发送到arb)值分别赋给reg类型中间变量a2f_id_r、a2f_data_r、a2f_val_r。

用assgin语句和条件操作符,判断id_sel_r值分别等于00、01、10的情况,将对应formatter向arbiter的读取确认信号f2a_ack_i(该信号=1时,表示formatter告诉arbiter:我要开始读取slave端的数据了)的值赋给的arbiter向slave的读取确认信号a2sx_ack_o(该信号=1时,表示arbiter要读取对应通道的数据值)。

用assgin语句将存放通道的各种数据(id、data、pkglen)的中间变量的值赋给向fmt的各个输出信号。

1.4 formatter.v

fmt中的fifo也是32bits宽和32bits深。

package length decode:

包长值是由寄存器控制。0对应长度4,1对应长度8,2对应长度16,3对应长度32,其它数值(4~7)均暂时对应长度32,复位值为0。

formatter fifo write pointer increase:

fmt读取确认信号fmt_ack_r=1(表示fmt发出过读数据的请求了),且arb向fmt的val有效信号a2f_val_i(表示arb向fmt成功发送数据)与通道数据缓存有效信号bufferx_val_r(表示fmt成功接收到来自啊arb的数据),二者其中有一个=1时,fmt中fifo的写指针累加1。

fullfill formatter fifo && buffer:

fmt读取确认信号fmt_ack_r=0,且arb向fmt的val有效信号a2f_val_i=1时,此时说明当前并未有读取需求,但上一clk读取过,且arb成功地将数据发送给了fmt(后边有状态机描述该信号的状态变化),那么就将数据存到相应的缓存slvx_buffer_r中,同时将通道数据缓存有效信号bufferx_val_r拉高。

fmt读取确认信号fmt_ack_r=1,用case选择语句判断arb向fmt传递的通道ID信号a2f_id_i的值,将不同ID对应着的缓存器buffer中的数据(若bufferx_val_r=1,buffer中数据写进fifo后,该buffer的val信号拉低)或arb向fmt发送的数据a2f_data_i(若bufferx_val_r=0且a2f_val_i=1),写进fmt_fifo中。

formatter fifo read pointer increase && empty formatter fifo:

fmt发送数据信号=1时,fmt中fifo的读指针累加1。并将fifo读指针地址的数据写进中间变量fmt_data_r。

为什么fmt_id_req_r=1时,fmt的fifo读写指针都为0?

fmt_id_req_r只有在复位和fmt发送完一个包时=1,在此期间,fmt不进行数据的读和写。

FSM start:

c_state=FMT_IDLE时,fifo写指针(从0开始)cnt_rec_r=包长-1的话,n_state=FMT_REQ,也就是写进fifo一个包长度(4、8、16、32等)的数据后,次态会变为请求写入状态,没写够一个包的长度则继续写,data之间没空闲周期。

c_state=FMT_REQ时,fifo写指针>=包长值的话(写够一个包了),n_state=FMT_WAIT_GRANT,次态会变为等待状态。

c_state=FMT_WAIT_GRANT,当外部向fmt的允许发送数据信号fmt_grant_i=1,次态会变为开始状态。

c_state=FMT_START,次态会变为发送状态。

c_state=FMT_SEND,fifo读指针cnt_sen_r=包长-2的话(因为fmt_end_o信号要在fmt发送最后一个data时拉高),次态会变为end状态。

c_state=FMT_END,次态变为空闲状态。

2.2.

2 tb代码理解

  • 相比lab3,lab4增加了对MCDF中register寄存器和formator整形器的driver和monitor(分别被封装到reg_agent和fmt_agent中),并增加了refmod参考模型来模拟DUT的功能,并将其输出结果与检测到的DUT输出结果一同送进checker中进行对比。

2.1 chnl_pkg.sv

chnl_initiator更名为chnl_driver,且相比lab3,该类中多了一个do_reset()任务,要求在rstn下降沿ch_valid、ch_data都置0(复位);

同一个通道上的chnl_monitor和chnl_driver用的是同一个接口、不同的clocking时钟块(drv_ck和mon_ck);

检测DUT输出的monitor放到了fmt_pkg中;

2.2 reg_pkg.sv

class reg_trans:

约束中:“addr[7:4] == 0 && cmd == `WRITE -> soft data[31:6] == 0;”,即地址是00、04、08(读写寄存器)且命令为写时,寄存器中高26位设置为保留位。

class reg_driver:

@事件控制中添加了iff修饰词,只有当iff后的条件为真时,@事件才会触发。注意,iff可以在always和always_ff下使用,但是不能在always_comb、always_latch中使用

cmd='WRITE(写指令)时,会将寄存器的addr、data、cmd写进寄存器接口里drv_ck时钟块中的变量cmd_addr、cmd、cmd_data_m2s(发送出去,写进slave)。

cmd='READ(读指令)时,也会将寄存器的addr、cmd发送出去,但是会在第二个clk下降沿(这和利用接口时钟块采样效果一样,是为了避免产生竞争冒险),从slave读取data读进寄存器。

cmd='IDLE(空闲指令)时,便会执行空闲task,对寄存器接口里drv_ck时钟块中的三个output变量复位。

class reg_generator:

为什么声明一个reg_trans类型的队列reg_req[$],好像没用到?

如果随机化结果得到的req中的cmd变量值为'READ的话,在拿到rsp后,将rsp.data值赋给this.data(即从slave端向reg中读取数据)。

class reg_monitor:

监测reg_intf接口上寄存器写或读(读的话,要在下一个clk周期才会将数据读进来)的数据(addr、cmd、data—包括cmd_data_m2s和cmd_data_s2m),并将存放这些数据的reg_trans类型的对象放进mon_mb信箱中。

什么时候从该信箱拿出?——在mcdf_refmod::do_reg_updata()中取信(reg_agt中refmod中的reg_mon信箱句柄都悬空,是checker中的reg_mon将句柄分别赋给了二者)。

为什么不直接将refmod的reg_mon句柄赋给reg_agt中mon_mb呢,还有in_mbs[i]句柄直接赋给chnl_agt中mon_mb?

——

class reg_agent:

agent中声明一个driver和一个monitor,在new函数中例化两者,设置接口连接函数和run函数。

2.3 fmt_pkg.sv

自定义了两个全局的枚举类型:一个表示fmt中fifo的容量,一个表示fmt的带宽值。

根据随机化后fmt_trans类型对象req中的变量“fifo”的值,去确定信箱fifo的容量(初始容量为4096,容量值越大,grant信号越容易被拉高)。

根据随机化后fmt_trans类型对象req中的变量“bandwidth(带宽)”的值,去确定消耗周期(后边do_consume()中用到)(初始周期为1)。

class fmt_trans:

对两个枚举类型对象软约束,都=1(MED_FIFO、MED_WIDTH)。

相比chnl_pkg和reg_pkg的trans类,fmt的trans类中多了一个compare比较函数,而后会在mcdf_pkg::mcdf_checker中会调用。

class fmt_driver:

定义了一个存放数据类型为32bits向量的信箱(接收来自DUT中fmt发出的数据,并且利用自己内部do_consume()函数消化这些数据,但没有利用这些数据),名为fifo(其容量由上述)。

do_receive()中,

fmt_req信号拉高后,在每个clk上升沿检查(forever语句),若信箱fifo的容量值fifo_bound减去信箱fifo中信的数量fifo_num()大于等于fmt_length值时(刚开始信箱fifo中条目为0,条件成立;若往信箱中放数据的速度大于拿数据的速度,造成信箱中数据条目堆积,直到剩余容量小于数据包长度,那么便一直在该循环中,fmt_grant便不会拉高:信箱快满了,等拿出一些信后再放。也就是说fifo容量越大,越不容易满,grant信号被拉高的越快),则跳出该循环语句,驱动fmt_grant信号为1,那么下一clk上升沿fmt_start信号拉高(在fmt_start拉高后下一个clk上升沿,再驱动fmt_grant信号为0),并且在该clk下降沿(在clk下降沿采样和用时钟块采样,效果是一样的,都可以采到正确的值)开始将fmt_data存放进信箱fifo中。

do_consume()中,

利用非阻塞try_get()从信箱fifo中取出信,并将返回值(返回值为信箱fifo中信的数目)转化为空,之后等待(1~data_consum_peroid)之间平均随机数个clk上升沿后,再一直重复该过程。

class fmt_generator:

与fmt_driver的通信方式和lab3一样。

class fmt_monitor:

每个start信号上升沿开始监测(也就是此时DUT的fmt开始输出数据),将接口上的fmt_data(在start拉高后的下一个clk上升沿,根据fmt_length例化动态数组m.data的长度,然后foreach,将该length长的fmt_data填进length长的数组中)、fmt_chid都赋给内部声明的fmt_trans类型的对象m,然后将m放进信箱mon_mb。

2.4 rpt_pkg.sv

设置了严重程度,最低为LOW(0),程度大于等于LOW的信息都会整合到string类型变量msg中:“打印时间 、报告等级(INFO、WARNING、ERROR、FATAL)、[REPORT]、报告总结(各类报告数量)”

logf = $fopen(logname, "a+");——logname为打开文件的名字,a+是指读写打开一个文本文件,允许读和写。如果文件不存在,则创建新文件。读取文件会从文件起始地址的开始,写入只能是追加模式。返回值是32bit的文件描述符赋给了logf。

$fwrite(logf, $sformatf("%s\n", msg));——将msg写入到文件logfname中。

2.5 mcdf_pkg.sv

组件类——class mcdf_refmod:

功能:

其内部的信箱in_mbs[3]与mcdf_checker中的信箱chnl_mbs[3](其又与chnl_pkg::chnl_agent::monitor中信箱mon_mb连接)连接,将chnl_pkg的monitor监测到的通道数据(data、id等信号)输入到参考模型内部;

其内部信箱reg_mb与mcdf_checker中的信箱reg_mbs(其又与reg_pkg::reg_agent::monitor中信箱mon_mb连接)连接,将reg_pkg的monitor监测到的寄存器数据(addr、cmd、data等信号)输入到参考模型内部。

然后运行内部升级reg(模拟DUT寄存器)、打包(模拟DUTdefmt)等方法,来模拟DUT行为,并将打包后结果由信箱out_mbs输出到checker,与fmt_pkg中监测DUT输出的monitor得到的数据,进行对比。

参考模型是在mcdf_checker中被new()和run()。

do_reg_update()中,

因为六个寄存器地址分别为:8'h00、8'h04、8'h08、8'h10、8'h14、8'h18(分别对应二进制0000_0000、0000_0100、0000_1000、0001_0000、0001_0100、0001_1000),分别对于RWregs和RDregs来说,第2到3bit刚好是00、01、10,对应0、1、2,那么就可以利用t.addr[2:3]的三种可能的值来对应定宽数组regs[3]的三个mcdf_reg_t结构体类型的值进行赋值(即用regs模拟DUT的寄存器)。收集更新读写寄存器的en、prio、len,只读寄存器的fifo余量。

do_packet()中,

ot.length = len > 3 ? 32 : 4 << len;——若len>3,那么对应包长为32,len<=3时,00、01、10、11左移三位,分别是0、8、16、24

组件类——class mcdf_checker:

mcdf_intf接口信号应该有哪些?

do_compare()中,

mcdf_checker中的fmt_mb信箱去取fmt_agent中mon_mb信箱(监测的是DUT输出的data、ch_id、length等信号)中的数据,并存放进fmt_trans类型变量mont中;exp_mbs信箱去取mcdf_refmod中out_mbs[i]信箱(根据fmt_mb取得数据中的id值去向对应的out_mbs取值)的数据,并存放到fmt_trans类型变量expt中。

然后对变量mont将调用fmt_pkg::fmt_trans::compare()函数,输入参数为expt。去对比来自DUT_fmt和参考模型refmod二者输出的ch_id、data、length信号,并将比较结果(成功1,失败0,函数内部会利用打印包中的函数(rpt_pkg::rpt_msg())将结果打印出来)值赋给变量cmp。

然后total_count总数累加1,各通道数chnl_count(根据mont.ch_id)累加1。当cmp=0或者1时,调用rpt_pkg:rpt_msg()函数分别打印对比失败或成功的消息。

环境类——class mcdf_env:

mcdf_env层次中包含所有了测试组件:三个chnl_agt、一个reg_agt、一个fmt_agt、一个checker。

测试类——class mcdf_base_test:

首先声明三个generator对象和一个mcdf_env对象。

先例化mcdf_env的对象env,因为env中声明并例化了各个agent,那么各个agent也会将其中的driver和monitor都声明并例化(自底向上)。

所有gens中的req和rsp都是“实”(例化gen的时候就例化了)的drv中的req和rsp是“虚”的,然后在test类中,再将各个组件的req和rsp两个信箱句柄分别赋值给各自drv中的req和rsp。

三个对寄存器功能进行测试的task():首先分别对gen对象进行随机化并且增加内嵌约束(分别限定cmd为空闲、写、读指令,addr和data由子test类传递进来),然后调用reg_gen中start()任务,发送这些随机化后的激励到driver(‘READ命令时要将reg的data保存到test中的变量data)。

测试类——class mcdf_data_consistence_basic_test:

do_reg()中,

wr_val = (1<<3)+(0<<1)+1;——wr_val = 8'b0000_1001;

wr_val = (2<<3)+(1<<1)+1;——wr_val = 8'b0001_0011;

wr_val = (3<<3)+(2<<1)+1;——wr_val = 8'b0001_1101;

分别对控制三个slave的读写寄存器进行测试,对比写进寄存器的数据,和从寄存器读出的数据,是否一致。

do_data()中,

多三个通道的发生器进行随机化,并启动generator,向driver发送数据。

3 测试

  • 第一个小​🐛 :当在Linux环境对设计代码和验证代码编译时发现报错:在验证文件mcdf_pkg.sv、reg_pkg.sv、tb.sv中找不到`include的文件“param_def.v”。解决办法:在三个代码中“param_def.v”前添加该文件所在的绝对路径即可编译成功,例如我的路径是:“/home/verifier/project/rkv_labs/v2.1_sv/mcdf/v0/param_def.v”。

  • 要求1.1 :画出lab4的验证环境

  • 要求1.2:参考测试类mcdf_data_consistence_ basic_test,去创建其它的测试类

3.1 寄存器读写测试

  • mcdf_reg_write_read_test

内容——所有控制寄存器、状态寄存器的读写测试。

通过标准——读写值是否正确。

  • 要测试所有寄存器的读写值,那么就在do_reg任务中,对于读写寄存器先写进寄存器一个或几个数,再读出来作比较;对于状态寄存器,只能读出其数据。

1、声明两个分别存放控制寄存器和状态寄存器地址值(8bits)的动态数组变量;声明一个int类型(32bits,寄存器中data是32位)变量pwidth,其值由自定义参数PAC_LEN_WIDTH + PRIO_WIDTH + 1组成,即包长值+优先级值+通道使能;声明一个32bit的动态数组变量,用于存放将要写入控制寄存器的值,写进寄存器的数据由(1<<pwidth)-1, 0, (1<<pwidth)-1三个值(传入三个寄存器,每个寄存器传入三个值:32'b111111, 0, 32'h111111)组成。

❓为什么要用1左移pwidth位-1作为测试数?——变量pwidth表示的是,写进寄存器data中,有数据位的位宽值(这里包长bit(5:3),3位,优先级bit(2:1),2位,使能bit(0),1位。所以共6位pwidth=6),那么(1<<pwidth)-1能得到6个1。

往里写的数据必须是低pwidth位(即低6位)是有数据位,其余高位必须全是0。因为控制寄存器[31:6]位是保留位,reg.v中,会将写进的32位的cmd_data_i的低6位截取,其余高位补0,写进寄存器。所以如果往里写的数据[31:6]若不全是0,肯定与读出的数据不相等(因为读出的数据[31:6]肯定全是0),会打印比较错误。

2、调用底层test类的write_reg任务,将要传入的值先赋给变量wr_val,再写进对应地址的寄存器中;调用底层test类的read_reg任务,将上一步写进寄存器中的值再拿出来,放到变量rd_val中;最后调用底层test类中diff_value函数,比较写进去的值wr_val和读出来的值是否一致。

3、在tb.sv中声明该测试类的对象,并例化,然后赋值进mcdf_base_test类型的关联数组tests[string]中。

🐛1️⃣当使用foreach嵌套foreach循环时,注意外层和内层,所要循环的array[ ]括号中填的字母要区别开,不能一样!例如:外层用i——“foreach (chnl_rw_addrs[i]) begin”,内层用j——“foreach check_array[j] begin”。

内外层字母不一样时,仿真结果正确:依次对每一个寄存器读写三次。

内外层字母一样时,仿真结果不符合预期:会对三个寄存器轮流写入这三个值,结果ox00只写进第一个值,ox04只写进第二个值,ox08只写进第三个值。

🐛2️⃣注意作符之间的优先级:加减>移位,所以(1<<pwidth)-1不要忘记加括号,上面仿真结果就没加括号,结果输入的是32(其实问题不大)。

	class mcdf_reg_read_write_test extends mcdf_base_test;
	  function new(string name = "mcdf_reg_read_write_test");
      super.new(name);
    endfunction
		
		task do_reg();
			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_array[] = '{(1<<pwidth)-1, 0, (1<<pwidth)-1};
			bit [31:0] wr_val, rd_val;
			
			foreach (chnl_rw_addrs[i]) begin
				foreach (check_array[j]) begin
					wr_val = check_array[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_rd_addrs[i], rd.val);
			end
			
			this.idle_reg();
		endtask
	endclass

3.2 寄存器稳定性测试

  • mcdf_reg_illegal_access_test:

内容——对控制寄存器的保留域进行读写,对状态寄存器进行写操作。

通过标准——通过写入和读出,确定寄存器的值是预期值,而不是紊乱值,同时非法寄存器操作也不能影响MCDF的整体功能。

  • 和寄存器读写测试写法一样,对写入值和期望值(与实际读出值rd_val进行对比的值为期望值wr_val)稍加改动,并增加对状态寄存器的写操作、期望值与读出值的比较。

1、测试控制寄存器的[31:6]保留位,应该写入32'hFFFF_FFC0(这里分了两次写入:{32'h0000_FFC0, 32'hFFFF_0000},是想要避免数据的粘连问题),即[31:6]全为1的值。

2、this.diff_value(wr_val & ((1<<pwidth)-1), rd_val),其中wr_val & ((1<<pwidth)-1),表示取wr_val的低6位,这是期望读回来的值(期望值是32'b000000),再与读出值进行对比。

3、测试状态寄存器的[31:8]保留位,应该写入32'hFFFF_FF00,即[31:8]全为1的值。

4、this.diff_value(0, rd_val & 32'hFFFF_FF00),我们期望读回来的值为0,因为状态寄存器高24位都是保留位,且状态寄存器不可写入,rd_val & 32'hFFFF_FF00,表示只取实际读回来的值的高24位,因为第八位表示fifo余量,是有数值的,现在测试的是保留位,不需要低八位的数值。

仿真波形结果如下:

如果将数据32'hFFFF_FFC0,一次写入控制寄存器也是可以的,结果也是对的:

	class mcdf_reg_illegal_access_test extends mcdf_base_test;
		function new(string name = "mcdf_reg_illegal_access_test");
			super.new(name);
		endfunction
		
		task do_reg();
			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] wr_check = 32'hFFFF_FFC0; 
			bit [31:0] ro_check = 32'hFFFF_FFC0; 
			bit [31:0] wr_val, rd_val;
		
			foreach (chnl_rw_addrs[i]) begin
				//foreach (check_array[j]) begin
					wr_val = wr_check;
					this.write_reg(chnl_rw_addrs[i], wr_val);
					this.read_reg(chnl_rw_addrs[i], rd_val);
					void'(this.diff_value(wr_val & ((1<<pwidth)-1), rd_val));
				//end
			end
			
			foreach (chnl_ro_addrs[i]) begin
				wr_val = ro_check;
				this.write_reg(chnl_ro_addrs[i], wr_val);
				this.read_reg(chnl_ro_addrs[i], rd.val);
				void'(this.diff_value(0, rd_val & 32'hFFFF_FF00));
			end
			
			this.idle_reg();
		endtask
	endclass

3.3 数据通道开关测试

  • mcdf_channel_disable_test:

内容——对每一个数据通道对应的控制寄存器域en配置为0,在关闭状态下测试数据写入是否通过。

通过标准——在数据通道关闭情况下,数据无法写入,同时ready信号应该保持为低,表示不接收数据,但又能使得数据不被丢失,因此数据只会停留在数据通道端口。

  • 控制寄存器的使能开关(第0位)是怎么影响通道的ready信号?——寄存器的输出信号slvx_en_o,连接通道的输入信号slvx_en_i,在slave_fifo.v里,有一个条件语句:当fifo不满并且slvx_en_i=1时,其输出信号chx_ready_o=1。

1、该测试需要将mcdf_base_test的do_reg任务中,写入控制寄存器的值wr_val的第0位,由1改写为0(需要测试哪个通道的开关,改哪个对应的wr_val)。

2、该测试需要监测DUT中控制寄存器输出的en信号(写在mcdf_checker中),有两种方式:

①利用参考模型mcdf_refmod中get_field_value()函数中获取的RW_EN信号。使用该方式的前提是:参考模型的功能是正常的,即其获取的RW_EN信号和DUT中寄存器的信号是一样的。

②利用mvdf_inf接口,去监测DUT内部寄存器的输出使能信号slvx_en_o。使用该方式的前提是:内部寄存器读写功能正常(前面已经测试过),并且寄存器和3个通道之间的连接也要正常(即通道要正确地接收到对应寄存器发来的en信号)。

4、另外,该测试也需要监测DUT中各通道的valid、ready信号,用来和en信号结合判断:当en=0且valid=1时,ready是否=0(我们期望的结果是ready=0,那么此时该测试通过),不等于0是将报错信息打印出来。

🐛写该测试时,其new()函数是复制​上一个测试——寄存器稳定性测试的,new()中字符串name忘改了,仍然是mcdf_reg_illegal_access_test。

🐛为什么测试不会结束??把通道一的en=0,为什么测试就一直跑不会停了呢?en=0——通过将test中do_data任务的fork...join改为fork...join_any发现,是“chnl_gens[0].start();”这个线程不会停,也就是哪个通道en=0,哪个通道便不会停。

		task do_channel_disable_check(int id);
			foreach 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)    //这里需要在checker声明变量时声明:local virtual chnl_intf chnl_vifs[3];,并在set_interface()中将传进来的3个chnl_intf句柄赋值进数组中去
					rpt_pkg::rpt_msg("[CHECKER]",
						$sformatf("ERROR! %0t when channel disable, ready signal rasied when valid high!", $time),
						rpt_pkg::ERROR,
						rpt_pkg::TOP);
			end
		endtask

3.4 优先级测试

  • mcdf_arbiter_priorit_test:

内容——将不同数据通道配置为相同或者不同的优先级,在数据通道使能的情况下进行测试。

通过标准——如果优先级相同,那么arbiter 应该采取轮询机制从各个通道接收数据;如果优先级不同,那么 arbiter 应该先接收高优先级通道的数据。同时,最终所有的数据都应该从MCDF发送出来。

  • 由该测试通过标准可知,我们需要判断arb有没有先接受高优先级的通道的数据,即arb对该slave的数据读取确认信号a2sx_acks_o是否=1

1、首先,在设计代码中,arbiter要在通道选择的always块中,增加“轮询机制”部分的代码。即当三个通道优先级相同时,对保存通道ID值的变量id_sel_r进行判断,id_sel_r=任一通道ID时,就看除了这个通道外其他两通道的slvx_req_i哪个=1,就令id_sel_r=哪个通道ID,如果都不=1,那么id_sel_r仍保持原来的值。

🐛轮询机制这部分代码有问题,​加上去后,不管优先级相同与否都会执行轮询(未解决)??

——解决!if-else要写完整,不能没有else!源代码“if({slv2_req_i,slv1_req_i,slv0_req_i} != 3'b000)”这个缺少else,即缺少所有通道的req都=0情况下的处理,而且该条件下begin...end中只有轮询这一种处理结果,将优先级判断的case语句排除在外,又由于应该不存在3通道req信号都=0的情况,所以便只会执行轮询语句。

2、然后,对接口arb_intf进行设置,对DUT的arb中“f2a_id_req,slv_prio,slv_reqs,a2s_acks”这4个信号进行采样,即input。并与DUT中arb的对应端口进行连接(利用assign语句赋值)。

3、接着,在checker中增加获取最高优先级的通道ID值的函数get_slave_id_with_prio(),对存放3个通道优先级的接口信号slv_prios[3](优先级0到3,0最高,3最低),进行遍历。先slv_prios[0]<999(这个数只要大于优先级的最大值3即可)且该通道req信号=1时,便将该通道id值(i)赋给变量id,对应slv_prios[0]赋给变量prio;下一个slv_prios[1]<slv_prios[0]且该通道req信号=1时,便将该通道id值(i)赋给变量id,对应slv_prios[1]赋给变量prio,以此类推。用依次比较的方法找出优先级最高的通道ID,并返回该id值。如果三个优先级相同且req都=1的话,那么只会返回id=0,所以监测任务中要对该情况进行判断:id=0时,是0通道优先级最高?还是3通道优先级相同?

4、最后,对checker进行更新,增加对接口arb_intf的监测任务do_arbiter_priority_check():复位信号rstn=1且表示fmt对arb的通道ID请求信号=1时,在每个clk上升沿监测。由函数get_slave_id_with_prio()得到需要监测的优先级最高的通道id值。

①若3通道优先级不同,那么arb向具有最高优先级的通道的数据读取确认信号a2s_acks[id]应该=1(因为是根据优先级的高低,进行的通道的选择,进而a2s_acks信号拉高),这也是该测试通过的标准。

②若3通道优先级相同,便进行轮询机制,轮询机制不是根据优先级,而是轮流询问各通道的req信号来判断的,故此时优先级对a2s_acks信号没有意义,也就不用判断a2s_acks。

always @ (posedge clk_i or negedge rstn_i)
begin : CHANEL_SELECT
  if (!rstn_i) id_sel_r  = 2'b11;
  else if (f2a_id_req_i)
		begin
	//------------------此处为新增加的轮询机制部分------------------------------------
		if({slv2_req_i,slv1_req_i,slv0_req_i} != 3'b000)//3通道至少有一个req信号拉高便执行,如果没有 Slave 申请权限,权限保持不变
			begin			
			if(slv0_prio_i == slv1_prio_i && slv1_prio_i == slv2_prio_i)// 如果权限相同,轮询调度
				begin
					case(id_sel_r)
						2'b00:	begin
									if(slv1_req_i == 1)	
										begin
											id_sel_r <= 2'b01;
											a2f_pkglen_sel_r = slv1_pkglen_i;
										end
											
									else if(slv2_req_i == 1)
										begin
											id_sel_r <= 2'b10;
											a2f_pkglen_sel_r = slv2_pkglen_i;
										end
								end
						2'b01:	begin
									if(slv2_req_i == 1)	
									begin
										id_sel_r <= 2'b10;
										a2f_pkglen_sel_r = slv2_pkglen_i;
									end
									else if(slv0_req_i == 1)
									begin
										id_sel_r <= 2'b00;
										a2f_pkglen_sel_r = slv0_pkglen_i;
									end
								end
						2'b10:	begin
									if(slv0_req_i == 1)
									begin
										id_sel_r <= 2'b00;
										a2f_pkglen_sel_r = slv0_pkglen_i;
									end
									else if(slv1_req_i == 1)
									begin
										id_sel_r <= 2'b01;
										a2f_pkglen_sel_r = slv1_pkglen_i;
									end
								end
						default: 
						begin
							id_sel_r <= 2'b00;
							a2f_pkglen_sel_r = slv0_pkglen_i;
						end
					endcase
				end 
//-------------------------------------------------------------------------------------------------------
			else			
        case ({slv2_req_i,slv1_req_i,slv0_req_i})  //hsy: slvx_req_i = 1 when this slave is not empty.
				3'b001: 	begin 
							id_sel_r <= 2'b00;
							a2f_pkglen_sel_r = slv0_pkglen_i;
							end 
							
				3'b010: 	begin
							id_sel_r <= 2'b01;
							a2f_pkglen_sel_r = slv1_pkglen_i;
							end 
				
				3'b011: 	begin
							if(slv1_prio_i >= slv0_prio_i) 
								begin
									id_sel_r <= 2'b00;
									a2f_pkglen_sel_r = slv0_pkglen_i;
								end 
							else 
								begin
									id_sel_r <= 2'b01;
									a2f_pkglen_sel_r = slv1_pkglen_i;
								end 
							end 
							
				3'b100: 	begin
								id_sel_r <= 2'b10;
								a2f_pkglen_sel_r = slv2_pkglen_i;
							end 
							
				3'b101: 	begin
							if(slv2_prio_i >= slv0_prio_i) 
								begin
									id_sel_r <= 2'b00;
									a2f_pkglen_sel_r = slv0_pkglen_i;
								end 
							else 
								begin
									id_sel_r <= 2'b10;
									a2f_pkglen_sel_r = slv2_pkglen_i;
								end 
							end
							
		     3'b110: 	begin
							if(slv2_prio_i >= slv1_prio_i) 
								begin
									id_sel_r <= 2'b01;
									a2f_pkglen_sel_r = slv1_pkglen_i;
								end 
							else 
								begin
									id_sel_r <= 2'b10;
									a2f_pkglen_sel_r = slv2_pkglen_i;
								end 
							end
							
		     3'b111: 	begin
							      if(slv2_prio_i >= slv0_prio_i && slv1_prio_i >= slv0_prio_i)  
										begin 
											id_sel_r <= 2'b00;
											a2f_pkglen_sel_r = slv0_pkglen_i;
										end  																														    //priority 0>1 && 0>2
							      if(slv2_prio_i >= slv0_prio_i && slv1_prio_i < slv0_prio_i)   
										begin
											id_sel_r <= 2'b01;              																  //priority 1>0>2
											a2f_pkglen_sel_r = slv1_pkglen_i;
										end 
							      if(slv2_prio_i < slv0_prio_i && slv2_prio_i >= slv1_prio_i)   
										begin
											id_sel_r <= 2'b01; 																								//priority 1>2>0
											a2f_pkglen_sel_r = slv1_pkglen_i;
										end 	
							      if(slv2_prio_i < slv0_prio_i && slv2_prio_i < slv1_prio_i)    
										begin 
											id_sel_r <= 2'b10; 																								//priority 2>0 && 2>1
											a2f_pkglen_sel_r = slv2_pkglen_i;
										end 
							    end

		     default: 	begin 
								id_sel_r <= 2'b11;
								a2f_pkglen_sel_r = 3'b111;
							end 
							
		     endcase 
//-----------------------------------------这里加上一个else语句-----------------------------------------
			end
		else
			begin
				id_sel_r <= id_sel_r;
				a2f_pkglen_sel_r <= a2f_pkglen_sel_r;
			end
		end
//------------------------------------------------------------------------------------------------------
  else 
		begin
			id_sel_r <= id_sel_r;
			a2f_pkglen_sel_r <= a2f_pkglen_sel_r;
		end 
end 
    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();
        if(id >= 0) begin
          @(posedge this.arb_vif.clk);
					if(!(refmod.get_field_value(0,RW_PRIO) == refmod.get_field_value(1,RW_PRIO) 
					&& refmod.get_field_value(1,RW_PRIO) == refmod.get_field_value(2,RW_PRIO)))
          if(this.arb_vif.mon_ck.a2s_acks[id] !== 1)
            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
		
    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
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

3.5 下行从端低宽带测试

  • mcdf_formatter_grant_test

内容——将MCDF下行数据接收端设置为小存储量、低带宽的类型,由此使得在由formatter发送出

据之后,下行从端有更多的机会延迟grant信号的置位,用来模拟真实场景。

通过标准——在req拉高之后,grant应该在至少两个时钟周期以后拉高,以此来模拟下行从端数据

余量不足的情况。当这种激励时序发生10 次之后,可以停止测试。

  • 该测试代码只需更改do_formatter()中fmt_gen的内嵌约束条件,将容量和带宽设置成不同值。

  • fmt_driver中fifo容量和带宽值(即每次从DUT的fmt,接收一次数据后的周期间隔)对仿真结束时间和grant信号拉高的影响:

FIFOWIDTHTime/nsState
SHORT_FIFOLOW_WIDTH257245第15次req拉高后,grant在之后第8个clk才拉高
MED_FIFOMED_WIDTH166665每次req拉高后,grant在之后第1个clk便拉高
LONG_FIFOHIGH_WIDTH//
ULTRA_FIFOULTRA_WIDTH//
  • 当个向3个通道各发送100个trans时,SHORT_FIFO、LOW_WIDTH以上的fifo容量和带宽值,都不会缩短仿真时间,grant信号也都是在req信号拉高后的一个周期拉高。即MED的容量和带宽在目前足够用,再大的容量和带宽并不会提升仿真速度和grant拉高的速度。

  • 31
    点赞
  • 86
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值