当前位置:   article > 正文

FPGA_学习_11_IP核_RAM_乒乓操作_fpga乒乓操作

fpga乒乓操作

本篇博客学习另一个IP核,RAM。 用RAM实现什么功能呢? 实现乒乓操作。 乒乓操作是什么呢?

参考:

FPGA中的乒乓操作思想_fpga中乒乓操作的原因_小林家的龙小年的博客-CSDN博客

何为乒乓操作_fanyuandrj的博客-CSDN博客

以下是本人理解:

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

例如:

数据位宽的转换,要将8位的数据转换为16位,按照传统方法,每两个时钟周期完成一次转换,输出数据的变化与时钟信号不是同步的。使用乒乓操作,数据写入数据缓冲模块的时候使用50M的时钟,读出时使用25M的时钟,每次读出16位,这样不仅实现了数据位宽的转换,还使得输出的数据可以被一个低速的时钟同步。

乒乓骚操作1:

假设输入数据的时钟50Mhz,100个8位的数据先写入 数据缓冲模块1, 然后再100个8位的数据写入 数据缓冲模块2,与此同时,输出模块读取 数据缓冲模块1的数据。 如果输出模块读取时钟也是50Mhz, 待100个8位的数据已存入 数据缓冲模块2时, 先前写入 数据缓冲模块1的100个8位的数据,已经被输出模块读取完了。

下面再次把100个8位的数据先写入 数据缓冲模块1,与此同时,输出模块读取 数据缓冲模块2的数据。 如果输出模块读取时钟也是50Mhz, 待100个8位的数据已存入 数据缓冲模块1时, 先前写入 数据缓冲模块2的100个8位的数据,也已经被输出模块读取完了。

如此循环,就可以,... 嗯,... , 我反正暂时不清楚这个操作有什么意义。

乒乓骚操作2:

假设输入数据的时钟50Mhz,100个8位的数据先写入 数据缓冲模块1, 然后再100个8位的数据写入 数据缓冲模块2,与此同时,输出模块读取 数据缓冲模块1的数据。 如果输出模块读取时钟也是25Mhz, 待100个8位的数据已存入 数据缓冲模块2时, 要想把先前写入 数据缓冲模块1的100个8位的数据全部取出,输出模块读取的位宽得1次读16位。

50Mhz 8位宽的写100的时间 =  25Mhz 16位宽的读50次的时间。

下面再次把100个8位的数据先写入 数据缓冲模块1,与此同时,输出模块读取 数据缓冲模块2的数据。 如果输出模块读取时钟是25Mhz读取位宽是16位, 待100个8位的数据已存入 数据缓冲模块1时, 先前写入 数据缓冲模块2的100个8位的数据,也已经被输出模块读取完了,50个16位。

如此循环,好像有那么点意思了,连续的 50M 8位宽数据,变成了连续的25M 16位宽的数据。 时钟变慢了,位宽变大了。

本文先尝试实现乒乓骚操作1,后续如果我功力深厚了再来尝试实现乒乓骚操作2

1 RAM IP核配置步骤

(Vivado 赛灵思)

截图warning!

Block Memory Generator是用FPPA内部专用存储资源给你生成RAM。

上面Distributed Memory Generator这个是用D触发器和查找表来帮你实现的RAM。

我取名IP_RAM

 本次实验位宽为8,深度为256

 

 OK后,后面的弹窗你就一顿OK,下一步就可以了。

我们是看Verilog的例化模板。 

2 时序图

乒乓操作是要例化个简单口RAM的IP核的,绿线上面例化的第一个简单口RAM→RAM1的相关时序,绿线下面是例化的第而个简单口RAM→RAM2的相关时序。

RAM_wea_d表示RAM_wea延迟(delay)一拍,可用于指示当前是RAM1输出有效还是RAM2的输出有效。

画时序图是费劲的活儿,但是画完自己的理解确实也会更进一分。

  1. {signal: [
  2. {name: 'clk', wave: 'p...........................................' },
  3. {},
  4. {name: 'rst_n', wave: '01..........................................' },
  5. {},
  6. {name: 'RAM1_wea', wave: '01....0....1....0....1....0....1....0....1..' },
  7. {},
  8. {name: 'RAM1_addra', wave: '2.22222.....22222.....22222.....22222.....22' , data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
  9. {name: 'RAM1_dina', wave: '2.22222.....22222.....22222.....22222.....22' , data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
  10. {},
  11. {name: 'RAM1_addrb', wave: '2......22222.....22222.....22222.....22222..' , data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
  12. {name: 'RAM1_doutb', wave: '2.......22222.....22222.....22222.....22222.' , data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
  13. {},
  14. {name: 'RAM2_wea', wave: '0.....1....0....1....0....1....0....1....0..' },
  15. {},
  16. {name: 'RAM2_addra', wave: '2......22222.....22222.....22222.....22222..' , data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
  17. {name: 'RAM2_dina', wave: '2......22222.....22222.....22222.....22222..' , data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
  18. {},
  19. {name: 'RAM2_addrb', wave: '2.22222.....22222.....22222.....22222.....22' , data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
  20. {name: 'RAM2_doutb', wave: '2..22222.....22222.....22222.....22222.....2' , data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
  21. {},
  22. {name: 'RAM1_wea_d', wave: '0.1....0....1....0....1....0....1....0....1.' },
  23. {}
  24. ]}

3 测试代码

本博客先暂时只实现第一种乒乓操作

  1. `timescale 1ns / 1ps
  2. module ram_pp(
  3. input wire clk ,
  4. input wire rst_n ,
  5. output reg [7:0] dout
  6. );
  7. //==================================================================
  8. // Parameter define
  9. //==================================================================
  10. parameter MAX = 256 - 1;
  11. //==================================================================
  12. // Internal Signals
  13. //==================================================================
  14. // 一个简单双口RAM的IP里面,a代表写,b代表读
  15. reg RAM1_wea ; // 写-使能
  16. reg [7:0] RAM1_addra ; // 写-地址
  17. wire [7:0] RAM1_dina ; // 写-数据
  18. reg [7:0] RAM1_addrb ; // 读-地址
  19. wire [7:0] RAM1_doutb ; // 读-数据
  20. reg RAM2_wea ;
  21. reg [7:0] RAM2_addra ;
  22. wire [7:0] RAM2_dina ;
  23. reg [7:0] RAM2_addrb ;
  24. wire [7:0] RAM2_doutb ;
  25. reg RAM1_wea_d ; // RAM1的写使能延迟一拍
  26. IP_RAM inst_RAM1 (
  27. .clka(clk), // input wire clka 写-时钟
  28. .wea(RAM1_wea), // input wire [0 : 0] wea 写-使能
  29. .addra(RAM1_addra), // input wire [7 : 0] addra 写-地址
  30. .dina(RAM1_dina), // input wire [7 : 0] dina 写-数据
  31. .clkb(clk), // input wire clkb 读-时钟
  32. .addrb(RAM1_addrb), // input wire [7 : 0] addrb 读-地址
  33. .doutb(RAM1_doutb) // output wire [7 : 0] doutb 读-数据
  34. ); // 谨记:不要同时读写同一地址的数据。
  35. IP_RAM inst_RAM2 (
  36. .clka(clk), // input wire clka 写-时钟
  37. .wea(RAM2_wea), // input wire [0 : 0] wea 写-使能
  38. .addra(RAM2_addra), // input wire [7 : 0] addra 写-地址
  39. .dina(RAM2_dina), // input wire [7 : 0] dina 写-数据
  40. .clkb(clk), // input wire clkb 读-时钟
  41. .addrb(RAM2_addrb), // input wire [7 : 0] addrb 读-地址
  42. .doutb(RAM2_doutb) // output wire [7 : 0] doutb 读-数据
  43. );
  44. // 在对RAM1做写操作的时候,与此同时对RAM2做读操作
  45. // 对RAM1做写操作完成,RAM2的做读操作也会完成,此时则对RAM1做读操作,对RAM2做写操作
  46. // 对RAM2做写操作完成,RAM1的做读操作也会完成,此时则对RAM2做读操作,对RAM1做写操作
  47. // 如此循环。
  48. // 上面的文字描述有点绕,但告诉了我们三个道理。
  49. // 1、一个RAM在写,则另一个RAM必然在读,所以两个RAM的写使能是完全相反的电平。
  50. // 2、当RAM2写完的时候,就到我RAM1写了,这时候RAM1写使能拉高。
  51. // 3、当RAM1写完的时候,RAM1进入读操作,这时候RAM1写使能拉低。
  52. //----------------------------- RAM1_wea -----------------------------
  53. always @(posedge clk or negedge rst_n) begin
  54. if (rst_n == 1'b0) begin
  55. RAM1_wea <= 1'b0;
  56. end
  57. // RAM2写完,到RAM1写,RAM1_wea拉高
  58. else if ( RAM2_addra == MAX && RAM1_wea == 1'b0) begin
  59. RAM1_wea <= 1'b1;
  60. end
  61. // RAM1写完,则RAM1变成读,RAM1_wea拉低
  62. else if ( RAM1_addra == MAX && RAM1_wea == 1'b1) begin
  63. RAM1_wea <= 1'b0;
  64. end
  65. else begin
  66. RAM1_wea <= RAM1_wea;
  67. end
  68. end
  69. //----------------------------- RAM2_wea -----------------------------
  70. always @(*) begin
  71. RAM2_wea <= ~RAM1_wea;
  72. end
  73. //----------------------------- RAM1_addra -----------------------------
  74. always @(posedge clk or negedge rst_n) begin
  75. if (rst_n == 1'b0) begin
  76. RAM1_addra <= 'd0;
  77. end
  78. else if (RAM1_wea == 1'b1) begin
  79. if (RAM1_addra == MAX) begin
  80. RAM1_addra <= 'd0;
  81. end
  82. else begin
  83. RAM1_addra <= RAM1_addra + 1'b1;
  84. end
  85. end
  86. else begin
  87. RAM1_addra <= 'd0;
  88. end
  89. end
  90. //----------------------------- RAM1_addrb -----------------------------
  91. always @(posedge clk or negedge rst_n) begin
  92. if (rst_n == 1'b0) begin
  93. RAM1_addrb <= 'd0;
  94. end
  95. else if (RAM1_wea == 1'b0) begin
  96. if (RAM1_addrb == MAX) begin
  97. RAM1_addrb <= 'd0;
  98. end
  99. else begin
  100. RAM1_addrb <= RAM1_addrb + 1'b1;
  101. end
  102. end
  103. else begin
  104. RAM1_addrb <= 'd0;
  105. end
  106. end
  107. //----------------------------- RAM2_addra -----------------------------
  108. always @(posedge clk or negedge rst_n) begin
  109. if (rst_n == 1'b0) begin
  110. RAM2_addra <= 'd0;
  111. end
  112. else if (RAM2_wea == 1'b1) begin
  113. if (RAM2_addra == MAX) begin
  114. RAM2_addra <= 'd0;
  115. end
  116. else begin
  117. RAM2_addra <= RAM2_addra + 1'b1;
  118. end
  119. end
  120. else begin
  121. RAM2_addra <= 'd0;
  122. end
  123. end
  124. //----------------------------- RAM2_addrb -----------------------------
  125. always @(posedge clk or negedge rst_n) begin
  126. if (rst_n == 1'b0) begin
  127. RAM2_addrb <= 'd0;
  128. end
  129. else if (RAM2_wea == 1'b0) begin
  130. if (RAM2_addrb == MAX) begin
  131. RAM2_addrb <= 'd0;
  132. end
  133. else begin
  134. RAM2_addrb <= RAM2_addrb + 1'b1;
  135. end
  136. end
  137. else begin
  138. RAM2_addrb <= 'd0;
  139. end
  140. end
  141. //----------------------------- RAM1_dina -----------------------------
  142. assign RAM1_dina = RAM1_addra;
  143. //----------------------------- RAM2_dina -----------------------------
  144. assign RAM2_dina = RAM2_addra;
  145. //----------------------------- RAM1_wea_d -----------------------------
  146. // 由于RAM的输出信号要延迟一拍
  147. // 因此利用RAM1的写使能信号RAM1_wea,延迟一拍作为整个模块输出dout的读有效信号。
  148. always @(posedge clk or negedge rst_n) begin
  149. if (rst_n == 1'b0) begin
  150. RAM1_wea_d <= 1'b0;
  151. end
  152. else begin
  153. RAM1_wea_d <= RAM1_wea;
  154. end
  155. end
  156. //----------------------------- dout -----------------------------
  157. always @(*) begin
  158. // RAM1_wea_d为高,则RAM1正在进行写操作,这时候不能读RAM1,而RAM2这时候正在输出,正好读RAM2的数据。
  159. if (RAM1_wea_d == 1'b1) begin
  160. dout <= RAM2_doutb;
  161. end
  162. // RAM1_wea_d为低,则RAM1正在进行读操作,这时候正好RAM1在输出,正好读RAM1
  163. else begin
  164. dout <= RAM1_doutb;
  165. end
  166. end
  167. endmodule

 这个代码里面的注释是目前为止我写的最多的啦,希望看了会帮助理解。

4 仿真代码

  1. `timescale 1ns/1ps
  2. module tb_ram_pp (); /* this is automatically generated */
  3. parameter MAX = 256 - 1;
  4. reg clk;
  5. reg rst_n;
  6. wire [7:0] dout;
  7. ram_pp inst_ram_pp (.clk(clk), .rst_n(rst_n), .dout(dout));
  8. initial begin
  9. clk = 1;
  10. forever #(10) clk = ~clk;
  11. end
  12. initial begin
  13. rst_n <= 0;
  14. #200
  15. rst_n <= 1;
  16. end
  17. endmodule

这个仿真代码比较简单,因为咱们写的ram_pp模块本身的输入输出接口很少,所以不怎么操心,直接调用ram_pp模块就行了。

By the way,我用的Modelsim仿真,本文不讨论怎么搞Modelsim仿真。

5 仿真结果

刚仿真出来的时候我以为哪里有问题,因为发现第一个周期是没有输出的。 后来想明白了原因,因为更早之前RAM没写入任何数据,所以它第一个周期就应该没有输出。 另外,建议仿真的时长弄长一点(我用的30us),从第二个周期开始看。

仿真结果来看,实验和预期的目标时序是相符的,实验是成功的。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
  

闽ICP备14008679号