FPGA设计的四种常用思想与技巧之乒乓操作

FPGA设计的四种常用思想与技巧之乒乓操作

乒乓操作结构框图

“ 乒乓操作” 是一个常常应用于数据流控制的处理技巧, 典型的乒乓操作方法如图 1 所示。

乒乓操作的处理流程为:输入数据流通过“ 输入数据选择单元” 将数据流等时分配到两个数据缓冲区, 数据缓冲模块可以为任何存储模块, 比较常用的存储单元为双口RAM(DPRAM)、单口RAM(SPRAM)、FIFO等。

在第 1个缓冲周期,将输入的数据流缓存到“ 数据缓冲模块1” ;

在第2 个缓冲周期, 通过“ 输入数据选择单元” 的切换, 将输入的数据流缓存到“ 数据缓冲模块2” , 同时将“ 数据缓冲模块1” 缓存的第1 个周期数据通过“ 输出数据选择单元” 的选择, 送到“ 数据流运算处理模块” 进行运算处理;

在第3 个缓冲周期通过“ 输入数据选择单元” 的再次切换,将输入的数据流缓存到“ 数据缓冲模块1” ,同时将“ 数据缓冲模块2”缓存的第2 个周期的数据通过“ 输出数据选择单元” 切换,送到“ 数据流运算处理模块” 进行运算处理。 如此循环。

乒乓操作的最大特点

通过输入数据选择单元和输出数据选择单元按节拍相互配合的切换,将经过缓冲的数据流没有停顿地送到数据流运算处理模块进行运算与处理。

把乒乓操作模块当做一个整体, 站在这个模块的两端看数据, 输入数据流和输出数据流都是连续不断的、没有任何停顿, 因此非常适合对数据流进行流水线式处理。 所以乒乓操作常常应用于流水线式算法, 完成数据的无缝缓冲与处理。

乒乓操作的第二个优点是可以节约缓冲区空间
节省缓存的例子:

比如在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。 以下分析各个节点端口的数据速率。

数据流动/切换过程: 端口处输入数据流速率为 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。

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

代码:

`timescale 1ns / 1ps
module top_d_buf(
    input   wire            sclk,       //100M
    input   wire            rst_n,
    input   wire            data_v,     //512x8 len
    input   wire    [7:0]   data_in,

    input   wire            r_clk,      //75MHZ
    output  wire            data_ov,    //256x16
    output  wire    [15:0]  data_out
    );

parameter   READ_END = 256-1;

reg             w_buf_sel;
reg             data_v_dly,data_v_dly1;
reg             w_ram_en1,w_ram_en2;
reg     [9:0]   w_ram_addr1,w_ram_addr2;
wire    [7:0]   w_ram_data1,w_ram_data2;
reg             r_start_flag;
//
reg     [2:0]   r_s_buf;
reg             r_flag;             //读数据过程
reg     [7:0]   r_cnt;              //r_cnt:读地址
reg             r_flag_dly;
reg             r_buf_sel;
wire    [8:0]   r_addr1,r_addr2;
wire    [15:0]  dout1,dout2;

//data valid信号缓冲输入,寄存器打两拍
always @(posedge sclk)begin
    data_v_dly      <=      data_v;//input
    data_v_dly1     <=      data_v_dly;
end

//双buffer切换逻辑,多路选择器。
//捕获data_valid的上升沿,一帧数据写入第一个ram,下一帧数据写入第二个ram
//————|
//    |_____
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        w_buf_sel <= 1'b0;
    else if(data_v_dly == 1'b1 && data_v == 1'b0)       //对data_v进行边沿检测
        w_buf_sel <= ~w_buf_sel ;

//数据写入ram1
//ram 1的写使能,MUX = low & data_v = 1
always @*
    w_ram_en1 <= (~w_buf_sel)&data_v;

//数据写入ram2
//ram 2的写使能,MUX = high & data_v = 1
always @*
    w_ram_en2 <= w_buf_sel & data_v;

//ram1使能后,ram1的写地址就加1
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        w_ram_addr1 <='d0;
    else if(w_ram_en1 == 1'b1)
        w_ram_addr1 <= w_ram_addr1 + 1'b1;
    else
        w_ram_addr1 <= 'd0;
//ram2使能后,ram2的写地址就加1
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        w_ram_addr2 <='d0;
    else if(w_ram_en2 == 1'b1)
        w_ram_addr2 <= w_ram_addr2 + 1'b1;
    else
        w_ram_addr2 <= 'd0;

//data_v失能后,使能读信号
//在data_v的下降沿进行读取操作
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        r_start_flag <= 1'b0;
    else if(data_v == 1'b0 && data_v_dly1 == 1'b1)
        r_start_flag <= 1'b1;
    else
        r_start_flag <= 1'b0;

ram_w8x1024_r16x512 ram_1
(
                    .clka   (sclk),                     // input clka
                    .wea    (w_ram_en1),                // input [0 : 0] wea
                    .addra  (w_ram_addr1),              // input [9 : 0] addra
                    .dina   (data_in),                  // input [7 : 0] dina
                    .clkb   (r_clk),                    // input clkb
                    .addrb  (r_addr1),                  // input [8 : 0] addrb
                    .doutb  (dout1)                     // output [15 : 0] doutb
);

ram_w8x1024_r16x512 ram_2 (
                    .clka   (sclk),                     // input clka
                    .wea    (w_ram_en2),                // input [0 : 0] wea
                    .addra  (w_ram_addr2),              // input [9 : 0] addra
                    .dina   (data_in),                  // input [7 : 0] dina
                    .clkb   (r_clk),                    // input clkb
                    .addrb  (r_addr2),                  // input [8 : 0] addrb
                    .doutb  (dout2)                     // output [15 : 0] doutb
);


//r_clk
//移位寄存器r_s_buf,对读取起始信号进行缓冲,打两拍
always @(posedge r_clk)
    r_s_buf <= {r_s_buf[1:0],r_start_flag};

//边沿检测
always @(posedge r_clk)
    if(r_s_buf[2:1] == 2'b01)           //开始读数据
        r_flag <= 1'b1;
    else if(r_cnt == READ_END)          //READ_END:ram中的数据全部读出
        r_flag <= 1'b0;

always @(posedge r_clk or negedge rst_n)
    if(rst_n == 1'b0)
        r_cnt <='d0;
    else if(r_flag == 1'b1)
        r_cnt <= r_cnt + 1'b1;//r_cnt:读地址
    else
        r_cnt <= 'd0;

always @(posedge r_clk)
    r_flag_dly <= r_flag;

always @(posedge r_clk or negedge rst_n)
    if(rst_n == 1'b0)
        r_buf_sel <=1'b0;
    else if(r_flag == 1'b0 && r_flag_dly == 1'b1)
        r_buf_sel <= ~r_buf_sel;

assign  r_addr1 = r_cnt;
assign  r_addr2 = r_cnt;

assign  data_out= r_buf_sel ? dout2 : dout1;
assign  data_ov = r_flag_dly;

endmodule
  • 2
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值