• FPGA实现“乒乓操作”


    一、“乒乓操作”概述

    1、结构       

            “乒乓操作”是一种常用于数据流控制的处理技巧,可以实现无缝高速数据流缓存。首先“乒乓操作”这个名字本身就很吸引人,其结构一般是由数据选择器和数据缓冲器构成的,数据缓冲模块可以为任何存储模块,比较常用的存储单元为双口 RAM(DPRAM) 、单口 RAM(SPRAM) 、FIFO等。乒乓ram结构:这种结构是将输入数据流通过输入数据选择单元等时地将数据流分配到两个数据缓冲区。通过两个数据缓冲区的读和写的切换,来实现数据的流水式传输。

    2、原理

            乒乓操作原理:就是打乒乓球一样,一个球(数据流),两个拍子(缓存),两个拍子相互击球(轮流读写数据,写1读2,写2读1)这样就可以做到球不停一直移动(数据流不会停,数据无丢失)。 

            其实就是把数据流轮流加载进两个数据缓冲器中,注意这里的数据流始终是要从输入端移动到输出端,只是不同时间选取的路径不同,而不要理解成“乒乓操作”是数据在两端来回传输。换句话说,我们这里的“拍子”是两个缓存器,而不是两端的数据选择器。

    3、处理流程

    (1)第一个缓冲周期:输入数据流缓存入数据缓冲器ram A

    (2)第二个缓冲周期:通过输入数据选择单元的切换,输入数据流缓存入数据缓冲器ram B,同时将ram A缓存的第1个周期数据传给输出数据选择单元

    (3)第三个缓冲周期:通过输入数据选择单元的切换,输入数据流缓存入数据缓冲器ram A,同时将ram B缓存的第2个周期数据传给输出数据选择单元

    …………

    4、特点

            乒乓操作的最大特点是通过“输入数据选择单元”和“输出数据选择单元”按节拍、相互配合的切换,将经过缓冲的数据流没有停顿地送到“数据流运算处理模块”进行运算与处理。通过乒乓操作实现低速模块处理高速数据的实质是:通过缓存单元实现了数据流的串并转换,并行用 “ 数据缓冲器ram A” 和 “ 数据缓冲器ram B” 处理分流的数据,是面积与速度互换原则的体现。

    5、使用案例

            在低速处理高速数据流时,可以使用乒乓操作,举个栗子,10M的数据流,用乒乓操作,分流成两个FIFO,一个FIFO的吞吐速度只有原来的一半5M,就可以满足低速的处理方法,处理高速的数据,处理后在用合并成一个10M的数据流,数据就被不丢失且高速的处理过了,top层看起就是10M的处理速度了。

    二、“乒乓操作”的verilog实现

            我们在这里就尝试实现上文提到的“乒乓操作”的例子,10M的数据流,用乒乓操作,分流成两个FIFO,一个FIFO的吞吐速度只有原来的一半5M,就可以满足低速的处理方法,处理高速的数据,处理后在用合并成一个10M的数据流。

    1、设计思路

            在进行代码编写之前要做的就是明确接口信号和模块划分,在之前的内容中我们已经明确了“乒乓操作”的结构及具体功能,那首先就可以进行模块的划分:

    (1)ramA,ramB:这个独立的两个数据缓冲器,这里考虑使用xilinx提供的IP核实现单口RAM。

    (2)ram_ctrl:用作RAM的控制,对应结构中的输入数据选择器和输出数据选择器结构,这里选择把这两个部分写在同一个模块中了。

    (3)data_gen:数据生成模块,由于我们这只是一个“乒乓操作”的测试代码,所以需要自己编写数据生成模块,实现数据的产生。

    (4)clk_gen:时钟生成模块,生成5M和10M时钟,也是用IP核的方式实现

    2、模块设计

    2.1 ramA,ramB模块设计

            通过BRAM资源,使用的是简单双口RAM,读写位宽设置为8,深度设置为32.

    2.2 clk_gen模块设计

            设计系统输入时钟是100MHZ,输出时钟有2个分别为clk_5M和clk_10M。

    2.3 data_gen模块设计

            数据生成模块的设计,这里我们可以设计数据从0开始在10MHZ时钟的驱动下,逐渐加1。

    1. //-----------------------------<数据生成模块>-----------------------------
    2. module data_gen(
    3. input clk,
    4. input rst,
    5. output data_en,
    6. output reg [7:0] data_gen
    7. );
    8. always@(posedge clk or posedge rst)begin
    9. if(rst)
    10. data_gen <= 8'b0;
    11. else if (data_gen == 8'd31)
    12. data_gen <= 8'b0;
    13. else
    14. data_gen <= data_gen + 1'b1;
    15. end
    16. assign data_en = (rst == 1'b1) ? 1'b0 : 1'b1;
    17. endmodule

    2.4 ram_ctrl模块设计

    1. module ram_ctrl(
    2. input clk_5M,
    3. input clk_10M,
    4. input rst,
    5. input [7:0] data_gen, //数据生成模块生成的数据
    6. input data_en, //数据使能信号,表示数据是否有效
    7. input [7:0]ram1_data, //ram1中读出的数据
    8. input [7:0]ram2_data, //ram2中读出的数据
    9. output ram1_ena,
    10. output ram1_wea,
    11. output ram1_enb,
    12. output reg [4:0] ram1_addra,
    13. output reg [4:0] ram1_addrb,
    14. output [7:0] ram1_din,
    15. output ram2_ena,
    16. output ram2_wea,
    17. output ram2_enb,
    18. output reg [4:0] ram2_addra,
    19. output reg [4:0] ram2_addrb,
    20. output [7:0] ram2_din,
    21. output reg [7:0] dout
    22. );
    23. //----------------------------<状态定义>---------------------------------
    24. parameter IDLE = 4'b0001; //初始状态
    25. parameter WRAM1 = 4'b0010; //写RAM1
    26. parameter R1_W2 = 4'b0100; //写RAM2,读RAM1
    27. parameter W1_R2 = 4'b1000; //写RAM1,读RAM2
    28. reg [3:0] state,next_state; //状态寄存器
    29. always@(posedge clk_10M or posedge rst)begin
    30. if(rst)
    31. state <= IDLE;
    32. else
    33. state <= next_state;
    34. end
    35. always@(*)begin
    36. case(state)
    37. IDLE : next_state = data_en ? WRAM1 : IDLE ;
    38. WRAM1 : next_state = (ram1_addra == 5'd31) ? R1_W2 : WRAM1 ;
    39. R1_W2 : next_state = (ram2_addra == 5'd31) ? W1_R2 : R1_W2 ;
    40. W1_R2 : next_state = (ram1_addra == 5'd31) ? R1_W2 : W1_R2 ;
    41. default : next_state = IDLE;
    42. endcase
    43. end
    44. assign ram1_ena = data_en;
    45. assign ram1_enb = data_en;
    46. assign ram2_ena = data_en;
    47. assign ram2_enb = data_en;
    48. assign ram1_wea = (state == WRAM1 || state == W1_R2) ? 1'b1 : 1'b0 ;
    49. assign ram2_wea = (state == R1_W2) ? 1'b1 : 1'b0 ;
    50. always@(posedge clk_10M or posedge rst)begin
    51. if(rst) begin
    52. ram1_addra <= 0;
    53. ram2_addra <= 0;
    54. end
    55. else if (ram1_addra == 'd31 || ram1_addra == 'd31)begin
    56. ram1_addra <= 0;
    57. ram2_addra <= 0;
    58. end
    59. else begin
    60. case(state)
    61. WRAM1 : ram1_addra <= ram1_addra + 1'b1;
    62. R1_W2 : ram2_addra <= ram2_addra + 1'b1;
    63. W1_R2 : ram1_addra <= ram1_addra + 1'b1;
    64. default : begin
    65. ram1_addra <= ram1_addra;
    66. ram2_addra <= ram2_addra;
    67. end
    68. endcase
    69. end
    70. end
    71. always@(posedge clk_10M or posedge rst)begin
    72. if(rst) begin
    73. ram1_addrb <= 0;
    74. ram2_addrb <= 0;
    75. end
    76. else if (ram1_addrb == 'd31 || ram2_addrb == 'd31)begin
    77. ram1_addrb <= 0;
    78. ram2_addrb <= 0;
    79. end
    80. else begin
    81. case(state)
    82. R1_W2 : ram1_addrb <= ram1_addrb + 1'b1;
    83. W1_R2 : ram2_addrb <= ram2_addrb + 1'b1;
    84. default : begin
    85. ram1_addrb <= ram1_addrb;
    86. ram2_addrb <= ram2_addrb;
    87. end
    88. endcase
    89. end
    90. end
    91. assign ram1_din = (state == WRAM1 || state == W1_R2) ? data_gen : 8'b0;
    92. assign ram2_din = (state == R1_W2) ? data_gen : 8'b0;
    93. //打一拍来获得正确的输出
    94. reg [3:0] state_reg;
    95. always@(posedge clk_10M)begin
    96. state_reg <= state;
    97. end
    98. always@(*)begin
    99. case(state_reg)
    100. R1_W2 : dout = ram1_data;
    101. W1_R2 : dout = ram2_data;
    102. default : dout = 8'b0;
    103. endcase
    104. end
    105. endmodule

    2.5 顶层模块设计

    1. module pingpang(
    2. input sys_clk,
    3. input rst,
    4. output [7:0] dout
    5. );
    6. wire clk_5M,clk_10M;
    7. wire data_en;
    8. wire [7:0] data_gen;
    9. wire ram1_ena,ram1_enb,ram2_ena,ram2_enb;
    10. wire ram1_wea,ram2_wea;
    11. wire [4:0] ram1_addra,ram2_addra;
    12. wire [4:0] ram1_addrb,ram2_addrb;
    13. wire [7:0] ram1_data,ram2_data;
    14. wire [7:0] ram1_din,ram2_din;
    15. clk_div clk_div_u1(
    16. .clk_5M ( clk_5M ),
    17. .clk_10M ( clk_10M ),
    18. .reset ( rst ),
    19. .clk_in1 ( sys_clk )
    20. );
    21. data_gen data_gen_u1(
    22. .clk ( clk_10M ),
    23. .rst ( rst ),
    24. .data_en ( data_en ),
    25. .data_gen ( data_gen )
    26. );
    27. ram ram1(
    28. .clka ( clk_10M ), // input wire clka
    29. .ena ( ram1_ena ), // input wire ena
    30. .wea ( ram1_wea ), // input wire [0 : 0] wea
    31. .addra ( ram1_addra ), // input wire [4 : 0] addra
    32. .dina ( ram1_din ), // input wire [7 : 0] dina
    33. .clkb ( clk_10M ), // input wire clkb
    34. .enb ( ram1_enb ), // input wire enb
    35. .addrb ( ram1_addrb ), // input wire [4 : 0] addrb
    36. .doutb ( ram1_data ) // output wire [7 : 0] doutb
    37. );
    38. ram ram2(
    39. .clka ( clk_10M ), // input wire clka
    40. .ena ( ram2_ena ), // input wire ena
    41. .wea ( ram2_wea ), // input wire [0 : 0] wea
    42. .addra ( ram2_addra ), // input wire [4 : 0] addra
    43. .dina ( ram2_din ), // input wire [7 : 0] dina
    44. .clkb ( clk_10M ), // input wire clkb
    45. .enb ( ram2_enb ), // input wire enb
    46. .addrb ( ram2_addrb ), // input wire [4 : 0] addrb
    47. .doutb ( ram2_data ) // output wire [7 : 0] doutb
    48. );
    49. ram_ctrl ram_ctrl_u1(
    50. .clk_5M ( clk_5M ),
    51. .clk_10M ( clk_10M ),
    52. .rst ( rst ),
    53. .data_gen ( data_gen ),
    54. .data_en ( data_en ),
    55. .ram1_data ( ram1_data ),
    56. .ram2_data ( ram2_data ),
    57. .ram1_ena ( ram1_ena ),
    58. .ram1_wea ( ram1_wea ),
    59. .ram1_enb ( ram1_enb ),
    60. .ram1_addra ( ram1_addra ),
    61. .ram1_addrb ( ram1_addrb ),
    62. .ram1_din ( ram1_din ),
    63. .ram2_ena ( ram2_ena ),
    64. .ram2_wea ( ram2_wea ),
    65. .ram2_enb ( ram2_enb ),
    66. .ram2_addra ( ram2_addra ),
    67. .ram2_addrb ( ram2_addrb ),
    68. .ram2_din ( ram2_din ),
    69. .dout ( dout )
    70. );
    71. endmodule

    3、测试文件

    1. `timescale 1ns / 1ps
    2. module tb_pingpang();
    3. reg sys_clk;
    4. reg rst;
    5. wire [7:0] dout;
    6. always #5 sys_clk = ~sys_clk;
    7. initial begin
    8. sys_clk <= 0;
    9. rst <= 0;
    10. #15
    11. rst <= 1;
    12. #10
    13. rst <= 0;
    14. end
    15. pingpang pingpang_u1(
    16. .sys_clk ( sys_clk ),
    17. .rst ( rst ),
    18. .dout ( dout )
    19. );
    20. endmodule

    三、测试结果

    四、乒乓操作与FIFO的区别

            如上图的情况,我们使用了两个RAM作为数据缓冲器,我们把这里的RAM换成FIFO,假设FIFO的深度为512bit ,那我们是否可以用一个深度为1024bit 的FIFO来替代这样的功能?

            能也不能。

            若处于同步状态,不存在跨时钟域,2个512bit大小的FIFO进行乒乓操作,和使用1个1024bit大小FIFO是一样的。

            若处于异步状态,存在跨时钟域,对于使用两个FIFO来说实现的乒乓操作来说,就不存在同时需要判断空、满的情况,因为在同一个时刻,对于单个FIFO来说,它只会处在读/写一个状态,而不会出现读、写同时操作的情况。对于使用一个FIFO来说,就存在以上问题,既要将读指针同步到写操作侧判断满,又要将写指针同步到读操作侧判断空。所以在这种情况下,我们可以考虑使用乒乓操作来解决此类问题。

  • 相关阅读:
    LeetCode刷题笔记【28】:贪心算法专题-6(单调递增的数字、监控二叉树)
    力扣-消失的数字(两种方法)
    macOS下matplotlib如何显示中文字体?
    候选公示!高工智能汽车金球奖首批入围年度产品/方案亮相
    【OJ比赛日历】快周末了,不来一场比赛吗? #10.21-10.27 #11场
    了解方法重写
    Coursera耶鲁大学金融课程:Financial Markets 笔记Week 01
    个人上百度百科难吗,个人基本资料怎么样上百度百科词条
    Vue3 国际化i18n
    力扣 593. 有效的正方形
  • 原文地址:https://blog.csdn.net/apple_53311083/article/details/132384482