• 数字IC手撕代码-同步FIFO


     前言: 

            本专栏旨在记录高频笔面试手撕代码题,以备数字前端秋招,本专栏所有文章提供原理分析、代码及波形,所有代码均经过本人验证。

    目录如下:

    1.数字IC手撕代码-分频器(任意偶数分频)

    2.数字IC手撕代码-分频器(任意奇数分频)

    3.数字IC手撕代码-分频器(任意小数分频)

    4.数字IC手撕代码-异步复位同步释放

    5.数字IC手撕代码-边沿检测(上升沿、下降沿、双边沿)

    6.数字IC手撕代码-序列检测(状态机写法)

    7.数字IC手撕代码-序列检测(移位寄存器写法)

    8.数字IC手撕代码-半加器、全加器

    9.数字IC手撕代码-串转并、并转串

    10.数字IC手撕代码-数据位宽转换器(宽-窄,窄-宽转换)

    11.数字IC手撕代码-有限状态机FSM-饮料机

    12.数字IC手撕代码-握手信号(READY-VALID)

    13.数字IC手撕代码-流水握手(利用握手解决流水线断流、反压问题)

    14.数字IC手撕代码-泰凌微笔试真题

    15.数字IC手撕代码-平头哥技术终面手撕真题

    16.数字IC手撕代码-兆易创新笔试真题

    17.数字IC手撕代码-乐鑫科技笔试真题(4倍频)

    18.数字IC手撕代码-双端口RAM(dual-port-RAM)

            ...持续更新

    更多手撕代码题可以前往 数字IC手撕代码--题库


    目录

    原理介绍

    同步FIFO的工作方式

    FIFO空满的产生

    计数器判断空满

    代码

    Dual Port RAM

    sync_FIFO

             testbench

    波形图


    原理介绍

            在设计系统时,会包含工作在不同时钟频率下的元件,例如处理器和外设。数据在这些元件之间传输时先进先出(FIFO first in first out)阵列起到了重要作用。FIFO是用于对在通信总线上传输的数据进行排列的简单存储结构。

            因此,FIFO常用来传输跨不同时钟域的数据。

            本节介绍简单的同步FIFO架构,读写使用同样的时钟,为我们后续写异步FIFO(读写时钟非同源)做铺垫。

            下面给出了一个同步FIFO的通用架构,DPRAM(Dual Port RAM 双端口RAM)作为存储器来存储信息,在此之上添加判断DPRAM空满信息的组件后,整个模块就是一个同步FIFO了,读写分别使用不同的使能和地址信号(读、写使能,读、写地址分立),使得整个模块可以进行同时读写。

             通过读、写指针产生各自的读、写地址,送到读、写端口。写指针指向下一个要写入的地址,读指针指向下一个要读取的地址。有效写使能使得写指针递增, 有效的读使能使读指针递增。

            图中的“状态模块”产生FIFO的空满信号,如果“fifo_full”有效,则说明FIFO内部的空间已满,不能再写入数据。如果“fifo_empty”有效,则说明FIFO内乜有可供读取的下一个有效数据。通过对读写指针位置的判断,该模块也可以指示出任意时刻FIFO中空或满区域的个数。

    同步FIFO的工作方式

            复位后,读写指针都归0。此时“fifo_empty”信号置为有效而“fifo_full”保持低电平。因为FIFO为空,所以阻止对FIFO的读操作,只能进行写操作。后续的写操作会增加写指针的值,并将“fifo_empty”信号置为无效。在写到最后一个数据时,写指针等于RAM_SIZE-1。此时进行一个写指针会使写指针滚回到0,并将“fifo_full”信号置为高电平。

            总之,在读、写指针相等时,FIFO要么空要么满,所以需要对两种情况进行区分。

    FIFO空满的产生

            以深度为4的FIFO为例,一开始读写指针指向同一个位置,FIFO为空。写入三个数据之后,写指针指向RAM_SIZE-1=3的位置,此时再写入一个数据,写指针(wr_ptr)滚回0,和读指针指向同一个位置,此时FIFO为满。

            根据这种逻辑,很容易推导出这么一个结论:无论读写指针此时指向什么位置,当wr_ptr+1==rd_ptr时,FIFO再写入一个数据就满了,所以有: 

            fifo_full = (rd_ptr == (wr_ptr + 1'b1))&& wr_fifo

    从而有判断FIFO为满的RTL 代码:

    1. always@(posedge clk or negedge rstn)begin
    2. if(!rstn)
    3. fifo_full <= 1'b0;
    4. else if(wr_fifo && rd_fifo)
    5. ;//do nothing
    6. else if(rd_fifo)
    7. fifo_full <= 1'b0;
    8. else if((rd_ptr = wr_ptr + 1'b1) && wr_fifo)
    9. fifo_full <= 1'b1;
    10. end

            类似的,当读操作使得两个指针在下一个时钟相等时,FIFO变空,产生“fifo_empty”信号。有如下关系:无论读写指针此时指向什么位置,当rd_ptr+1==wr_ptr时,FIFO再读出一个数据就空了。

            fifo_empty = (wr_ptr == (rd_ptr + 1'b1))&& rd_fifo

    从而有判断FIFO为空的RTL 代码: 

    1. always @(posedge clk or negedge rstn)begin
    2. if(!rstn)
    3. fifo_empty <= 1'b1;
    4. else if(wr_fifo && rd_fifo)
    5. ;//do nothing
    6. else if(wr_fifo)
    7. fifo_empty <= 1'b0;
    8. else if((wr_ptr = rd_ptr + 1'b1) && rd_fifo)
    9. fifo_empty <= 1'b1;
    10. end

    计数器判断空满

            FIFO还有另一种利用计数器来指示FIFO空满的方法。

            计数器的宽度要与FIFO的深度相等,这样计数器才能记录FIFO数据的最大个数。计数器在复位时初始化为0,随后的任何写操作会将其递增1,任何读操作会使其递减1。

            在计数器为0时,很容易判断FIFO处于空状态,而当计数器的值等于FIFO的大小时,就能判断FIFO处于满状态。

            对于这种采用计数器来判断空满的方式实现比较简单,但是和上一个比较读写指针位置的方法相比资源占用会高一些。因为这种方法要求增加额外的硬件(计数器)来进行计数。

    代码

            简单来说,FIFO就是一个有判断空满逻辑的双端口RAM,下面我们来写一下以指针循环一周期判断空满的方式的同步FIFO,但是在之前我们先要写一个双端口RAM来存储数据。

    Dual Port RAM

    1. module dual_port_ram#(
    2. parameter DEPTH = 16,
    3. parameter WIDTH = 8
    4. )(
    5. input wr_clk ,
    6. input wr_en ,
    7. input [$clog(DEPTH)-1:0] wr_addr ,
    8. input [WIDTH-1:0] wr_data ,
    9. input rd_clk ,
    10. input rd_en ,
    11. input [$clog(DEPTH)-1:0] rd_addr ,
    12. output [WIDTH-1:0] rd_data
    13. );
    14. reg [WIDTH-1:0] RAM_MEM [DEPTH-1:0];
    15. always @(posedge wr_clk)begin
    16. if(wr_en)
    17. RAM_MEM[wr_addr] <= wr_data;
    18. end
    19. always @(posedge rd_clk)begin
    20. if(rd_en)
    21. RAM_MEM[rd_addr] <= rd_data;
    22. end
    23. endmodule

            整个双端口RAM其实就是一个简单的写使能时,把数据写入输入的写地址。读使能时,把数据从地址里读出来的那么一个功能,后续在FIFO中例化该Dual Port RAM模块。

    sync_FIFO

    1. `include "clog.v"
    2. module sync_fifo#(
    3. parameter WIDTH = 8 ,
    4. parameter DEPTH = 16
    5. )(
    6. input clk ,
    7. input rstn , // reset while rstn is negative
    8. //write interface
    9. input [WIDTH-1:0] data_in , // input data
    10. input wr_en , // write enable
    11. //read interface
    12. input rd_en , // read enable
    13. output [WIDTH-1:0] data_out ,
    14. output reg fifo_empty ,
    15. output reg fifo_full
    16. );
    17. //signal define
    18. reg [clog(DEPTH)-1:0] wr_ptr;
    19. reg [clog(DEPTH)-1:0] rd_ptr;
    20. wire wr_fifo;
    21. wire rd_fifo;
    22. //write data opration
    23. always @(posedge clk or negedge rstn)begin
    24. if(!rstn)
    25. wr_ptr <= 1'b0;
    26. else if(wr_fifo)
    27. wr_ptr <= wr_ptr + 1'b1;
    28. end
    29. assign wr_fifo = wr_en && !fifo_full;
    30. //read data opration
    31. always @(posedge clk or negedge rstn)begin
    32. if(!rstn)
    33. rd_ptr <= 1'b0;
    34. else if(rd_fifo)
    35. rd_ptr <= rd_ptr + 1'b1;
    36. end
    37. assign rd_fifo = rd_en && !fifo_empty;
    38. //full signal judgment
    39. always@(posedge clk or negedge rstn)begin
    40. if(!rstn)
    41. fifo_full <= 1'b0;
    42. else if(wr_fifo && rd_fifo)
    43. ;//do nothing
    44. else if(rd_fifo)
    45. fifo_full <= 1'b0;
    46. else if((rd_ptr == wr_ptr + 1'b1) && wr_fifo)
    47. fifo_full <= 1'b1;
    48. end
    49. //empty signal judgment
    50. always @(posedge clk or negedge rstn)begin
    51. if(!rstn)
    52. fifo_empty <= 1'b1;
    53. else if(wr_fifo && rd_fifo)
    54. ;//do nothing
    55. else if(wr_fifo)
    56. fifo_empty <= 1'b0;
    57. else if((wr_ptr == rd_ptr + 1'b1) && rd_fifo)
    58. fifo_empty <= 1'b1;
    59. end
    60. dual_port_ram #(
    61. .DEPTH (DEPTH) ,
    62. .WIDTH (WIDTH)
    63. )u_dual_port_ram
    64. (
    65. .wr_clk (clk) , //sync FIFO ,wr_clk = rd_clk
    66. .wr_en (wr_fifo) ,
    67. .wr_addr (wr_ptr) ,
    68. .wr_data (data_in) ,
    69. .rd_clk (clk) ,
    70. .rd_en (rd_fifo) ,
    71. .rd_addr (rd_ptr) ,
    72. .rd_data (data_out)
    73. );
    74. endmodule

            此外,还写了一个判断位宽的函数clog.v

    1. `ifndef MY_CLOG
    2. `define MY_CLOG
    3. function integer clog (input integer depth);
    4. begin
    5. for (clog=0; depth-1>0; clog=clog+1)
    6. depth = depth >>1;
    7. end
    8. endfunction
    9. `endif

    testbench

    1. `timescale 1ns/1ns
    2. module sync_fifo_tb();
    3. parameter WIDTH = 8;
    4. parameter DEPTH = 16;
    5. reg clk ;
    6. reg rstn ;
    7. reg [WIDTH-1:0] data_in ;
    8. reg rd_en ;
    9. reg wr_en ;
    10. wire [WIDTH-1:0] data_out ;
    11. wire empty ;
    12. wire full ;
    13. always #5 clk = ~clk;
    14. initial begin
    15. clk <= 1'b0;
    16. rstn <= 1'b0;
    17. data_in <= 'd0;
    18. rd_en <= 1'b0;
    19. wr_en <= 1'b0;
    20. //write 16 times to make fifo full
    21. #10
    22. rstn <= 1'b1;
    23. repeat(16)begin
    24. @(negedge clk)begin
    25. wr_en <= 1'b1;
    26. data_in <= $random; // generate 8bit random number data_in
    27. end
    28. end
    29. //read 16 times to make fifo empty
    30. repeat(16)begin
    31. @(negedge clk)begin
    32. wr_en <= 1'b0;
    33. rd_en <= 1'b1;
    34. end
    35. end
    36. //read and write 8 times
    37. repeat(8)begin
    38. @(negedge clk)begin
    39. wr_en <= 1'b1;
    40. data_in <= $random;
    41. rd_en <= 1'b0;
    42. end
    43. end
    44. //Continuous read and write
    45. forever begin
    46. @(negedge clk)begin
    47. wr_en <= 1'b1;
    48. data_in <= $random;
    49. rd_en <= 1'b1;
    50. end
    51. end
    52. end
    53. initial begin
    54. #800
    55. $finish();
    56. end
    57. initial begin
    58. $fsdbDumpfile("sync_fifo.fsdb");
    59. $fsdbDumpvars(0);
    60. end
    61. sync_fifo #(
    62. .WIDTH (WIDTH) ,
    63. .DEPTH (DEPTH)
    64. )u_sync_fifo
    65. (
    66. .clk (clk) ,
    67. .rstn (rstn) ,
    68. .data_in (data_in) ,
    69. .rd_en (rd_en) ,
    70. .wr_en (wr_en) ,
    71. .data_out (data_out) ,
    72. .fifo_empty (empty) ,
    73. .fifo_full (full)
    74. );
    75. endmodule

    波形图

            仿真结果和分析的结果一致, 写入16个数据将FIFO写满,此时full信号拉高;读出16个数据将FIFO读空,此时empty信号拉高;写入8个数据之后,同时读写,写入数据和读出数据均保持一致,功能正确。

            了解了同步FIFO的设计方法后,再进行异步FIFO的设计就比较简单了,下篇博客记录如何写一个异步FIFO,解决FIFO中跨时钟域的问题。


            更多手撕代码题可以前往 数字IC手撕代码--题库

  • 相关阅读:
    【监控系统】日志可视化监控体系ELK搭建
    生产力范式变革,华为云多管齐下推动AI产业化
    Compose中的FlowLayout
    JVM系列之语法糖的味道
    技术岗/算法岗面试如何准备?5000字长文、6个角度以2023秋招经历分享面试经验
    Python期末复习题库(上)——“Python”
    QT中QJson详细解析+代码演示
    TCP协议详细图解(含三次握手、滑动窗口等十大机制)
    数据库系统及应用复习——第十章数据库恢复技术与第十一章并发调度的可串行性
    手工编译安装nginx
  • 原文地址:https://blog.csdn.net/qq_57502075/article/details/128180751