一、前言
本系列旨在提供100%准确的数字IC设计/验证手撕代码环节的题目,原理,RTL设计,Testbench和参考仿真波形,每篇文章的内容都经过仿真核对。快速导航链接如下:
1.奇数分频
2.偶数分频
3.半整数分批
4.小数/分数分频
5.序列检测器
6.模三检测器
7.饮料机
8.异步复位,同步释放
9.边沿检测(上升沿,下降沿,双边沿)
10.全加器,半加器
11.格雷码转二进制
12.单bit跨时钟域(打两拍,边沿同步,脉冲同步)
13.奇偶校验
14.伪随机数生成器[线性反馈移位寄存器]
15.同步FIFO
16.无毛刺时钟切换电路
应当说,手撕代码环节是面试流程中既重要又简单的一个环节,跟软件类的岗位相比起来,数字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的结构差异
以上是异步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逻辑中判断满信号产生的条件
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
六、仿真分析
我们可以看到,之前按顺序输入的数据,之后按照相同顺序输出,同时空满信号的产生正常,设计成立。