首页 > 其他分享 >乒乓操作

乒乓操作

时间:2024-02-29 10:58:00浏览次数:17  
标签:wire clk 乒乓 ram1 ram2 rd 操作 data

乒乓操作常用与对数据流的处理,他可以节约缓存空间,对数据流无缝处理。

理论学习

典型乒乓操作示意图
  外部输入数据流通过输入数据流选择单元将数据流输入到数据缓存模块,比较常用的存储单元有双口 RAM, FIFO, SDRAM 等。在第一个缓冲周期,数据流通过“输入数据流选择单元”将数据写入“数据缓冲模块 1”。写完之后进入第二个缓冲周期,在第二个缓冲周期数据流通过“输入数据流选择单元”将数据写入到“数据缓冲模块 2”的同时“输出数据流选择单元”将“数据缓冲模块 1”的数据流读出,此时进入第三个缓冲周期。在第三个缓冲周期数据流通过“输入数据流选择单元”将数据写入到“数据缓存模块 1”的同时将“数据缓冲模块 2”的数据读出。如此反复循环地操作,即为乒乓操作。
  乒乓操作整个输入与输出的数据流都是连续不断,没有任何停顿的,因此非常适合对数据流进行流水线式的处理。常用与流水线式算法,完成数据的无缝缓冲与处理。,其次可以节约缓存空间,使用双存储单元比单存储单元更加节省空间。乒乓操作还可以实现低速模块处理高速数据,这种处理方式可以实现数据的串并转换,就是数据位宽之间的转换,是面积与速度互换原则的体现

程序框图

  由于我们是使用不同时钟进行数据输入输出,所以我们需要使用不同的 RAM读写时钟,由于我们是使用 50MHz 时钟输入数据, 25MHz 时钟输出数据,由于我们设置的读写时钟不一致,而要实现乒乓操作的无缝缓存与处理,这里我们就需要设置不同的写入数据位宽与不同的读出数据位宽才能与读写时钟相匹配。这里就是我们前面理论部分所说的面积与速度互换原则的体现,这里我们设置的输入时钟的频率是输出时钟的两倍,即输入数据的速度是输出数据速度的两倍,所以这里我们需要设置输出数据的位宽是输入数据位宽的两倍,即面积的两倍。换句话说就是我们输入速度与面积的乘积与输出速度与面积的乘积要相等,即输入和输出的时间相等,这样才能保证在“数据缓冲模块 1”读/写完的同时“数据缓冲模块 2”也写/读完,才能保证输入与输出数据的无缝传输与处理。这就是其低高速数据传输特点的原理,只要我们遵从输入与输出数据的频率与位宽的乘积相等,那么我们就可以实现不同模块频率之间的数据传输。
image.png

模块介绍

时钟生成模块

  使用IP,生成工程需要的时钟信号:50MHz与25MHz,其中 50MHz为RAM写入时钟信号,25MHz为RAM读取时钟信号。

数据生成

生成测试所需要的数据(0~199循环输出)

RAM控制模块

控制RAM1与RAM2执行乒乓操作

代码

时钟信号以及RAM均使用IP生成
image.png
需要注意的是
image.png
  这里不要勾选输出寄存器,如果勾选,数据输出将会延时一拍,则需要将代码修改。并且由于输入输出时钟不一致,为使数据连续输出,我将输入位宽设为8位,输出设为16位,上文有详细的讲解为什么这样设置。

RAM控制
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/02/27 11:33:09
// Design Name: 
// Module Name: ram_ctrl
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module ram_ctrl(
input  wire clk_50m,
input  wire clk_25m,
input  wire rst_n,
input  wire [7:0] data_in,
input  wire data_en,
input  wire [15:0] ram1_rd_data,
input  wire [15:0] ram2_rd_data,

output reg ram1_wr_en,
output reg ram2_wr_en,
output reg ram1_rd_en,
output reg ram2_rd_en,
output wire [7:0] ram1_wr_data,
output wire  [7:0] ram2_wr_data,
output reg [6:0] ram1_wr_addr,
output reg [6:0] ram2_wr_addr,
output reg [5:0] ram1_rd_addr,
output reg [5:0] ram2_rd_addr,
output reg [15:0] data_out

    );
//parameter define
//状态机
parameter IDLE = 2'b00;
parameter WR_RAM1 = 2'b01;
parameter WR_RAM2_RD_RAM1 = 2'b10;
parameter WR_RAM1_RD_RAM2 = 2'b11;

//reg define
reg [1:0] state;
reg [7:0] data_in_reg;

//***************************************************************//
//***************************mian code***************************//
//***************************************************************//

 //使用写数据时钟下降沿寄存数据,使数据写入存储器时上升沿能踩到稳定的数据
 always @(negedge clk_50m or negedge rst_n) begin
    if(rst_n == 1'b0)
        data_in_reg <= 8'd0;
    else
        data_in_reg <= data_in;
 end
//RAM1\RAM2 写数据输入

 assign ram1_wr_data = (ram1_wr_en == 1'b1) ? data_in_reg: 8'd0; //ram时序要求使能与数据同时变化
 assign ram2_wr_data = (ram2_wr_en == 1'b1) ? data_in_reg: 8'd0;


 //状态机跳转
 always @(negedge clk_50m or negedge rst_n) begin
    if(rst_n == 1'b0)
        state <= IDLE;
    else
        case(state)
            IDLE: begin
                if(data_en == 1'b1)
                    state <= WR_RAM1;
                else
                    state <= state;
            end
            WR_RAM1: begin
                if(ram1_wr_addr == 7'd99)
                    state <= WR_RAM2_RD_RAM1;
                else
                    state <= state;
            end
            WR_RAM2_RD_RAM1: begin
                if((ram1_rd_addr == 6'd49)&&(ram2_wr_addr == 7'd99))
                    state <= WR_RAM1_RD_RAM2;
                else
                    state <=state;
            end
            WR_RAM1_RD_RAM2: begin
                if((ram2_rd_addr == 6'd49)&&(ram1_wr_addr == 7'd99))
                    state <= WR_RAM2_RD_RAM1;
                else
                    state <= state;
            end
            default: state <= IDLE;
        endcase
    end
//RAM1\RAM2 写使能控制(时钟50M)
always @(*) begin
    if(rst_n == 1'b0)begin
        ram1_wr_en <= 1'b0;
        ram2_wr_en <= 1'b0;
    end
    else    
        case (state)
           IDLE :begin
                ram1_wr_en <= 1'b0;
                ram2_wr_en <= 1'b0;
            end
            WR_RAM1 :begin
                ram1_wr_en <= 1'b1;
                ram2_wr_en <= 1'b0;
            end
            WR_RAM2_RD_RAM1 :begin
                ram1_wr_en <= 1'b0;
                ram2_wr_en <= 1'b1;
                
            end
            WR_RAM1_RD_RAM2 :begin
                ram1_wr_en <= 1'b1;
                ram2_wr_en <= 1'b0;
               
            end 
            default:    begin
                ram1_wr_en <= 1'b0;
                ram2_wr_en <= 1'b0;
                
            end 
        endcase
end

//RAM1\RAM2 写地址控制
always @(negedge clk_50m or negedge rst_n) begin
    if(rst_n == 1'b0)
        ram1_wr_addr <= 7'd0;
    else if(ram1_wr_addr == 7'd99)
        ram1_wr_addr <= 7'd0;
    else if(ram1_wr_en == 1'b1)
        ram1_wr_addr <= ram1_wr_addr + 7'd1;
end
always @(negedge clk_50m or negedge rst_n) begin
    if(rst_n == 1'b0)
        ram2_wr_addr <= 7'd0;
    else if(ram2_wr_addr == 7'd99)
        ram2_wr_addr <= 7'd0;
    else if(ram2_wr_en == 1'b1)
        ram2_wr_addr <= ram2_wr_addr + 7'd1;
end

//RAM1\RAM2 读使能控制(时钟25M)
always @(negedge clk_25m or negedge rst_n) begin
    if(rst_n == 1'b0)
        ram1_rd_en <= 1'b0;
    else if(state == WR_RAM2_RD_RAM1)
        ram1_rd_en <= 1'b1;
    else
        ram1_rd_en <= 1'b0;
end
always @(negedge clk_25m or negedge rst_n) begin
    if(rst_n == 1'b0)
        ram2_rd_en <= 1'b0;
    else if(state == WR_RAM1_RD_RAM2)
        ram2_rd_en <= 1'b1;
    else
        ram2_rd_en <= 1'b0;
end

//RAM1\RAM2 读地址控制
always @(negedge clk_25m or negedge rst_n) begin
    if(rst_n == 1'b0)
        ram1_rd_addr <= 6'd0;
    else if(ram1_rd_addr == 6'd49)
        ram1_rd_addr <= 6'd0;
    else if(ram1_rd_en == 1'b1)
        ram1_rd_addr <= ram1_rd_addr + 6'd1;
end
always @(negedge clk_25m or negedge rst_n) begin
    if(rst_n == 1'b0)
        ram2_rd_addr <= 6'd0;
    else if(ram2_rd_addr == 6'd49)
        ram2_rd_addr <= 6'd0;
    else if(ram2_rd_en == 1'b1)
        ram2_rd_addr <= ram2_rd_addr + 6'd1;
end
//输出数据
always @(negedge clk_25m or negedge rst_n) begin
    if(rst_n == 1'b0)
        data_out <= 16'd0;
    else if(ram1_rd_en == 1'b1)
        data_out <= ram1_rd_data;
    else if(ram2_rd_en == 1'b1)
        data_out <= ram2_rd_data;
    else
        data_out <= data_out;
end
endmodule

  此外,本模块所有的信号均由时钟下降沿触发,因为对于RAM来说无论是写入还是读取都是时钟的上升沿进行的,而如果我们使用时钟的上升沿产生使能、地址、数据的话、写入或读取时上升沿采到的就是数据变化的那一刻,这样采到的信号可能就是不稳定的状态,从而导致数据出错,所以这里我们使用时钟的下降沿 去产生这些信号的话,上升沿就能采到数据的稳定状态了。

数据生成
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/02/27 09:56:23
// Design Name: 
// Module Name: data_gen
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module data_gen(
input  wire clk_50m,
input  wire rst_n,
output reg  [7:0] data_out,
output reg  data_en
    );

//***************************************************************//
//***************************mian code***************************//
//***************************************************************//

//data_en 控制数据输出使能
always @(posedge clk_50m or negedge rst_n) begin
    if(rst_n == 1'b0)
        data_en <= 1'b0;
    else
        data_en <= 1'b1;
end

//data_out 数据输出,输入从8'd0 ~ 8'd199
always @(posedge clk_50m or negedge rst_n) begin
    if(rst_n == 1'b0)
        data_out <= 8'd0;
    else if(data_out == 8'd199)
        data_out <= 8'd0;
    else if(data_en == 1'b1)
        data_out <= data_out + 8'd1;
    else
        data_out <= data_out;
end
endmodule
顶层模块
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/02/27 15:58:08
// Design Name: 
// Module Name: top_pingpang
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module top_pingpang(
input wire sys_clk,
input wire sys_rst_n
    );
//wire define
    //clk_gen
wire clk_in1;
wire reset;
wire clk_out1;
wire clk_out2;
wire locked;
    //ram_ctrl
wire clk_50m;
wire clk_25m;
wire rst_n;
wire [7:0] data_out;
wire data_en;
wire [15:0] ram1_rd_data;
wire [15:0] ram2_rd_data;

wire ram1_wr_en;
wire ram2_wr_en;
wire ram1_rd_en;
wire ram2_rd_en;
wire [7:0] ram1_wr_data;
wire [7:0] ram2_wr_data;
wire [6:0] ram1_wr_addr;
wire [6:0] ram2_wr_addr;
wire [5:0] ram1_rd_addr;
wire [5:0] ram2_rd_addr;
wire [15:0] data_out_1;
//
assign rst_n = (sys_rst_n) & (locked);
ram_ctrl ram_ctrl_inst(
    .clk_50m(clk_50m),
    .clk_25m(clk_25m),
    .rst_n(rst_n),
    .data_in(data_out),
    .data_en(data_en),
    .ram1_rd_data(ram1_rd_data),
    .ram2_rd_data(ram2_rd_data),
    .ram1_wr_en(ram1_wr_en),
    .ram2_wr_en(ram2_wr_en),
    .ram1_rd_en(ram1_rd_en),
    .ram2_rd_en(ram2_rd_en),
    .ram1_wr_data(ram1_wr_data),
    .ram2_wr_data(ram2_wr_data),
    .ram1_wr_addr(ram1_wr_addr),
    .ram2_wr_addr(ram2_wr_addr),
    .ram1_rd_addr(ram1_rd_addr),
    .ram2_rd_addr(ram2_rd_addr),
    .data_out(data_out_1)
    );
    clk_gen clk_gen_inst(
    .clk_out1(clk_50m),
    .clk_out2(clk_25m),
    .reset(~sys_rst_n),
    .locked(locked),
    .clk_in1(sys_clk)
    );
    data_gen data_gen_inst(
    .clk_50m(clk_50m),
    .rst_n(rst_n),
    .data_en(data_en),
    .data_out(data_out)
    );
    ram1 ram1_inst(
        .dina (ram1_wr_data ), //写数据
        .addra (ram1_wr_addr ), //写地址
        .clka (clk_50m ), //写时钟
        .wea (ram1_wr_en ), //写使能
        .ena (1'b1),
        .addrb (ram1_rd_addr ), //读地址
        .clkb (clk_25m ), //读时钟
        .enb (ram1_rd_en ), //读使能

        .doutb (ram1_rd_data ) //读数据

         );
    ram2 ram2_inst(
        .dina (ram2_wr_data ), //写数据
        .addra (ram2_wr_addr ), //写地址
        .clka (clk_50m ), //写时钟
        .ena (1'b1),
        .wea (ram2_wr_en), //写使能
        .addrb (ram2_rd_addr ), //读地址
        .clkb (clk_25m ), //读时钟
        .enb (ram2_rd_en ), //读使能

        .doutb (ram2_rd_data ) //读数据


         );


endmodule
仿真程序
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/02/27 16:42:26
// Design Name: 
// Module Name: tb_pingpang
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////

module tb_pingpang();

//reg define
reg sys_clk ;
reg sys_rst_n ;

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
 
 initial
 begin
    sys_clk = 1'b1;
    sys_rst_n <= 1'b0;
 #200
    sys_rst_n <= 1'b1;
 end
 
 //sys_clk:模拟系统时钟,每 10ns 电平取反一次,周期为 20ns,频率为 50MHz
    always #10 sys_clk = ~sys_clk;
 
 //********************************************************************//
 //*************************** Instantiation **************************//
 //********************************************************************//
 
 //-------------pingpang_inst-------------
 top_pingpang pingpang_inst
 (
    .sys_clk (sys_clk ), //系统时钟
    .sys_rst_n (sys_rst_n ) //复位信号,低有效
    
 );
 
 endmodule

仿真结果

image.png  
可以看到,程序正常且连续的输出数据,其中C7C6为198、199拼接成的16进制数据。之后程序继续输出0100、0302........

复盘

image.png
对于生成的简单双口RAM,A口只可进行写操作,B口只可进行读操作。

wea: 使能A口写操作。
ena 使能A端口
enb 使能B端口

  写优先模式write_first:(又称为Read after Write,即先写后读或写优先):当写使能WEA有效时(高有效),此时读依然有效,故该模式下从地址bb和cc读出的是新写入的数据1111和2222,也意味着写入地址bb和cc的数据分别为1111和2222 ;即写入数据的同时,端口也将写入的数据输出。
  读优先模式read_first:(又称为Read before Write,即先读后写或读优先):读出的是该地址上的原有数据,同时会把新数据写入该地址 ,即写入数据的同时,端口也将该地址存储的原始数据输出。
  保持模式no_change:(No Read on Write,保持模式)一旦写操作有效,读操作即无效,此时输出端口保持写操作之前读出的数据不变;
  具体可以参照: RAM三种模式

源代码:

链接: https://pan.baidu.com/s/12sVGOc6rdOtzNWfdo5QOoA?pwd=z8sb 提取码: z8sb 复制这段内容后打开百度网盘手机App,操作更方便哦

参考文献

  [1] 正点原子. 达芬奇之FPGA开发指南
  [2] 野火. FPGA+Verilog开发实战指南——基于Xilinx+Spartan6
  [3] bleauchat.RAM IP core(3) write_first/read_first/no_change什么区别 CSDN(https://blog.csdn.net/bleauchat/article/details/88965307)

标签:wire,clk,乒乓,ram1,ram2,rd,操作,data
From: https://www.cnblogs.com/fangrunze/p/18042936

相关文章

  • 神中神之【Map】遍历操作
    前言首先插入几个值方便操作'''Map<Integer,String>map=newHashMap<>();map.put(1,"A");map.put(2,"B");map.put(3,"C");//System.out.println(map);->{1=A,2=B,3=C}调用tostring'''//......
  • xlua - lua中操作c#对象
    1,c#对象映射为lua的userdataa)默认情况,c#对象都是映射为lua的userdata,数据存放在c#层,调用函数也是走c#层。functionLuaUseVector3(v1,v2)v1.x=10print(type(v1),v1.x,v1.y,v1.z)print("v1:",v1,"v2:",v2)print("v1+v2:",v1+v2)v1......
  • 《程序是怎样跑起来的》第九章“操作系统和应用的关系”
    在阅读了“操作系统和应用的关系”后,我对操作系统和应用程序之间的关系有了更深入的理解。我明白了操作系统为应用程序提供了运行的环境和服务,而应用程序则需要依赖这些服务来正常运行。这一章详细地解释了操作系统如何为应用程序提供服务,以及应用程序如何利用操作系统提供的服务......
  • FastAPI系列:fastapi定制的数据库操作库sqlmodel
    官网sqlmodel安装#安装sqlmodel会自动安装pydantic和sqlalchemypipinstallsqlmodel使用#步骤1,创建sqlmodel引擎fromsqlmodelimportcreate_engine#driver://用户名:密码@ip/数据库engine=create_engine("mysql+mysqldb://root:123456@localhost/api")#步骤......
  • linux进程操作
    linux进程操作查看用户进程top//查看系统中实时的进程信息htop//top的增强版,可以交互的方式显示系统中的进程信息ps //列出当前用户的所有进程信息pstree //以树形结构显示当前用户的而所有进程信息pidof //可以查找指定进程名的进程IDhtop安装:sudoapt-getinstallh......
  • 操作系统和应用的关系
    本章的标题为操作系统和应用的关系,顾名思义我们需要先了解操作系统和应用是什么。通过网上查阅我了解到操作系统是人想出来的,为了让自己管理计算机方便而创造出来的一套管理办法。应用程序要用某种语言编写,而语言又是编译器来提供的。什么是应用?实现某些功能的程序就是应用,想想......
  • ADO.NET基本操作之增、删、改、查
    通过SQL数据库账号连接stringconString="server=(local);uid=sa;pwd=sa;database=Student";stringconString="DataSource=(local);uid=sa;pwd=sa;database=Student";通过Windows身份认证连接stringconString="DataSource=.;InitialCatalog=dataset;Inte......
  • 国产自动化装机平台-定制镜像打包操作方案
    一、基础环境搭建1、原始ISO获取IOS版本:银河麒麟高级服务器操作系统V10SP1(X86_64)百度网盘:https://pan.baidu.com/s/1xAdxWht0wMB-ebQVbjehbQ 提取码:cns22、系统安装这里我们选择银河麒麟服务器系统常用的“带UKUIGUI的服务器”以及“自动分区”方案。 二、ISO镜像定制......
  • 向日葵远程报错:对方正在操作鼠标,暂时无法控制
    控制时被控方始终报错显示“对方正在操作鼠标,暂时无法控制”,即使重启电脑和重装向日葵也于事无补。问过向日葵工单后,得到的解决办法是:打开被控端向日葵软件,向日葵软件内--右上角三横杠--设置--安全--将被控端鼠标优先选项进行取消勾选,再发起远程即可......
  • Mybatis系列之(六)MyBatis的CRUD操作
    MyBatis的CRUD操作CRUD操作指的是增加(Create)、检索(Retrieve)、更新(Update)和删除(Delete)操作1.编程步骤在dao接口中写方法映射配置文件中写对应的配置如果是Insert操作,占位符使用#{},括号中的内容要与实体类属性的get/set方法名保持一致(如果是通过右键生成的方法,......