01 【Verilog实战】同步FIFO的设计(附源码RTL/TB)
虚拟机:VMware -14.0.0.24051
环 境:ubuntu 18.04.1
脚 本:makefile(点击查看)
应用工具:vcs 和 verdi
写在前面
路 线:
- 【verilog实战】同步FIFO设计(附源码RTL/TB)
- 【Verilog实战】异步FIFO设计(附源码RTL/TB)
- 【Verilog实战】UART通信协议,半双工通信方式(附源码RTL/TB)
- 【Verilog实战】SPI协议接口设计(附源码RTL/TB)
- 【Verilog实战】AMBA 3 APB接口设计(附源码RTL/TB)
- 【Verilog实战】AMBA AHB接口设计(附源码RTL/TB)
- 【Verilog实战】AMBA AXI接口设计(附源码RTL/TB)
- 【Verilog实战】UART2APB bridge 设计(附源码RTL/TB)
- 【Verilog实战】AHB2APB bridge 设计(附源码RTL/TB)
文章目录
一、学习内容
- 同步FIFO的写时钟和读时钟为同一个时钟,FIFO内部所有逻辑都是同步逻辑,常常用于交互数据缓冲。
- 典型同步FIFO有三部分组成: (1) FIFO写控制逻辑; (2)FIFO读控制逻辑;(3)FIFO 存储实体(如Memory、Reg)。
- FIFO写控制逻辑主要功能:产生FIFO写地址、写有效信号,同时产生FIFO写满、写错等状态信号;
- FIFO读控制逻辑主要功能:产生FIFO读地址、读有效信号,同时产生FIFO读空、读错等状态信号
二、基本概念
- 同步FIFO的“同步”是什么意思?
- FIFO是什么,有什么用?
- 接口都有什么
- 同步:时钟间有确定的倍数关系或确定的相位关系
- FIFO:Frist-in-first-out,先进先出,是一种数据缓存器,实现速率匹配。
既然是数据缓冲器,那么缓冲器的大小,存储深度,读写地址和存储器空满状态都需要确定。
一般FIFO使用循环指针
(计数溢出自动归零)。一般可以称写指针为头head,读指针为尾tail。初始化时,读写指针指向同一数据地址。
上图可见,FIFO初始化时,WP和RP指针指向同一数据单元。WP指向下一个将要写入的数据单元,RP指向将要读出的数据单元,两者是一个追赶过程。可以设置一个计数器,只写,来一个数据,写一个,写地址,+1,计数器+1,写满为止;只读,来一个,读出一个数据,读地址+1,计数器-1;同时读写,计数器值不变,读写地址均+1。
三、Spec
(1) Function description
同步FIFO实现了对write/read的控制,其接口解决了接口两端数据速率不匹配的问题。
(2) Feature list
- 支持存储宽度、深度可配置
- 时钟工作频率为1MHz
(3) Block diagram
模块主要分为读/写接口、读/写指针、读写指针的比较逻辑和array存储阵列四部分。
- 读/写接口:为模块提供读写数据和读写使能信号;
- 读写指针:主要标志读写指针当前array的地址
- 比较逻辑:
- 使用element counter(elem_cnt)记录FIFO RAM 中的数据个数:
▷ 等于0时,给出empty信号;等于BUF_LENGTH时,给出full信号 - elem_cnt:
▷ 写而未满时增加1
▷ 读而未空时减1
▷ 同时发生读写操作时,elem_cnt不变
(4) Interface description
(5) Timing
分为三部分,写操作,读操作,读写操作。
四、RTL design
- DUT模块
module sync_fifo
#(
parameter DATA_WIDTH = 32,
parameter DATA_DEPTH = 8 ,
parameter PTR_WIDTH = 3
//parameter PTR_WIDTH = $clog2(DATA_DEPTH)
)
(
input wire clk_i ,
input wire rst_n_i ,
//write interface
input wire wr_en_i ,
input wire [DATA_WIDTH-1:0] wr_data_i,
//read interface
input wire rd_en_i ,
output reg [DATA_WIDTH-1:0] rd_data_o,
//Flags_o
output reg full_o ,
output reg empty_o
);
reg [DATA_WIDTH-1:0] regs_array [DATA_DEPTH-1:0];
reg [PTR_WIDTH-1 :0] wr_ptr ;
reg [PTR_WIDTH-1 :0] rd_ptr ;
reg [PTR_WIDTH :0] elem_cnt ;
reg [PTR_WIDTH :0] elem_cnt_nxt ;
//Flags
wire full_comb ;
wire empty_comb ;
/*---------------------------------------------------\
--------------- write poiter addr ----------------
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
wr_ptr <= 3'b0;
end
else if (wr_en_i && !full_o) begin
wr_ptr <= wr_ptr + 3'b1;
end
end
/*---------------------------------------------------\
-------------- read poiter addr ------------------
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
rd_ptr <= 3'b0;
end
else if (rd_en_i && !empty_o) begin
rd_ptr <= rd_ptr + 3'b1;
end
end
/*---------------------------------------------------\
--------------- element counter ------------------
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
elem_cnt <= 4'b0;
end
else if (wr_en_i && rd_en_i && !full_o && !empty_o) begin
elem_cnt <= elem_cnt;
end
else if(wr_en_i && !full_o) begin
elem_cnt <= elem_cnt + 1'b1;
end
else if(rd_en_i && !empty_o) begin
elem_cnt <= elem_cnt - 1'b1;
end
end
/*---------------------------------------------------\
------------- generate the flags -----------------
\---------------------------------------------------*/
always @(*) begin
if(!rst_n_i) begin
elem_cnt_nxt = 1'b0;
end
else if(elem_cnt != 4'd0 && rd_en_i && !empty_o) begin
elem_cnt_nxt = elem_cnt - 1'b1;
end
else if(elem_cnt != 4'd8 && wr_en_i && !full_o) begin
elem_cnt_nxt = elem_cnt + 1'b1;
end
else begin
elem_cnt_nxt = elem_cnt;
end
end
assign full_comb = (elem_cnt_nxt == 4'd8);
assign empty_comb = (elem_cnt_nxt == 4'd0);
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
full_o <= 1'b0;
end
else begin
full_o <= full_comb;
end
end
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
empty_o <= 1'b1;
end
else begin
empty_o <= empty_comb;
end
end
/*---------------------------------------------------\
-------------------- read data -------------------
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
rd_data_o <= 32'b0;
end
else if(rd_en_i && !empty_o) begin
rd_data_o <= regs_array[rd_ptr];
end
end
/*---------------------------------------------------\
------------------- write data -------------------
\---------------------------------------------------*/
reg [PTR_WIDTH:0] i;
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
for(i=0;i<DATA_DEPTH;i=i+1) begin
regs_array[i] <= 32'b0;
end
end
else if(wr_en_i && !full_o) begin
regs_array[wr_ptr] <= wr_data_i;
end
end
endmodule
- tb
module tb_sync_fifo;
reg clk_i ;
reg rst_n_i ;
reg wr_en_i ;
reg [31:0] wr_data_i;
reg rd_en_i ;
reg [31:0] rd_data_o;
wire full_o ;
reg empty_o ;
initial begin
rst_n_i = 1 ;
clk_i = 0 ;
rd_en_i = 0 ;
wr_en_i = 0 ;
wr_data_i = 32'b0;
#2 rst_n_i = 0 ;
#5 rst_n_i = 1 ;
end
initial begin
#10 wr_en_i = 1;
rd_en_i = 0;
#10 wr_en_i = 0;
rd_en_i = 1;
#10 wr_en_i = 1;
rd_en_i = 0;
#3 rd_en_i = 1;
#10
repeat(100) begin
#5 wr_en_i = {$random}%2;
rd_en_i = {$random}%2;
end
end
initial #2000 $finish;
always #0.5 clk_i = ~clk_i ;
always #1 wr_data_i = {$random}%10;
sync_fifo u_sync_fifo
(
.clk_i (clk_i ),
.rst_n_i (rst_n_i ),
.wr_en_i (wr_en_i ),
.wr_data_i(wr_data_i),
.rd_en_i (rd_en_i ),
.rd_data_o(rd_data_o),
.full_o (full_o ),
.empty_o (empty_o )
);
initial begin
$fsdbDumpfile("sync_fifo.fsdb");
$fsdbDumpvars ;
$fsdbDumpMDA ;
end
endmodule
五、分析和小结
(1)分析
- 写阶段
复位之后,进行写操作,直至写满,产生满标志后,不再写入新数据。
- 读阶段
进行读操作,直至读空,产生空标志后,不再读出新数据。
- 同时读写阶段
先进行写操作,写入三个新数据之后,同时进行读写操作,期间写入新数据和读出数据,但是elem_cnt计数器不再变化,动态平衡。
(2)小结
设计思路:先分析需求,定义接口,画出具体的实现框图;按照协议和理解,画出相应时序图;看图写程序,验证仿真波形是否与时序图对应。
同步FIFO设计要点是什么时候产生空满标志位,即怎么衡量array被写满或者被读空。在这里,我使用了4bit的elem_cnt表示,通过elem_cnt的值表示当前array存储阵列的资源使用情况。0表示没有数据,即空状态;8表示写满,因为array的存储深度就是8。在spec中提到实现FIFO可配置,在这里只实现了宽度为32bit,深度为8的同步fifo设计,初步验证仿真波形与时序图相对应。
✍✍☛ 题库入口
经过一段时间的沉淀,发现入行IC行业,自己的底子还是很差,写的文章质量参差不齐,也没能解答大家的疑问。还是要实打实从基础学起,由浅入深。因此决定推倒重来,通过补充/完善基础知识的同时,通过题库刷题不断提高自己的设计水平,题库推荐给大家(点击直达),<题库记录>栏目不定期更新,欢迎前来讨论。2022.08.29 记
作者:xlinxdu
版权:本文版权归作者所有
转载:未经作者允许,禁止转载,转载必须保留此段声明,必须在文章中给出原文连接。
相关文章
- Swoole协程与传统fpm同步模式比较
- [Issue]repo/repo init-解决同步源码Cannot get http://gerrit.googlesource.com/git-repo/clone.bundle
- java核心知识点学习----多线程并发之线程同步
- 安卓Android手机直播推送同步录像功能设计与实现源码
- centos8平台redis5的主从同步搭建及sentinel哨兵配置
- Java中的同步集合与并发集合有什么区别?
- SAP CRM和C4C数据同步的两种方式概述:SAP PI和HCI
- 微信图文内容自动同步到腾讯内容开放平台的操作步骤
- 【最全最详细】同步、异步、阻塞、非阻塞
- ffplay源码之音视频同步分析
- ffplay源码分析:音视频同步
- 强化学习和同步感知
- Windows串口编程(同步异步)
- 同步FIFO(计数器法),8位数据,没有扩展位,以计数器的方式来输出空满!
- Ubuntu16.04安装lsyncd并测试自动同步文件夹至备份服务器
- 【测试】Gunicorn , uWSGI同步异步测试以及应用场景总结
- [C#]c#中数据的同步加锁机制 的几种方法