• 每日一摘:乒乓操作


    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” 处理分流的数据, 是面积与速度互换原则的体现!

  • 相关阅读:
    我拒绝接受的几个最佳编程实践方法
    女人千万别写代码
    Visual Studio原生开发的10个调试技巧(二)
    20个很有用的PHP类库
    8个开发必备的PHP功能
    青少年如何使用 Python 开始游戏开发
    rmdir 删除空目录
    rm 删除文件或目录
    mv 移动或重命名文件
    cp 复制文件或目录
  • 原文地址:https://www.cnblogs.com/FPGAer/p/14129424.html
Copyright © 2020-2023  润新知