1.双口RAM概述

双口RAM(dual port RAM)在异构系统中应用广泛,通过双口RAM,不同硬件架构的芯片可以实现数据的交互,从而实现通信。例如,一般情况下,ARM与DSP之间的通信,可以利用双口RAM实现,ARM通过EBI总线连接到双口RAM的A口,DSP通过EMIF总线(也可以是uPP总线,取决于速度需求)连接到双口RAM的B口,两者对同一块存储区域进行操作,即可实现两者的数据交互。

但是,因为双口RAM的A口和B口都可以对相同的内存地址进行操作,这就引出了一个问题——假如通信双方在两个端口对同一地址同时读写,就会引发冲突。要解决这个问题,办法有二。一是通信双方在时序上保证不会同时读写同一地址,将ARM和DSP可写地址范围进行分区,无论任何一方写完数据后都通过IO发送中断通知对方,对方进行数据读取(乒乓RAM操作),这样是比较可靠的;另外一个办法就是在fpga里设置写busy信号,实现两端写同步[]。在FPGA中,构建双口RAM可以通过两种方法,一种是利用distributed RAM构建,另一种是利用Block RAM构建,关于两者的具体区别,可以参考这两篇文章[][]。简而言之,Block RAM是是使用FPGA中的整块双口RAM资源,而distributed RAM则是用FPGA中的逻辑资源拼凑形成的。一般的原则是,较大的存储应用,建议用bram;零星的小ram,一般就用dram。

在Vivado中,RAM IP核在Memories & Strorage Elements\RAM & ROMs和RAM & ROMs & BRAM文件夹下,如图所示,下面简要介绍一下Vivado的双口RAM IP核。

(图1.1)

2.Vivado 双口RAM IP核

2.1 Block Memory Generator概述

点击图1.1的Block Memory Generator项,利用BRAM来构建双口RAM。Block Memory Generator窗口如图2.1所示。

图中,第1部分,在IP symbol选项卡,点击"+"号可以展开端口具体信号,如图2.2所示。第2部分,Component Name可以设置IP核的名字。第3部分,Basic选项卡,在Memory Type下拉列表中,可以设置内存的类型,如图2.3所示。Block Memory Gnerator一共可以产生5种不同类型的内存空间,其中block RAM有三种:单口RAM、简化双口RAM和真双口RAM[]。单口RAM只有一个端口(A端口),可以对A端口进行读写。简化双口RAM有两个端口(A和B端口),但是A端口只能进行写入操作,不能进行读出操作,而B端口则只能进行读出操作,不能进行写入操作。真双口RAM有两个端口(A和B端口),A和B端口都能进行读写操作[]。

(图2.1)

(图2.2)

(图2.3)

2.2 真双口RAM的设置

2.2.1 Basic设置

在Basic选项卡的Memory type选项中选择真双口RAM,IP Symbol如图2.4所示。ECC Options为默认设置,Write Enable中也选择默认设置,不使能字节写,Algorithm Options选择默认设置。

(图2.4)

2.2.2 Port设置

点击Port A Options选项卡,对A端口进行设置, 设置Write Width为16(即RAM单元为16位),Write Width为1024(即内存深度为1024,该端口可读写的RAM单元有1024个),Operating Mode(操作模式)一共有三种:Write First,Read First,No Change。在Write First模式中,在一个时钟周期里,写入内存单元的数据被同步输出到输出数据总线上;在Read First模式中,在一个时钟周期里,写入到内存单元的数据是当前输入数据总线上的数据,而输出到输出数据总线上的数据则是上一个时钟周期存储在内存单元中的数据。细节可参考PG058的49到50页4。Enable Port Type设置为Always Enabled,一直使能端口A。其它设置使用默认设置。如图2.5所示。

(图2.5)

端口B设置为与A一致。在Other Options选项卡中,保留默认设置。Load Init File设置是否用Coe文件对内存区域初始化,这个在初始化ROM的时候会用到,这里不勾选,保持默认。最后,在Summary选项卡会显示消耗的资源。

3.双口RAM例程

例程1,该例程是Altera官方例程[],采用寄存器构建双口RAM,代码如下:

moduletrue_dpram_sclk

(

input [7:0] data_a, data_b,

input [5:0] addr_a, addr_b,

input we_a, we_b, clk,

outputreg [7:0] q_a, q_b

);

// Declare the RAM variable

reg [7:0] ram[63:0];

// Port A

always @ (posedge clk)

begin

if (we_a)

begin

ram[addr_a] <= data_a;

q_a <= data_a;

end

else

begin

q_a <= ram[addr_a];

end

end

// Port B

always @ (posedge clk)

begin

if (we_b)

begin

ram[addr_b] <= data_b;

q_b <= data_b;

end

else

begin

q_b <= ram[addr_b];

end

end

endmodule

例程2,该例程是Xilinx官方例程[],采用寄存器构建真双口RAM,代码如下:

// Dual-Port Block RAM with Two Write Ports

// File: rams_16.v

modulev_rams_16 (clka,clkb,ena,enb,wea,web,addra,addrb,dia,dib,doa,dob);

input clka,clkb,ena,enb,wea,web;

input [9:0] addra,addrb;

input [15:0] dia,dib;

output [15:0] doa,dob;

reg[15:0] ram [1023:0];

reg[15:0] doa,dob;

always @(posedge clka) beginif (ena)

begin

if (wea)

ram[addra] <= dia;

doa <= ram[addra];

end

end

always @(posedge clkb) beginif (enb)

begin

if (web)

ram[addrb] <= dib;

dob <= ram[addrb];

end

end

endmodule

例程3,该例程是网友博客中的例程[],代码如下:

moduleTOP(

input USER_CLK

)

`define DLY #1

reg FPGA_Enable=0;

reg[3:0] FPGA_Write_Enable=4'h0;

reg[31:0] FPGA_Address=0;

reg[31:0] FPGA_Write_Data=0;

reg[31:0] FPGA_Read_Data_reg=0;

wire[31:0] FPGA_Read_Data;

reg[10:0] count=0;

always @ (posedge USER_CLK)

begin

count <= count +1;

if(count<=100)

begin

FPGA_Enable <=0;

FPGA_Write_Enable <=4'h0;

end

elseif((count <=105)&&(count >100))

begin

FPGA_Enable <=1;

FPGA_Write_Enable <=4'hf;

FPGA_Address <= FPGA_Address +4;

FPGA_Write_Data <= FPGA_Write_Data +1;

end

elseif((count <=110)&&(count >105))

begin

FPGA_Enable <=0;

FPGA_Write_Enable <=4'h0;

FPGA_Address <=0;

FPGA_Write_Data <=0;

end

elseif((count <=117)&&(count >110))

begin

FPGA_Enable <=1;

FPGA_Write_Enable <=4'h0;

FPGA_Read_Data_reg <= FPGA_Read_Data;

FPGA_Address <= FPGA_Address +4;

end

elseif(count ==118)

begin

FPGA_Enable <=0;

count <= count;

end

end

BBBByour_instance_name (

.clka(USER_CLK), // input clka

.ena(FPGA_Enable), // input ena

.wea(FPGA_Write_Enable), // input [3 : 0] wea

.addra(FPGA_Address), // input [31 : 0] addra

.dina(FPGA_Write_Data), // input [31 : 0] dina

.douta(FPGA_Read_Data), // output [31 : 0] douta

.clkb(clkb), // input clkb

.enb(enb), // input enb

.web(web), // input [3 : 0] web

.addrb(addrb), // input [31 : 0] addrb

.dinb(dinb), // input [31 : 0] dinb

.doutb(doutb) // output [31 : 0] doutb

);

endmodule

该例程中,在count为101(>100)后开始往地址4到20写入1-5,然后在count为111(>110)的时候读出写入的数据。

4.仿真

下面利用Modelsim和Vivado进行联合仿真,关于vivado如何与modelsim进行联合仿真可以参考这篇文章:

vivado与modelsim的关联以及器件库编译

有一点要注意的是,我用的是Vivado2017.1版本,这个版本只支持Modelsim10.5及以上的版本,如果是低版本的Modelsim,在用Vivado2017.1编译Modelsim的仿真库时,会出错。Modelsim10.5版本可以在这里下载:

modelsim 10.5 适用vivado 2017.1

用Modelsim仿真时,会在sim_1/behav文件夹下产生3个.do文件,分别是xx_compile.do,xx_simulate.do,xx _wave.do文件。在设计的verilog文件修改之后,如果在Modelsim中直接restart,仿真的其实还是没有修改前的文件,要使修改的.v文件在Modelsim中生效,可以在Modelsim的命令窗口输入do xx_compile.do文件,对仿真的库文件以及设计文件(.v文件)重新编译,然后在输入do xx_simulate.do文件,才能仿真修改后的文件。输入do xx_compile.do命令对设计文件重新编译的时候,Modelsim会强制退出,这时由最后一句force quit命令引起的,只要把它删掉就行了。如果要保存波形文件,可以save format,另存为xx_wave.do文件。

参考上面双口RAM的例程3进行功能仿真,RAM IP使用Write First模式,设计文件代码如下:

`timescale 1ns / 1ps

//

// Company:

// Engineer:

//

// Create Date: 2017/12/09 22:36:48

// Design Name:

// Module Name: dual_port_ram_demo

// Project Name:

// Target Devices:

// Tool Versions:

// Description:

//

// Dependencies:

//

// Revision:

// Revision 0.01 - File Created

// Additional Comments:

//

//

moduledual_port_ram_demo(

input USER_CLK

);

`define DLY #1

//Port A declaration

reg FPGA_Enable=0;

reg FPGA_Write_Enable=0;

reg[31:0] FPGA_Address=0;

reg[31:0] FPGA_Write_Data=0;

reg[31:0] FPGA_Read_Data_reg=0;

wire[31:0] FPGA_Read_Data;

//Port B declaration

reg enb=0;

reg[3:0] web=4'h0;

reg[31:0] addrb=0;

reg[31:0] dinb=0;

reg[31:0] doutb_reg=0;

wire[31:0] doutb=0;

reg[10:0] count=0;

always @ (posedge USER_CLK)

begin

count <= count +1;

if(count<=100)

begin

FPGA_Enable <=1;

FPGA_Write_Enable <=0;

end

elseif((count <=105)&&(count >100))

begin

FPGA_Enable <=1;

FPGA_Write_Enable <=1;

FPGA_Address <= FPGA_Address +4;

FPGA_Write_Data <= FPGA_Write_Data +1;

end

elseif((count <=110)&&(count >105))

begin

FPGA_Enable <=1;

FPGA_Write_Enable <=0;

FPGA_Address <=0;

FPGA_Write_Data <=0;

end

elseif((count <=117)&&(count >110))

begin

FPGA_Enable <=1;

FPGA_Write_Enable <=1;

FPGA_Read_Data_reg <= FPGA_Read_Data;

FPGA_Address <= FPGA_Address +4;

end

elseif(count ==118)

begin

FPGA_Enable <=0;

count <= count;

end

end

dpRAMu1 (

.clka(USER_CLK), // input clka

.ena(FPGA_Enable), // input ena

.wea(FPGA_Write_Enable), // input [3 : 0] wea

.addra(FPGA_Address), // input [31 : 0] addra

.dina(FPGA_Write_Data), // input [31 : 0] dina

.douta(FPGA_Read_Data), // output [31 : 0] douta

.clkb(USER_CLK), // input clkb

.enb(enb), // input enb

.web(web), // input [3 : 0] web

.addrb(addrb), // input [31 : 0] addrb

.dinb(dinb), // input [31 : 0] dinb

.doutb(doutb) // output [31 : 0] doutb

);

endmodule

testbench文件如下:

`timescale 1ns / 1ps

//

// Company:

// Engineer:

//

// Create Date: 2017/12/09 22:47:26

// Design Name:

// Module Name: simu

// Project Name:

// Target Devices:

// Tool Versions:

// Description:

//

// Dependencies:

//

// Revision:

// Revision 0.01 - File Created

// Additional Comments:

//

//

modulesimu(

);

//testbench 时钟信号

reg clk =0;

always # 10 clk < =~clk;

//调用dual_port_ram_demo模块

dual_port_ram_demodemo1(clk);

endmodule

仿真结果如下:

(图4.1)

程序在1时刻准备好地址和要写入RAM的数据,在2时刻写入RAM中,在3时刻端口才会输出2时刻写入RAM的数据,注意与PG058的图稍有不同。

(图4.2)

4.后记

关于BRAM,推荐一个youtube视频,里面讲的非常清晰易懂。

What is a Block RAM in an FPGA?

Vivado 双口RAM 的调用和实现相关推荐

  1. Xilinx伪双口RAM实现同步FIFO(解决读写冲突)

    一.伪双端口RAM配置 关于创建和配置IP,可以参考我的另一篇文章:Vivado 双口RAM IP核的使用,不同之处只是在于本文使用的伪双端口RAM的写端口和读端口都加了使能信号,也即没有选择始终使能 ...

  2. linux mpc8313启动流程,基于MPC8313E和FPGA的双口RAM驱动开发

    摘要 以MPC8313E芯片为平台,介绍了一个基于嵌入式Linux探作系统的双口RAM设备驱动.通过该设备驱动搭建Linux服务器,利用缓存技术实时读取FPGA双口RAM数据,最终实现将海量图像数据高 ...

  3. FPGA双口RAM使用

    模块名称: dpram() IP Core 双口RAM,有俩组数据线和地址线,读写可以同时进行,FIFO读写可以同时进行,可以看作是双口.分为Simple two-dual RAM和true two- ...

  4. 【verilog】 Vivado-Simple Dual-Port RAM IP的使用(Xilinx FPGA,双口RAM,IP使用)

    简单双口RAM的IP核 配置IP 例化顶层 测试 波形 简单双口RAM的IP核 分A端口和B端口 A为输入端口,负责数据的写入 B端口为输出端口,负责数据的读出 两端的时钟可以不同,还允许在写入A的同 ...

  5. xilinx 真双口RAM的primitives /core output 区别

    软件平台 Vivado 2016.4 属性设置说明 1在 ip catalog -> block memory generator . 这里仅介绍真双口RAM, 真双口RAM支持A/B两个口可读 ...

  6. FPGA设计心得(1)真双口RAM使用及其仿真问题记录

    文章目录 前言 设计介绍 关于仿真 老生常谈 最后想说的话 前言 RAM是一个好东西,FIFO也是,关键是适应你的设计场景,本文是一个记录性质的博文,所以也没必要什么都交代清楚了,只是在项目开发中,有 ...

  7. Linux创建线程读取双口数据,linux环境下读写一次双口ram尽然要十几个毫秒。(附驱动代码)...

    linux环境下读写一次双口ram尽然要十几个毫秒.(附驱动代码) 我用的双口ram是IDT70V28,手册上说的读写时间应该是几个纳秒,我写了个linux驱动,然后做测试,发现读写一次的时间尽然是十 ...

  8. 搭建串口收发与存储双口RAM简易应用系统

    搭建串口收发与存储双口RAM简易应用系统 为了实现通过串口发送数据到 FPGA 中, FPGA 接收到数据后将数据存储在双口 ram 的 一段连续空间中,当需要时,按下按键 S0,则 FPGA 将 R ...

  9. 3 计算机组成原理第三章 存储系统 主存简单模型及寻址 半导体寄存器 存储器分类 主存与CPU连接 双口RAM和多模块寄存器

    文章目录 1 主存简单模型及寻址的概念 1.1 主存储器 1.1.1 存储器芯片的基本结构 1.1.2 寻址 2 半导体存储器 2.1 半导体随机存取存储器 2.1.1 DRAM的刷新 2.1.2 S ...

最新文章

  1. 首次使用mysql_mysql的初次使用操作
  2. 贪心 BestCoder Round #39 1001 Delete
  3. hasOwnProperty.call
  4. mvn 命令向本地仓库上传 jar
  5. 【数据库原理及应用教程】【数据库系统的体系结构】【1.4-1.6】
  6. Fluorescent-PEG2000-Pyrene,荧光素和芘丁酸修饰的PEG,Pyrene-PEG2000-FITC
  7. 计算机组成原理中CPI、MIPS、CPU执行时间、主频等计算
  8. 微信小程序获取并修改app.js中的值
  9. 07-HTML5举例:简单的视频播放器
  10. win7系统下连网络打印机打印反应很慢解决方法
  11. 2022Q3手机配件增长榜:手机壳、数据线等供求不断增加
  12. 利用MFC库获取指定城市的天气实况
  13. 拓嘉启远:拼多多的奖惩机制有哪些
  14. iphone6s 计算机算次方,你一定不能错过的8个iphone使用技巧(纯干货)
  15. emWin默认皮肤下重新设置颜色
  16. lds天线技术流程图_什么是LDS天线技术
  17. 【C++碎碎念】面向对象(封装与访问控制、构造函数与析构函数、拷贝函数)
  18. C++ Windows 窗体程序入门 - 1.你的第亿个窗体程序
  19. html导航怎么加图标,纯 CSS 实现导航图标动画
  20. 音视频技术开发周刊 | 202

热门文章

  1. 2021年春季学期-信号与系统-第十二次作业参考答案
  2. 2021年春季学期-信号与系统-第二次作业参考答案-第六小题
  3. 灵动微电子逐飞 智能车支持计划汇总
  4. 测试STC8H8K64U-AD转换
  5. python调用spark和调用hive_Spark(Hive) SQL数据类型使用详解(Python)
  6. 安装testlink时mysql_windows下安装testlink
  7. php拆分jsion_Php如何返回json数据,前后端分离的基本解决方案
  8. php simpletest 测试数据库,在PHP中使用SimpleTest进行单元测试
  9. python绘制直方图根据不同分类_如何在python中绘制具有多个类别的直方图
  10. oracle转金额,ORACLE金额转换成英文大写的函数