同步FIFO design and IP level verification
一、前言
应聘IC前端相关岗位时,FIFO是最常考也是最基本的题目。FIFO经常用于数据缓存、位宽转换、异步时钟域处理。随着芯片规模的快速增长,灵活的system verilog成为设计/验证人员的基本功。本文从简易版的同步FIFO开始,熟悉IP设计与验证的基础技能。
二、IP设计
FIFO这一IP核已经相当成熟,因此网上资料也是一抓一大把。其中笔者认为较好的一个在文末附录中,需要详细了解FIFO工作原理的朋友可以仔细看看。这里简单介绍下本文设计FIFO的原理与结构。FIFO的内部存储单元是常见的双口RAM,这个IP的精髓在于读写地址的对外屏蔽与自动管理。避免写满、读空至关重要。本文设计的FIFO顶层例化双口RAM和FIFO控制两大模块:前者仅作为存储单元响应读写信号,后者根据读写计数器产生读写指针和重要的空满指示信号。
代码如下:
存储模块:
`timescale 1ns/1ps
module dpram
#(parameter D_W=,
A_W=)
(
input clk,
input rst_n,
//write ports
input wr_en,
input [D_W-:] wr_data,
input [A_W-:] wr_addr,
//read ports
input rd_en,
input [A_W-:] rd_addr,
output reg [D_W-:] rd_data
);
//RAM
reg [D_W-:] memory [:**A_W-];
reg out_start;
//write operation
always@(posedge clk)begin
if(wr_en)begin
memory[wr_addr] <= wr_data;
end
end //read operation
always@(posedge clk or negedge rst_n)begin
if(~rst_n)
rd_data <= ;
//else if(rd_en)begin
// rd_data <= memory[rd_addr];
//end
//else if(rd_addr == 1)
// rd_data <= memory[0];
else if(out_start)
rd_data <= memory[];
else if(rd_en)
rd_data <= memory[rd_addr];
end always@(posedge clk or negedge rst_n)begin
if(~rst_n)
out_start <= ;
else if(wr_en && wr_addr == 'd0)
out_start <= ;
else
out_start <= ;
end endmodule
dpram.v
FIFO控制模块:
`timescale 1ns/1ps
module fifo_ctrl
#(parameter A_W = ,
parameter [:] MODE = //0- standard read 1- first word fall through
)
(
input clk,
input rst_n, output [A_W-:] wr_addr,
output [A_W-:] rd_addr, output empty,
output full,
input wr_en,
input rd_en
);
localparam MAX_CNT = **A_W;
localparam FD_W = A_W; function [FD_W-:] abs;
input signed [FD_W-:] data;
begin
assign abs = data >= ? data : -data;
end
endfunction reg [A_W-:] wr_cnt;
wire add_wr_cnt,end_wr_cnt;
reg wr_flag;
reg [A_W-:] rd_cnt;
wire add_rd_cnt,end_rd_cnt;
reg rd_flag;
wire [A_W+-:] wr_ptr,rd_ptr;
wire empty_o;
reg empty_r,empty_r0,empty_r1; always@(posedge clk or negedge rst_n)begin
if(~rst_n)begin
wr_cnt <= ;
end
else if(add_wr_cnt)begin
if(end_wr_cnt)
wr_cnt <= ;
else
wr_cnt <= wr_cnt + 'b1;
end
end assign add_wr_cnt = wr_en & ~full;
assign end_wr_cnt = add_wr_cnt && wr_cnt == MAX_CNT - ; always@(posedge clk or negedge rst_n)begin
if(~rst_n)begin
wr_flag <= ;
end
else if(end_wr_cnt)begin
wr_flag <= ~wr_flag;
end
end always@(posedge clk or negedge rst_n)begin
if(~rst_n)begin
rd_cnt <= ;
end
else if(add_rd_cnt)begin
if(end_rd_cnt)
rd_cnt <= ;
else
rd_cnt <= rd_cnt + 'b1;
end
end assign add_rd_cnt = rd_en & ~empty;
assign end_rd_cnt = add_rd_cnt && rd_cnt == MAX_CNT - ; always@(posedge clk or negedge rst_n)begin
if(~rst_n)begin
rd_flag <= ;
end
else if(end_rd_cnt)begin
rd_flag <= ~rd_flag;
end
end assign wr_ptr = {wr_flag,wr_cnt};
assign rd_ptr = {rd_flag,rd_cnt}; assign wr_addr = wr_cnt;
assign rd_addr = rd_cnt + MODE; assign empty_o = wr_ptr == rd_ptr;
assign full = (abs(wr_ptr[A_W-:] - rd_ptr[A_W-:]) < ) && (wr_ptr[A_W] != rd_ptr[A_W]); assign empty = (wr_ptr[A_W-:] > rd_ptr[A_W-:]) ? empty_r : empty_o; always@(posedge clk)begin
empty_r0 <= empty_o;
empty_r1 <= empty_r0;
empty_r <= empty_r1;
end endmodule
fifo_ctrl.v
同步FIFO顶层:
`timescale 1ns/1ps
module fifo_sync
#(parameter D_W = ,
LOG_2_DEPTH = ,//2^8 = 256
parameter [:] MODE =
)
(
input clk,
input rst_n, input wr_en,
input [D_W-:] wr_data,
input rd_en,
output [D_W-:] rd_data,
output wr_full,
output rd_empty
);
wire [LOG_2_DEPTH-:] wr_addr,rd_addr; dpram #(.D_W(D_W),
.A_W(LOG_2_DEPTH))
dpram
(
.clk (clk),
.rst_n (rst_n),
.wr_en (wr_en),
.wr_data (wr_data),
.wr_addr (wr_addr),
.rd_en (rd_en),
.rd_addr (rd_addr),
.rd_data (rd_data)
); fifo_ctrl #(.A_W(LOG_2_DEPTH),
.MODE(MODE))
fifo_ctrl
(
.clk (clk),
.rst_n (rst_n),
.wr_addr (wr_addr),
.rd_addr (rd_addr),
.empty (rd_empty),
.full (wr_full),
.wr_en (wr_en),
.rd_en (rd_en)
); endmodule
fifo_sync
之前在使用FPGA做项目时,经常看到厂商提供的FIFO IP提供“首字跌落”模式,故在本设计中也提供了这个模式,即在读信号有效前便送出第一个写入的数据。另外,为提高代码的通用性,在设计中尽量使用parameter而不是固定数值作为信号位宽。
三、SV搭建testbench
一般来说使用verilog非综合子集也能编写testbench来验证设计的正确性,但当DUT较为复杂时就显得不够灵活。设计同步FIFO也是为了学习利用system verilog编写testbench的一些技巧。
首先明确验证方案。同步FIFO无非就是读写操作,只要每次都能将写入的数据读出就认为设计无误。我们可以通过SV的约束性随机特性完成任意长度以及任意间隔的读写操作。数据较多时逐一比较数据困难,testbench也应有自动对比数据并统计错误的机制。
采用OOP思想,设计descriptor transcation scorebord三个类,因此是随机产生读写操作的访问器,根据访问器信息的读写操作以及自动对比读写数据的计分板。SV语法非常灵活,各个类可以的方法不仅包括function,也支持task,这为时序操作带来了便利。还有一点较为重要的是,选择合适的数据类型。由于待写入数据长度不固定,使用动态数组比较恰当。而不断增加的读取数据信息,放置在队列中会有更高的效率。FIFO是否选择“首字跌落”模式,对读操作时序有直接影响,testbench中采用宏定义方式条件编译参数和读取采集逻辑。
代码如下:
`timescale 1ns/1ps
`define VERDI
//`define FW module testbench(); parameter CYC = ,
RST_TIM = ;
parameter D_W = ,
LOG_2_DEPTH = ; `ifdef FW
parameter [:] MODE = 'b1;//1'b1 'b0
`else
parameter [:] MODE = 'b0;
`endif
parameter MAX_LEN = **LOG_2_DEPTH; typedef int unsigned uint32;
typedef enum {true,false} status_e; bit clk,rst_n;
bit wr_en;
bit [D_W-:] wr_data;
bit rd_en;
logic [D_W-:] rd_data;
logic wr_full;
logic rd_empty;
reg rd_en_t; `ifdef VERDI
initial begin
$fsdbDumpfile("wave.fsdb");
$fsdbDumpvars("+all");
end
`endif initial begin
clk = ;
forever #(CYC/2.0) clk= ~clk;
end initial begin
rst_n = ;
#;
rst_n = ;
#(RST_TIM*CYC) rst_n = ;
end class Descriptor;
rand bit [-:] len_w,len_r,interval; constraint c {
len_w inside {[:]};
len_r inside {[:]};
interval inside {[:]};
}
function new;
$display("Created a object");
endfunction
endclass:Descriptor class Transcation;
bit [D_W-:] data_packet[];
static uint32 q_len[$];
static uint32 q_rd_data[$];
uint32 q_ref_data[$]; Descriptor dp; function new();
dp = new();
assert(dp.randomize());
q_len.push_back(dp.len_w);
endfunction extern task wri_oper;
extern task rd_oper;
extern task wr_rd_operation;
extern function void ref_gen(ref uint32 q_ref_data[$]); endclass:Transcation task Transcation::wri_oper;
uint32 wr_num;
$display("Write:%d",$size(tr.data_packet));
@(posedge clk);
#;
while(wr_num < dp.len_w)begin
if(~wr_full)begin
wr_en = ;
wr_data = tr.data_packet[wr_num];
wr_num++;
end
else begin
wr_en = ;
wr_data = tr.data_packet[wr_num];
end
#(CYC*);
end
wr_en = ;
endtask task Transcation::rd_oper;
uint32 rd_num;
$display("Read: %d",dp.len_r);
@(posedge clk);
#;
#(dp.interval*CYC);
while(rd_num < dp.len_r)begin
if(~rd_empty)begin
rd_en = ;
rd_num++;
end
else
rd_en = ;
#(CYC*);
end
rd_en = ;
endtask task Transcation::wr_rd_operation;
tr.data_packet = new[dp.len_w];
$display("len_w = %d, len_r = %d, inverval = %d",dp.len_w,dp.len_r,dp.interval);
foreach(tr.data_packet[i])begin
tr.data_packet[i] = i+;
//$display(tr.data_packet[i]);
end
fork
wri_oper;
rd_oper;
join
endtask function void Transcation::ref_gen(ref uint32 q_ref_data[$]);
integer j;
foreach(q_len[i])begin
for(j=;j<q_len[i];j++)begin
q_ref_data = {q_ref_data,j+};
end
end
endfunction class Scoreboard;
uint32 total_num,error_num = ; function compare(ref uint32 q_data[$],ref uint32 q_ref[$]);
uint32 comp_num;
uint32 i;
uint32 data_len,ref_len;
status_e status;
data_len = $size(q_data);
ref_len = $size(q_ref);
$display("The lengths of q_data and q_ref are %d,%d",$size(q_data),$size(q_ref));
if(data_len >= ref_len)
comp_num = ref_len;
else
comp_num = data_len;
total_num = comp_num;
for(i=;i<comp_num;i++)begin
if(q_data[i] != q_ref[i])begin
error_num++;
$display("The %dth data is different between the two!",i);
status = false;
return status;
end
end
status = true;
return status;
endfunction
endclass //Descriptor dp;
Transcation tr;
Scoreboard sb; //main
initial begin
//int status;
status_e status;
wr_en = ;
rd_en = ;
wr_data = ;
#;
#(*CYC);
repeat()begin
tr = new();
tr.wr_rd_operation;
#(*CYC);
end
#;
tr.ref_gen(tr.q_ref_data); //soreboard
sb = new();
status = sb.compare(tr.q_rd_data,tr.q_ref_data);
if(status == true)
$display("Simulation success!");
else
$display("Simulation filure!");
$stop;
end //save readed data
initial begin
forever begin
@(posedge clk);
`ifdef FW
if(rd_en)
`else
if(rd_en_t)
`endif
tr.q_rd_data = {tr.q_rd_data,rd_data};
end
end always@(posedge clk)begin
rd_en_t <= rd_en;
end fifo_sync
#(.D_W(D_W),
.LOG_2_DEPTH(),//
.MODE(MODE)
)uut
(
.clk (clk),
.rst_n (rst_n),
.wr_en (wr_en),
.wr_data (wr_data),
.rd_en (rd_en),
.rd_data (rd_data),
.wr_full (wr_full),
.rd_empty (rd_empty)
); endmodule:testbench
testbench.sv
四、VCS+Verdi工具使用
不得不说大多EDA工具确实没有IT行业的开发工具友好,用起来着实费了一番功夫。VCS这一仿真工具有自己的GUI debug tool,但功能不够强大。这里我们使用Verdi来debug。在上一节的SV代码中有一段fsdb的代码是专门产生Verdi波形文件的。因SV本身并没有这两个system function,使用时需要指定两个库文件路径。笔者直接将冗长的命令和选项定义一个alias:(bash shell)
alias vcs_verdi="vcs -full64 -sverilog -debug_all -P ${NOVAS_HOME}/share/PLI/VCS/linux64/novas.tab ${NOVAS_HOME}/share/PLI/VCS/linux64/pli.a +define+DUMPFSDB"
.bashrc file:

这个路径名好像必须是NOVAS_HOME,否则会报错,也是挺坑。利用上边的指令完成第一步代码编译,之后依次是执行仿真程序和调用Verdi GUI界面观察波形。命令依次是:
./simv
verdi -sv -f filename -ssf wave.fsdb
执行仿真后会产生testbench中指定的波形文件。第三步命令执行后verdi界面被打开。

通过波形及执行仿真后的Log可以看出仿真通过,在读写FIFO过程中没有产生错误。


这里分享一些使用verdi的基本技巧。
观察指定信号波形:选中代码中变量,ctrl+w添加该变量到波形窗口。
保存波形配置文件:在波形界面,按下shift+s保存.rc文件。
调取存储的配置文件:点击r,选中存储的.rc文件并打开。
笔者第一次利用SV采用OOP思想搭建testbench,也是首次使用VCS+Verdi工具链进行仿真调试。虽然设计验证都非常简单,但还是卡住了很多次。之后会尝试异步FIFO设计,以及基于UVM的可重用testbench编写。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
附录
1 [图文]同步FIFO - 百度文库 https://wenku.baidu.com/view/620e3934a32d7375a4178037.html
2 linux下的EDA——VCS与Verdi仿真 - moon9999的博客 - CSDN博客 https://blog.csdn.net/moon9999/article/details/76615869
同步FIFO design and IP level verification的更多相关文章
- 同步fifo的verilogHDL设计实例
原创 设计一个fifo,输入16bit,输出16bit的data,寻址宽度5bit,有空满标志. top 层如下所示: /* date : 2014/10/14 version : modelsim ...
- 怎么用Verilog语言描述同步FIFO和异步FIFO
感谢 知乎龚大佬 打杂大佬 网上几个nice的博客(忘了是哪个了....) 前言 虽然FIFO都有IP可以使用,但理解原理还是自己写一个来得透彻. 什么是FIFO? Fist in first out ...
- 同步FIFO学习
在网上找的一个经典同步FIFO例子. 一.前言 FIFO (First-In-First-Out) 是一种先进先出的数据交互方式,在数字ASIC设计中常常被使用.FIFO按工作时钟域的不同又可以分为: ...
- Verilog学习笔记简单功能实现(八)...............同步FIFO
Part 1,功能定义: 用16*8 RAM实现一个同步先进先出(FIFO)队列设计.由写使能端控制该数据流的写入FIFO,并由读使能控制FIFO中数据的读出.写入和读出的操作(高电平有效)由时钟的上 ...
- 同步fifo的Verilog实现
FIFO是一种先进先出的数据缓存器,他与普通存储器相比: 优点:没有外部读写地址线,这样使用起来非常简单: 缺点:只能顺序写入数据,顺序的读出数据, 其数据地址由内部读写指针自动加1完成,不能像普通存 ...
- E203 同步fifo
1. 输入端, 输入信号, i_vld,表示输入请求写同步fifo,如果fifo不满,则fifo发送i_rdy 到输入端,开始写fifo.i_vld和i_rdy是写握手信号. 2.输出端 o_rdy表 ...
- 同步fifo与异步fifo
参考以下帖子: https://blog.csdn.net/hengzo/article/details/49683707 https://blog.csdn.net/Times_poem/artic ...
- CYPEESS USB3.0程序解读之---同步FIFO(slaveFifoSync)
上一篇文章解读了CYPRESS FX3的GPIO的操作过程,下面解读同步FIFO的一个例子(slaveFifoSync). *生产者,消费者. 1.首先看DMA的回调函数(cyu3dma.h): ty ...
- Chrome同步最新host文件IP列表
使用Chrome的童靴是不是很多都碰到同步问题呢?网上查来查去的都是给些host文件的修改,可是都是几年前的东西,地址都不对了,想想还是自己找到需要解析的域名的IP地址吧 步骤: 1.DNS设置为8. ...
随机推荐
- 【记忆化搜索】掷骰子 hpuoj
B. 掷骰子 单点时限: 2.0 sec 内存限制: 512 MB 骰子,中国传统民间娱乐用来投掷的博具,早在战国时期就已经被发明. 现在给你 n 个骰子,求 n 个骰子掷出点数之和为 a 的概率是多 ...
- 《统计学习方法》极简笔记P5:决策树公式推导
<统计学习方法>极简笔记P2:感知机数学推导 <统计学习方法>极简笔记P3:k-NN数学推导 <统计学习方法>极简笔记P4:朴素贝叶斯公式推导
- 逆向破解之160个CrackMe —— 014
CrackMe —— 014 160 CrackMe 是比较适合新手学习逆向破解的CrackMe的一个集合一共160个待逆向破解的程序 CrackMe:它们都是一些公开给别人尝试破解的小程序,制作 c ...
- 在Docker for Windows中运行GUI程序
Docker运行GUI原理 Docker目前大多应用在服务器领域,那么在Docker中可以运行GUI程序吗?怀着好奇心google了一番,还真有人写了一篇文章 running-gui-applicat ...
- Jmeter接口自动化实例(使用Beanshell保存csv文件、csv参数化、setUp线程组)
很久没更新博客了,荒废了很久了,今天更新一下博客,主要记录一下子最近遇到的问题和解决方法:blonde_woman: 这篇文章主要记录的是jmeter批量跑接口中遇到的各种疑难,主要涉及到的问题如下 ...
- unity之游戏特效
一.运动轨迹 运动轨迹常常用于表现武器的挥舞效果,在提高速度感的同时又能让玩家看清楚招式动作,所以是常见的一种格斗特效. Unity中可以直接使用TrailRender来实现该效果. 二.运动模糊 运 ...
- Filter(过滤器)(有待补充)
Filter(过滤器) 一.Filter(过滤器)简介 Filter 的基本功能是对 Servlet 容器调用 Servlet 的过程进行拦截,从而在 Servlet 进行响应处理的前后实现一些特殊的 ...
- JDBC之PreparedStatement
JDBC之PreparedStatement 一.java.sql.PreparedStatement接口简介 该接口表示预编译的 SQL 语句的对象. SQL 语句被预编译并存储在 Prepared ...
- 11 python与redis交互
安装:pip install redis 导入模块:from redis import * 创建StrictRedis 通过init创建对象,指定参数host.port与指定的服务器和端口连接. ho ...
- 第8章 浏览器对象模型BOM 8.1 window对象
ECMAScript是javascript的核心,但如果要在web中使用javascript,那么BOM(浏览器对象模型)则无疑是真正的核心.BOM提供了很多对象,用于访问浏览器的功能,在浏览器之间共 ...