• FPGA刷题——存储器(RAM和FIFO的Verilog实现)


    牛客网存储器部分的题目有RAM的实现,我把FIFO的实现也放在一起:

     

    目录

    单端口RAM实现

    双口RAM的实现

    同步FIFO实现

    异步FIFO实现

    读写地址发生器

    格雷码的产生与打拍

    空满信号发生器


    单端口RAM实现

    由题目中给的条件可以知道,输入端主要有数据,地址以及写使能三个信号,对于单端口RAM,只有一端有地址和使能信号,那么使能拉高时输入数据有效并且寄存,然后地址从0-127变化,存储输入数据,输出数据时需要保证输入端不写入数据,即拉低写使能。

    对比于双端口RAM,双端口RAM读写两端都有使能信号,写数据时读使能拉低,读数据时写使能拉低。而且两个端口的地址也是相互独立的。

    代码流程如下

    (1)首先定义题目要求的位宽和深度的数据寄存器,位宽=写数据=读数据位宽,深度按照题目要求,格式为:reg 【位宽】名称【深度】;

    (2)在复位信号下,给数据寄存器赋初值0,这里用到了for语句

    定义参数integer i 

    for(i=0;i<深度;i++)begin

           名称【i】<=0;

    end

    (3)使能信号=1时,将写数据存入数据寄存器

    (4)使能信号=0时,读取数据寄存器数据,否则不读取

    1. module RAM_1port(
    2. input clk,
    3. input rst,
    4. input enb,
    5. input [6:0]addr,
    6. input [3:0]w_data,
    7. output wire [3:0]r_data
    8. );
    9. //*************code***********//
    10. reg [3:0] data_temp[127:0];//定义深度为128的4位宽数据寄存器
    11. integer i;//定义参数i
    12. always@(posedge clk or negedge rst)begin
    13. if(~rst)begin
    14. for(i=0;i<127;i++)begin
    15. data_temp[i]<=0;//清零数据寄存器
    16. end
    17. end
    18. else if(enb)begin//写使能有效
    19. data_temp[addr]<=w_data;//把数据存进去
    20. end
    21. end
    22. assign r_data=(~enb)? data_temp[addr]:4'd0;//写使能低电平时输出,高电平不输出
    23. //*************code***********//
    24. endmodule

    双口RAM的实现

    首先要实现RAM,首先要声明数据的存储空间。声明存储变量之后,需要对ram进行初始化,写入数据,当write_en有效,向write_addr写入write_data,当read_en有效,根据输入的read_addr输出read_data。需要注意的是,题目要求实现真双端口RAM,即可以同时写入和读出,所以需要使用两个always语句块实现写入和读出逻辑,不可以在同一个always块中使用if-else if-else if结果。

    代码思路如下:

    (1)首先定义题目要求的位宽和深度的数据寄存器,位宽=写数据=读数据位宽,深度按照题目要求,格式为:reg 【位宽】名称【深度】;

    (2)在复位信号下,给数据寄存器赋初值0,这里用到了for语句

    定义参数integer i 

    for(i=0;i<深度;i++)begin

           名称【i】<=0;

    end

    (3)写使能信号=1时,将写数据存入数据寄存器

    (4)读使能信号=1时,读取数据寄存器数据,否则不读取

    1. module ram_mod(
    2. input clk,
    3. input rst_n,
    4. input write_en,
    5. input [7:0]write_addr,
    6. input [3:0]write_data,
    7. input read_en,
    8. input [7:0]read_addr,
    9. output reg [3:0]read_data
    10. );
    11. reg[3:0] data_reg[7:0];//定义位宽为4深度为8的数据寄存器
    12. integer i;
    13. always@(posedge clk or negedge rst_n)begin
    14. if(!rst_n)begin
    15. for(i=0;i<7;i++)begin
    16. data_reg[i]<=0;
    17. end
    18. end
    19. else if(write_en)begin
    20. data_reg[write_addr]<=write_data;//
    21. end
    22. end
    23. always@(posedge clk or negedge rst_n)begin
    24. if(!rst_n)begin
    25. read_data<=4'd0;
    26. end
    27. else if(read_en)begin
    28. read_data<=data_reg[read_addr];//读
    29. end
    30. else begin
    31. read_data<=4'd0;
    32. end
    33. end
    34. endmodule

    同步FIFO实现

     

    FIFO的实现依赖双口RAM,如下图,RAM的使能和数据可以与FIFO直接连接,需要产生RAM的写地址和读地址,还要产生写满full和读空empty信号(下右图标红):

     

    产生读写地址

    waddr写地址+1的情况:写使能有效&&没写满

    raddr读地址+1的情况:读使能有效&&没读空 

    1. assign wenc = winc && !wfull; //写地址+
    2. assign renc = rinc && !rempty;//读地址+
    3. always @(posedge clk or negedge rst_n) begin
    4. if (!rst_n)
    5. waddr <= 'd0;
    6. else if (wenc)
    7. waddr <= waddr + 'd1;
    8. end
    9. always @(posedge clk or negedge rst_n) begin
    10. if (!rst_n)
    11. raddr <= 'd0;
    12. else if (renc)
    13. raddr <= raddr + 'd1;
    14. end

    产生空满信号

    wfull写满的情况:读地址==写地址+深度

    rempty读空的情况:读地址==写地址

    1. always @(posedge clk or negedge rst_n) begin
    2. if (!rst_n) begin
    3. wfull <= 'd0;
    4. rempty <= 'd0;
    5. end
    6. else begin
    7. wfull <= waddr == raddr + DEPTH;
    8. rempty <= waddr == raddr;
    9. end
    10. end

    异步FIFO实现

     

    异步FIFO是各大公司面试笔试的重点。难点仍然是空满信号。异步FIFO的与同步FIFO的核心区别是它的读时钟和写时钟是不同步的。所以用对比读写地址的方法产生空满信号时,要进行跨时钟域处理。为了降低亚稳态可能性,异步FIFO还引入了格雷码。同时,格雷码也更方便产生空满信号。

     

     上图是本异步FIFO的结构示意图。蓝色区域是读时钟域,黄色部分是写时钟域。异步FIFO主要包含四部分:读写地址发生器、格雷码的产生与打拍、空满信号发生器以及RAM。本题已经给出了RAM部分。

    读写地址发生器

    产生FIFO的自然二进制读写地址。当读使能rinc==1且FIFO非空rempty==0时,读地址在读时钟rclk下自增;当写使能winc==1且FIFO非满wfull==0时,写地址在写时钟wclk下自增。这两种地址可以直接传入RAM模块。

    1. wire wenc, renc;
    2. wire [$clog2(DEPTH)-1:0] waddr, raddr;
    3. assign wenc = winc&!wfull;
    4. assign renc = rinc&!rempty;
    5. always@(posedge wclk or negedge wrstn) begin
    6. if(~wrstn)
    7. waddr_bin <= 0;
    8. else
    9. waddr_bin <= wenc? waddr_bin+1: waddr_bin;
    10. end
    11. always@(posedge rclk or negedge rrstn) begin
    12. if(~rrstn)
    13. raddr_bin <= 0;
    14. else
    15. raddr_bin <= renc? raddr_bin+1: raddr_bin;
    16. end

    格雷码的产生与打拍

    为了产生空满信号,需要比较读写地址的大小。但两个地址是由不同的时钟控制的,需要先做跨时钟域处理才能比较。所以使用了打两拍。又因为FIFO的读写地址是连续变化的,采用格雷码可以有效减少相邻地址的bit变化,进一步降低打拍过程产生亚稳态的可能性。

    最后,自然二进制地址和格雷码地址都是$clog2(DEPTH)+1bit,比waddrraddr多了1bit。该bit是用来辅助产生满信号的。

    1. reg [$clog2(DEPTH):0] waddr_bin, raddr_bin;
    2. wire [$clog2(DEPTH):0] waddr_gray, raddr_gray;
    3. reg [$clog2(DEPTH):0] waddr_gray1, raddr_gray1;
    4. reg [$clog2(DEPTH):0] waddr_gray2, raddr_gray2;
    5. reg [$clog2(DEPTH):0] waddr_gray3, raddr_gray3;
    6. assign waddr_gray = waddr_bin^(waddr_bin>>1);
    7. assign raddr_gray = raddr_bin^(raddr_bin>>1);
    8. assign waddr = waddr_bin[$clog2(DEPTH)-1:0];
    9. assign raddr = raddr_bin[$clog2(DEPTH)-1:0];
    10. always@(posedge rclk or negedge rrstn) begin
    11. if(~rrstn)
    12. raddr_gray1 <= 0;
    13. else
    14. raddr_gray1 <= raddr_gray;
    15. end
    16. always@(posedge rclk or negedge rrstn) begin
    17. if(~rrstn) begin
    18. waddr_gray2 <= 0;
    19. waddr_gray3 <= 0;
    20. end
    21. else begin
    22. waddr_gray2 <= waddr_gray1;
    23. waddr_gray3 <= waddr_gray2;
    24. end
    25. end
    26. always@(posedge wclk or negedge wrstn) begin
    27. if(~wrstn) begin
    28. raddr_gray2 <= 0;
    29. raddr_gray3 <= 0;
    30. end
    31. else begin
    32. raddr_gray2 <= raddr_gray1;
    33. raddr_gray3 <= raddr_gray2;
    34. end
    35. end

    使用自然二进制码计数时,相邻数据之间可能会产生多bit的变化。这会产生较大的尖峰电流以及其他问题。格雷码是一种相邻数据只有1bit变化的码制

    十进制自然二进制格雷码
    0000000
    1001001
    2010011
    3011010
    4100110
    5101111
    6110101
    7111100

     

    空满信号发生器

    当读写地址的格雷码仅有最高的2bit不同时,FIFO满;当读写地址完全相同时,FIFO空。

    1. assign wfull = (waddr_gray1=={~raddr_gray3[$clog2(DEPTH):$clog2(DEPTH)-1], raddr_gray3[$clog2(DEPTH)-2:0]});
    2. assign rempty = (raddr_gray1==waddr_gray3);

    异步FIFO完整代码:

    1. `timescale 1ns/1ns
    2. /***************************************RAM*****************************************/
    3. module dual_port_RAM #(parameter DEPTH = 16,
    4. parameter WIDTH = 8)(
    5. input wclk
    6. ,input wenc
    7. ,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
    8. ,input [WIDTH-1:0] wdata //数据写入
    9. ,input rclk
    10. ,input renc
    11. ,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
    12. ,output reg [WIDTH-1:0] rdata //数据输出
    13. );
    14. reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
    15. always @(posedge wclk) begin
    16. if(wenc)
    17. RAM_MEM[waddr] <= wdata;
    18. end
    19. always @(posedge rclk) begin
    20. if(renc)
    21. rdata <= RAM_MEM[raddr];
    22. end
    23. endmodule
    24. /***************************************AFIFO*****************************************/
    25. module asyn_fifo#(
    26. parameter WIDTH = 8,
    27. parameter DEPTH = 16
    28. )(
    29. input wclk ,
    30. input rclk ,
    31. input wrstn ,
    32. input rrstn ,
    33. input winc ,
    34. input rinc ,
    35. input [WIDTH-1:0] wdata ,
    36. output wire wfull ,
    37. output wire rempty ,
    38. output wire [WIDTH-1:0] rdata
    39. );
    40. wire [$clog2(DEPTH)-1:0] waddr, raddr;
    41. reg [$clog2(DEPTH) :0] waddr_bin, raddr_bin;
    42. wire [$clog2(DEPTH) :0] waddr_gray, raddr_gray;
    43. reg [$clog2(DEPTH) :0] waddr_gray1, raddr_gray1;
    44. reg [$clog2(DEPTH) :0] waddr_gray2, raddr_gray2;
    45. reg [$clog2(DEPTH) :0] waddr_gray3, raddr_gray3;
    46. wire wenc, renc;
    47. always@(posedge wclk or negedge wrstn) begin
    48. if(~wrstn)
    49. waddr_bin <= 0;
    50. else
    51. waddr_bin <= wenc? waddr_bin+1: waddr_bin;
    52. end
    53. always@(posedge rclk or negedge rrstn) begin
    54. if(~rrstn)
    55. raddr_bin <= 0;
    56. else
    57. raddr_bin <= renc? raddr_bin+1: raddr_bin;
    58. end
    59. always@(posedge wclk or negedge wrstn) begin
    60. if(~wrstn)
    61. waddr_gray1 <= 0;
    62. else
    63. waddr_gray1 <= waddr_gray;
    64. end
    65. always@(posedge rclk or negedge rrstn) begin
    66. if(~rrstn)
    67. raddr_gray1 <= 0;
    68. else
    69. raddr_gray1 <= raddr_gray;
    70. end
    71. always@(posedge rclk or negedge rrstn) begin
    72. if(~rrstn) begin
    73. waddr_gray2 <= 0;
    74. waddr_gray3 <= 0;
    75. end
    76. else begin
    77. waddr_gray2 <= waddr_gray1;
    78. waddr_gray3 <= waddr_gray2;
    79. end
    80. end
    81. always@(posedge wclk or negedge wrstn) begin
    82. if(~wrstn) begin
    83. raddr_gray2 <= 0;
    84. raddr_gray3 <= 0;
    85. end
    86. else begin
    87. raddr_gray2 <= raddr_gray1;
    88. raddr_gray3 <= raddr_gray2;
    89. end
    90. end
    91. assign waddr_gray = waddr_bin^(waddr_bin>>1);
    92. assign raddr_gray = raddr_bin^(raddr_bin>>1);
    93. assign waddr = waddr_bin[$clog2(DEPTH)-1:0];
    94. assign raddr = raddr_bin[$clog2(DEPTH)-1:0];
    95. assign wenc = winc&!wfull;
    96. assign renc = rinc&!rempty;
    97. assign wfull = (waddr_gray1=={~raddr_gray3[$clog2(DEPTH):$clog2(DEPTH)-1], raddr_gray3[$clog2(DEPTH)-2:0]});
    98. assign rempty = (raddr_gray1==waddr_gray3);
    99. dual_port_RAM #(
    100. .DEPTH(DEPTH),
    101. .WIDTH(WIDTH)
    102. )
    103. myRAM(
    104. .wclk (wclk ),
    105. .wenc (wenc ),
    106. .waddr(waddr),
    107. .wdata(wdata),
    108. .rclk (rclk ),
    109. .renc (renc ),
    110. .raddr(raddr),
    111. .rdata(rdata)
    112. );
    113. endmodule

  • 相关阅读:
    台式电脑电源功率越大越费电吗?装机选购多少W电源
    深度监督(中继监督)
    NLP:《ChatGPT: Optimizing Language Models for Dialogue一种优化的对话语言模型》翻译与解读
    树的直径 树形dp+2次dfs
    数学基础之概率论1
    C# - 委托、事件、Action、Func
    高逼格UI-ASD(Android Support Design)
    Unity热更新那些事
    SpringBoot(自定义注解)
    Linux篇【3】:Linux环境基础开发工具使用(上)
  • 原文地址:https://blog.csdn.net/weixin_46188211/article/details/126042295