FPGA_学习_11_IP核_RAM_乒乓操作

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

本篇博客学习另一个IP核,RAM。 用RAM实现什么功能呢? 实现乒乓操作。 乒乓操作是什么呢?

参考:

FPGA中的乒乓操作思想_fpga中乒乓操作的原因_小林家的龙小年的博客-CSDN博客

何为乒乓操作_fanyuandrj的博客-CSDN博客

以下是本人理解:

乒乓操作可以实现低速模块处理高速数据,这种处理方式可以实现数据的串并转换,就是数据位宽之间的转换,是面积与速度互换原则的体现。

例如:

数据位宽的转换,要将8位的数据转换为16位,按照传统方法,每两个时钟周期完成一次转换,输出数据的变化与时钟信号不是同步的。使用乒乓操作,数据写入数据缓冲模块的时候使用50M的时钟,读出时使用25M的时钟,每次读出16位,这样不仅实现了数据位宽的转换,还使得输出的数据可以被一个低速的时钟同步。

FPGA_学习_11_IP核_RAM_乒乓操作

乒乓骚操作1:

假设输入数据的时钟50Mhz,100个8位的数据先写入 数据缓冲模块1, 然后再100个8位的数据写入 数据缓冲模块2,与此同时,输出模块读取 数据缓冲模块1的数据。 如果输出模块读取时钟也是50Mhz, 待100个8位的数据已存入 数据缓冲模块2时, 先前写入 数据缓冲模块1的100个8位的数据,已经被输出模块读取完了。

下面再次把100个8位的数据先写入 数据缓冲模块1,与此同时,输出模块读取 数据缓冲模块2的数据。 如果输出模块读取时钟也是50Mhz, 待100个8位的数据已存入 数据缓冲模块1时, 先前写入 数据缓冲模块2的100个8位的数据,也已经被输出模块读取完了。

如此循环,就可以,... 嗯,... , 我反正暂时不清楚这个操作有什么意义。

乒乓骚操作2:

假设输入数据的时钟50Mhz,100个8位的数据先写入 数据缓冲模块1, 然后再100个8位的数据写入 数据缓冲模块2,与此同时,输出模块读取 数据缓冲模块1的数据。 如果输出模块读取时钟也是25Mhz, 待100个8位的数据已存入 数据缓冲模块2时, 要想把先前写入 数据缓冲模块1的100个8位的数据全部取出,输出模块读取的位宽得1次读16位。

50Mhz 8位宽的写100的时间 =  25Mhz 16位宽的读50次的时间。

下面再次把100个8位的数据先写入 数据缓冲模块1,与此同时,输出模块读取 数据缓冲模块2的数据。 如果输出模块读取时钟是25Mhz读取位宽是16位, 待100个8位的数据已存入 数据缓冲模块1时, 先前写入 数据缓冲模块2的100个8位的数据,也已经被输出模块读取完了,50个16位。

如此循环,好像有那么点意思了,连续的 50M 8位宽数据,变成了连续的25M 16位宽的数据。 时钟变慢了,位宽变大了。

本文先尝试实现乒乓骚操作1,后续如果我功力深厚了再来尝试实现乒乓骚操作2

1 RAM IP核配置步骤

(Vivado 赛灵思)

截图warning!

FPGA_学习_11_IP核_RAM_乒乓操作

Block Memory Generator是用FPPA内部专用存储资源给你生成RAM。

上面Distributed Memory Generator这个是用D触发器和查找表来帮你实现的RAM。

FPGA_学习_11_IP核_RAM_乒乓操作

我取名IP_RAM

FPGA_学习_11_IP核_RAM_乒乓操作

FPGA_学习_11_IP核_RAM_乒乓操作

FPGA_学习_11_IP核_RAM_乒乓操作

FPGA_学习_11_IP核_RAM_乒乓操作 本次实验位宽为8,深度为256

FPGA_学习_11_IP核_RAM_乒乓操作

FPGA_学习_11_IP核_RAM_乒乓操作 FPGA_学习_11_IP核_RAM_乒乓操作

FPGA_学习_11_IP核_RAM_乒乓操作 OK后,后面的弹窗你就一顿OK,下一步就可以了。

FPGA_学习_11_IP核_RAM_乒乓操作

FPGA_学习_11_IP核_RAM_乒乓操作

我们是看Verilog的例化模板。 

2 时序图

FPGA_学习_11_IP核_RAM_乒乓操作

乒乓操作是要例化个简单口RAM的IP核的,绿线上面例化的第一个简单口RAM→RAM1的相关时序,绿线下面是例化的第而个简单口RAM→RAM2的相关时序。

RAM_wea_d表示RAM_wea延迟(delay)一拍,可用于指示当前是RAM1输出有效还是RAM2的输出有效。

画时序图是费劲的活儿,但是画完自己的理解确实也会更进一分。

{signal: [
    {name: 'clk',   		wave: 'p...........................................'	},
    {},
    {name: 'rst_n',			wave: '01..........................................'	},
    {},
  	{name: 'RAM1_wea',		wave: '01....0....1....0....1....0....1....0....1..'	},
    {},
  	{name: 'RAM1_addra',	wave: '2.22222.....22222.....22222.....22222.....22'	, data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},    
  	{name: 'RAM1_dina',		wave: '2.22222.....22222.....22222.....22222.....22'	, data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
    {},	
  
  	{name: 'RAM1_addrb',	wave: '2......22222.....22222.....22222.....22222..'	, data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
  	{name: 'RAM1_doutb',	wave: '2.......22222.....22222.....22222.....22222.'	, data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
    {}, 
  	
  	{name: 'RAM2_wea',		wave: '0.....1....0....1....0....1....0....1....0..'	},
    {},
    
  	{name: 'RAM2_addra',	wave: '2......22222.....22222.....22222.....22222..'	, data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
  	{name: 'RAM2_dina',		wave: '2......22222.....22222.....22222.....22222..'	, data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
    {}, 
  
   	{name: 'RAM2_addrb',	wave: '2.22222.....22222.....22222.....22222.....22'	, data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},    
  	{name: 'RAM2_doutb',	wave: '2..22222.....22222.....22222.....22222.....2'	, data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
    {},	
    {name: 'RAM1_wea_d',	wave: '0.1....0....1....0....1....0....1....0....1.'	},

  
    {}
]}

3 测试代码

本博客先暂时只实现第一种乒乓操作

`timescale 1ns / 1ps

module ram_pp(
        input   wire            clk     ,
        input   wire            rst_n   ,
        output  reg     [7:0]   dout
);


//==================================================================
//                        Parameter define
//==================================================================

parameter		MAX = 256 - 1;


//==================================================================
//                        Internal Signals
//==================================================================
// 一个简单双口RAM的IP里面,a代表写,b代表读
reg             RAM1_wea        ;       // 写-使能
reg     [7:0]   RAM1_addra      ;       // 写-地址
wire    [7:0]   RAM1_dina       ;       // 写-数据
reg     [7:0]   RAM1_addrb      ;       // 读-地址
wire    [7:0]   RAM1_doutb      ;       // 读-数据

reg             RAM2_wea        ;
reg     [7:0]   RAM2_addra      ;
wire    [7:0]   RAM2_dina       ;
reg     [7:0]   RAM2_addrb      ; 
wire    [7:0]   RAM2_doutb      ;

reg             RAM1_wea_d      ;       // RAM1的写使能延迟一拍

IP_RAM inst_RAM1 (
  .clka(clk),           // input wire clka              写-时钟
  .wea(RAM1_wea),       // input wire [0 : 0] wea       写-使能
  .addra(RAM1_addra),   // input wire [7 : 0] addra     写-地址
  .dina(RAM1_dina),     // input wire [7 : 0] dina      写-数据
  .clkb(clk),           // input wire clkb              读-时钟
  .addrb(RAM1_addrb),   // input wire [7 : 0] addrb     读-地址
  .doutb(RAM1_doutb)    // output wire [7 : 0] doutb    读-数据
);                      //                              谨记:不要同时读写同一地址的数据。

IP_RAM inst_RAM2 (
  .clka(clk),           // input wire clka              写-时钟
  .wea(RAM2_wea),       // input wire [0 : 0] wea       写-使能
  .addra(RAM2_addra),   // input wire [7 : 0] addra     写-地址
  .dina(RAM2_dina),     // input wire [7 : 0] dina      写-数据
  .clkb(clk),           // input wire clkb              读-时钟
  .addrb(RAM2_addrb),   // input wire [7 : 0] addrb     读-地址
  .doutb(RAM2_doutb)    // output wire [7 : 0] doutb    读-数据
);

// 在对RAM1做写操作的时候,与此同时对RAM2做读操作
// 对RAM1做写操作完成,RAM2的做读操作也会完成,此时则对RAM1做读操作,对RAM2做写操作
// 对RAM2做写操作完成,RAM1的做读操作也会完成,此时则对RAM2做读操作,对RAM1做写操作
// 如此循环。

// 上面的文字描述有点绕,但告诉了我们三个道理。
// 1、一个RAM在写,则另一个RAM必然在读,所以两个RAM的写使能是完全相反的电平。
// 2、当RAM2写完的时候,就到我RAM1写了,这时候RAM1写使能拉高。
// 3、当RAM1写完的时候,RAM1进入读操作,这时候RAM1写使能拉低。


//----------------------------- RAM1_wea -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                RAM1_wea <= 1'b0;                  
        end
        // RAM2写完,到RAM1写,RAM1_wea拉高
        else if ( RAM2_addra == MAX && RAM1_wea == 1'b0) begin
                RAM1_wea <= 1'b1;
        end
        // RAM1写完,则RAM1变成读,RAM1_wea拉低
        else if ( RAM1_addra == MAX && RAM1_wea == 1'b1) begin
                RAM1_wea <= 1'b0;
        end
        else begin
                RAM1_wea <= RAM1_wea;
        end
end

//----------------------------- RAM2_wea -----------------------------
always @(*) begin
        RAM2_wea        <= ~RAM1_wea;
end



//----------------------------- RAM1_addra -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                RAM1_addra      <= 'd0;     
        end
        else if (RAM1_wea == 1'b1) begin
                if (RAM1_addra == MAX) begin
                        RAM1_addra      <= 'd0;
                end
                else begin
                        RAM1_addra      <= RAM1_addra + 1'b1;
                end
        end
        else begin
                RAM1_addra <= 'd0;
        end
end

//----------------------------- RAM1_addrb -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                RAM1_addrb      <= 'd0;     
        end
        else if (RAM1_wea == 1'b0) begin
                if (RAM1_addrb == MAX) begin
                        RAM1_addrb      <= 'd0;
                end
                else begin
                        RAM1_addrb      <= RAM1_addrb + 1'b1;
                end
        end
        else begin
                RAM1_addrb <= 'd0;
        end
end

//----------------------------- RAM2_addra -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                RAM2_addra      <= 'd0;     
        end
        else if (RAM2_wea == 1'b1) begin
                if (RAM2_addra == MAX) begin
                        RAM2_addra      <= 'd0;
                end
                else begin
                        RAM2_addra      <= RAM2_addra + 1'b1;
                end
        end
        else begin
                RAM2_addra <= 'd0;
        end
end

//----------------------------- RAM2_addrb -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                RAM2_addrb      <= 'd0;     
        end
        else if (RAM2_wea == 1'b0) begin
                if (RAM2_addrb == MAX) begin
                        RAM2_addrb      <= 'd0;
                end
                else begin
                        RAM2_addrb      <= RAM2_addrb + 1'b1;
                end
        end
        else begin
                RAM2_addrb <= 'd0;
        end
end

//----------------------------- RAM1_dina -----------------------------
assign RAM1_dina = RAM1_addra;

//----------------------------- RAM2_dina -----------------------------
assign RAM2_dina = RAM2_addra;

//----------------------------- RAM1_wea_d -----------------------------
// 由于RAM的输出信号要延迟一拍
// 因此利用RAM1的写使能信号RAM1_wea,延迟一拍作为整个模块输出dout的读有效信号。
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                RAM1_wea_d <= 1'b0;        
        end
        else begin
                RAM1_wea_d <= RAM1_wea;         
        end
end

//----------------------------- dout -----------------------------
always @(*) begin
        // RAM1_wea_d为高,则RAM1正在进行写操作,这时候不能读RAM1,而RAM2这时候正在输出,正好读RAM2的数据。
        if (RAM1_wea_d == 1'b1) begin
                dout <= RAM2_doutb;
        end
        // RAM1_wea_d为低,则RAM1正在进行读操作,这时候正好RAM1在输出,正好读RAM1
        else begin
                dout <= RAM1_doutb;
        end
end



endmodule

 这个代码里面的注释是目前为止我写的最多的啦,希望看了会帮助理解。

4 仿真代码

`timescale 1ns/1ps

module tb_ram_pp (); /* this is automatically generated */

    parameter MAX = 256 - 1;
    reg         clk;
    reg         rst_n;
    wire [7:0]  dout;

    ram_pp inst_ram_pp (.clk(clk), .rst_n(rst_n), .dout(dout));

    initial begin
        clk = 1;
        forever #(10) clk = ~clk;
    end

    initial begin
        rst_n <= 0;
        #200
        rst_n <= 1;
    end

endmodule

这个仿真代码比较简单,因为咱们写的ram_pp模块本身的输入输出接口很少,所以不怎么操心,直接调用ram_pp模块就行了。

By the way,我用的Modelsim仿真,本文不讨论怎么搞Modelsim仿真。

5 仿真结果

FPGA_学习_11_IP核_RAM_乒乓操作

FPGA_学习_11_IP核_RAM_乒乓操作

刚仿真出来的时候我以为哪里有问题,因为发现第一个周期是没有输出的。 后来想明白了原因,因为更早之前RAM没写入任何数据,所以它第一个周期就应该没有输出。 另外,建议仿真的时长弄长一点(我用的30us),从第二个周期开始看。

仿真结果来看,实验和预期的目标时序是相符的,实验是成功的。文章来源地址https://www.toymoban.com/news/detail-493658.html

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

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

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

相关文章

  • FPGA设计——verilog实现乒乓操作并modelsim仿真

    乒乓操作是FPGA设计中常用的一种技巧,它通过数据流控制实现按节拍相互配合的切换,来提高数据处理效率,达到无缝缓冲和处理的效果。本文针对乒乓操作进行学习总结。 完整工程 一、原理图如下 : 1、二选一控制器来对缓冲模块1和2进行选择。 2、数据缓冲模块一般就是

    2023年04月08日
    浏览(33)
  • (34)FPGA IP设计(RAM)

    1.1 本节目录 1.2 IP核介绍 1.3 FPGA介绍 1.4 Verilog介绍 1.5 Vivado_RAM_IP设计 1.6 结束语 IP核有行为(Behavior)级、结构(Structure)级和物理(Physical)级三个层次的分类,对应着三个种类型的IP核,它们是由硬件描述语言设计的软核(Soft IP Core)、完成结构描述的固核(Firm IP Core) 和基于物理描述并

    2024年01月19日
    浏览(30)
  • #FPGA(IP_RAM 调用 )

    1.IDE:Quartus II 2.设备:Cyclone II  EP2C8Q208C8N   3.实验:调用单端口RAM仿真读取写入 4.时序图: 5.步骤: (1)添加一个初始化文件做对比,255递减到0(HEX文件) (2)仿真时出现报错,为配置的ram的.v文件没有添加,按如下方式解决。   6.代码: ram_ctrl.v tb_ram_ctrl.v  

    2024年02月19日
    浏览(25)
  • FPGA简单双端口RAM——IP核

    环境: 1、Quartus18.0 2、vscode 3、板子型号:原子哥开拓者2(EP4CE10F17C8) 要求: 使用 Altera RAM IP 核生成一个简单双端口的 RAM,然后对 RAM 进行读写操作,并通过 Modelsim 软件进行仿真及 SignalTap 软件进行在线调试。 由前面的单端口学习我们知道RAM IP核分为单端口RAM以及双端口RAM,

    2024年02月07日
    浏览(34)
  • FPGA-基本IP核的应用之RAM

    RAM为随机存取存储器,它可以随时把数据写入任一指定地址的存储单元,也可以随时从任一指定地址中读出数据,其读写速度是由时钟频率决定的。RAM主要用来存放程序及程序执行过程中产生的中间数据、运行结果等。其特点适合双向交换数据。 RAM的端口有单端口和双端口:

    2024年02月16日
    浏览(31)
  • FPGA | RAM IP端口输出延迟问题解决

    Vivado中调用RAM IP,端口输出有延迟 版本器件 Version Vivado 2021.2 ZYNQ 7020 问题描述 ram_wea 信号拉低后,RAM读出数据有两个时钟的延迟 解决方式 在 Summary 中可以看到端口的读出延迟,关于该选项的具体使用方式可以参考Xilinx的数据手册:

    2024年02月16日
    浏览(30)
  • Altera FPGA 储存单元IP核之RAM、FIFO

         只读存储器,系统上电后数据就被写入ROM,运行过程中只能从ROM中读取数据,而不能改变ROM中的数值。      随机存取储存器,可以随时把数据写入任一指定地址的储存单元,也可以随时从任一指定地址中读取数据。其读写速度是由时钟频率决定的。RAM主要用来存放程

    2023年04月08日
    浏览(34)
  • FPGA原理与结构——RAM IP核的使用与测试

    系列文章目录:FPGA原理与结构(0)——目录与传送门 目录 一、前言 二、RAM IP核定制 1、RAM IP核 step1 打开vivado工程,点击左侧栏中的IP Catalog step2 在搜索栏搜索RAM,找到Block Memory Generator IP核: 2、IP核定制 step3 Baisc界面定制 step4 端口定制 step5 Other Options step6 Summary 3、IP核例化

    2024年02月11日
    浏览(27)
  • [FPGA IP系列] FPGA常用存储资源大全(RAM、ROM、CAM、SRAM、DRAM、FLASH)

    本文主要介绍FPGA中常用的RAM、ROM、CAM、SRAM、DRAM、FLASH等资源。 RAM(Random Access Memory)是FPGA中最基本和常用的内部存储块,根据不同架构可以实现不同容量,最大可达几十Mb。 FPGA中的RAM主要包括: 分布式RAM:存在于逻辑块(LE)中的小容量RAM,通常为几百比特到几千比特。 块RAM:F

    2024年02月12日
    浏览(31)
  • 从底层结构开始学习FPGA(6)----分布式RAM(DRAM,Distributed RAM)

    文章目录 系列目录与传送门 一、什么是RAM?什么是ROM? 二、块RAM和分布式RAM 2.1、BRAM

    2024年02月02日
    浏览(27)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包