FPGA设计的四种常用思想与技巧:乒乓操作、串并转换、流水线操作、数据接口同步化。
其中乒乓操作是一个常应用于数据流控制的处理技巧,典型的乒乓操作方法如图:
乒乓操作的处理流程为:输入数据流通过“输入数据选择单元”将数据流分配到两个数据缓冲区,数据缓冲模块可以为任何存储模块,比较常用的存储单元为双端口RAM(DPRAM)、单口RAM(SPRAM)、FIFO等。
在第一个缓冲周期,将输入的数据流缓存到“数据缓冲模块1”;
在第二个缓冲周期,通过“输入数据选择单元”的切换,将输入的数据流缓存到“数据缓存模块2”,同时将“数据缓存模块1”缓存的第一个缓冲周期的数据通过“输出数据流选择单元”的选择,送到“数据流运算处理模块”进行运算处理。
在第三个缓冲周期,通过“输入数据选择单元”的再次切换,将输入的数据流缓存到“数据缓冲模块1”,同时将“数据缓冲模块2”缓存的第二个缓冲周期的数据通过“输出数据选择单元”的选择,送到“数据流运算处理模块”进行运算处理。如此循环。
代码示范:
module ping_pong ( input sys_clk, input sys_rst_n, input [7:0] data_in, // 输入数据 output reg [7:0] data_out // 输出数据 ); // ------------------------------------------------------ // reg [7:0] buffer1; // 缓存1 reg [7:0] buffer2; // 缓存2 reg wr_flag; // 写标志,wr_flag=0,写buffer1,wr_flag=1,写buffer2 reg rd_flag; // 读标志,rd_flag=0,读buffer2,rd_flag=1,读buffer1 reg state; // 状态机,0:写1读2,1:写2读1,状态转移和输出分开编码 // ------------------------------------------------------ // // 状态转移 always @ (posedge sys_clk or negedge sys_rst_n) begin if(sys_rst_n == 1'b0) begin state <= 1'b0; end else begin case(state) 1'b0 : state <= 1'b1; // 写1读2->写2读1 1'b1 : state <= 1'b0; // 写2读1->写1读2 default : state <= 1'b0; endcase end end // ------------------------------------------------------ // // 状态输出 always @ (state) begin case(state) 1'b0: begin wr_flag = 1'b0; // 写1 rd_flag = 1'b0; // 读2 end 1'b1: begin wr_flag = 1'b1; // 写2 rd_flag = 1'b1; // 读1 end default: begin wr_flag = 1'b0; rd_flag = 1'b0; end endcase end // ------------------------------------------------------ // // 写buffer数据 always @ (posedge sys_clk or negedge sys_rst_n) begin if(sys_rst_n == 1'b0) begin buffer1 <= 8'b0; buffer2 <= 8'b0; end else begin case(wr_flag) 1'b0 : buffer1 <= data_in; // wr_flag = 0,写buffer1 1'b1 : buffer2 <= data_in; // wr_flag = 1,写buffer2 default: begin buffer1 <= 8'b0; buffer2 <= 8'b0; end endcase end end // ------------------------------------------------------ // // 读buffer数据 always @ (posedge sys_clk or negedge sys_rst_n) begin if(sys_rst_n == 1'b0) begin data_out <= 8'b0; end else begin case(rd_flag) 1'b0 : data_out <= buffer2; // rd_flag=0,读buffer2 1'b1 : data_out <= buffer1; // rd_flag=1,读buffer1 default : data_out <= 8'b0; endcase end end // ------------------------------------------------------ // endmodule
`timescale 1ns/1ns module ping_pong_tb(); // ------------------------------------------------------ // // Inputs reg sys_clk; reg sys_rst_n; reg [7:0] data_in; // Outputs wire [7:0] data_out; // ------------------------------------------------------ // ping_pong u_ping_pong ( .sys_clk(sys_clk), .sys_rst_n(sys_rst_n), .data_in(data_in), // ???? .data_out(data_out) // ???? ); // ------------------------------------------------------ // initial begin sys_clk = 0; sys_rst_n = 0; data_in = 0; #100; sys_rst_n = 1; end // ------------------------------------------------------ // always #10 sys_clk = ~sys_clk; // ------------------------------------------------------ // always @ (posedge sys_clk or negedge sys_rst_n) begin if(sys_rst_n == 1'b0) begin data_in <= 8'b0; end else begin data_in <= data_in + 1'b1; end end // ------------------------------------------------------ // endmodule
代码结果:
结果分析:从仿真波形中可以看出按照0、1、2、3......递增的方式输入数据,两个缓存区交替存储数据,最后依次输出数据,不过输出数据会有两个时钟的延迟。
乒乓操作的最大特点是通过“输入数据选择单元”和“输出数据选择单元”按节拍、相互配合的切换,将经过缓冲的数据流没有停顿地送到“数据流运算处理模块”进行运算处理。
把乒乓操作模块当作一个整体,站在这个模块的两端看数据,输入数据流和输出数据流都是连续不断的,没有任何停顿,因此非常适合对数据流进行流水线式处理。所以乒乓操作常常应用于流水线式算法,完成数据的无缝缓冲与处理。
乒乓操作的第二个优点是可以节约缓冲区空间。比如在WCDMA基带应用中,1个帧是由15个时隙组成的,有时需要将1整帧的数据延时一个时隙后处理,比较直接的办法是将这帧数据缓存起来,然后延时1个时隙进行处理。这时缓冲区的长度是1整帧数据长,假设数据速率是3.84Mbps,1帧长10ms,则此时需要缓冲区长度是38400位。如果采用乒乓操作,只需定义两个能缓冲1个时隙数据的RAM(单口RAM即可)。当向一块RAM写数据的时候,从另一块RAM读数据,然后送到处理单元处理,此时每块RAM的容量仅需2560位即可,2块RAM加起来也只有5120位的容量。
另外, 巧妙运用乒乓操作还可以达到用低速模块处理高速数据流的效果。 如图2所示, 数据缓冲模块采用了双口 RAM, 并在 DPRAM 后引入了一级数据预处理模块, 这个数据预处理可以根据需要的各种数据运算, 比如在WCDMA 设计中, 对输入数据流的解扩、 解扰、去旋转等。 假设端口 A 的输入数据流的速率为 100Mbps, 乒乓操作的缓冲周期是 10ms。 以下分析各个节点端口的数据速率。
A 端口处输入数据流速率为 100Mbps, 在第1 个缓冲周期10ms 内, 通过“ 输入数据选择单元” , 从B1 到达DPRAM1。B1 的数据速率也是100Mbps,DPRAM1 要在10ms 内写入1Mb 数据。同理, 在第2 个 10ms, 数据流被切换到DPRAM2, 端口 B2 的数据速率也是 100Mbps, DPRAM2在第 2 个 10ms 被写入 1Mb 数据。 在第 3 个 10ms, 数据流又切换到 DPRAM1, DPRAM1 被写入1Mb数据。仔细分析就会发现到第 3 个缓冲周期时,留给 DPRAM1 读取数据并送到“ 数据预处理模块 1”的时间一共是 20ms。 有的工程师困惑于 DPRAM1 的读数时间为什么是 20ms, 这个时间是这样得来的: 首先, 在在第 2 个缓冲周期向DPRAM2 写数据的 10ms 内, DPRAM1 可以进行读操作;
另外, 在第 1 个缓冲周期的第 5ms起(绝对时间为5ms 时刻),DPRAM1 就可以一边向500K 以后的地址写数据, 一边从地址0 读数, 到达10ms 时,DPRAM1 刚好写完了1Mb 数据, 并且读了500K 数据, 这个缓冲时间内DPRAM1 读了5ms; 在第3 个缓冲周期的第5ms 起(绝对时间为35ms 时刻), 同理可以一边向500K 以后的地址写数据一边从地址0 读数, 又读取了5 个ms, 所以截止DPRAM1 第一个周期存入的数据被完全覆盖以前,DPRAM1 最多可以读取20ms时间, 而所需读取的数据为1Mb, 所以端口C1 的数据速率为:1Mb/20ms=50Mbps。 因此, “ 数据预处理模块1” 的最低数据吞吐能力也仅仅要求为50Mbps。 同理, “ 数据预处理模块2”的最低数据吞吐能力也仅仅要求为50Mbps。 换言之, 通过乒乓操作, “ 数据预处理模块”的时序压力减轻了, 所要求的数据处理速率仅仅为输入数据速率的1/2。
通过乒乓操作实现低速模块处理高速数据的实质是:通过DPRAM 这种缓存单元实现了数据流的串并转换, 并行用“ 数据预处理模块1” 和“ 数据预处理模块2” 处理分流的数据, 是面积与速度互换原则的体现!