学习内容
RAM、ROM、FIFO等概念对比,SRAM读写测试。
实现功能
一个8位数字从0000000开始,每一秒自加1,并将每一秒的数字存入SRAM中,并在这一秒内读出存入的数字用八位的LED灯来显示。
开发环境
xilinx spartan6开发板、ISE14.7、modelsim10.5、verilog
存储器相关知识
这篇文章学习的是SRAM的读写测试,但是我借这个机会好好再明确一下RAM、ROM、FIFO的概念,对比一下他们的区别。
RAM
随机存取存储器(random access memory,RAM)又称作“随机存储器”,是与CPU直接交换数据的内部存储器,也叫主存(内存)。它可以随时读写,而且速度很快,通常作为操作系统或其他正在运行中的程序的临时数据存储媒介。
存储单元的内容可按需随意取出或存入,这种存储器在断电时将丢失其存储内容,故主要用于存储短时间使用的程序。 按照存储单元的工作原理,随机存储器又分为静态随机存储器SRAM(英文:StaTIc RAM,SRAM)和动态随机存储器DRAM(英文Dynamic RAM,DRAM)。
RAM的特点主要有:
1.随机存储:指的是当存储器中的数据被读取或写入时,所需要的时间与这段信息所在的位置或所写入的位置无关。
2.易失性:当RAM被断电以后保存在上面的数据会自动消失,而ROM不会自动消失可以长时间断电保存,
3.对静电敏感:随机存取存储器对环境的静电荷非常敏感。静电会干扰存储器内电容器的电荷,引致数据流失,甚至烧坏电路。故此触碰随机存取存储器前,应先用手触摸金属接地。
4.访问速度快:现代的随机存取存储器几乎是所有访问设备中写入和读取速度最快的。
5.需要刷新:现代的随机存取存储器依赖电容器存储数据。电容器充满电后代表1(二进制),未充电的代表0。由于电容器或多或少有漏电的情形,若不作特别处理,数据会渐渐随时间流失。刷新是指定期读取电容器的状态,然后按照原来的状态重新为电容器充电,弥补流失了的电荷。需要刷新正好解释了随机存取存储器的易失性。
SRAM速度非常快。是目前读写最快的存储设备,但是比较昂贵,所以只在要求比较苛刻的地方使用,比如CPU的一二级缓冲;
DRAM保留数据的时间很短,我们计算机的内存就是DRAM。
ROM
只读存储器(Read-Only Memory,ROM)以非破坏性读出方式工作,只能读出无法写入信息。信息一旦写入后就固定下来,即使切断电源,信息也不会丢失,所以又称为固定存储器。ROM所存数据通常是装入整机前写入的,整机工作过程中只能读出,不像随机存储器能快速方便地改写存储内容。ROM所存数据稳定 ,断电后所存数据也不会改变,并且结构较简单,使用方便,因而常用于存储各种固定程序和数据。进一步发展出可编程只读存储器(PROM)、可擦可编程序只读存储器(EPROM)和带电可擦可编程只读存储器(EEPROM)等不同的种类。
FIFO
FIFO( First Input First Output)简单说就是指先进先出。在系统设计中,以增加数据传输率、处理大量数据流、匹配具有不同传输率的系统为目的而广泛使用FIFO存储器,从而提高了系统性能。FIFO存储器是一个先入先出的双口缓冲器,即第一个进入其内的数据第一个被移出,其中一个是存储器的输入口,另一个口是存储器的输出口。对于单片FIFO来说,主要有两种结构:
- 触发导向传输结构的FIFO是由寄存器阵列构成的。
- 零导向传输结构的FIFO是由具有读和写地址指针的双口RAM构成。
同时分为同步FIFO和异步FIFO
- 同步FIFO,读和写应用同一个时钟。它的作用一般是做交互数据的一个缓冲,也就是说它的主要作用就是一个buffer。
- 异步FIFO,读写应用不同的时钟,它有两个主要的作用,一个是实现数据在不同时钟域进行传递,另一个作用就是实现不同数据宽度的数据接口。
SRAM读写分析
你找来任何一颗 SRAM 芯片的 datasheet,会发现它们的时序操作大同小异,在这里总结一些它们共性的东西,也提一些用 Verilog 简单的快速操作 SRAM 的技巧。SRAM 内部的结构如图 7.38 所示,要访问实际的 Momory 区域,FPGA 必须送地址(A0-A14)和控制信号(CE#\OE#\WE#),SRAM 内部有与此对应的地址译码(decoder)和控制处理电路(control circuit)。这样,数据总线(I/O0-I/O7)上的数据就可以相应的读或写了。
本文以特权同学开发板上的 IS62LV256-45U为例进行说明,其管脚定义如下表所示。
序号 | 管脚 | 方向 | 功能 |
---|---|---|---|
1 | A 0 − A 14 {A_0} - {A_{14}} A0−A14 | input | 地址总线 |
2 | CE | input | 芯片使能输入,低有效 |
3 | OE | input | 输出使能输入,低有效 |
4 | WE | input | 写使能输入,低有效 |
5 | I O 0 − I O 7 I{O_0} - I{O_7} IO0−IO7 | inout | 数据总线 |
6 | VCC | input | 电源 |
7 | GND | input | 数字地 |
SRAM芯片接口如下图所示。
SRAM的读操作时序图如下图所示。
SRAM的写操作时序图如下图所示。
简单介绍一下读/写操作是怎么进行的:
具体操作是这样的,要写数据时,(这里是相对于用 FPGA 操作 SRAM 而言的,软件读写可能有时间顺序的问题需要注意),比较高效率的操作是送数据和地址,把 CE#和 WE#拉低。然后延时tWC 时间再把 CE#和 WE#拉高,这时就把数据写入了相应地址了,就这么简单。读数据就更简单了,只要把需要读出的地址放到 SRAM 的地址总线上,把 CE#和 OE#拉低,然后延时tAA时间后就可以读出数据了。
SRAM读写时序表如下图所示
verilog代码分为三个部分,顶层文件sp6.v
子模块test_timing.v
sram_controller.v
顶层文件sp.v如下
module sp6(
input ext_clk_25m, //外部输入25MHz时钟信号
input ext_rst_n, //外部输入复位信号,低电平有效
output[7:0] led, //LED指示灯,点亮表示读写SRAM同一个地址正确,熄灭表示读写SRAM同一个地址失败。
output sram_cs_n, // SRAM片选信号,低电平有效。
output sram_we_n, // SRAM写选通信号,低电平有效。
output sram_oe_n, // SRAM输出选通信号,低电平有效。
output[14:0] sram_addr, // SRAM地址总线。
inout[7:0] sram_data // SRAM数据总线。
);
//-------------------------------------
//PLL例化
wire clk_12m5; //PLL输出12.5MHz时钟
wire clk_25m; //PLL输出25MHz时钟
wire clk_50m; //PLL输出50MHz时钟
wire clk_65m; //PLL输出65MHz时钟
wire clk_108m; //PLL输出108MHz时钟
wire clk_130m; //PLL输出130MHz时钟
wire sys_rst_n; //PLL输出的locked信号,作为FPGA内部的复位信号,低电平复位,高电平正常工作
pll_controller uut_pll_controller
(// Clock in ports
.CLK_IN1(ext_clk_25m), // IN
// Clock out ports
.CLK_OUT1(clk_12m5), // OUT
.CLK_OUT2(clk_25m), // OUT
.CLK_OUT3(clk_50m), // OUT
.CLK_OUT4(clk_65m), // OUT
.CLK_OUT5(clk_108m), // OUT
.CLK_OUT6(clk_130m), // OUT
// Status and control signals
.RESET(~ext_rst_n),// IN
.LOCKED(sys_rst_n)); // OUT
//-------------------------------------
//每秒钟定时SRAM读和写时序产生模块
wire sramwr_req; // SRAM写请求信号,高电平有效,用于状态机控制。
wire sramrd_req; // SRAM读请求信号,高电平有效,用于状态机控制。
wire[7:0] sramwr_data; // SRAM写入数据寄存器。
wire[7:0] sramrd_data; // SRAM读出数据寄存器。
wire[14:0] sramwr_addr; // SRAM写入地址寄存器。
wire[14:0] sramrd_addr; // SRAM读出地址寄存器。
test_timing uut_test_timing(
.clk(clk_50m), //时钟信号
.rst_n(sys_rst_n), //复位信号,低电平有效
.led(led[0]), //LED指示灯,点亮表示读写SRAM同一个地址正确,熄灭表示读写SRAM同一个地址失败。
.sramwr_req(sramwr_req), // SRAM写请求信号,高电平有效,用于状态机控制。
.sramrd_req(sramrd_req), // SRAM读请求信号,高电平有效,用于状态机控制。
.sramwr_data(sramwr_data), // SRAM写入数据寄存器。
.sramrd_data(sramrd_data), // SRAM读出数据寄存器。
.sramwr_addr(sramwr_addr), // SRAM写入地址寄存器。
.sramrd_addr(sramrd_addr) // SRAM读出地址寄存器。
);
//-------------------------------------
//SRAM的基本读写时序模块
sram_controller uut_sram_controller(
.clk(clk_50m), //时钟信号
.rst_n(sys_rst_n), //复位信号,低电平有效
.sramwr_req(sramwr_req), // SRAM写请求信号,高电平有效,用于状态机控制。
.sramrd_req(sramrd_req), // SRAM读请求信号,高电平有效,用于状态机控制。
.sramwr_data(sramwr_data), // SRAM写入数据寄存器。
.sramrd_data(sramrd_data), // SRAM读出数据寄存器。
.sramwr_addr(sramwr_addr), // SRAM写入地址寄存器。
.sramrd_addr(sramrd_addr), // SRAM读出地址寄存器。
.sram_cs_n(sram_cs_n), // SRAM片选信号,低电平有效。
.sram_we_n(sram_we_n), // SRAM写选通信号,低电平有效。
.sram_oe_n(sram_oe_n), // SRAM输出选通信号,低电平有效。
.sram_addr(sram_addr), // SRAM地址总线。
.sram_data(sram_data) // SRAM数据总线。
);
assign led[7:1] = 7'b1111111;
endmodule
子模块test_timing.v如下,这个模块主要是一个生成数据的测试模块,主要是生成一个1S的计时器,来通过判断计数器的值的大小来进行写和读的操作。
module test_timing(
input clk, //时钟信号
input rst_n, //复位信号,低电平有效
output reg led, //LED指示灯,点亮表示读写SRAM同一个地址正确,熄灭表示读写SRAM同一个地址失败。
output sramwr_req, // SRAM写请求信号,高电平有效,用于状态机控制。
output sramrd_req, // SRAM读请求信号,高电平有效,用于状态机控制。
output reg[7:0] sramwr_data, // SRAM写入数据寄存器。
input[7:0] sramrd_data, // SRAM读出数据寄存器。
output reg[14:0] sramwr_addr, // SRAM写入地址寄存器。
output reg[14:0] sramrd_addr // SRAM读出地址寄存器。
);
//-------------------------------------
//1s定时逻辑产生
reg[25:0] delay; //延时计数器,不断计数,周期为1s,用于产生定时信号。
always @ (posedge clk or negedge rst_n)
if(!rst_n) delay <= 26'd0;
//else if(delay < 26'd19_999) delay <= delay+1; //for test
else if(delay < 26'd49_999_999) delay <= delay+1'b1;
else delay <= 26'd0;
assign sramwr_req = (delay == 26'd1000); //产生写请求信号,每秒钟产生一个高电平脉冲
assign sramrd_req = (delay == 26'd1100); //产生读请求信号,每秒钟产生一个高电平脉冲
//-------------------------------------
//定时SRAM写入数据寄存器
always @ (posedge clk or negedge rst_n) //写入数据每1s自增1。
if(!rst_n) sramwr_data <= 8'd0;
else if(delay == 26'd4000) sramwr_data <= sramwr_data+1'b1;
//-------------------------------------
//定时SRAM读和写地址寄存器
always @ (posedge clk or negedge rst_n) //写入和读出地址每1s自增1。
if(!rst_n) sramwr_addr <= 15'd0;
else if(delay == 26'd4000) sramwr_addr <= sramwr_addr+1'b1;
always @ (posedge clk or negedge rst_n) //写入和读出地址每1s自增1。
if(!rst_n) sramrd_addr <= 15'd0;
else if(delay == 26'd4000) sramrd_addr <= sramrd_addr+1'b1;
//-------------------------------------
//在同一地址读和写操作完成后,比对写入和读出的数据是否一致,通过LED输出比对结果
always @ (posedge clk or negedge rst_n) //每1s比较一次同一地址写入和读出的数据。
if(!rst_n) led <= 1'b0;
else if(delay == 26'd3000) begin
if(sramwr_data == sramrd_data) led <= 1'b0; //写入和读出数据一致,LED点亮
else led <= 1'b1; //写入和读出数据不同,LED熄灭
end
endmodule
子模块sram_controller.v
如下,主要是sram读写的时序控制,根据时序图和时序表来写。
module sram_controller(
input clk, //时钟信号
input rst_n, //复位信号,低电平有效
//FPGA内部对SRAM的读写控制信号
input sramwr_req, // SRAM写请求信号,高电平有效,用于状态机控制。
input sramrd_req, // SRAM读请求信号,高电平有效,用于状态机控制。
input[7:0] sramwr_data, // SRAM写入数据寄存器。
output reg[7:0] sramrd_data, // SRAM读出数据寄存器。
input[14:0] sramwr_addr, // SRAM写入地址寄存器。
input[14:0] sramrd_addr, // SRAM读出地址寄存器。
//FPGA与SRAM芯片的接口信号
output reg sram_cs_n, // SRAM片选信号,低电平有效。
output reg sram_we_n, // SRAM写选通信号,低电平有效。
output reg sram_oe_n, // SRAM输出选通信号,低电平有效。
output reg [14:0] sram_addr, // SRAM地址总线。
inout[7:0] sram_data // SRAM数据总线。
);
//-------------------------------------
//状态机控制SRAM的读或写操作。
parameter IDLE = 4'd0,
WRT0 = 4'd1,
WRT1 = 4'd2,
REA0 = 4'd3,
REA1 = 4'd4;
reg[3:0] cstate,nstate;
`define DELAY_00NS (cnt==3'd0) //用于产生SRAM读写时序所需要的0ns延时
`define DELAY_20NS (cnt==3'd1) //用于产生SRAM读写时序所需要的20ns延时
`define DELAY_40NS (cnt==3'd2) //用于产生SRAM读写时序所需要的40ns延时
`define DELAY_60NS (cnt==3'd3) //用于产生SRAM读写时序所需要的60ns延时
reg[2:0] cnt; //延时计数器
always @ (posedge clk or negedge rst_n)
if(!rst_n) cnt <= 3'd0;
else if(cstate == IDLE) cnt <= 3'd0;
else cnt <= cnt+1'b1;
//-------------------------------------
//SRAM读写状态机
always @ (posedge clk or negedge rst_n) //时序逻辑控制状态变迁。
if(!rst_n) cstate <= IDLE;
else cstate <= nstate;
always @ (cstate or sramwr_req or sramrd_req or cnt) begin //组合逻辑控制不同状态的转换。
case (cstate)
IDLE: if(sramwr_req) nstate <= WRT0; //进入写状态。
else if(sramrd_req) nstate <= REA0; //进入读状态。
else nstate <= IDLE;
WRT0: if(`DELAY_60NS) nstate <= WRT1;
else nstate <= WRT0;
WRT1: nstate <= IDLE;
REA0: if(`DELAY_60NS) nstate <= REA1;
else nstate <= REA0;
REA1: nstate <= IDLE;
default: nstate <= IDLE;
endcase
end
//-------------------------------------
//地址赋值
always @ (posedge clk or negedge rst_n)
if(!rst_n) sram_addr <= 15'd0;
else if(cstate == WRT0) sram_addr <= sramwr_addr; //写SRAM地址
else if(cstate == WRT1) sram_addr <= 15'd0;
else if(cstate == REA0) sram_addr <= sramrd_addr; //读SRAM地址
else if(cstate == REA1) sram_addr <= 15'd0;
//-------------------------------------
//SRAM读写数据的控制
reg sdlink; // SRAM数据总线方向控制信号,1为输出,0为输入。
always @ (posedge clk or negedge rst_n) //在状态REA1时执行SRAM读数据操作。
if(!rst_n) sramrd_data <= 8'd0;
else if((cstate == REA0) && `DELAY_60NS) sramrd_data <= sram_data;
always @ (posedge clk or negedge rst_n) //控制不同状态下SRAM数据总线的方向。SRAM只有在执行写操作时为输出,其他时候均为输入。
if(!rst_n) sdlink <= 1'b0;
else if(cstate == WRT0) sdlink <= 1'b1;
else if(cstate == WRT1) sdlink <= 1'b0;
assign sram_data = sdlink ? sramwr_data : 8'hzz;
//-------------------------------------
//SRAM片选、读选通和写选通信号的控制
//SRAM片选信号产生
always @ (posedge clk or negedge rst_n)
if(!rst_n) sram_cs_n <= 1'b1;
else if(cstate == WRT0) begin
if(`DELAY_00NS) sram_cs_n <= 1'b1;
else sram_cs_n <= 1'b0;
end
else if(cstate == REA0) sram_cs_n <= 1'b0;
else sram_cs_n <= 1'b1;
//SRAM读选通信号产生
always @ (posedge clk or negedge rst_n)
if(!rst_n) sram_oe_n <= 1'b1;
else if(cstate == REA0) sram_oe_n <= 1'b0;
else sram_oe_n <= 1'b1;
//SRAM写选通信号产生
always @ (posedge clk or negedge rst_n)
if(!rst_n) sram_we_n <= 1'b1;
else if(cstate == WRT0) begin
if(`DELAY_20NS) sram_we_n <= 1'b0;
end
else sram_we_n <= 1'b1;
endmodule
写操作仿真结果如下图所示:
仿真测试结果
读操作仿真结果如下图所示
参考
特权同学
野火FPGA