## SRAM接口实验
### 1. 实验内容
本实验的目的是熟悉SRAM的工作原理,并能够通过编程来访问SRAM。本实验利用VHDL或Verilog编程访问开发系统核心板上的SRAM IS61LV24516,要求:\\
1. 依次向SRAM中的每个地址写入一个数,然后将此数从该地址中读出,判断写入的数和读出的数是否正确;向每个地址中写入的数从0开始依次加1。\\
2. 如果对某个地址写入的数和读出的数相同,则LED0缓慢闪烁,每2秒闪烁一次,表示读写正确;如果对某个地址写入的数和读出的数不同,则LED7快速闪烁,每0.4秒闪烁一次,表示读写错误。
\\
\\
### 2. 实验原理
SRAM是英文Static RAM的缩写,即静态存储器。它是一种具有静态存取功能的存储器,不需要刷新电路即能保存它内部存储的数据。SRAM访问速度快,不必配合内存刷新电路,可提高整体的工作速度。但SRAM集成度低,功耗较大,相同的容量下体积较大,而且价格较高,少量用于关键性系统以提高效率。\\
SRAM按是否需要时钟又分为同步SRAM和异步SRAM。同步SRAM采用一个输入时钟来启动存储器的所有事务处理(读、写等);而异步SRAM并不具备时钟输入,通过监视输入以获取来自控制器的命令,一旦识别出某条命令,SRAM将立即执行命令。\\
开发系统核心板上的SRAM IS61LV24516为异步SRAM,无时钟信号,管脚定义如表13-1所示。
**表13-1 SRAM管脚定义**
|A0~A17|访问SRAM的地址,18位,地址空间为256K。|
|I/O0~I/O1L12,L11|数据线,16位宽,SRAM的总容量为512K字节。|
|CE|片选信号,低电平有效。|
|OE|读使能信号,低电平有效。|
|WE|写使能信号,低电平有效。|
|LB|低8位访问控制信号,低电平有效。如果LB为0则表示访问16位数据的低8位。|
|UB|高8为访问控制信号,低电平有效。如果UB为低则表示访问16为数据的高8位。|
图13-1给出了各管脚的详细功能说明。
{{ :图13-1.png |图13-1 管脚详细功能定义}}
**图13-1 管脚详细功能定义**
SRAM的写入时序如图13 2所示。
{{ :图13-2.png |图13-2 SRAM写入时序}}
**图13-2 SRAM写入时序**
在进行写入操作时,CE、WE为低,OE为高,在Address上输出要写入的地址,对于UB、LB可以根据访问数据的需要来设置。在WE信号拉低tHZWE时间后SRAM在数据线(Dout)上输出高阻,在此之前信号是不确定的;在数据线输出高阻之后,FPGA可以在数据线(Din)上输出要写入的数据。为了保证不会发生数据冲突,要求FPGA在tHZWE时间后才可以输出要写入的数据。写入时序时间要求如表13 2所示。
**表13-2 写入时序时间要求**
{{ :图13-3.png |表13-2 写入时序时间要求}}
SRAM的读取时序如图13-3所示。
{{ :图13-4.png |图13-3 SRAM写入时序}}
**图13-3 SRAM读取时序**
在读取操作时要求,OE和CE为低,LB和UB根据需要设置电平,在Address上输出要读取数据的地址,在max{tAA, tACE, tBA}时间后,数据线上输出要读取的数据。读取时序时间要求如表13 3所示。
**表13-3 读取时序时间要求**
{{ :图13-5.png |表13-3 读取时序时间要求}}
\\
\\
### 3. 程序设计
#### 3.1 总体架构(SRAM.v)
{{ :图13-6.png |图13-4 程序总体架构}}
**图13-4 程序总体架构**
程序由四个模块组成(图 13-4):clkgen、SRAM_Tester、SRAM_IF和LED_indicator:\\
1) 由于核心板上的SRAM是异步SRAM, SRAM_IF将SRAM的异步接口转换成同步接口,SRAM_IF可以作为一个通用模块供其它模块调用。\\
2) SRAM_Tester是对SRAM进行测试,对每一个地址写入一个数据,同时读取该地址的数据,检测和写入的数据是否一致。\\
3) SRAM_Tester检测结果通知LED_indicator,用LED来指示读写是否正确。\\
4) Clkgen是一个PLL,将50MHz的输入时钟信号变成100MHz的时钟信号,以验证SRAM的高速访问性能。
#### 3.2 SRAM_IF模块(SRAM_IF.v)
SRAM_IF的功能是将SRAM的异步接口转换成同步接口,提供一个同步总线接口的SRAM访问模块。由于FPGA的设计主要是同步设计,转换成同步接口易于被其它模块所使用,提高设计的易用性和可靠性。
SRAM的接口定义如下:
input clk, reset; //时钟和复位输入信号。
//以下信号为总线侧接口信号。
input bus_en; //SRAM访问使能信号,高电平有效。
input bus_RW; //SRAM读写控制信号,高电平为读取访问,低电平为写入访问。
input [17:0] bus_addr; //SRAM访问地址。
input [15:0] bus_datain; //写入SRAM的数据。
output [15:0] bus_dataout; //从SRAM中读取的数据。
output bus_dataready; //从SRAM中读取数据完成,高电平有效。
//以下信号为SRAM的接口信号,各信号含义如表13-1所示。
output ce_i, oe_i, we_i, lb_i, ub_i;
inout wire [15:0] data_i;
output [17:0] addr_i;
{{ :图13-7.png |图13-5 SRAM_IF写入时序}}
**图13-5 SRAM_IF写入时序**
总线侧接口控制信号bus_en、bus_rw的宽度为一个时钟周期,期间地址线和数据线有效。在接收到写控制信号时,SRAM_IF将SRAM控制信号ce_i、we_i、oe_i、lb_i、ub_i拉低,同时输出地址信号addr_i,在下一个时钟周期输出数据。因为在写控制信号输出一段时间后SRAM才会置数据线为高阻,这时才可以在数据线上输出要写入的数据信号。
SRAM_IF的读取时序如图13-6所示。
{{ :图13-8.png |图13-6 SRAM_IF读取时序}}
**图13-6 SRAM_IF读取时序**
读控制信号bus_en和bus_rw为一个时钟周期的宽度,期间读地址bus_addr有效。在检测到读控制信号后,SRAM_IF将ce_i、oe_i、lb_i和ub_i置为有效,同时输出读地址。在两个时钟周期后读取data_i上的数据,并通过bus_dataout将数据输出给总线,同时置bus_dataready有效,宽度为一个时钟周期。在两个时钟周期后读取数据是因为SRAM读操作至少需要10ns,而时钟周期为10ns,一个周期不能保证读取操作完成,为保险起见,在两个时钟周期后读取数据。
各接口信号由内部控制信号控制:
assign busy = reading | writing; //信号reading和writing为读、写操作标志信号,为高表示正在进行读操作或者写操作,任一操作都表示模块忙。
assign ce_i = !busy; //如果有读操作或写操作,则ce_i拉地,对SRAM使能。
assign oe_i = !reading; //如果有读操作,则读控制信号oe_i有效。
assign we_i = !writing; //如果有写操作,则写控制信号we_i有效。
assign lb_i = !busy;
assign ub_i = !busy; //模块采用16位读写模式,所以lb_i和ub_i拉低表示16位读写。
写操作由状态write_state控制,由3个状态组成:
always @(posedge clk or negedge reset ) begin : SRAM_writing
if(!reset) begin
writing <= 0;
write_state <= 0;
end
else begin
case( write_state )
2'd0: begin //等待状态
if( bus_en && !bus_RW ) begin //表示写操作。
writing <= 1'b1; // 置写操作标识。
write_state <= 2'b1; //状态转移。
end
end
2'd1: begin //数据输出
write_state <= 2'd2; //状态转移。
data_t <= bus_datain; // 输出数据, data_i = data_t
end
3'd2: begin //写操作完成。
write_state <= 2'd0; //状态转移至等待状态。
writing <= 1'b0; //写操作标识清0。
data_t <= 16'bzzzz_zzzz_zzzz_zzzz; //将输出数据置为高阻。
end
endcase
end
end
下面模块控制SRAM接口模块的地址线:
always @(posedge clk or negedge reset ) begin
if( !reset )
addr_i <= 0;
else
if( bus_en ) //当读写控制使能时,输出地址线。
addr_i <= bus_addr;
end
读操作由状态机read_state控制,有四个状态:
always @(posedge clk or negedge reset ) begin : SRAM_reading
if(!reset) begin
reading <= 0;
bus_dataready <= 0;
bus_dataout <= 16'd0;
read_state <= 2'd0;
end
else begin
case( read_state )
2'd0: begin //等待状态。
if( bus_en && bus_RW ) begin //检测到读操作。
reading <= 1'b1; //读标识置位。
bus_dataout <= 16'd0; //将总线输出清0。
read_state <= 2'd1; //状态转移。
end
end
2'd1: begin //读等待。
read_state <= 2'd2; //状态转移。
end
2'd2: begin //读取数据。
reading <= 1'b0; // 复位读标识。
bus_dataout <= data_i; //读取数据并送给总线接口。
bus_dataready <= 1'b1; //数据读取完成标识置位。
read_state <= 2'd3; //状态转移。
end
2'd3: begin
bus_dataready <= 1'b0; //数据读取完成标识清0。
read_state <= 2'd0; //状态转移。
end
endcase
end
end
#### 3.3 SRAM_tester模块(SRAM_tester.v)
SRAM_ Tester模块完成对SRAM的扫描测试,对每一个地址写入一个数据并读取,检测写入和读取的是否一致。操作方式为:给地址A写入数据B(B为A的低16位);读取地址A-1中的数据B2,检测B2是否为A-1的低16位。读取地址A-1而不是A的原因是防止对A进行写操作时候在信号线的残留信号影响读取结果。
SRAM_Tester的接口信号定义如下:
input reset, clk; //时钟、复位信号。
input data_ready; //数据有效信号;在读取数据时,data_ready为高表示读取完成,data_in数据有效。
input [15:0]data_in; //读取操作时读取的数据,data_ready为高时有效。
output reg [15:0] data_out; //写入操作时要写入的数据。
output reg [17:0] addr; //读取或写入的地址。
output reg enable; //使能信号,表示rw、addr、data_out有效。
output reg rw; //rw读写操作,为高时表示读操作,为低时表示写操作。
output reg data_right; //读写正确指示,为高时表示读取和写入的数据一致。
以上接口信号的时序如图13-5和图13-6所示。
由于时钟是100MHz,读写操作每2个时钟周期完成一次,因此定义50MHz的使能信号enable50M:
always @(posedge clk or negedge reset ) begin
if( !reset ) begin
enable50M <= 0;
end
else begin
enable50M <= ~enable50M;
end
end
扫描检测代码如下:
always @(posedge clk or negedge reset ) begin
if( !reset ) begin //各信号复位。
enable <= 1'b0;
rw <= 1'b1;
state <= 2'd0; //state为状态控制信号,有3个状态。
addr <= 18'd0;
data_out <= 0;
readaddr <= 0;
end
else begin
if( enable )
enable <= 0; //当enable为高时,置enable为低,确保enable宽度为一个时钟周期。
if( enable50M ) begin //当enable50M为高时进行读写操作。
case( state )
2'd0: begin //进行写操作。
rw <= 0;
state <= 3'd1;
enable <= 1;
end
2'd1: begin //进行读操作。
rw <= 1;
enable <= 1;
addr <= addr - 16'd1; //读取的地址为addr-1。
readaddr <= addr - 16'd1;
state <= 3'd2;
end
2'd2: begin //读取完成,准备下一轮写操作。
state <= 0;
addr <= addr + 16'd2; //下一次读取的地址,由于在写入的时候减1了,所以要加2才是下一次写入的地址。
data_out <= data_out + 16'd1; //下一次写入的数据。
enable <= 0;
end
default:
state <= 0;
endcase
end
end
end
read_comparison对读取的结果进行检测,看是否和写入的一致,读取值应为地址的低16位。当模块刚开始运行时,读取的地址为3FFFF,即最高地址位,但第一次运行时该地址中还未写入数据,无法进行比较。因此,定义信号first_read,复位时first_read为高,当first_read为高时不进行比较,并将first_read置0。之后便可以对结果进行比较:
always @(posedge clk or negedge reset ) begin : result_comparison
if( !reset ) begin
first_read <= 1;
data_right <= 1;
end
else begin
if( data_ready ) begin
if( first_read ) begin //当first_read为高时,不比较。
first_read <= 0;
end
else if( data_in != readaddr[15:0] ) //对结果进行比较。
data_right <= 0; //如果结果错误,将data_right置0。
end
end
end
#### 3.4 LED_indicator模块(LED_indicator.v)
Led_indicator模块是根据检测结果来控制led灯,这个模块较为简单,不再详述。
\\
\\
### 4. 程序仿真
SRAM_SIM(SRAM_SIM.v)是SRAM的仿真模块,用以模拟SRAM的行为,二维数组SRAMub和SRAMlb 分别用来模拟的SRAM的高8位和低8位,数组的长度可以自由设置,为了仿真方便设为10,也就是地址空间为0-9。由于核心板上的SRAM是异步SRAM,为了更真实地模拟SRAM的行为,采用组合逻辑加延时的方式来实现:
//接口信号定义:
input ce,oe,we,lb,ub;
input [17:0] addr;
inout wire [15:0] data;
//用数组模拟存储器:
reg [7:0] SRAMub[10:0];
reg [7:0] SRAMlb[10:0];
reg [15:0] data_t;
assign data = data_t;
always@* begin
case({ce,oe,we,lb,ub}) //对ce, oe, we, lb, ub几个信号进行判断。
5'b01000: //对16位数据进行写操作。
begin
#5 SRAMub[addr] <= data[15:8]; //这里加了5ns的延时,延时长短可以调整。
SRAMlb[addr] <= data[7:0];
end
5'b01010: //对高8位数据进行写操作。
begin
#5 SRAMub[addr] <= data[15:8];
end
5'b01001: //对低8位数据进行写操作。
begin
#5 SRAMlb[addr] <= data[7:0];
end
5'b00100: //对16位数据进行读操作。
begin
#5 data_t[15:8] <= SRAMub[addr];
data_t[7:0] <= SRAMlb[addr];
end
5'b00110: //对高8位数据进行读操作。
begin
#5 data_t[15:8] <= SRAMub[addr];
end
5'b00101: //对低8位数据进行读操作。
begin
#5 data_t[7:0] <= SRAMlb[addr];
end
default: data_t <= 16'bzzzz_zzzz_zzzz_zzzz; //其它情况下,数据线输出高阻。
endcase
end
testtop是测试顶层模块,产生时钟和复位信号。
仿真结果如图13-7所示,图中给出了一个完整的读写操作,写入地址是1,读取地址是0。
{{ :图13-9.png |图13-7 SRAM仿真结果}}
**图13-7 SRAM仿真结果**
### 5. 运行结果
图13-8为将程序下载到开发系统后从SignalTap中观察到的实际运行结果。
{{ :图13-10.png |图13-8 SRAM读写运行结果}}
**图13-8 SRAM读写运行结果**
从上图可以看出,从SRAM中读出的数和写入的一致。
最后在实验板上的结果是只有right_light指示灯LED0缓慢地闪烁,验证了实验板上SRAM工作是正常的。
\\
\\
### 6. 演示程序文件说明
|文件名|功能|
|SRAM.v|顶层文件。|
|SRAM_IF.v|SRAM_IF将SRAM的异步接口转换成同步接口。|
|SRAM_Tester.v|SRAM_Tester是对SRAM进行测试,对每一个地址写入一个数据,同时读取该地址的数据,检测和写入的数据是否一致。|
|SRAM_SIM.v|SRAM仿真模块,模拟SRAM的行为,用于对程序的仿真。|
|Testtop.v|仿真顶层模块。|
|SRAM_sim.mpf|ModelSim仿真项目。|
\\
\\
### 7. 演示程序使用
把程序下载到开发系统上后, sw1为复位端reset,可以看到LED0缓慢闪烁或LED7快速闪烁。LED0为right_light,表示读写正确;LED7为error_light,如从sram中读出的数据有误,则此灯快速闪烁。