一、异步FIFO verilog代码
异步FIFO的verilog代码,以及testbench出自这篇文章:
【原创】异步FIFO设计原理详解 (含RTL代码和Testbench代码)_异步fifo testbench-CSDN博客
对其做简要概括:异步FIOFO主要由一个双口RAM 和两个地址控制器(读、写)组成。
双口RAM有:写使能、写数据、写地址、读使能、读数据、读数据、读写时钟和复位信号组成,其中写使能由使能和满标志共同决定,读使能由空标准和读使能共同决定。这部分的代码很简单,主要难点是在读写地址的跨时钟域传输及判断。
读写地址判断器有:使能、地址、复位和空满判断等组成,存在跨时钟域的判断。跨时钟判断的意义是:在写时钟域中需要得到读指针的位置,才能判断是否写满,就不能再写入了,RAM的写使能为0;在读时钟域中需要得到写指针的位置,才能判断是否读空,就不能再读出了,RAM的读使能为0。
1.异步FIFO的地址规则
异步FIFO不能写入指定地址,该代码默认从地址1开始存数据,写入一个数据地址就加一,但是地址的加一并不是通过是否写入数据来判断,而是在时钟上升沿,无复位,无满信号下自动加一。那么这种情况在:写入几个数据后延迟一定时间写入,会出现问题,会出现没有写入新数据但是,地址在增加。针对这种情况后续会在UVM验证平台解决。并且还要在最高位加一位判断位,来判断是否已经多跑了一圈
其次还有一个小问题:在写入数据的同一时钟内地址加一,还是后一个时钟再加一??
如上图所示,他们的区别在于wr_en 在时钟上升沿变为1时,地址在写入后下一个时钟加一;如果wr_en在时钟上升沿之前变为1,地址在写入同时钟加一。他们的区别不大。(原因是在clocking中为避免竞争冒险加入了default延迟,input#1 表示在上升沿前一个时间单位判断)
2.地址转为格雷码的原因,如何转?
由于异步FIFO是工作在两个不同的时钟域中,如果读地址在某一时刻从0111->1000转变 ,恰好写时钟要在这时刻采样读地址,那么得到的值有可能是000-1111中的任一值,这个不确定的读地址值会导致空满状态判断错误。
相邻格雷码之间变化只有一位进行翻转,假如出现错误0000->0001,也是读时钟读到了0000,也就是地址没有跳变,但是用这个错误的写地址去做空判断不会出错(写满还写,读空还读),最多是让空标志在FIFO不是真正空的时候产生,而不会出现空读的情形。
格雷码保证了地址传输过程中出现错误的最小代价:传输的地址只能是实际地址或者上一位地址。写的地址小了>>假空,读的地址小了>>假满,不会产生数据的丢失。
对于如何转的问题有两种方法:第一种就是原文中的:保留自然二进制码的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或,而格雷码其余各位与次高位的求法相类似
第二种就是二进制数与右移一位的二进制数进行异或,这种更简单一点。
二进制转为格雷码之后就开始跨时钟域传输了。
3.格雷码的跨时钟域传输
如何进行跨时钟域传输? 打两拍。
这一招对于快时钟到慢时钟奏效,不会漏数据,但是慢时钟到快时钟会出问题,导致读出不连续的数据。如下图,wr_bin_rd为读到写时钟域,rd_bin_wr为写到读时钟域,出现了数据断档,但是传的都是实时的地址(写时钟比读时钟快。)
快时钟到慢时钟的数据丢失会给空满标志产生影响吗?不会,因为他不是采的慢,是漏采,会及时更新现在的指针地址位置。
4.格雷码的解码为二进制(也可不进行解码)
保留格雷码的最高位作为自然二进制码的最高位,而次高位自然二进制码为高位自然二进制码与次高位格雷码相异或,而自然二进制码的其余各位与次高位自然二进制码的求法相类似。
解不解码是关系着后面空满标志的判断。
5.空满标志的判断
解码后位二进制数,空标志为读写地址完全相同,满标志为最高一位不同,其余相同。
如果未解码,直接用格雷码比较的话,写满:最高位和次高位不同,其余相同;读空:完全相同。
最后重温一下开题文章作者在最后的讨论:打两拍是否会对判断产生影响。以“写满”为例,写地址为本地地址,读地址实际经过了:一个读时钟(在本地将二进制读地址转为格雷码)->最多两个写时钟(跨时钟)。才传到了写地址,此时如果判断为满的话,实际上传到时又读了一个多数,所以现在是假满。假空一样的思路。如图所示
完整代码在开头的那篇文章中有。
二、先使用sequence机制发送随机数
这个验证思路来自:
异步FIFO的UVM验证(VCS+Verdi 附源代码)_异步fifo的验证代码-CSDN博客
异步fifo的UVM验证_uvm 异步fifo-CSDN博客
整个验证环境从零开始搭建是很困难的,在这里推荐B站的一个视频《UVM基础》讲得很好,看完之后就对整个平台框架有很好的理解
验证文件夹包含:rtl 、 uvm1.1d 、uvm_tb。
uvm1.1d下载方式如下:
将ASFIFO 、DPRAM 、defines 放入rtl 文件夹。defines包含了FIFO的深度和位宽。
这是UVM的结构图,首先需要搭建top层,top层中要包含DUT(即rtl代码,testcase,env,scoreboard,write-master,read-monitor,driver,sequencer,以及事务transaction等等)。
先从一个简单的top+driver结构开始
fifo_driver.sv
`ifndef MY_DRIVER__SV
`define MY_DRIVER__SV
class fifo_driver extends uvm_driver;
function new(string name = "fifo_driver",uvm_component parent = null);
super.new(name,parent);
endfunction
extern virtual task main_phase(uvm_phase phase);
endclass
task fifo_driver::main_phase(uvm_phase phase);
fork
//forever begin
@(top_tb.init_done == 0)begin
top_tb.wr_data <= 'b0;
end
@(top_tb.init_done == 1) begin
for(int i = 0;i < 100;i++) begin
@(posedge top_tb.wrclk);
if(top_tb.wr_full == 0)begin
top_tb.wr_data <= $urandom_range(0,255);
`uvm_info("fifo_driver",$sformatf("%0d is driverd at %0t",top_tb.wr_data,$time),UVM_LOW)
end
else begin
top_tb.wr_data <= top_tb.wr_data;
`uvm_info("fifo_driver","fifo is full",UVM_LOW)
end
end
end
join
`uvm_info("fifo_driver","drive is finished",UVM_LOW)
endtask
`endif
top.sv
由于只输入了data,对其他参数还是需要自定义的输入,之后会把所有参数都封装起来。
`include "./rtl/defines.v"
`include "./rtl/dual_port_dram.v"
`include "./rtl/ASFIFO.v"
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "fifo_driver.sv"
module top_tb;
reg wrclk;
reg wr_rst_n;
reg[`WIDTH-1:0] wr_data;
reg wr_en;
wire wr_full;
reg rdclk;
reg rd_rst_n;
wire[`WIDTH-1:0] rd_data;
reg rd_en;
wire rd_empty;
reg init_done;
ASFIFO
uASFIFO
(
.wrclk(wrclk),
.rdclk(rdclk),
.rd_rst_n(rd_rst_n),
.wr_rst_n(wr_rst_n),
.wr_en(wr_en),
.rd_en(rd_en),
.wr_data(wr_data),
.rd_data(rd_data),
.wr_full(wr_full),
.rd_empty(rd_empty)
);
initial begin
wrclk = 0;
forever begin
#2 wrclk = ~wrclk;
end
end
initial begin
rdclk = 0;
forever begin
#4 rdclk = ~rdclk;
end
end
initial begin
wr_rst_n = 1;
rd_rst_n = 1;
wr_en = 0;
rd_en = 0;
wr_data = 'b0;
init_done= 0;
#30 wr_rst_n = 0;
rd_rst_n = 0;
#30 wr_rst_n = 1;
rd_rst_n = 1;
#29 init_done = 1;
end
always@(*)begin
if(init_done)begin
if(wr_full) wr_en = 0;
else wr_en = 1;
end
end
always@(*)begin
if(init_done)begin
if(rd_empty) rd_en = 0;
else rd_en = 1;
end
end
initial begin
fifo_driver drv;
drv = new("drv",null);
drv.main_phase(null);
$finish();
end
endmodule
在终端输入命令 vcs -full64 -sverilog -debug_all +v2k -R -nc -debug_pp -LDFLAGS -rdynamic -P ${NOVAS_HOME}/share/PLI/VCS/LINUX64/novas.tab ${NOVAS_HOME}/share/PLI/VCS/LINUX64/pli.a -cpp g++-4.8 -cc gcc-4.8 -LDFLAGS -Wl,--no-as-needed +define+UVM_NO_DPI -ntb_opts uvm uvm-1.1d/src/uvm_pkg.sv top.sv
结果如图:完成了对异步FIFO的随机注入,且读出数据为正确。