当前位置:   article > 正文

FPGA实现双口RAM的乒乓操作——FPGA学习笔记2_ram乒乓存储

ram乒乓存储

一、RAM资源介绍

        RAM的英文全称是Random Access Memory,即随机存取存储器,它可以随时把数据写入任一指定地址的存储单元,也可以随时从任一指定地址中读出数据,其读写速度是由时钟频率决定的。RAM主要用来存放程序及程序执行过程中产生的中间数据、运算结果等。


单端口:只有一个端口,读写数据不能同时进行,共用数据通道。
伪双端口:拥有两个数据通道,一个用来写一个用来读。
真双端口:拥有两个数据通道,一个用来写一个用来读。

        RAM区别于ROM:

RAM(Random Access Memory)和ROM(Read-Only Memory)是计算机中常见的两种存储器件,它们有以下主要区别:

1. 可读写性:
   - RAM是一种可读写的内存,用于临时存储数据和程序。数据可以随时被写入、修改和读取,但在断电或重启计算机后数据会消失。
   - ROM是一种只读的内存,其中存储了固定的数据或程序,用户无法对其进行修改。即使在断电或重启计算机后,其中的数据也会保持不变。

2. 数据保存:
   - RAM用于存储正在运行的程序和临时数据,可以被CPU频繁读写,速度较快,但数据不稳定。
   - ROM用于存储固化的系统程序和数据,通常包括启动程序和固件等,数据稳定不易丢失。

3. 容量和成本:
   - RAM容量相对较大,通常用于临时存储大量数据和程序,但成本较高。
   - ROM容量通常较小,主要用于存储固定的程序和数据,成本相对较低。

综上所述,RAM和ROM在功能、读写性质、数据保存方式以及成本等方面有明显的区别,它们在计算机系统中发挥着不同的作用。

二、RAM IP核使用

        1、具体配置

                (1)Basic

        FPGA中没有ROM资源,ROM资源利用RAM资源模拟生成

                (2)Port A Options

                使用输出寄存器,数据会延后一个时钟

                其他保持默认

        2、代码驱动

        使用三个计数器分别控制读写切换、写入数据、读写地址

  1. `timescale 1ns / 1ps
  2. //
  3. // Company:
  4. // Engineer:
  5. //
  6. // Create Date: 2024/02/20 09:25:45
  7. // Design Name:
  8. // Module Name: rw_ram
  9. // Project Name:
  10. // Target Devices:
  11. // Tool Versions:
  12. // Description:
  13. //
  14. // Dependencies:
  15. //
  16. // Revision:
  17. // Revision 0.01 - File Created
  18. // Additional Comments:
  19. //
  20. //
  21. module rw_ram(
  22. input clk,
  23. input rst_n,
  24. output ram_en ,
  25. output ram_wea ,
  26. output reg[4:0]ram_addr ,
  27. output reg[7:0]ram_wr_data ,
  28. output reg[7:0]ram_rd_data
  29. );
  30. reg[5:0]rw_cnt;
  31. //读写切换 1写 0读
  32. assign ram_wea=((rw_cnt <= 6'd31)&&(ram_en == 1'b1)) ? 1'b1 : 1'b0;
  33. assign ram_en=rst_n;
  34. //读写状态计数器
  35. always @(posedge clk or negedge rst_n) begin
  36. if (!rst_n) begin
  37. rw_cnt <= 6'b0;
  38. end
  39. else if(rw_cnt < 6'd63)begin
  40. rw_cnt <= rw_cnt + 1'b1;
  41. end
  42. else begin
  43. rw_cnt <= 6'b0;
  44. end
  45. end
  46. //读写数据计数器
  47. always @(posedge clk or negedge rst_n) begin
  48. if (!rst_n) begin
  49. ram_wr_data <= 8'b0;
  50. end
  51. else if(ram_wea == 1'b1)begin
  52. ram_wr_data <= ram_wr_data + 1'b1;
  53. end
  54. else begin
  55. ram_wr_data <= 8'b0;
  56. end
  57. end
  58. //读写地址计数器
  59. always @(posedge clk or negedge rst_n) begin
  60. if (!rst_n) begin
  61. ram_addr <= 5'b0;
  62. end
  63. else if(ram_addr < 5'd31)begin
  64. ram_addr <= ram_addr + 1'b1;
  65. end
  66. else begin
  67. ram_addr <= 5'b0;
  68. end
  69. end
  70. endmodule

              3、顶层例化文件

  1. `timescale 1ns / 1ps
  2. //
  3. // Company:
  4. // Engineer:
  5. //
  6. // Create Date: 2024/02/20 09:22:59
  7. // Design Name:
  8. // Module Name: ip_ram
  9. // Project Name:
  10. // Target Devices:
  11. // Tool Versions:
  12. // Description:
  13. //
  14. // Dependencies:
  15. //
  16. // Revision:
  17. // Revision 0.01 - File Created
  18. // Additional Comments:
  19. //
  20. //
  21. module ip_ram(
  22. input sys_clk,
  23. input sys_rst_n
  24. );
  25. wire ram_en ;//RAM使能
  26. wire ram_wea ;//ram读写使能信号,高电平写入,低电平读出
  27. wire [4 : 0] ram_addr ;//ram读写地址
  28. wire [7 : 0] ram_wr_data;//ram写数据
  29. wire [7 : 0] ram_rd_data;//ram读数据
  30. blk_mem_gen_0 u_blk_mem_gen_0 (
  31. .clka(sys_clk), // input wire clka
  32. .ena(ram_en), // input wire ena
  33. .wea(ram_wea), // input wire [0 : 0] wea
  34. .addra(ram_addr), // input wire [4 : 0] addra
  35. .dina(ram_wr_data), // input wire [7 : 0] dina
  36. .douta(ram_rd_data) // output wire [7 : 0] douta
  37. );
  38. rw_ram u_rw_ram(
  39. .clk (sys_clk) ,
  40. .rst_n (sys_rst_n) ,
  41. //RAM
  42. .ram_en (ram_en) ,
  43. .ram_wea (ram_wea) ,
  44. .ram_addr (ram_addr) ,
  45. .ram_wr_data (ram_wr_data) ,
  46. .ram_rd_data (ram_rd_data)
  47. );
  48. ila_0 u_ila_0 (
  49. .clk(sys_clk), // input wire clk
  50. .probe0(ram_en), // input wire [0:0] probe0
  51. .probe1(ram_wea), // input wire [0:0] probe1
  52. .probe2(ram_addr), // input wire [4:0] probe2
  53. .probe3(ram_wr_data), // input wire [7:0] probe3
  54. .probe4(ram_rd_data) // input wire [7:0] probe4
  55. );
  56. endmodule

        引脚约束XDC

  1. create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
  2. set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS33} [get_ports sys_clk]
  3. set_property -dict {PACKAGE_PIN U2 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]

        4、综合下载验证

                ram_wea 信号拉高之后,地址和数据都是从 0 开始累加,也就说当 ram 地址为 0 时,写入的数据也是 0;当 ram 地址为 1 时,写入的数据也是 1。

                ram_wea(读使能)信号拉低之后, ram_addr 从 0 开始增加,也就是说从 ram 的地址 0 开始读数据; ram中读出的数据 ram_rd_data 在延时一个时钟周期之后, 开始输出数据, 输出的数据为 0, 1, 2。。。。。

三、双口RAM——乒乓操作

        1、乒乓操作简介

        外部输入数据流通过输入数据流选择单元将数据流输入到数据缓存模块,比较常用的存储单元有双口RAM,FIFO,SDRAM等。在第一个缓冲周期,数据流通过“输入数据流选择单元”将数据写入“数据缓冲模块1”。写完之后进入第二个缓冲周期,在第二个缓冲周期数据流通过“输入数 据流选择单元”将数据写入到“数据缓冲模块2”的同时“输出数据流选择单元”将“数据缓冲模块1”的数据流读出,此时进入第三个缓冲周期。在第三个缓冲周期数据流通过“输入数据流选择单元”将数据写入到“数据缓存模块1”的同时将“数据缓冲模块2”的数据读出。如此反复循环地操作,即为乒乓操作。

        乒乓操作的最大特点是通过“输入数据流选择单元”和“输出数据选择单元”按节拍、相互配合的切换,将经过缓冲的数据流没有停顿地送到“数据流运算处理模块”进行运算与处理。把乒乓操作模块当做一个整体,站在这个模块的两端看数据,输入数据流和输出数据流都是连续不断的,没有任何停顿的,因此非常适合对数据流进行流水线 式处理。所以乒乓操作常常应用于流水线式算法,完成数据的无缝缓冲与处理。

乒乓操作的第二个特点是可以节约缓存空间,使用双存储单元比单存储单元更节省存储空间,这是很明显的。同时在某些数据处理时,必须要数据达到一定个数才能进行运算,故还可以达到数据缓存的目的。

乒乓操作还可以实现低速模块处理高速数据,这种处理方式可以实现数据的串并转换,就是数据位宽之间的转换,是面积与速度互换原则的体现。

        2、程序设计

        利用状态机,划分四个状态,空闲、写R1、读R1写R2、写R1读R2,从而实现乒乓操作,具体状态转化如下图

IDLE:初始状态,在不工作或复位时就让状态机置为初始状态。

WRAM1:写RAM1状态。该状态我们开始往RAM1中写入数据,此时由于RAM2中并没有写入数据,所以我们不用对RAM2进行读取。

WRAM2_RRAM1:写RAM2读RAM1状态,当第一包数据写入完毕之后,马上跳到该状态,将第二包数据写入到RAM2中的同时读出RAM1中的写入的第一包数据。

WRAM1_RRAM2:写RAM1读RAM2状态。在该状态下我们开始向RAM1中写入第三包数据,此时第三包数据会把第一包数据覆盖,而我们的第一包数据已经读取出来了,并不会使数据丢失。在往RAM1中写入第三包数据的同时,我们读出RAM2中的第二包数据,当读写完成之后,跳回WRAM2_RRAM1状态开始 下一包的数据写入以及读取,如此循环我们就能无缝地将连续的输入数据读取出来了。

                (1)RAM IP核配置

                (2)驱动代码

        读写数据内部,注释部分可单独使用

  1. `timescale 1ns / 1ps
  2. //
  3. // Company:
  4. // Engineer:
  5. //
  6. // Create Date: 2024/02/20 11:12:22
  7. // Design Name:
  8. // Module Name: rw_ramx2
  9. // Project Name:
  10. // Target Devices:
  11. // Tool Versions:
  12. // Description:
  13. //
  14. // Dependencies:
  15. //
  16. // Revision:
  17. // Revision 0.01 - File Created
  18. // Additional Comments:
  19. //
  20. //
  21. module rw_ramx2(
  22. input clk ,
  23. input rstn ,
  24. //R1
  25. output ena ,
  26. output wea ,
  27. output reg [5:0] addra ,
  28. output reg [7:0] dina ,
  29. output reg [7:0] douta ,
  30. //R2
  31. output enb ,
  32. output web ,
  33. output reg [5:0] addrb ,
  34. output reg [7:0] dinb ,
  35. output reg [7:0] doutb ,
  36. output reg [7:0] ram_dout
  37. );
  38. //读写状态计数器
  39. reg [5:0] ram_cnt;
  40. //
  41. reg [5:0] ram_addr;
  42. //
  43. reg [7:0] ram_wr_data;
  44. reg [3:0] state;
  45. //状态
  46. parameter IDLE = 4'b0001; //空闲
  47. parameter WRAM1 = 4'b0010; //写R1
  48. parameter WRAM2_RRAM1 = 4'b0100; //读R1写R2
  49. parameter WRAM1_RRAM2 = 4'b1000; //写R1读R2
  50. //R1读写切换
  51. assign wea = ((ram_cnt <= 6'd31)&&(ena == 1'b1)) ? 1 : 0;
  52. //R2读写切换
  53. assign web = ~wea;
  54. //RAM使能信号
  55. assign ena = rstn;
  56. assign enb = rstn;
  57. //读写状态计数器
  58. always @(posedge clk or negedge rstn) begin
  59. if (!rstn) begin
  60. ram_cnt <= 6'd0;
  61. end
  62. else if(ram_cnt < 6'd63)begin
  63. ram_cnt <= ram_cnt + 1'b1;
  64. end
  65. else begin
  66. ram_cnt <= 6'd0;
  67. end
  68. end
  69. //读写地址计数器
  70. always @(posedge clk or negedge rstn) begin
  71. if (!rstn) begin
  72. ram_addr <= 6'd0;
  73. end
  74. else if(ram_addr < 6'd31)begin
  75. ram_addr <= ram_addr + 1'b1;
  76. end
  77. else begin
  78. ram_addr <= 6'd0;
  79. end
  80. end
  81. //读写数据计数器
  82. always @(posedge clk or negedge rstn) begin
  83. if (!rstn) begin
  84. ram_wr_data <= 8'b0;
  85. end
  86. else if(ram_wr_data < 8'd63)begin
  87. ram_wr_data <= ram_wr_data + 1'b1;
  88. end
  89. else begin
  90. ram_wr_data <= 8'b0;
  91. end
  92. end
  93. //状态跳转
  94. always @(posedge clk or negedge rstn) begin
  95. if (!rstn) begin
  96. state <= IDLE;
  97. end
  98. else begin
  99. case(state)
  100. IDLE:begin //空闲
  101. if (wea == 1'b1) begin
  102. state <= WRAM1;
  103. end
  104. else begin
  105. state <= IDLE;
  106. end
  107. end
  108. WRAM1:begin //写R1
  109. if ((wea == 1'b0)&&(web == 1'b1)) begin
  110. state <= WRAM2_RRAM1;
  111. end
  112. else begin
  113. state <= WRAM1;
  114. end
  115. end
  116. WRAM2_RRAM1:begin //读R1,写R2
  117. if ((wea == 1'b1)&&(web == 1'b0))begin
  118. state <= WRAM1_RRAM2;
  119. end
  120. else begin
  121. state <= WRAM2_RRAM1;
  122. end
  123. end
  124. WRAM1_RRAM2:begin //写R1,读R2
  125. if ((wea == 1'b0)&&(web == 1'b1))begin
  126. state <= WRAM2_RRAM1;
  127. end
  128. else begin
  129. state <= WRAM1_RRAM2;
  130. end
  131. end
  132. default:begin
  133. state <= IDLE;
  134. end
  135. endcase
  136. end
  137. end
  138. //读写数据
  139. always @(posedge clk or negedge rstn) begin
  140. if (!rstn) begin
  141. // state <= IDLE;
  142. end
  143. else begin
  144. case (state)
  145. IDLE:begin
  146. addra <= 5'b0;
  147. dina <= 8'b0;
  148. addrb <= 5'b0;
  149. dinb <= 8'b0;
  150. end
  151. WRAM1:begin
  152. addra <= ram_addr;
  153. dina <= ram_wr_data;
  154. // if(addra < 5'd31) begin
  155. // addra <= addra + 1'b1;
  156. // end
  157. // else begin
  158. // addra <= 5'b0;
  159. // end
  160. // if(dina < 5'd31) begin
  161. // dina <= dina + 1'b1;
  162. // end
  163. // else begin
  164. // dina <= 5'b0;
  165. // end
  166. end
  167. WRAM2_RRAM1:begin
  168. addra <= ram_addr;
  169. addrb <= ram_addr;
  170. dinb <= ram_wr_data;
  171. // if(addrb < 5'd31) begin
  172. // addrb <= addrb + 1'b1;
  173. // end
  174. // else begin
  175. // addrb <= 5'b0;
  176. // end
  177. // if(dinb < 5'd31) begin
  178. // dinb <= dinb + 1'b1;
  179. // end
  180. // else begin
  181. // dinb <= 5'b0;
  182. // end
  183. // if(addra < 5'd31) begin
  184. // addra <= addra + 1'b1;
  185. // end
  186. // else begin
  187. // addra <= 5'b0;
  188. // end
  189. end
  190. WRAM1_RRAM2:begin
  191. addra <= ram_addr;
  192. addrb <= ram_addr;
  193. dina <= ram_wr_data;
  194. // if(addra < 5'd31) begin
  195. // addra <= addra + 1'b1;
  196. // end
  197. // else begin
  198. // addra <= 5'b0;
  199. // end
  200. // if(dina < 5'd31) begin
  201. // dina <= dina + 1'b1;
  202. // end
  203. // else begin
  204. // dina <= 5'b0;
  205. // end
  206. // if(addrb < 5'd31) begin
  207. // addrb <= addrb + 1'b1;
  208. // end
  209. // else begin
  210. // addrb <= 5'b0;
  211. // end
  212. end
  213. default:begin
  214. addra <= addra;
  215. dina <= dina;
  216. addrb <= addrb;
  217. dinb <= dinb;
  218. end
  219. endcase
  220. end
  221. end
  222. endmodule

                        (3)顶层例化

  1. `timescale 1ns / 1ps
  2. //
  3. // Company:
  4. // Engineer:
  5. //
  6. // Create Date: 2024/02/20 11:05:52
  7. // Design Name:
  8. // Module Name: ip_ramx2
  9. // Project Name:
  10. // Target Devices:
  11. // Tool Versions:
  12. // Description:
  13. //
  14. // Dependencies:
  15. //
  16. // Revision:
  17. // Revision 0.01 - File Created
  18. // Additional Comments:
  19. //
  20. //
  21. module ip_ramx2(
  22. input sys_clk,
  23. input sys_rst_n
  24. );
  25. wire ena ;
  26. wire wea ;
  27. wire [4:0] addra ;
  28. wire [7:0] dina ;
  29. wire [7:0] douta ;
  30. wire enb ;
  31. wire web ;
  32. wire [4:0] addrb ;
  33. wire [7:0] dinb ;
  34. wire [7:0] doutb ;
  35. wire [7:0] ram_dout;
  36. blk_mem_gen_0 u_blk_mem_gen_0 (
  37. //R1
  38. .clka(sys_clk), // input wire clka
  39. .rsta(!sys_rst_n), // input wire rsta
  40. .ena(ena), // input wire ena
  41. .wea(wea), // input wire [0 : 0] wea
  42. .addra(addra), // input wire [4 : 0] addra
  43. .dina(dina), // input wire [7 : 0] dina
  44. .douta(douta), // output wire [7 : 0] douta
  45. //R2
  46. .clkb(sys_clk), // input wire clkb
  47. .rstb(!sys_rst_n), // input wire rstb
  48. .enb(enb), // input wire enb
  49. .web(web), // input wire [0 : 0] web
  50. .addrb(addrb), // input wire [4 : 0] addrb
  51. .dinb(dinb), // input wire [7 : 0] dinb
  52. .doutb(doutb) // output wire [7 : 0] doutb
  53. // .rsta_busy(rsta_busy), // output wire rsta_busy
  54. // .rstb_busy(rstb_busy) // output wire rstb_busy
  55. );
  56. rw_ramx2 u_rw_ramx2(
  57. .clk (sys_clk),
  58. .rstn (sys_rst_n),
  59. .ena (ena),
  60. .wea (wea),
  61. .addra (addra),
  62. .dina (dina),
  63. .douta (douta),
  64. .enb (enb),
  65. .web (web),
  66. .addrb (addrb),
  67. .dinb (dinb),
  68. .doutb (doutb),
  69. .ram_dout (ram_dout)
  70. );
  71. ila_0 your_instance_name (
  72. .clk(sys_clk), // input wire clk
  73. .probe0(ena), // input wire [0:0] probe0
  74. .probe1(wea), // input wire [0:0] probe1
  75. .probe2(addra), // input wire [4:0] probe2
  76. .probe3(dina), // input wire [7:0] probe3
  77. .probe4(douta), // input wire [7:0] probe4
  78. .probe5(enb), // input wire [0:0] probe5
  79. .probe6(web), // input wire [0:0] probe6
  80. .probe7(addrb), // input wire [4:0] probe7
  81. .probe8(dinb), // input wire [7:0] probe8
  82. .probe9(doutb), // input wire [7:0] probe9
  83. .probe10(ram_dout) // input wire [7:0] probe10
  84. );
  85. endmodule

        引脚约束XDC

  1. create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
  2. set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS33} [get_ports sys_clk]
  3. set_property -dict {PACKAGE_PIN U2 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]

                        (4)综合下载验证

四、总结

        乒乓操作较为显著的特点是数据的无缝处理,只要大家理解了乒乓操作的处理技巧,那么当我们使用不同的存储单元作为缓冲模块时,操作起来也能得心应手。后续将更新FIFO的乒乓操作。。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小桥流水78/article/detail/801482
推荐阅读
相关标签
  

闽ICP备14008679号