本实验的目的是熟悉SRAM的工作原理,并能够通过编程来访问SRAM。本实验利用VHDL或Verilog编程访问开发系统核心板上的SRAM IS61LV24516,要求:
1. 依次向SRAM中的每个地址写入一个数,然后将此数从该地址中读出,判断写入的数和读出的数是否正确;向每个地址中写入的数从0开始依次加1。
2. 如果对某个地址写入的数和读出的数相同,则LED0缓慢闪烁,每2秒闪烁一次,表示读写正确;如果对某个地址写入的数和读出的数不同,则LED7快速闪烁,每0.4秒闪烁一次,表示读写错误。
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-2 SRAM写入时序
在进行写入操作时,CE、WE为低,OE为高,在Address上输出要写入的地址,对于UB、LB可以根据访问数据的需要来设置。在WE信号拉低tHZWE时间后SRAM在数据线(Dout)上输出高阻,在此之前信号是不确定的;在数据线输出高阻之后,FPGA可以在数据线(Din)上输出要写入的数据。为了保证不会发生数据冲突,要求FPGA在tHZWE时间后才可以输出要写入的数据。写入时序时间要求如表13 2所示。
表13-2 写入时序时间要求
图13-3 SRAM读取时序
在读取操作时要求,OE和CE为低,LB和UB根据需要设置电平,在Address上输出要读取数据的地址,在max{tAA, tACE, tBA}时间后,数据线上输出要读取的数据。读取时序时间要求如表13 3所示。
表13-3 读取时序时间要求
图13-4 程序总体架构
程序由四个模块组成(图 13-4):clkgen、SRAMTester、SRAMIF和LEDindicator:
1) 由于核心板上的SRAM是异步SRAM, SRAMIF将SRAM的异步接口转换成同步接口,SRAMIF可以作为一个通用模块供其它模块调用。
2) SRAMTester是对SRAM进行测试,对每一个地址写入一个数据,同时读取该地址的数据,检测和写入的数据是否一致。
3) SRAMTester检测结果通知LEDindicator,用LED来指示读写是否正确。
4) Clkgen是一个PLL,将50MHz的输入时钟信号变成100MHz的时钟信号,以验证SRAM的高速访问性能。
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-5 SRAM_IF写入时序
总线侧接口控制信号busen、busrw的宽度为一个时钟周期,期间地址线和数据线有效。在接收到写控制信号时,SRAMIF将SRAM控制信号cei、wei、oei、lbi、ubi拉低,同时输出地址信号addr_i,在下一个时钟周期输出数据。因为在写控制信号输出一段时间后SRAM才会置数据线为高阻,这时才可以在数据线上输出要写入的数据信号。
SRAM_IF的读取时序如图13-6所示。
图13-6 SRAM_IF读取时序
读控制信号busen和busrw为一个时钟周期的宽度,期间读地址busaddr有效。在检测到读控制信号后,SRAMIF将cei、oei、lbi和ubi置为有效,同时输出读地址。在两个时钟周期后读取datai上的数据,并通过busdataout将数据输出给总线,同时置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
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
readcomparison对读取的结果进行检测,看是否和写入的一致,读取值应为地址的低16位。当模块刚开始运行时,读取的地址为3FFFF,即最高地址位,但第一次运行时该地址中还未写入数据,无法进行比较。因此,定义信号firstread,复位时firstread为高,当firstread为高时不进行比较,并将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
Led_indicator模块是根据检测结果来控制led灯,这个模块较为简单,不再详述。
SRAMSIM(SRAMSIM.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-7 SRAM仿真结果
图13-8为将程序下载到开发系统后从SignalTap中观察到的实际运行结果。
图13-8 SRAM读写运行结果
从上图可以看出,从SRAM中读出的数和写入的一致。
最后在实验板上的结果是只有right_light指示灯LED0缓慢地闪烁,验证了实验板上SRAM工作是正常的。
文件名 | 功能 |
SRAM.v | 顶层文件。 |
SRAMIF.v|SRAMIF将SRAM的异步接口转换成同步接口。 | |
SRAMTester.v|SRAMTester是对SRAM进行测试,对每一个地址写入一个数据,同时读取该地址的数据,检测和写入的数据是否一致。 | |
SRAMSIM.v|SRAM仿真模块,模拟SRAM的行为,用于对程序的仿真。| |Testtop.v|仿真顶层模块。| |SRAMsim.mpf | ModelSim仿真项目。 |
把程序下载到开发系统上后, sw1为复位端reset,可以看到LED0缓慢闪烁或LED7快速闪烁。LED0为rightlight,表示读写正确;LED7为errorlight,如从sram中读出的数据有误,则此灯快速闪烁。