在开始uvm的实验3之前,对之前的层次没啥印象,重新看了一遍实验4,大致是可以理解了。
不太理解的时候就继续往前走,蓦然回首的时候,会更加明朗。
硬件
硬件部分分为4个组件,分别是slave_fifo,arbiter, formater,register
slave_fifo
功能:接收三个channel的数据并存储到fifo中
时序:valid为高且ready为高写入数据,valid为高但ready为低则等待ready为高再写
这里写硬件module的时候,三个的通道的信号可以用带x的来表示通用
- 程序解读
- 声明端口,一共6输入5输出:clk和rstn不用多说,chx_data_i是输入过来的数据,chx_valid_i为高时才可以开始写入数据,同时chx_ready_o为高,才把数据写入,以上是4个输入和1个输出;来自arbiter的a2sx_ack_i告诉这里哪一个通道可以读出数据给arbiter。
- 内部变量声明:由于要实现的是同步fifo,需要声明读写指针和缓存寄存器(前宽后深),fifo的第二个关键点是空满标志,wire声明后assign;剩余空间data_cnt_s,同时也assign给margin_o;a2sx_ack_i被assign给rd_en_s,表示可以读。
- 关于chx_ready_o的always块:fifo不满且寄存器说可以写(slvx_en_i为高),chx_ready_o为高
- slvx_req_o:复位信号为低,为0;否则,只要fifo不空slvx_req_o就为1,就可以向arbiter请求读出数据
- 读写指针+读写数据:直接把指针(而不用地址)写在mem[]中;在rstn+arbiter说可读rd_en_s+不空时读出数据,在rsnt+chx_valid_i+chx_ready_o+寄存器说可以写slvx_en_i时写入
- slvx_val_o:输出到arbiter的信号,收到arb可读信号rd_en_s且对应fifo不空时为有效数据,slvx_val_o为高
arbiter
功能:按照寄存器
时序:
- 程序解读
- 声明端口:和寄存器连接,寄存器给过来通道的优先级slvx_prio_i和数据包长度slvx_pkglen_i;和上面的slave_fifo连接,输入数据+slvx_req_o+slvx_val_o,输出a2sx_ack_i;和formater连接,输出有效信号a2f_val_o+通道id a2f_id_o+数据a2f_data_o+数据包长度a2f_pkglen_sel_o
- 通道选择:由于3个fifo只要不空slvx_req_o就会为1,也就是最多会有3个通道同时请求arb来读数据,这时候就要看谁的优先级slvx_prio_i更大,就给通道选择寄存器id_sel_r赋对应的id,然后把数据包长度slvx_pkglen_i给到输出寄存器a2f_pkglen_sel_r
- 只要通道id、slvx_val_o、slvx_data_i任何一个有变化就赋值给输出
formater
功能:根据寄存器的指令,接收arbiter来的数据及其id等,发送指定长度的数据包
时序:准备发送出数据包时,fmt_req_o置高,等待fmt_grant_i置高后下一个周期fmt_req_o置低同时fmt_start_o置高并维持一个clk,此时数据开始发送,发完一个数据的同时fmt_end_o也置高,数据发送完毕fmt_end_o也置低。相邻数据包之间至少一个clk,fmt_req_o才可被置高
- 代码解读
- 端口声明:从arb过来的3个输出作为输入a2f_val_i,a2f_id_i,a2f_data_i;从寄存器过来的pkglen_sel_i;开始传送数据的信号fmt_grant_i;输出数据以及传送数据过程中需要的信号fmt_chid_o,fmt_length_o,fmt_data_o、fmt_start_o,fmt_end_o
- 数据包长度length_r:根据寄存器信号pkglen_sel_i确定对应的长度
- fmt内部fifo写入指针:根据arb给过来的a2f_id_i,若有效a2f_val_i | 内部buffer有效buffer0_val_r且fmt要求fmt_ack_r,则将对应的写入指针+1,也就是写入一个数据
- fmt内部fifo与slave的buffer:根据buffer有效位和fmt要求位,将slave的buffer缓存着的数据给到fmt_fifo;根据arb数据有效位和fmt不要求发送,将数据缓存到slave_buffer里
- fmt内部fifo读出指针:根据fmt_send_r,每发送出一次就加1,
- fmt内部fifo读出数据:根据fmt_send_r,将内部fifo对应地址的数据给到输出
- 有限状态机完成各个动作状态:req,wait_grant,start,send,end,idle
- 对状态机各个状态下的信号进行配置
register
功能:根据指令cmd配置mcdf的功能
时序:指令一共有三个,分别对应不同的地址,写指令在当前clk就可以写入,读指令
地址0x00、0x04、0x08分别对应32bits的读写寄存器的3个通道;地址0x10、0x14、0x18分别对应32bits的只读寄存器的3个通道;
对于读写寄存器,第0位是通道使能信号,复位值1;1-2位是优先级,0最高,复位值3最小;3-5位存放数据包长度,0123分别表示4、8、16、32,4-7位表示32,复位值是0;6-31位无法写入,复位值0。
对于只读寄存器,0-7位表示fifo的可写余量,和fifo保持同步变化,复位值为fifo深度32;8-31位是保留位,复位值是0。
- 代码解读
- 端口声明:输入指令cmd及其地址addr,及其数据data,输入三个fifo的margin为slvx_margin_i;输出优先级slvx_prio_o,通道使能slvx_en_o,数据包长度slvx_pkglen_o,数据cmd_data_o
- 内置mem追踪fifo余量:mem的5-3位分别存放通道2-0的fifo余量,由于只读寄存器的描述,高24位是保留位为0,低7-0存放slvx_margin_i
- 内置mem的读写寄存器——写指令:mem的2-0位根据指令地址cmd_addr_i分别存放通道2-0读写寄存器的数据cmd_data_i,高26位是保留位0,低5-0位存放输入的数据cmd_data_i
- 读指令:指令为读READ时,根据指令地址cmd_addr_i将对应地址中的mem,也就是6个取一个给到输出端cmd_data_reg
软件
给硬件的各个部分都做个package
-
chnl_pkg
- trans事务类:包含数据data、id、间隔;约束;克隆;打印
- driver:通过mailbox获取trans的句柄req,将数据给到输出端,信号由接口打包着
- generator:例化了两个mailbox和trans的句柄req,因为需要做约束;将trans数值赋给mailbox
- monitor:检测输出端的数据,放到邮箱中
- agent:例化了driver和monitor,并连接接口,执行各自的run任务
-
arb_pkg
为空,组件类型还是一样
-
fmt_pkg
- trans事务类:包含数据data、id、长度;由于克隆和compare可以用uvm实现,这里不再赘述,
- driver:内置了一个邮箱模拟下行的fifo;一样是通过mailbox获取trans的句柄req,根据trans中fifo的深度和带宽进行相应的配置
- generator:例化了两个mailbox和trans的句柄req,做约束;将trans数值赋给mailbox
- monitor:检测输出端的数据,放到邮箱中
- agent:例化了driver和monitor,并连接接口,执行各自的run任务
-
mcdf_pkg
- mcdf_refmodel:模拟mcdf的硬件行为,包括复位,更新寄存器数据,打包三个通道过来的数据
- mcdf_checker:例化所有mailbox,把对应mailbox的trans给到refmod,把refmod输出端mailbox给到期望的信箱;比较monitor采集到的trans和期望的是否一致,记录错误数量
- mcdf_env:例化所有agent,让所有pkg的agent运行起来
- mcdf_base_test:例化env、所有generator,把mailbox传递给driver的信箱,让env运行起来
- mcdf_data_consistence_basic_test
-
rpt_pkg
利用uvm的信息机制就可以替代,这里忽略
-
tb
- 接口:输入端chnl_intf,寄存器端reg_intf,仲裁器端arb_intf,整形器端fmt_intf,整个mcdf的mcdf_intf;每个接口都有两个时钟块,drv和mon
- tb:接口连接;植入各个pkg,连接接口
UVM 实验3——TLM通信和回调函数
主要就是将原本用mailbox进行传递信号的部分全部替换成tlm端口,除了generator。用tlm通信管道替换了refmod里面的三个输出端mailbox,并用其自带端口进行通信。然后是利用回调函数进行test的延伸扩展。最后将利用uvm_root类配置一些仿真参数,配置了信息冗余度、仿真退出count、仿真最大时间长度。