双口RAM应用实例
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
双端口RAM在高速数据采集中的应用
利用传统方法设计的高速数据采集系统由于集成度低、电路复杂,高速运行电路干扰大,电路可靠性低,难以满足高速数据采集工作的要求。
应用FPGA可以把数据采集电路中的数据缓存、控制时序逻辑、地址译码、总线接口等电路全部集成进一片芯片中,高集成性增强了系统的稳定性,为高速数据采集提供了理想的解决方案。
下面以一个高速数据采集系统为例介绍双端口RAM的应用。
该系统要求实现对频率为5MHz的信号进行采样,系统的计算处理需要对信号进行波形分析,信号采样时间为25μs。
根据设计要求,为保证采样波形不失真,A/D采样频率用80MHz,采样精度为8位数据宽度。
计算得出存储容量需要2K字节。
其系统结构框图如图3所示,图4给出了具体电路连接图。
根据设计要求,双端口RAM的LPM_WIDTH参数设置为8,LPM_WIDTHAD 参数设置为11(211=2048),使用读写使能端及读写时钟。
ADCLK、WRCLK和地址发生器的计数频率为80MHz。
A/D转换值对双端口RAM的写时序为顺序写方式,每完成一次A/D转换,存储一次数据,地址加1指向下一单元,因此写地址发生器(RAM_CONTROL)采用递增计数器实现,计数频率与ADCLK、WRCLK一致以保证数据写入时序的正确性。
写操作时序由地址和时钟发生器、A/D转换时钟和双端口RAM的写时钟产生。
停止采样时AD_STOP有效,写地址发生器停止计数,同时停止对RAM的写操作。
将地址发生器的计数值接至DSP总线可以获取采样的首尾指针。
地址发生器单元一般用(VHDL)语言编程实现,然后生成符号文件
RAM_CONTROL在上层文件调用。
其部分VHDL语言程序如下:
对双端口RAM的读操作采用存储器映像方式,其读出端口接DSP的外扩RAM 总线,DSP可随机读取双端口RAM的任一单元数据,以方便波形分析。
由于
LPM_RAM_DP模块的读端数据总线q不具有三态特性,因此调用三态缓冲器74244,通过其将输出数据连接到DSP数据总线上。
在高速数据采集电路中,数据缓存也可以用FIFO或单端口RAM实现。
用FIFO 进行数据缓存,由于其已经把地址发生部分集成在模块单元内,因此省去了一部分程序编写,但是DSP却不能任意地访问FIFO的存储单元,只能是顺序写入/读出数据,这样设计,系统的灵活性就大大降低。
如果DSP的分析计算需要特定单元的数据,则系统的效率和速度会因为无效数据的读取而降低。
使用单端口RAM进行数据缓存同样存在一些问题。
由RAM侧看,DSP和A/D转换器是挂在一条总线上的,当从RAM向DSP传输数据的时候,A/D转换器就不能有数据传到该总线上,否则会产生总线冲突,引起芯片损坏。
解决这个问题就需要增加电路。
应用双端口RAM就不存在这个问题,而且使系统结构划分更明确,符合模块化设计思想。
结语
综上所述,利用FPGA芯片的高速工作特性,以及其内部集成嵌入式阵列和大规模逻辑阵列的特点,设计存储器,三态缓存器、地址发生器、以及复杂的时序逻辑电路等,应用于高速数据采集电路中可以使电路大大简化,性能提高。
同时由于FPGA可实现在系统编程(ISP),使系统具有可在线更新、升级容易等特点,是一种较为理想的系统及电路实现方法。
在FPGA中构造存储器
许多系列的FPGA芯片内嵌了存储阵列,如ALTERA EPlK50芯片内嵌了5K字节的存储阵列。
因此,在FPGA中实现各种存储器,如单/双端口RAM、单/双端口ROM、先进先出存储器FIFO等非常方便,而且具有诸多优点。
其硬件可编程的特点允许开发人员灵活设定存储器数据的宽度、存储器的大小、读写控制逻辑等,尤其适用于各种特殊存储要求的场合。
FPGA/FPGA器件可工作于百兆频率以上,其构造的存储器存取速度也可达百兆次/秒以上,这样构成的高速存储器能够胜任存储数据量不太大,但速度要求很高的工作场合。
FPGA中构造存储器主要有两种方法实现。
一是通过硬件描述语言如VHDL、AHDL、Verilog HDL等编程实现。
二是调用MAX+PLUSⅡ自带的库函数实现。
调用库函数方法构造存储器较硬件描述语言输入方式更为方便、灵活、快捷和可靠,故也更常用之。
利用库函数构造双端口RAM
在MAX+PLUSⅡ中有几个功能单元描述库。
prim逻辑元库,包括基本逻辑单元电路,如与、或、非门,触发器、输入、输出引脚等;mf宏功能库,包括TTL数字逻辑单元如74系列芯片;而下文将要详细介绍的参数化双端口RAM模块所在的参数化模块库(mega-lpm)
中,包括各种参数化运算模块(加、减、乘、除)、参数化存储模块(单、双端口RAM、ROM、FIFO等)以及参数化计数器、比较器模块等等。
库中的这些元件功能逻辑描述经过了优化验证,是数字电路设计中的极好选择。
mega-lpm库中共有五种参数化双端口RAM模块:ALTDPRAM、LPM_RAM_DP、CSDPRAM、LPM_RAM_DQ和LPM_RAM_IO。
其中ALTDPRAM和LPM_RAM_DP模块读写有两套总线,读和写有各自的时钟线、地址总线、数据总线和使能端,可同时进行读写操作。
除此之外,ALTDPRAM模块还有一个全局清零端口。
CSDPRAM模块则有a、b两组写端时钟线、地址总线、数据总线和使能端,可同时对RAM进行写操作,但对RAM读、写只能分时进行。
LPM_RAM_DQ模块相对简单,读与写共用一组地址总线,有各自的数据线和时钟线。
LPM_RAM_IO模块只有一组地址总线和数据总线。
mega-1pm函数库中的双端口RAM模块全是参数化调用,这为设计带来极大的方便。
通过对各种参数的取舍、参数设置和组合,再结合读写控制逻辑就可以构造出设计需要的存储器模块。
双端口RAM常见的应用模式主要有以下两种:
1.存储器映像方式。
该方式可以随意对存储器的任何单元进行读写操作。
其主要应用于多CPU的共享数据存储、数据传送等。
该方式中,读、写控制线、地址总线和数据总线有两套。
根据两端口之间数据的传送方向为单向或双向,又有单向数据总线和双向数据总线之分。
2.顺序写方式。
该方式对RAM的写操作只能顺序写入。
这种情况适用于对象特性与时间紧密相关或传送数据与顺序密切相关的场合,如文件传送、时序过程、波形分析等。
根据写控制逻辑的不同,可对RAM进行循环写入或一次写入方式。
该方式下的读操作可以是存储器映像读或顺序读,前一种有较大的灵活性,而后一种则类似于FIFO形式。
在读、写使用独立的地址总线和数据总线时,可以同时对RAM不同单元进行读写操作。
根据不同控制逻辑的要求,对读写时钟、时钟使能端口可以适时设置,以满足控制需要。
我用的FPGA芯片是EP1C6Q240C6,其内部静态RAM容量Up to 294,912 RAM bits (36,864 bytes)
如果作为采集图像的中间缓存,分辨率为1280*1024的一帧图像,图像数据量为1280*1024*8bit(RAW DATA)= 10485760 bit = 1310720 bytes,远远大于RAM的容量,那么就采用双口RAM存储1行的图像数据。
lpm_ram_dp 参数化双端口RAM 宏模块
// synopsys translate_off
`timescale 1 ps / 1 ps
// synopsys translate_on
module dpram16k_1to512_32 (
data,
wren,
wraddress,
rdaddress,
wrclock,
rdclock,
wr_aclr,
rd_aclr,
q);
input [0:0] data;
input wren;
input [13:0] wraddress;
input [8:0] rdaddress;
input wrclock;
input rdclock;
input wr_aclr;
input rd_aclr;
output [31:0] q;
wire [31:0] sub_wire0;
wire [31:0] q = sub_wire0[31:0];
altsyncram altsyncram_component (
.wren_a (wren),
.aclr0 (wr_aclr),
.clock0 (wrclock),
.aclr1 (rd_aclr),
.clock1 (rdclock),
.address_a (wraddress),
.address_b (rdaddress),
.data_a (data),
.q_b (sub_wire0)
// synopsys translate_off
,
.addressstall_a (),
.addressstall_b (),
.byteena_a (),
.byteena_b (),
.clocken0 (),
.clocken1 (),
.data_b (),
.q_a (),
.rden_b (),
.wren_b ()
// synopsys translate_on
);
defparam
altsyncram_component.intended_device_family = "Cyclone",
altsyncram_component.operation_mode = "DUAL_PORT",
altsyncram_component.width_a = 1,
altsyncram_component.widthad_a = 14,
altsyncram_component.numwords_a = 16384, //2^14 = 16384
altsyncram_component.width_b = 32,
altsyncram_component.widthad_b = 9,
altsyncram_component.numwords_b = 512, //2^9 = 512
altsyncram_component.lpm_type = "altsyncram",
altsyncram_component.width_byteena_a = 1,
altsyncram_component.outdata_reg_b = "UNREGISTERED",
altsyncram_component.indata_aclr_a = "CLEAR0",
altsyncram_component.wrcontrol_aclr_a = "CLEAR0",
altsyncram_component.address_aclr_a = "CLEAR0",
altsyncram_component.address_reg_b = "CLOCK1",
altsyncram_component.address_aclr_b = "CLEAR1",
altsyncram_component.outdata_aclr_b = "NONE",
altsyncram_component.power_up_uninitialized = "FALSE";
endmodule
//
双端口RAM的设计与测试(verilog)
以下是用verilog语言写的同步双端口设计文件(来自Actel官方文件中)
写的很精炼
编程风格也不错,该文档中注释的相当明确
自己综合和仿真测试过,没问题
测试
测试写的比设计长多了,写的很好值得学习,也可以直接用在你的设计中`timescale 1 ns/100 ps
module test;
parameter width = 8; // bus width
parameter addr = 8; // # of addr lines
parameter numvecs = 20; // actual number of vectors
parameter Clockper = 1000; // 100ns period
reg [width-1:0] Data;
reg [addr-1:0] WAddress, RAddress;
reg Clock, WE, RE,rst; //addition rst
reg [width-1:0] data_in [0:numvecs-1];
reg [width-1:0] data_out [0:numvecs-1];
wire [width-1:0] Q;
integer i, j, k, numerrors;
ram u0(.data(Data), .q(Q), .clk(Clock),
.rst(rst),
.wen(WE),
.ren(RE), .waddr(WAddress), .raddr(
RAddress));
initial
begin
// sequential test patterns entered at neg edge Clock data_in[0]=8'h00; data_out[0]=8'hxx;
data_in[1]=8'h01; data_out[1]=8'hxx;
data_in[2]=8'h02; data_out[2]=8'hxx;
data_in[3]=8'h04; data_out[3]=8'hxx;
data_in[4]=8'h08; data_out[4]=8'hxx;
data_in[5]=8'h10; data_out[5]=8'hxx;
data_in[6]=8'h20; data_out[6]=8'hxx;
data_in[7]=8'h40; data_out[7]=8'hxx;
data_in[8]=8'h80; data_out[8]=8'hxx;
data_in[9]=8'h07; data_out[9]=8'h01;
data_in[10]=8'h08; data_out[10]=8'h02;
data_in[11]=8'h09; data_out[11]=8'h04;
data_in[12]=8'h10; data_out[12]=8'h08;
data_in[13]=8'h11; data_out[13]=8'h10;
data_in[14]=8'h12; data_out[14]=8'h20;
data_in[15]=8'h13; data_out[15]=8'h40;
data_in[16]=8'h14; data_out[16]=8'h80;
data_in[17]=8'haa; data_out[17]=8'h80;
data_in[18]=8'h55; data_out[18]=8'haa;
data_in[19]=8'haa; data_out[19]=8'h55;
end
initial
begin
rst=0;
Clock = 0;
WE = 0;
RE = 0;
WAddress = 0;
RAddress = 0;
Data = 0;
numerrors = 0;
#200 rst=1; //there rst reset to ram
#200 rst=0;
end
always#(Clockper / 2) Clock = ~Clock;
initial
begin
#2450 WE = 1;
#8000 WE = 0;
RE = 1;
#8000 RE = 0;
WE = 1;
#1000 RE = 1;
end
initial
begin
#1450;
for (k = 0; k <= width; k = k + 1)
#1000 WAddress = k;
WAddress = 0;
end
initial
begin
#9450;
for (j = 0; j <= width; j = j + 1)
#1000 RAddress = j;
RAddress = 0;
end
initial
begin
$display("\nBeginning Simulation...");
//skip first rising edge
for (i = 0; i <= numvecs-1; i = i + 1)
begin
@(negedge Clock);
// apply test pattern at neg edge
Data = data_in;
@(posedge Clock)
#450; //45 ns later
// check result at posedge + 45 ns
$display("Pattern#%d time%d: WE=%b; Waddr=%h; RE=%b; Raddr=%h; Data=%h; Expected Q=%h;Actual Q=%h", i, $stime, WE, WAddress, RE, RAddress,Data, data_out, Q);
if ( Q !== data_out )
begin
$display(" ** Error");
numerrors = numerrors + 1;
end
end
if (numerrors == 0)
$display("Good! End of Good Simulation.");
else
if (numerrors > 1)
$display(
"%0d ERRORS! End of Faulty Simulation.",
numerrors);
else
$display(
"1 ERROR! End of Faulty Simulation.");
#1000 $finish; // after 100 ns later
end
endmodule
设计
为了方便,我将其设计代码也放在这儿,你可以直接用综合和仿真`timescale 1 ns/100 ps
//#############################################
//# Behavioral dual-port SRAM description :
//# Active High write enable (WE)
//# Active High read enable (RE)
//# Rising clock edge (Clock)
//#############################################
module reg_dpram(Data, Q, Clock, WE, RE, WAddress, RAddress);
parameter width = 8;
parameter depth = 8;
parameter addr = 3;
input Clock, WE, RE;
input [addr-1:0] WAddress, RAddress;
input [width-1:0] Data;
output [width-1:0] Q;
reg [width-1:0] Q;
reg [width-1:0] mem_data [depth-1:0];
// ############################################
// # Write Functional Section
// ############################################
always @(posedge Clock)
begin
if(WE)
mem_data[WAddress] = #1 Data;
end
//############################################# //# Read Functional Section
//############################################# always @(posedge Clock)
begin
if(RE)
Q = #1 mem_data[RAddress];
end
endmodule。