fpga开发-存储器及其应用
目录
ROM
RAM
FIFO
以结构化方式存储大量二值信息的半导体器件。存储器单元数×每个单元的存储位数
掩膜ROM PROM EPROM *E2PROM *快闪存储器 *静态RAM(SRAM) *动态RAM(DRAM),存储器分为ROM和RAM两种基本类型。ROM分为单口ROM和双口ROM两种类型。RAM分为单口RAM、双口RAM和伪双端口RAM三种类型。除ROM和RAM之外,在数字系统设计中还经常应用一类特殊的存储器,称为FIFO,具有先进先出的特性,用于串行数据的缓存和跨时钟域数据的传输。
ROM
ROM本质上为组合逻辑电路。小容量的ROM可以直接应用case语句定义存储数据。描述二进制显示译码的16×7位ROM的Verilog代码参考如下
module rom_16x7b(bincode,oHex7); input [3:0] bincode;output reg [6:0] oHex7;// 存储数据描述always @( bincode )case ( bincode ) // gfedcba, 高电平有效4'b0000 : oHex7 = 7'b0111111; // 显示 04'b0001 : oHex7 = 7'b0000110; // 显示 1 4'b0010 : oHex7 = 7'b1011011; // 显示 24'b0011 : oHex7 = 7'b1001111; // 显示 34'b0100 : oHex7 = 7'b1100110; // 显示 44'b0101 : oHex7 = 7'b1101101; // 显示 54'b0110 : oHex7 = 7'b1111101; // 显示 64'b0111 : oHex7 = 7'b0000111; // 显示 74'b1000 : oHex7 = 7'b1111111; // 显示 84'b1001 : oHex7 = 7'b1101111; // 显示 94'b1010 : oHex7 = 7'b1110111; // 显示 A4'b1011 : oHex7 = 7'b1111100; // 显示 b4'b1100 : oHex7 = 7'b0111001; // 显示 c4'b1101 : oHex7 = 7'b0011110; // 显示 d4'b1110 : oHex7 = 7'b1111001; // 显示 E4'b1111 : oHex7 = 7'b1110001; // 显示 Fdefault : oHex7 = 7'b0000000; // 不显示endcase
endmodule
设计数码序列控制电路,能够在单个数码管上依次循环显示自然数序列(0~9)、奇数序列(1、3、5、7、9)、音乐序列(0~7)和偶数序列(0、2、4、6、8)。
分析:自然序列有10个数码,奇数序列和偶数序列分别有5个数码,音乐顺序有8个数码,因此一个完整的显示循环共有28个数码。因此,先描述28×4位的ROM,并应用case语句定义28个单元的数据为序列BCD码,再设计一个28进制计数器,将计数器的状态作为ROM的地址,驱动ROM输出BCD码序列,最后再应用显示译码器将BCD码译为七段码输出,驱动数码管显示相应的数字。
设计过程: 描述数码序列控制电路的Verilog代码参考如下:
module SEG_controller(iclk,rst_n,oseg7);input iclk,rst_n;output reg [6:0] oseg7;// 内部寄存器变量定义reg [4:0] cnt_q;reg [3:0] disp_bcd; // 时序逻辑过程,描述28进制计数器always @( posedge iclk or negedge rst_n ) if ( !rst_n ) cnt_q <= 5'd0; else if ( cnt_q == 5'd27 )cnt_q <= 5'd0;elsecnt_q <= cnt_q + 1'b1;// 组合逻辑过程,定义显示序列BCD码always @( cnt_q ) case( cnt_q )5'd0 : disp_bcd = 4'd0;5'd1 : disp_bcd = 4'd1;5'd2 : disp_bcd = 4'd2;5'd3 : disp_bcd = 4'd3;5'd4 : disp_bcd = 4'd4;5'd5 : disp_bcd = 4'd5;5'd6 : disp_bcd = 4'd6;5'd7 : disp_bcd = 4'd7;5'd8 : disp_bcd = 4'd8;5'd9 : disp_bcd = 4'd9;5'd10 : disp_bcd = 4'd1;5'd11 : disp_bcd = 4'd3;5'd12 : disp_bcd = 4'd5;5'd13 : disp_bcd = 4'd7;5'd14 : disp_bcd = 4'd9;5'd15 : disp_bcd = 4'd0;5'd16 : disp_bcd = 4'd1;5'd17 : disp_bcd = 4'd2;5'd18 : disp_bcd = 4'd3; 5'd19 : disp_bcd = 4'd4;5'd20 : disp_bcd = 4'd5;5'd21 : disp_bcd = 4'd6;5'd22 : disp_bcd = 4'd7;5'd23 : disp_bcd = 4'd0;5'd24 : disp_bcd = 4'd2;5'd25 : disp_bcd = 4'd4;5'd26 : disp_bcd = 4'd6;5'd27 : disp_bcd = 4'd8;default : disp_bcd = 4'd0;endcasealways @ (posedge iclk) // 显示译码输出 case ( disp_bcd ) 4'd0 : oseg7 <= 7'b1000000; 4'd1 : oseg7 <= 7'b1111001;4'd2 : oseg7 <= 7'b0100100;4'd3 : oseg7 <= 7'b0110000;4'd4 : oseg7 <= 7'b0011001;4'd5 : oseg7 <= 7'b0010010;4'd6 : oseg7 <= 7'b0000010;4'd7 : oseg7 <= 7'b1111000;4'd8 : oseg7 <= 7'b0000000;4'd9 : oseg7 <= 7'b0010000;default : oseg7 <= 7'b1111111;endcase
endmodule
一般地,通用ROM可以用寄存器数组描述,然后将定义存储数据的存储器初始化数据文件(.mif或者.hex)加载到寄存器数组中实现。
设计音乐播放控制模块,将文件swanlake_scene_notes.mif加载到《天鹅湖》场景音乐ROM中,然后按音符的时长控制播放。
module swanlake_controller( clk2p8Hz,rst_n,tone_fpdat );input clk2p8Hz;input rst_n;output reg [11:0] tone_fpdat;reg [15:0] swanlake_scene_rom [0:83] /* synthesis ram_init_file ="swanlake_scene_notes.mif" */; reg [3:0] beat_cnt; // 节拍计数reg [6:0] tone_addr; // 音调地址always @( posedge clk2p8Hz or negedge rst_n ) if ( !rst_n ) begintone_addr = 7'd0;beat_cnt = swanlake_scene_rom[0][15:12];tone_fpdat = swanlake_scene_rom[0][11:0]; endelse beginbeat_cnt = beat_cnt - 1'b1; if ( beat_cnt == 0 )if ( tone_addr == 7'd83 ) tone_addr = 7'd0;else begin tone_addr = tone_addr + 1'b1;beat_cnt = swanlake_scene_rom[tone_addr][15:12];tone_fpdat = swanlake_scene_rom[tone_addr][11:0]; endend
endmodule
上升沿检测电路的工作原理是: 应用三级(或两级)移位寄存器,在440kHz时钟脉冲的作用下,对2.8Hz时钟信号进行采样并依次右移存入移位寄存器中。当移位寄存器中的存储数据为“x10”时,则输出时钟上升沿检测标志脉冲。
描述上升沿检测模块的Verilog代码参考如下:
module rising_edge_det (clock, clkin, detout );input clock; // 440kHzinput clkin; // 2.8Hzoutput wire detout; // 检测输出reg [0:2] dat_reg; // 移位寄存器定义// 右移存入过程,以消除亚稳态always @ ( posedge clock ) dat_reg <= { clkin,dat_reg[0:1] };// 上升沿检测逻辑assign detout = dat_reg[1] & ~dat_reg[2] ;
endmodule
RAM
在基于FPGA的数字系统设计中,构建小容量RAM有两种方法:一是应用RAM IP核,基于片上存储资源构建;二是应用Verilog HDL描述,基于FPGA内部逻辑资源构建。构建片内RAM的原则是,构建较大的存储器应用片上存储资源,构建较小的存储器可以使用代码直接描述。
单口(Single-Port)RAM具有一组地址线、一组输入数据线和一组数据输出线。由于读/写时共用时钟和地址线, 所以单口RAM的读操作和写操作不能同时进行。
1024×8位单口RAM功能描述。
module RAM_1port #( parameter ADDR_WIDTH=10,DATA_WIDTH=8 )( clock,data,wren,address,q );localparam RAM_DEPTH = 1 << ADDR_WIDTH;input clock;input [DATA_WIDTH-1:0] data; input wren;input [ADDR_WIDTH-1:0] address;output wire [DATA_WIDTH-1:0] q;// 定义存储器reg [DATA_WIDTH-1:0] mem [RAM_DEPTH-1:0];always @ ( posedge clock ) // 存储过程if ( wren ) mem[address] <= data;assign q = mem[address]; // 直接输出型endmodule
双口(Dual-Port)RAM具有两组地址线、两组输入数据线和两组输出数据线,分为单时钟和双时钟两种类型, 由于双口RAM具有两组独立的读写端口,因此读写可以同时进行。双口RAM在异构系统中应用广泛,可以通过双口RAM实现跨时钟域数据的传输。 但是,如果双口RAM的两个端口同时对同一个存储单元进行读写操作,就会引发冲突。
伪双口(Simple Dual-Port)RAM与双口RAM的区别在于一个端口只读,另一个端口只写,而双口RAM的两组端口都可以进行读写。 1024×8位伪双口RAM的电路框图如图所示,其中inclock和outclock分别为写时钟和读时钟,data为写数据输入端,wraddress为写地址端,wren为写使能信号,rdaddress为读地址端,rden为读使能信号,q为读数据输出端。
FIFO
FIFO为先进先出(first-in first-out)的缓存器,通常由双口RAM附加读写逻辑电路构成。与双口RAM不同的是,FIFO没有外部地址线,只能按顺序写入和读出,而双口RAM可以根据地址对指定的存储单元进行读写。 描述FIFO有宽度(width)和深度(depth)两个主要参数,其中宽度表示每个存储单元能够存储二进制数据的位数,而深度表示存储单元的个数。
根据FIFO读写时钟的差异,将FIFO分为同步FIFO和异步FIFO两种类型。
同步FIFO的读操作和写操作基于同一时钟,使用读/写指针(即地址)指定数据的读写单元。写指针wp(write pointer)总是指向下一个要写入数据的单元。读指针rp(read pointer)总是指向下一个要读出数据的单元。
由于同步FIFO的读/写时钟相同,所以可以通过统计数据存储个数来产生空标志(empty)和满标志(full)。FIFO初始化时数据存储个数设置为0,写入一个数据后存储个数加1,读出一个数后后存储个数减1。当数据存储个数为0时,产生empty标志;当数据存储个数等于FIFO的深度时,产生full标志。
1024×8位同步FIFO功能描述。
`timescale 1ns/1psmodule sync_FIFO #( FIFO_WIDTH=8 )(input FIFO_clk, // FIFO时钟input FIFO_rst_n, // 复位信号,低电平有效input FIFO_rdreq, // 读请求信号 input FIFO_wrreq, // 写请求信号input [FIFO_WIDTH-1:0] wdata, // 需要写入的数据output reg [FIFO_WIDTH-1:0] rdata, // 读出的数据output wire full, // FIFO满标志output wire empty // FIFO空标志);// FIFO存储深度定义parameter FIFO_ADDR=10;localparam FIFO_DEPTH = 1 << FIFO_ADDR;// 内部线网和变量定义wire rden,wren; // 允许读信号和允许写信号
reg [FIFO_ADDR-1:0] rp,wp; // 读指针和写指针
reg [FIFO_ADDR:0] used_cnt; // 存储数据个数统计
// 描述FIFO存储实体
reg [FIFO_WIDTH-1:0] FIFO_mem [FIFO_DEPTH-1:0];
// 读写允许逻辑定义
assign rden = FIFO_rdreq && !empty ;
assign wren = FIFO_wrreq && !full ;
// 状态标志逻辑,高电平有效
assign empty = ( used_cnt == 0 );
assign full = ( used_cnt == FIFO_DEPTH );
always @( posedge FIFO_clk ) // 读过程if ( rden ) rdata <= FIFO_mem[rp];
always @( posedge FIFO_clk ) // 写过程if ( wren ) FIFO_mem[wp] <= wdata;
// 读指针处理过程
always @ ( posedge FIFO_clk or negedge FIFO_rst_n)if ( !FIFO_rst_n ) rp <= 0;else if( rden ) rp <= rp + 1;
// 写指针处理过程
always @ ( posedge FIFO_clk or negedge FIFO_rst_n)if ( !FIFO_rst_n ) wp <= 0;else if( wren ) wp <= wp + 1;
// 数据存储个数统计
always @( posedge FIFO_clk or negedge FIFO_rst_n )if ( !FIFO_rst_n )used_cnt <= 0;else case ({rden,wren})2'b01: if ( used_cnt != FIFO_DEPTH ) used_cnt <= used_cnt + 1'b1;2'b10: if ( used_cnt != 0 ) used_cnt <= used_cnt - 1'b1;default: used_cnt <= used_cnt;endcase
endmodule