verilog 内存建模.docx

上传人:b****6 文档编号:8099266 上传时间:2023-05-12 格式:DOCX 页数:14 大小:61.83KB
下载 相关 举报
verilog 内存建模.docx_第1页
第1页 / 共14页
verilog 内存建模.docx_第2页
第2页 / 共14页
verilog 内存建模.docx_第3页
第3页 / 共14页
verilog 内存建模.docx_第4页
第4页 / 共14页
verilog 内存建模.docx_第5页
第5页 / 共14页
verilog 内存建模.docx_第6页
第6页 / 共14页
verilog 内存建模.docx_第7页
第7页 / 共14页
verilog 内存建模.docx_第8页
第8页 / 共14页
verilog 内存建模.docx_第9页
第9页 / 共14页
verilog 内存建模.docx_第10页
第10页 / 共14页
verilog 内存建模.docx_第11页
第11页 / 共14页
verilog 内存建模.docx_第12页
第12页 / 共14页
verilog 内存建模.docx_第13页
第13页 / 共14页
verilog 内存建模.docx_第14页
第14页 / 共14页
亲,该文档总共14页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

verilog 内存建模.docx

《verilog 内存建模.docx》由会员分享,可在线阅读,更多相关《verilog 内存建模.docx(14页珍藏版)》请在冰点文库上搜索。

verilog 内存建模.docx

verilog内存建模

Verilog中的内存建模收藏

这里的内存模型指的是内存的行为模型。

Verilog中提供了两维数组来帮助我们建立内存的行为模型。

具体来说,就是可以将内存宣称为一个reg类型的数组,这个数组中的任何一个单元都可以通过一个下标去访问。

这样的数组的定义方式如下:

reg[wordsize:

0]array_name[0:

arraysize];

例如:

reg[7:

0]my_memory[0:

255];

其中[7:

0]是内存的宽度,而[0:

255]则是内存的深度(也就是有多少存储单元),其中宽度为8位,深度为256。

地址0对应着数组中的0存储单元。

如果要存储一个值到某个单元中去,可以这样做:

my_memory[address]=data_in;

而如果要从某个单元读出值,可以这么做:

data_out=my_memory[address];

但要是只需要读一位或者多个位,就要麻烦一点,因为Verilog不允许读/写一个位。

这时,就需要使用一个变量转换一下:

例如:

data_out=my_memory[address];

data_out_it_0=data_out[0];

这里首先从一个单元里面读出数据,然后再取出读出的数据的某一位的值。

初始化内存

初始化内存有多种方式,这里介绍的是使用$readmemb和$readmemh系统任务来将保存在文件中的数据填充到内存单元中去。

$readmemb和$readmemh是类似的,只不过$readmemb用于内存的二进制表示,而$readmemh则用于内存内容的16进制表示。

这里以$readmemh系统任务来介绍。

语法

$readmemh("file_name",mem_array,start_addr,stop_addr);

注意的是:

file_name是包含数据的文本文件名,mem_array是要初始化的内存单元数组名,start_addr和stop_addr是可选的,指示要初始化单元的起始地址和结束地址。

下面是一个简单的例子:

modulememory();

reg[7:

0]my_memory[0:

255];

initialbegin

$readmemh("memory.list",my_memory);

end

endmodule

这里使用内存文件memory.list来初始化my_memory数组。

而下面就是一个内存文件的例子。

//Commentsareallowed

CC//Thisisfirstaddressi.e8'h00

AA//Thisissecondaddressi.e8'h01

@55//Jumptonewaddress8'h55

5A//Thisisaddress8'h55

69//Thisisaddress8'h56

对于内存文件,要注意的是下列几点:

(1)注释标记//在内存文件中是被允许的;

(2)使用@符号将跳到新的目标地址,没有@符号就表示地址将顺序递增。

关于这个系统任务,有下列常见的用法:

(1)顺序初始化所有的数组单元;

这种情况下,可以使用@符号来指示地址,也可以不使用它,而只在每一行存放要存放的数据。

这样数据将顺序按地址递增存放,从0地址开始。

(2)只初始化部分的数组单元;

这种情况下,可以使用@符号来指示下一个要初始化的地址,然后对该地址单元进行初始化。

如下列的内存文件就只初始化8'h00,8'h01,8'h55和8'h564个内存地址单元。

//Commentsareallowed

CC//Thisisfirstaddressi.e8'h00

AA//Thisissecondaddressi.e8'h01

@55//Jumptonewaddress8'h55

5A//Thisisaddress8'h55

69//Thisisaddress8'h56

(3)只初始化数组的地址区间的一部分单元。

这个时候,还可以使用$readmemh任务的start_addr和stop_addr选项来指定初始化的范围。

例如,只初始化100到104这5个单元,就可以这么做:

内存文件memory.list定义为:

CC

AA

55

5A

69

而$readmemh("memory.list",my_memory,100,104);就指定使用memory.list来初始化my_memory的100-104单元。

用memorygenerationtool产生的时候有文件名吧,还有生成了些端口。

在你的模块中实例化那个文件就可以了。

比如你生成了文件名是mem

你调用的时候写

memmem0(clk(),.rst(),.addra(),.addrb(),.da(),.db(),.qa(),.qb(),...,);

填上各个端口号就行了。

寄存器的初始化。

你定义了些寄存器,那你在用寄存器的时候多半是在always@里面,那复位的时候就可以付初值

always@(posedgeclkornegedgerst)

if(rst)

reg<=0

elseif()

reg<=...

像这样就可以初始化了。

当然还有其他方法,多看书多编程就好了

`timescale1ns/100ps

modulereadmem_tb;

reg[7:

0]Mem[0:

'h7ff];

initial

begin

$readmemh("frame.mif",Mem);

end

Endmodule

1@000

2//***********1sframedata**************/

3000102030405060708090A0B0C0D0E0F

4101112131415161718191A1B1C1D1E1F

520212223000000000000000000000000

630313233000000000000000000000000

740414243000000000000000000000000

850515253000000000000000000000000

960616263000000000000000000000000

10707172737475767778797A7B7C7D7E7F

11808182838485868788898A8B8C8D8E8F

1290919293111111111111111111111111

13A0A1A2A3111111111111111111111111

14B0B1B2B3111111111111111111111111

15C0C1C2C3111111111111111111111111

16D0D1D2D3111111111111111111111111

17E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF

18F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF

19

20@100

21//***********2dframedata**************/

22000102030405060708090A0B0C0D0E0F

23101112131415161718191A1B1C1D1E1F

2420212223222222222222222222222222

2530313233222222222222222222222222

2640414243222222222222222222222222

2750515253222222222222222222222222

2860616263222222222222222222222222

29707172737475767778797A7B7C7D7E7F

30808182838485868788898A8B8C8D8E8F

3190919293333333333333333333333333

32A0A1A2A3333333333333333333333333

33B0B1B2B3333333333333333333333333

34C0C1C2C3333333333333333333333333

35D0D1D2D3333333333333333333333333

36E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF

37F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF

FFT算法的一种FPGA实现

[2006-12-211:

11:

00|By:

skycanny]

0

推荐

1引言

  OFDM(正交频分复用)是一种多载波数字调制技术,被公认为是一种实现高速双向无线数据通信的良好方法。

在OFDM系统中,各子载波上数据的调制和解调是采用FFT(快速傅里叶变换)算法来实现的。

因此在OFDM系统中,FFT的实现方案是一个关键因素。

其运算精度和速度必须能够达到系统指标。

对于一个有512个子载波,子载波带宽20kHz的OFDM系统中,要求在50μs内完成512点的FFT运算。

  硬件实现FFT算法的主要方案有:

DSP(通用数字信号处理器);FFT专用芯片;FPGA(现场可编程门阵列)。

DSP具有纯软件实现的灵活性,适合用于流程复杂的算法,例如在通信系统中的信道编、解码,QAM映射等算法。

如果在DSP中完成FFT运算,不仅要占用大量DSP的运算时间,使整个系统的数据吞吐率降低,也无法发挥DSP软件实现的灵活性。

因此,前端的FFT运算应由ASIC或FPGA完成。

采用专用的FFT处理芯片,虽然速度能达到要求,但其可扩展性差。

FPGA具有硬件结构可重构的特点。

适合于算法结构固定、运算量大的前端数字信号处理。

新近推出的FPGA产品都采用多层布线结构,更低的核心电压,更丰富的IO管脚,容量可达到100k个逻辑单元(LES),内置嵌入式RAM资源,内部集成多个数字锁相环,多个嵌入的硬件乘法器,所有这一切都使得FPGA在数字信号处理领域显示出自己特有的优势。

  本设计根据OFDM系统的实际需要,提出一种用FPGA实现FFT运算的方案,并以64点FFT为例,在QuartusII软件上通过了综合和仿真。

2方案分析

  FFT是DFT(离散傅里叶变换)的快速算法。

以图1为例,N点FFT共需要log2N级运算,每级需要N/2个蝶型运算。

因为系统中FFT运算点数为2的奇数次方,因此本设计采用的是按时间抽取的基二FFT运算。

  FPGA实现FFT需考虑的问题有:

  

(1)整体实现结构的设计。

  对FFT算法进行合理的模块划分,各个模块在中央控制单元的管理下并行工作,实现框图如图2所示。

  

(2)数据格式和长度的选择

  通常的数据表示方法有3种:

浮点,定点和块浮点。

浮点数用2组固定的bit来表示指数和小数,动态范围大。

只要表示指数的位数足够多,浮点运算就不会发生溢出现象。

但是完成浮点运算所需要的电路复杂,运算速度慢。

定点数小数点位置固定,用固定的bit来表示整数和小数部分。

定点数动态范围小,容易溢出。

但是其运算电路简单、速度快。

块浮点介于浮点和定点之间。

在FFT运算过程中,逐级进行溢出判断和移位选择,实现动态范围扩展。

  本方案采用块浮点运算。

即小数点的位置随中间运算结果动态范围的增大而右移。

入口基带数据为10b补码格式。

FFT运算结果输出2路10b。

实部、虚部各一路,补码表示。

由于入口数据精度的限制,旋转因子的实部、虚部取和输入数据同样的量化位数[3]。

  (3)地址生成单元的设计

  在FFT中,输入数据和旋转因子的寻址是设计中最灵活的部分。

一种容易想到的实现方案是利用RAM为每级蝶型运算生成地址查找表,蝶型运算序数作为查找表地址,读出的内容为输入数据和旋转因子地址。

优点是地址生成速度快,缺点是消耗大量的片上RAM资源。

N点FFT需要log2Nb表示地址。

N/2个旋转因子用log2(N/2)b表示地址。

每级地址查找表都需要(N·log2N+N/2·log(N/2))b。

  本文设计的一种地址生成逻辑,采用流水线结构,通过简单的移位和加法运算,延迟4个时钟得到数据地址,并对原理进行了详细的阐述。

3实现技术

  整体设计分为数据接收部分和FFT运算部分。

数据接收部分工作在频率低的clk_data。

其他逻辑工作在频率高的clk_fast。

两个时钟通过双口的接收RAM进行衔接。

3.1数据接收单元

  数据接收单元将每帧数据按码位倒置的顺序乒乓存入接收RAM1或接收RAM2。

数据接收模块有2b的标志信息通知给中心控制单元。

中心控制单元根据标志位,交替的对接收RAM中的数据进行处理。

当中央控制单元将接收RAM中的数据取出,经过蝶型运算,结果存入运算RAM1的同时,上一帧数据的FFT运算结果从运算RAM2取出。

  为了实现乒乓操作,接收RAM共有2块,用FPGA的片上RAM实现,为SimpleDualPort模式。

读、写端口分别有自己独立的数据总线、地址总线和工作时钟。

数据接收单元控制写端口,中心控制单元控制读端口。

3.2运算单元

  运算单元由蝶型运算器和运算RAM组成。

  蝶型运算器完成对输入数据A,B的蝶型运算。

设输

 

  从上两式可以看出共有2个乘累加项(bpWp-bqWq)和(bpWq+bqWp)。

蝶型运算的各个模块都利用QuartusII开发软件中所提供的宏单元生成。

整个蝶型运算为同步逻辑,共需要4个时钟周期。

输入数据A,B为10b,因此乘累加运算单元输出为20b,才能保证不溢出。

乘累加运算单元输出结果,截断低8位(蝶型运算因子低8位为小数),保留整数部分输入到加法器、减法器。

同时为了使数据同步,数据通过寄存器组延迟3个时钟。

同时符号扩展2位,与乘累加单元输出结果对齐。

  从以上描述和FFT算法的分析[2]表明:

每级FFT运算结果的动态范围最多需要扩展2b。

因此中间运算结果的位宽应为24b,实部、虚部各12b。

在进行下一级蝶型运算时,12b数据被取出。

按照块浮点单元记录的动态范围扩展bit数,完成定标。

截取其中的10b送入蝶型运算器。

  运算RAM1和运算RAM2作为FFT的中间的数据缓存。

两块RAM交替作为数据读出和运算结果写入单元。

直到第6级蝶型运算完成。

运算RAM采用TrueDualPortMemory模式。

每一侧的端口均可在独立的工作时钟下完成随机地址数据的读或者写操作。

即可以同时将蝶型运算所需要的两个入口数据读出,也可以同时将两个运算结果写入。

比单口RAM减小了一半的读写周期。

3.3旋转因子查找表

  旋转因子查找表,存储FFT运算所需要的旋转因子

 

3.4地址产生单元

  地址产生单元产生每一级蝶型运算所需的两个输入数据和旋转因子的地址。

从图1可以看到,该FFT实现结构为原位运算。

蝶型运算的结果仍然写回输入数据读出地址。

因此需将读数据地址延迟若干时钟周期,作为运算结果写入地址。

  对于N点FFT运算,设m∈(0…log2N-1),n∈(0…N/2-1)表示第m级的第n个蝶型运算。

addr_A,addr_B,(addr_A

观察地址生成规律:

每一级蝶型运算可分为若干组(0…N/2/2m-1),每组2m个蝶型运算,两个入口数据共占用2m+1个地址。

则m级的第n个蝶型运算的位置为第n/2m组的第n%2m个。

因此:

 

  (n/2m)*2m+1操作可描述为将n的低m位清零,再左移1位。

n%2m描述为取n的低mb位。

为了获得更高频率的工作时钟,将该运算拆分为较小的逻辑单元,分为4个节拍流水输出。

  N点FFT共需要旋转因子N/2个,

r=(0…N/2-1)。

设addr_w为当前蝶型运算所对应的旋转因子查找表入口地址。

从时间抽取FFT算法的推导过程[2]可知第m级蝶型运算所需旋转因子的排列规律。

即在0~N/2-1的数中,可以被2整除的数,按从小到大的顺序重复排列,规则为:

 

需要移位和求余两种简单的运算。

  在图1的8点FFT中,第1级

而在0~3中间可以被2整除的数为0和2。

因此4个旋转因子地址为(0,2,0,2),进而又可以表示为(0×2)%4,(1×2)%4,(2×2)%4,(3×2)%4。

64点FFT旋转因子有32个,共需要5b表示地址。

第m级,第n个蝶型运算的WrN地址可由逻辑移位的方法快速得到。

将n(5b)左移位(5-m),低位补0,就得到了在(0…31)中的addr_w。

  地址产生单元框图见图5。

m,n作为输入,4个时钟周期后产生所需要的地址。

3.5块浮点单元

  每级蝶型运算结果动态范围最大需要扩展2b。

块浮点单元对蝶型运算结果的高3位进行检测,判断当前结果动态范围扩展bit数,记录当前级的最大扩展bit数。

下一级蝶型运算时,根据前一级的最大扩展bit数,对读出的数据进行定标,选取数据送入蝶型运算器。

块浮点单元将每一级运算结果动态范围扩展bit数进行累加,和FFT运算结果一同输出。

3.6中央控制单元

  中央控制单元为整个系统的控制核心。

功能模块之间的数据传递均通过中央控制单元。

中央控制单元记录当前蝶型运算所处的级数和个数,传递给地址产生单元。

地址产生单元在经过4个时钟后产生数据和旋转因子的地址。

中央控制单元读取地址,向处于伺服状态的运算RAM和旋转因子查找表发出读使能请求。

2个时钟延迟以后,数据和旋转因子被读出。

读出的数据进行必要的延迟和定标处理,送给蝶型运算器。

4个时钟延迟后,运算结果将被连续算出并写入RAM。

设计中共用了2个时钟。

4验证与仿真

  采用自顶向下的设计思路,完成系统设计和各个功能模块的VHDL代码编写。

使用QuartusII针对EP1C6F256完成了综合和仿真,占用30%的逻辑单元和90%的片上RAM。

clk_fast最快87.23MHz。

第一级蝶型运算需要75个时钟,以后各级需要45个时钟,完成64点FFT共需要300个时钟共3.4μs。

完成512点FFT,需要2691个时钟共33.63μs,OFDM符号速率可以达到29kS/s。

5结语

  本设计的主要特点有:

  

(1)采用pipleline流水结构,各个单元协调同步工作。

若FFT点数增大,流水线初始化时钟开销比率将显著降低。

  

(2)使用了2块双口RAM作为中间结果存储器,1个时钟可以同时读出或者写入2个操作数。

减少了访问存储器的时钟消耗。

  (3)块浮点单元的使用,定点数动态范围根据实际运算结果进行扩大,在实现复杂度,运算速度与运算精度方面得到了折衷。

  (4)简易快捷的地址生成逻辑。

  (5)模块化设计。

设计中的各个逻辑单元功能单一,具有通用性,可以方便地扩展为更多点数的FFT处理模块。

也可以将log2N个该FFT单元进行极连,在同等时延下,可使数据吞吐率扩大log2N倍。

又由于FFT和IFFT运算的对偶性:

  

  可以在FFT运算单元的数据入口和数据出口处添加取共轭逻辑来实现IFFT运算。

  本设计提出了一种用FPGA完成FFT运算的方案,其运算结果经过验证达到设计要求。

为以后使用FPGA完成OFDM实时通信系统中的调制和解调打下了基础,也是使用FPGA完成DSP运算的一次有益尝试。

参考文献

[1]褚振勇,翁木云.FPGA设计及应用[M].西安:

西安电子科技大学出版社,2002.

[2]AlanVOppenheim,RonaldWSchafer,JohnRBuck.离散时间信号处理[M].第2版.刘树棠,黄建国译.西安:

西安交通大学出版社,2001.

[3]FFTMegaCoreFunctionUserGuide.

[4]韩颖,王旭,吴嗣亮.FPGA实现高速FFT处理器的设计[J].电讯技术,2003,

(2).

用FPGA实现FFT算法(图)

作者:

 日期:

2002-2-1 来源:

本网

字符大小:

【大】【中】【小】

西安电子科技大学通信工程学院罗雪苟詹阳

引言

  DFT(DiscreteFourierTransformation)是数字信号分析与处理如图形、语音及图像等领域的重要变换工具,直

接计算DFT的计算量与变换区间长度N的平方成正比。

当N较大时,因计算量太大,直接用DFT算法进行谱分析和信号的实时处理是不切实际的。

快速傅立叶变换(FastFourierTransformation,简称FFT)使DFT运算效率提高1~2个数量级。

其原因是当N较大时,对DFT进行了基4和基2分解运算。

FFT算法除了必需的数据存储器ram和旋转因子rom外,仍需较复杂的运算和控制电路单元,即使现在,实现长点数的FFT仍然是很困难。

本文提出的FFT实现算法是基于FPGA之上的,算法完成对一个序列的FFT计算,完全由脉冲触发,外部只输入一脉冲头和输入数据,便可以得到该脉冲头作为起始标志的N点FFT输出结果。

由于使用了双ram,该算法是流型(Pipelined)的,可以连续计算N点复数输入FFT,即输入可以是分段N点连续复数数据流。

采用DIF(DecimationInFrequency)-FFT和DIT(DecimationInTime)-FFT对于算法本身来说是无关紧要的,因为两种情况下只是存储器的读写地址有所变动而已,不影响算法的结构和流程,也不会对算法复杂度有何影响。

算法实现的可以是基2/4混合基FFT,也可以是纯基4FFT和纯基2FFT运算。

傅立叶变换和逆变换

对于变换长度为N的序列x(n)其傅立叶变换可以表示如下:

     

N

nk

X(k)=

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 求职职场 > 简历

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2