【数字IC手撕代码】Verilog同步FIFO|题目|原理|设计|仿真

Verilog同步FIFO

    • 前言
    • 题目
    • 原理
    • RTL设计
    • Testbench设计
    • 仿真分析

前言

本系列旨在提供100%准确的数字IC设计/验证手撕代码环节的题目,原理,RTL设计,Testbench和参考仿真波形,每篇文章的内容都经过仿真核对。快速导航链接如下:

奇数分频
偶数分频
半整数分批
小数/分数分频
序列检测器
模三检测器
饮料机
异步复位,同步释放
边沿检测(上升沿,下降沿,双边沿)
全加器,半加器
格雷码转二进制
单bit跨时钟域(打两拍,边沿同步,脉冲同步)
奇偶校验
伪随机数生成器[线性反馈移位寄存器]
同步FIFO

应当说,手撕代码环节是面试流程中既重要又简单的一个环节,跟软件类的岗位相比起来,数字IC的手撕代码题目固定,数量有限,属于整个面试中必得分的一个环节,在这个系列以外,笔者同样推荐数字IC求职者使用“HdlBits”进行代码的训练
链接如下
HDLBits — Verilog Practice

这篇开始之前,先安利大家两篇文章,都是Clifford E. Cummings写的,分别是《Simulation and Synthesis Techniques for Asynchronous FIFO Design》和《Simulation and Synthesis Techniques for Asynchronous FIFO Design with Asynchronous Pointer Comparisons》,感兴趣的朋友可以baidu或者google搜来看看,这两篇文章涵盖了有关FIFO的所有关键问题,语言生动,深入浅出,加一起一共四十页左右。
而作为《数字IC手撕代码篇》,我们想要讨论的同步FIFO一方面继承自异步FIFO,另一方面也应该是所有面试中可能会遇到的最为复杂的手撕代码题,具体的内容如下

题目

1.使用verilog,完成同步FIFO的设计,其中数据位宽为8位,FIFO的深度为16,其中输入端口为clk,rst_n(复位信号),write_en(写使能),read_en(读使能),data_in,输出端口为empty(空信号),full(满信号),data_out。

原理

有关什么是FIFO之类的问题就不讨论了,我们重点讨论几个关键的问题

1.同步FIFO和异步FIFO的结构差异
【数字IC手撕代码】Verilog同步FIFO|题目|原理|设计|仿真_第1张图片

以上是异步FIFO的结构图,可以发现,对于异步FIFO来说,它由几部分组成,最中间的是一块双口RAM,RAM的左边是写控制模块, 右边是读控制模块,下面的两级寄存器起到了格雷码状态下的地址信号跨时钟域作用,而对于同步FIFO来说,因为读写共用同一个时钟频率,因此不需要寄存器同步的那一部分。为了最大程度的贴近于异步FIFO的设计,作者依旧采用格雷码的比较来产生空满信号,当然,使用一个计数器来判断空满信号也是可行的,读者感兴趣可以在评论区讨论一下。

2.FIFO深度的约束

在格雷码转二进制中,我们已经讨论了“gray_code= binary_code ^ (binary_code >> 1)”这种格雷码的产生方式的限制,即只适用于2^N的情况,这里对FIFO的深度约束依旧成立,假如我们设计一个14位深度的fifo,地址由14跳回1不满足单bit变化,会产生采样错误的问题,因此本篇我们依旧对偶数情况的格雷码按下不表,只讨论2的N次方的深度问题

3.空满信号如何产生

设置读指针与写指针,在格雷码的情况下,若读写指针相等,证明FIFO为空,这个很好理解,那么什么情况下,FIFO为满呢?写指针转了一圈,追上了读指针的时候,FIFO应该是满状态,这里如果拿格雷码表格来举例的话,假设FIFO的深度为8位,只需要3位二进制编码就能表示,我们多使用一位,用4位的二进制编码来额外加上写指针追读指针的情况,那么以下的截图,读写指针为1+9,2+10,3+11以此类推的情况都是应该产生满信号的情况,找下规律,发现对于4bit格雷码来说,前两位相反,之后的位相同,就是verilog逻辑中判断满信号产生的条件

【数字IC手撕代码】Verilog同步FIFO|题目|原理|设计|仿真_第2张图片

4.数据通路和控制通路是否都需要复位
在作者的设计中,并不是所有的寄存器块都进行了复位的,比如说描述data_in所在的块,作者没有进行复位,但这并不影响FIFO的正常工作,这是因为我们设计追寻的原则为“控制通路必须包含复位”,数据通路“是否复位可以选择”,即读写指针所在的通路rst_n到来的时候都需要复位,但data_in作为数据通路可以选择不带复位信号。具体涉及到的代码如下:

always@(posedge clk)
if(write_en && !full)
data[wr_point] <= data_in;
else
data[wr_point] <= data[wr_point];

RTL设计

module fifo(clk,rst_n,write_en,read_en,data_in,empty,full,data_out);

input clk;
input rst_n;
input write_en;
input read_en;

input [7:0] data_in;

output empty;
output full;
output  reg [7:0] data_out;

reg [7:0] data  [0:15];


reg [4:0] wr_point;
reg [4:0] rd_point;

wire [4:0] wr_gray_point;
wire [4:0] rd_gray_point;

assign wr_gray_point = wr_point ^ (wr_point>>1);
assign rd_gray_point = rd_point ^ (rd_point>>1);

assign empty = (wr_gray_point == rd_gray_point) ? 1 : 0;
assign full =  (wr_gray_point[2:0] == rd_gray_point[2:0]
                && wr_gray_point[4] == !rd_gray_point[4]
                && wr_gray_point[3] == !rd_gray_point[3] ) ? 1 : 0 ;

always@(posedge clk or negedge rst_n)
if(!rst_n)
        wr_point <= 5'b0_0000;
else if (write_en && !full)
        begin
        if(wr_point < 5'b1_1111)
        wr_point <= wr_point + 1'b1;
        else
        wr_point <= 5'b0_0000;
        end
else
        wr_point <= wr_point;

always@(posedge clk or negedge rst_n)
if(!rst_n)
        rd_point <= 5'd0;
else if (read_en && !empty)
        begin
        if(rd_point <5'b1_1111)
        rd_point <= rd_point + 1'b1;
        else
        rd_point <= 5'b0_0000;
        end
else
        rd_point <= rd_point;


always@(posedge clk)
if(write_en && !full)
data[wr_point] <= data_in;
else
data[wr_point] <= data[wr_point];

always@(posedge clk or negedge rst_n)
if(!rst_n)
data_out <= 8'h00;
else if(read_en && !empty)
data_out <= data[rd_point];
else
data_out <= 8'h00;
endmodule


Testbench设计

module fifo_tb();
reg clk;
reg rst_n;
reg write_en;
reg read_en;
reg [7:0] data_in;

wire empty;
wire full;
wire [7:0] data_out;

fifo u1(.clk(clk),
.rst_n(rst_n),
.write_en(write_en),
.read_en(read_en),
.data_in(data_in),
.empty(empty),
.full(full),
.data_out(data_out)
);

always #5 clk = !clk;
always #10.01 data_in = $random;

initial
begin
clk = 0;
rst_n = 1;

#10
rst_n =0;

#14
rst_n = 1;
write_en = 1;

#200
write_en = 0;
read_en = 1;

#400
$stop;


end


endmodule

仿真分析

【数字IC手撕代码】Verilog同步FIFO|题目|原理|设计|仿真_第3张图片

我们可以看到,之前按顺序输入的数据,之后按照相同顺序输出,同时空满信号的产生正常,设计成立。

你可能感兴趣的:(数字IC手撕代码,fpga开发,verilog,硬件架构,芯片,fpga)