FPGA实现“乒乓操作”

这篇具有很好参考价值的文章主要介绍了FPGA实现“乒乓操作”。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、“乒乓操作”概述

1、结构       

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

fpga乒乓操作,fpga基础,fpga开发,fpga,乒乓操作,verilog

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。

//-----------------------------<数据生成模块>-----------------------------
module data_gen(
    input clk,
    input rst,
    output data_en,
    output reg [7:0] data_gen
    );

    always@(posedge clk or posedge rst)begin
        if(rst)
            data_gen <= 8'b0;
        else if (data_gen == 8'd31)
            data_gen <= 8'b0;
        else 
            data_gen <= data_gen + 1'b1;
    end

    assign data_en = (rst == 1'b1) ? 1'b0 : 1'b1;

endmodule

2.4 ram_ctrl模块设计

module ram_ctrl(
    input clk_5M,
    input clk_10M,
    input rst,
    input [7:0] data_gen,             //数据生成模块生成的数据
    input data_en,                    //数据使能信号,表示数据是否有效
    input [7:0]ram1_data,             //ram1中读出的数据
    input [7:0]ram2_data,             //ram2中读出的数据

    output ram1_ena,
    output ram1_wea,
    output ram1_enb,
    output reg [4:0] ram1_addra,
    output reg [4:0] ram1_addrb,
    output [7:0] ram1_din,

    output ram2_ena,
    output ram2_wea,
    output ram2_enb,
    output reg [4:0] ram2_addra,
    output reg [4:0] ram2_addrb,
    output [7:0] ram2_din,

    output reg [7:0] dout
    );

//----------------------------<状态定义>---------------------------------
    parameter	IDLE  = 4'b0001;                  //初始状态
	parameter	WRAM1 = 4'b0010;                  //写RAM1
    parameter	R1_W2 = 4'b0100;                  //写RAM2,读RAM1
	parameter	W1_R2 = 4'b1000;                  //写RAM1,读RAM2
				
    reg [3:0] state,next_state;                   //状态寄存器

    always@(posedge clk_10M or posedge rst)begin
        if(rst)
            state <= IDLE;
        else 
            state <= next_state;
    end

    always@(*)begin
        case(state)
            IDLE  : next_state = data_en ? WRAM1 : IDLE ;
            WRAM1 : next_state = (ram1_addra == 5'd31) ? R1_W2 : WRAM1 ;
            R1_W2 : next_state = (ram2_addra == 5'd31) ? W1_R2 : R1_W2 ;
            W1_R2 : next_state = (ram1_addra == 5'd31) ? R1_W2 : W1_R2 ;
            default : next_state = IDLE;
        endcase
    end

    assign ram1_ena = data_en;
    assign ram1_enb = data_en;
    assign ram2_ena = data_en;
    assign ram2_enb = data_en;
    assign ram1_wea = (state == WRAM1 || state == W1_R2) ? 1'b1 : 1'b0 ;
    assign ram2_wea = (state == R1_W2) ? 1'b1 : 1'b0 ;

    always@(posedge clk_10M or posedge rst)begin 
        if(rst) begin 
            ram1_addra <= 0;
            ram2_addra <= 0;
        end
        else if (ram1_addra == 'd31 || ram1_addra == 'd31)begin 
            ram1_addra <= 0;
            ram2_addra <= 0;
        end
        else begin 
            case(state)
                WRAM1 : ram1_addra <= ram1_addra + 1'b1;
                R1_W2 : ram2_addra <= ram2_addra + 1'b1;
                W1_R2 : ram1_addra <= ram1_addra + 1'b1;
                default : begin 
                    ram1_addra <= ram1_addra;
                    ram2_addra <= ram2_addra;
                end
            endcase
        end
    end

    always@(posedge clk_10M or posedge rst)begin
        if(rst) begin 
            ram1_addrb <= 0;
            ram2_addrb <= 0;
        end
        else if (ram1_addrb == 'd31 || ram2_addrb == 'd31)begin
            ram1_addrb <= 0;
            ram2_addrb <= 0;
        end
        else begin 
            case(state)
                R1_W2 : ram1_addrb <= ram1_addrb + 1'b1;
                W1_R2 : ram2_addrb <= ram2_addrb + 1'b1;
                default : begin 
                    ram1_addrb <= ram1_addrb;
                    ram2_addrb <= ram2_addrb;
                end
            endcase
        end
    end

    assign ram1_din = (state == WRAM1 || state == W1_R2) ? data_gen : 8'b0;
    assign ram2_din = (state == R1_W2) ? data_gen : 8'b0;

//打一拍来获得正确的输出
    reg [3:0] state_reg;
    always@(posedge clk_10M)begin
        state_reg <= state;
    end

    always@(*)begin 
        case(state_reg)
            R1_W2 : dout = ram1_data;
            W1_R2 : dout = ram2_data;
            default : dout = 8'b0;
        endcase
    end

endmodule

2.5 顶层模块设计

module pingpang(
    input sys_clk,
    input rst,
    output [7:0] dout
    );

    wire clk_5M,clk_10M;
    wire data_en;
    wire [7:0] data_gen;
    wire ram1_ena,ram1_enb,ram2_ena,ram2_enb;
    wire ram1_wea,ram2_wea;
    wire [4:0] ram1_addra,ram2_addra;
    wire [4:0] ram1_addrb,ram2_addrb;
    wire [7:0] ram1_data,ram2_data;
    wire [7:0] ram1_din,ram2_din;

    clk_div clk_div_u1(
        .clk_5M      (   clk_5M    ),
        .clk_10M     (   clk_10M   ),
        .reset       (   rst       ),
        .clk_in1     (   sys_clk   )
    );

    data_gen data_gen_u1(
        .clk         (   clk_10M   ),
        .rst         (   rst       ),
        .data_en     (   data_en   ),
        .data_gen    (   data_gen  )
    );

    ram ram1(
        .clka        (   clk_10M    ),    // input wire clka
        .ena         (   ram1_ena   ),    // input wire ena
        .wea         (   ram1_wea   ),    // input wire [0 : 0] wea
        .addra       (   ram1_addra ),    // input wire [4 : 0] addra
        .dina        (   ram1_din   ),    // input wire [7 : 0] dina
        .clkb        (   clk_10M    ),    // input wire clkb
        .enb         (   ram1_enb   ),    // input wire enb
        .addrb       (   ram1_addrb ),    // input wire [4 : 0] addrb
        .doutb       (   ram1_data  )     // output wire [7 : 0] doutb
    );

    ram ram2(
        .clka        (   clk_10M    ),    // input wire clka
        .ena         (   ram2_ena   ),    // input wire ena
        .wea         (   ram2_wea   ),    // input wire [0 : 0] wea
        .addra       (   ram2_addra ),    // input wire [4 : 0] addra
        .dina        (   ram2_din   ),    // input wire [7 : 0] dina
        .clkb        (   clk_10M    ),    // input wire clkb
        .enb         (   ram2_enb   ),    // input wire enb
        .addrb       (   ram2_addrb ),    // input wire [4 : 0] addrb
        .doutb       (   ram2_data  )     // output wire [7 : 0] doutb
    );

    ram_ctrl ram_ctrl_u1(
        .clk_5M      (   clk_5M     ),
        .clk_10M     (   clk_10M    ),
        .rst         (   rst        ),
        .data_gen    (   data_gen   ),
        .data_en     (   data_en    ),
        .ram1_data   (   ram1_data  ),
        .ram2_data   (   ram2_data  ),

        .ram1_ena    (   ram1_ena   ),
        .ram1_wea    (   ram1_wea   ),
        .ram1_enb    (   ram1_enb   ),
        .ram1_addra  (   ram1_addra ),
        .ram1_addrb  (   ram1_addrb ),
        .ram1_din    (   ram1_din   ),

        .ram2_ena    (   ram2_ena   ),
        .ram2_wea    (   ram2_wea   ),
        .ram2_enb    (   ram2_enb   ),
        .ram2_addra  (   ram2_addra ),
        .ram2_addrb  (   ram2_addrb ),
        .ram2_din    (   ram2_din   ),

        .dout        (   dout       )
    );


endmodule

3、测试文件

`timescale 1ns / 1ps
module tb_pingpang();
    reg sys_clk;
    reg rst;
    wire [7:0] dout;

    always #5 sys_clk = ~sys_clk;

    initial begin
        sys_clk <= 0;
        rst     <= 0;
    #15 
        rst     <= 1;
    #10 
        rst     <= 0;
    end

    pingpang pingpang_u1(
        .sys_clk    (   sys_clk   ),
        .rst        (   rst       ),
        .dout       (   dout      )
    );

    
endmodule

三、测试结果

fpga乒乓操作,fpga基础,fpga开发,fpga,乒乓操作,verilog

四、乒乓操作与FIFO的区别

fpga乒乓操作,fpga基础,fpga开发,fpga,乒乓操作,verilog

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

        能也不能。

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

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

到了这里,关于FPGA实现“乒乓操作”的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • verilog 实现乒乓操作(附代码)

    乒乓操作整体流程图如下图所示: 乒乓操作的原理简单点说就是: 控制两个存储RAM1和RAM2,当数据开始存储进入RAM1时,将RAM2的数据输出进行处理;当数据开始存储进入RAM2时,将RAM1的数据输出进行处理。 何时存储数据由输入数据流选择模块控制,何时输出,由输出数据流选

    2023年04月25日
    浏览(40)
  • 【FPGA】verilog基础语法与应用:位操作 / 模块调用——流水灯(跑马灯)

    今天的实验是计数器实验的升级,设计让8个LED灯以每个0.5s的速率循环闪烁 1 移位法实现 1.1 移位方法1 每个LED灯代表一位,共8位,亮为1,灭为0 如何实现这样的逻辑呢? 移位操作即可! 怎么样才能移位呢? 第一个状态需满足最低位为1,然后每次左移1个 源代码 仿真代码 功

    2024年01月16日
    浏览(50)
  • FPGA开发] 使用Verilog实现一个简单的计数器

    计数器是数字电路中常见的元件之一,它能够按照一定的规律进行计数。在FPGA开发中,我们可以使用硬件描述语言Verilog来实现一个简单的计数器。本文将为您详细介绍如何使用Verilog编写一个基于FPGA的计数器,并提供相应的源代码。 首先,我们需要定义计数器的功能和规格

    2024年02月03日
    浏览(61)
  • 【Verilog实现FPGA上的信号延迟】—— 用Verilog代码实现将信号延迟N拍,这是FPGA中非常重要的一个操作,可以使数据在不同模块之间精确同步。

    【Verilog实现FPGA上的信号延迟】—— 用Verilog代码实现将信号延迟N拍,这是FPGA中非常重要的一个操作,可以使数据在不同模块之间精确同步。 模块是FPGA中最基本的构建模块。通常一个模块代表一个电路,包括输入、输出和处理逻辑。模块中包含的处理逻辑被称为时序逻辑。

    2024年02月04日
    浏览(75)
  • 【Verilog实现8个输入1个输出的选择器】--FPGA开发

    【Verilog实现8个输入1个输出的选择器】–FPGA开发 在FPGA开发中,选择器是一种重要的电路,它可以将多个输入端口上的数据选择一个输出端口进行传输。通过Verilog语言实现选择器,我们可以灵活地根据实际需求来配置输入和输出端口,提高FPGA的功能和性能。 下面,我们将讨

    2024年02月07日
    浏览(44)
  • 基于FPGA的4x4矩阵键盘控制器verilog开发实现

    欢迎订阅《FPGA学习入门100例教程》、《MATLAB学习入门100例教程》 目录 一、理论基础 二、核心程序 三、测试结果        基于FPGA的4x4矩阵键盘控制器是一种使用FPGA(现场可编程门阵列)来实现对4x4矩阵键盘进行控制的设备。该控制器能够有效地降低硬件资源的使用,提高系

    2024年02月11日
    浏览(51)
  • 基于FPGA的OFDM-BPSK链路verilog实现,开发平台为quartusii

    目录 一、理论基础 二、核心程序 三、测试结果        OFDM(Orthogonal Frequency Division Multiplexing)和BPSK(Binary Phase Shift Keying)都是数字通信中常用的调制和多路复用技术。在一个OFDM-BPSK链路中,BPSK用于调制数据信号,而OFDM用于多路复用和传输。 BPSK调制(Binary Phase Shift Keyi

    2024年02月07日
    浏览(46)
  • FPGA小脚丫开发板实现数字时钟,具备调时、整点报时、闹钟功能(含verilog代码)

    一、实现功能 1. 能正常完成时钟的时、分、秒走时; 2. 使用 LED 闪烁或者改变颜色等方式实现秒的指示,要求闪烁频率或者颜色切换频率为 1Hz ; 3. 使用两位七段数码管显示时和分,其切换方式为:默认显示“分钟”,按住 K4 键显示“小时”,按下 K3 显示秒针; 4. 关上开关

    2024年02月11日
    浏览(61)
  • 【FPGA协议篇】UART通信及其verilog实现(代码采用传参实现模块通用性,适用于快速开发)

    ​ 即通用异步收发器(Universal Asynchronous Receiver/Transmitter),是一种 串行、异步、全双工 的通信协议。特点是通信线路简单,适用于远距离通信,但传输速度慢。 数据传输速率:波特率(单位:baud,波特) 常见波特率有:1200、2400、4800、19200、38400、57600等,最常用的是9600和11520

    2024年02月05日
    浏览(50)
  • FPGA | Verilog基础语法

    菜鸟教程连接 举例(\\\"//\\\"符号后的内容为注释文字): initial $dumpfile (“myfile.dump”); //指定VCD文件的名字为myfile.dump,仿真信息将记录到此文件 可以指定某一模块层次上的所有信号,也可以单独指定某一个信号。 典型语法为$dumpvar(level, module_name); 参数level为一个整数,用于指

    2024年02月05日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包