一、前言

  应聘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

linux下的EDA——VCS与Verdi仿真 - moon9999的博客 - CSDN博客 https://blog.csdn.net/moon9999/article/details/76615869

同步FIFO design and IP level verification的更多相关文章

  1. 同步fifo的verilogHDL设计实例

    原创 设计一个fifo,输入16bit,输出16bit的data,寻址宽度5bit,有空满标志. top 层如下所示: /* date : 2014/10/14 version : modelsim ...

  2. 怎么用Verilog语言描述同步FIFO和异步FIFO

    感谢 知乎龚大佬 打杂大佬 网上几个nice的博客(忘了是哪个了....) 前言 虽然FIFO都有IP可以使用,但理解原理还是自己写一个来得透彻. 什么是FIFO? Fist in first out ...

  3. 同步FIFO学习

    在网上找的一个经典同步FIFO例子. 一.前言 FIFO (First-In-First-Out) 是一种先进先出的数据交互方式,在数字ASIC设计中常常被使用.FIFO按工作时钟域的不同又可以分为: ...

  4. Verilog学习笔记简单功能实现(八)...............同步FIFO

    Part 1,功能定义: 用16*8 RAM实现一个同步先进先出(FIFO)队列设计.由写使能端控制该数据流的写入FIFO,并由读使能控制FIFO中数据的读出.写入和读出的操作(高电平有效)由时钟的上 ...

  5. 同步fifo的Verilog实现

    FIFO是一种先进先出的数据缓存器,他与普通存储器相比: 优点:没有外部读写地址线,这样使用起来非常简单: 缺点:只能顺序写入数据,顺序的读出数据, 其数据地址由内部读写指针自动加1完成,不能像普通存 ...

  6. E203 同步fifo

    1. 输入端, 输入信号, i_vld,表示输入请求写同步fifo,如果fifo不满,则fifo发送i_rdy 到输入端,开始写fifo.i_vld和i_rdy是写握手信号. 2.输出端 o_rdy表 ...

  7. 同步fifo与异步fifo

    参考以下帖子: https://blog.csdn.net/hengzo/article/details/49683707 https://blog.csdn.net/Times_poem/artic ...

  8. CYPEESS USB3.0程序解读之---同步FIFO(slaveFifoSync)

    上一篇文章解读了CYPRESS FX3的GPIO的操作过程,下面解读同步FIFO的一个例子(slaveFifoSync). *生产者,消费者. 1.首先看DMA的回调函数(cyu3dma.h): ty ...

  9. Chrome同步最新host文件IP列表

    使用Chrome的童靴是不是很多都碰到同步问题呢?网上查来查去的都是给些host文件的修改,可是都是几年前的东西,地址都不对了,想想还是自己找到需要解析的域名的IP地址吧 步骤: 1.DNS设置为8. ...

随机推荐

  1. File Compression and Archiving in linux (linux 中文件的归档)

    1. Compressing Files at the Shell Prompt Red Hat Enterprise Linux provides the bzip2, gzip, and zip ...

  2. Zabbix-绘制动态拓扑图基础篇

    一.实验环境 1.1 zabbix 4.0.2 二.实验需求介绍 公司希望网络拓扑能够动态反应物理接口的状态或者业务的状态,希望将网络拓扑显示到大屏上 三.Zabbix在绘制拓扑的优缺点 3.1 优点 ...

  3. docker方式部署elk日志搜索平台

    Docker部署ELKF操作文档 前提介绍 1.之前搭建elk+f+k使用原生系统软件安装方式,由于docker镜像日趋成熟,docker官网和elastic官网都有相关镜像和各自安装文档可供参考,各 ...

  4. Python-demo(photo)

    import osimport urllib import requests#import wximport time from fake_useragent import UserAgentfrom ...

  5. Javaweb Session机制(有待补充)

    Javaweb Session机制 一.前言 session,中文经常翻译为会话,其本来的含义是指有始有终的一系列动作/消息,比如打电话是从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个ses ...

  6. 使用ansible对思科交换机备份

    先决条件 - 了解ansible基本操作 - 了解网络设备相关操作 - 了解linux相关操作 安装 安装EPEL yum install https://dl.fedoraproject.org/p ...

  7. Storm 系列(七)—— Storm 集成 Redis 详解

    一.简介 Storm-Redis 提供了 Storm 与 Redis 的集成支持,你只需要引入对应的依赖即可使用: <dependency> <groupId>org.apac ...

  8. Android读取date中年月日

    1.Date对象:Date date = getDate(); 2.Calendar实例:Calendar calendar = Calendar.getInstance(); 3.calendar. ...

  9. java基础-多线程二

    java基础-多线程二 继承thread和实现Runnable的多线程每次都需要经历创建和销毁的过程,频繁的创建和销毁大大影响效率,线程池的诞生就可以很好的解决这一个问题,线程池可以充分的利用线程进行 ...

  10. selenium webdriver (python)第三版.pdf

    转载自:http://download.csdn.net/detail/waiwaijsj/7214035 是原作者根据自己的经验整理的,很实用.