首先肯定是先看datasheet。
首先看看sram的结构框图。
有20位地址总线,对地址进行译码,得到存储器的单元,然后根据控制电路,判断各个控制信号,从而判定是读还是写操作。读的话,数据线16位就是输出,输出数据。如果是写的话,数据线就是输入,从外部接收数据。
这里数据线高8位和低8位是分开的,因为这里有一个位屏蔽功能,可以屏蔽高8位数据线或者是屏蔽低8位数据线。
具体看芯片的真值表就知道了。
从真值表就可以看出芯片的功能了。
上图是管脚说明。
下面就是关键的时序图了。因为要驱动它的话,是肯定要按照规定的时序来的。
首先是读时序。
考虑通用的方式,即CE和OE来控制。而不是直接将OE和CE使能。
先解读下时序:
在地址数据发送后,要过10ns时间后,但是OE拉低6.5ns时间,并且CE拉低10ns时间,数据数据才是有效数据,当然要读数据的话,需要将OE和CE拉低,同时不要忘记WE要拉高,表示是读数据。
第二次读数据,要10ns之后,所以这个10ns就是读取数据的最小时间,才能发送地址。然后OE和CE要满足上图的保持时间。
总体来说,这个时序图还是很简单的。
然后是写的时序,
这时序图也是很简单的。写时候,OE为高为低都是可以的。
写数据间隔也是10ns,在写控制发出后,数据在5ns后为输入有效,且数据要有6ns的建立时间,保持时间可以为0.
搞定时序图后,就开始进行程序的编写了。。
首先是搞定sram的底层驱动。
首先是信号列表。
+ 查看代码
module sram ( input clk, //input 50M clock input rst_n, //reset signal ,avtive low input command, //1 read 0 write input [19:0] address, // address to send sram input [1:0] byte_control, //00 all select //10 selcet low byte //01 select high byte //11 select no input [15:0] write_data, //data to write sram output [15:0] read_data, //read data from sram input start, //start signal , if 1 ,begin to operate sram output reg finish, //finish signal, if 1 ,mean operate sram finish //sram interface output reg CE_N, //sram Chip Enable output reg OE_N, //sram Output Enable output reg WE_N, //sram Write Enable output LB_N, //sram Lower-byte Control (I/O0-I/O7) output UB_N, //sram Upper-byte Control (I/O8-I/O15) output [19:0] sram_address, //sram Address inout [15:0] sram_data //sram Data Inputs/Outputs )
从注释可以看出各个信号的作用。
这里采用状态机来进行控制,首先是定义状态
+ 查看代码
localparam idle_state = 'd1; localparam send_address_state = 'd2; localparam read_state = 'd4; localparam write_state = 'd8; localparam finish_state = 'd16
定义了5个状态。看名字就知道是干什么的了。。。
状态机采用二段式设计。
核心代码如下。
+ 查看代码
always@(*) begin state_next = state; sram_data_reg_next = sram_data_reg; CE_N = 1'b1; OE_N = 1'b1; WE_N = 1'b1; finish = 1'b0; case(state) idle_state: begin if(start) state_next = send_address_state; end send_address_state: begin OE_N = 1'b0; CE_N = 1'b0; if(command == 1) //read begin state_next = read_state; end else //write begin state_next = write_state; WE_N = 1'b0; end end write_state: begin OE_N = 1'b0; CE_N = 1'b0; WE_N = 1'b0; state_next = finish_state; end read_state: begin OE_N = 1'b0; CE_N = 1'b0; sram_data_reg_next = sram_data; state_next = finish_state; end finish_state:begin finish = 1'b1; state_next = idle_state; end default: state_next = idle_state; endcase en
大致说明一下。
在idle_state:如果判断start信号有效的话,就跳转到发送地址状态。
在send_address_state:这个状态就是将OE和CE给拉低。然后依据命令是读还是写,进行判断跳转到读还是写状态。
在write_state:保持OE,CE为低,同时拉低WE。然后状态跳转到finish_state。
在read_state:保持OE,CE为低,把sram_data上的数据给读取进来。然后状态跳转到finish_state.
在finish_state:就将finish信号给置1 ,然后状态跳转。
整个状态机设计是很简单的。就是要注意各个信号的时序,满足手册上要求的时序,因为系统时钟很慢,只有50M,周期为20ns,完全满足手册上要求的时序。
用状态机设计很简单,但是也有一个问题就是读写太慢了。。手册上写的是10ns就可以进行一次读写,但是这里设计的需要4个时钟,即80ns才能进行一次读写。
程序在说明一下,关于sram_data的处理,因为这个信号是双向的。
assign sram_data = command ? 16'bz : write_data;
这里使用的是双向口的通用处理方法,使用asignn赋值,因为command为1的时候,操作是读数据,所以这个时候赋值为高阻z,就表明是输入了。为0表示写数据,那么就赋值为write_data,作为输出,输出的值由write_data决定。
下面来对这个模块进行仿真看看。
核心的测试代码:
+ 查看代码
reg [15:0] sram_data_reg; assign sram_data = command ? sram_data_reg : 16'bz; initial begin clk = 0; forever #10 clk = ~clk; end initial begin rst_n = 0; address = 0; command = 0; //write byte_control = 0; //all select sram_data_reg = 0; write_data = 0; #105 rst_n = 1; repeat(10) begin @(posedge clk) start = 1; @(posedge clk) start = 0; write_data = {$random()}% 65536; wait(finish == 1); @(posedge clk); address = address + 1; end command = 1; //read address = 0; repeat(10) begin @(posedge clk) start = 1; @(posedge clk) start = 0; sram_data_reg = {$random()}% 65536; write_data = {$random()}% 65536; wait(finish == 1); @(posedge clk); address = address + 1; end #50 $stop; end
第一个repeat是写数据,这里就观测写的数据是不是和sram_data的值一样的,这个时候sram_data是作为输出的。
第二个repeat是读数据,这里就观测读的数据是不是和sram_data_reg一样的,这个时候sram_data是作为输入的,而输入的值由sram_data_reg决定的。
对于testbench,最后结束的时候,最好用$stop,暂停仿真,不然仿真就一直继续下去了。。
使用modelsim仿真,因为quartus只能用modelsim仿真。
先观察黄线左边的部分,这部分是写数据。就看写的数据和sram_data是不是一样的,发现是一样的。说明功能正确。
然后观察黄线右边的部分,这部分是读数据。就看读的数据是不是和sram_data_reg一样的。发现sram_data_reg和sram_data值一样,这个肯定是当然的,然后有和read_data一样的。说明功能正确。
然后来看看时序部分:
先看写数据。
这个时钟周期是20ns。
Start为高,表明操作开始。这个时候地址数据已经送出,CE,OE,WE持续20ns的高电平,然后持续40ns的低电平,最后才拉高,是符合写的时序的,而且余量还很多。
在看读数据:
Start为高,表明操作开始。这个时候地址数据已经送出,CE,OE持续20ns的高电平,然后拉低,持续40ns的低电平,最后才拉高。也是符合读的时序的,而且余量也很多。
按照仿真的情况来看的话,似乎功能是正确的。那么就写个简单的顶层代码,对sram驱动。
代码功能也很简单,每隔200ms对sram写一个数据,然后将刚写数据读出来,然后赋值给led。然后sram地址加1,写的数据也加1.
+ 查看代码
module sram_control ( input clk, input rst_n, //sram interface output CE_N, output OE_N, output WE_N, output LB_N, output UB_N, output [19:0] sram_address, inout [15:0] sram_data, output [15:0] led, output [8:0] led2 ); localparam idle_state = 'd1; localparam write_state = 'd2; localparam read_state = 'd4; localparam finish_state = 'd8; reg start; wire finish; reg command; wire [1:0] byte_control; assign byte_control = 2'b00; wire [19:0] address; wire [15:0] read_data; reg [19:0] address_counter; reg [19:0] address_counter_next; always@(posedge clk or negedge rst_n) begin if(!rst_n) address_counter <= 'd0; else address_counter <= address_counter_next; end //reg [15:0] write_data_reg; //reg [15:0] write_data_reg_next; //always@(posedge clk or negedge rst_n) begin // if(!rst_n) // write_data_reg <= 'd0; // else // write_data_reg <= write_data_reg_next; //end reg [23:0] counter_200ms; // 10000000 localparam counter_200ms_value = 24'd1000_0000; //localparam counter_200ms_value = 24'd10; //for simulation always@(posedge clk or negedge rst_n) begin if(!rst_n) counter_200ms <= 'd0; else if(counter_200ms >= counter_200ms_value) counter_200ms <= 'd0; else counter_200ms <= counter_200ms + 1'b1; end reg [3:0] state; reg [3:0] state_next; always@(posedge clk or negedge rst_n) begin if(!rst_n) state <= idle_state; else state <= state_next; end always@(*) begin state_next = state; address_counter_next = address_counter; command = 1'b0; //default write //write_data_reg_next = write_data_reg; start = 1'b0; case(state) idle_state: begin address_counter_next = 'd0; //write_data_reg_next = 'd0; state_next = write_state; end write_state: begin if(finish == 1) begin start = 1'b0; state_next = read_state; end else start = 1; end read_state: begin command = 1'b1; //read if(finish == 1) begin start = 1'b0; address_counter_next = address_counter + 1'b1; //write_data_reg_next = write_data_reg + 1'b1; state_next = finish_state; end else start = 1'b1; end finish_state: begin if(counter_200ms == counter_200ms_value) state_next = write_state; end default: state_next = idle_state; endcase end assign address = address_counter; sram sram_inst ( .clk(clk) , // input clk .rst_n(rst_n) , // input rst_n .command(command) , // input command .address(address) , // input [19:0] address .byte_control(byte_control) , // input [1:0] byte_control .write_data(address_counter[15:0]) , // input [15:0] write_data .read_data(read_data) , // output [15:0] read_data .start(start) , // input start .finish(finish) , // output finish .CE_N(CE_N) , // output CE_N .OE_N(OE_N) , // output OE_N .WE_N(WE_N) , // output WE_N .LB_N(LB_N) , // output LB_N .UB_N(UB_N) , // output UB_N .sram_address(sram_address) , // output [19:0] sram_address .sram_data(sram_data) // inout [15:0] sram_data ); assign led = read_data; assign led2 = address_counter[8:0]; endmodul
然后分配管脚,编译生成sof文件,下到芯片中,发现,成功驱动了。led按照二进制加1的方式进行亮灭。
最后附上整个sram控制的代码:
+ 查看代码
module sram
(
input clk, //input 50M clock
input rst_n, //reset signal ,avtive low
input command, //1 read 0 write
input [19:0] address, // address to send sram
input [1:0] byte_control, //00 all select
//10 selcet low byte
//01 select high byte
//11 select no
input [15:0] write_data, //data to write sram
output [15:0] read_data, //read data from sram
input start, //start signal , if 1 ,begin to operate sram
output reg finish, //finish signal, if 1 ,mean operate sram finish
//sram interface
output reg CE_N, //sram Chip Enable
output reg OE_N, //sram Output Enable
output reg WE_N, //sram Write Enable
output LB_N, //sram Lower-byte Control (I/O0-I/O7)
output UB_N, //sram Upper-byte Control (I/O8-I/O15)
output [19:0] sram_address, //sram Address
inout [15:0] sram_data //sram Data Inputs/Outputs
);
localparam idle_state = 'd1;
localparam send_address_state = 'd2;
localparam read_state = 'd4;
localparam write_state = 'd8;
localparam finish_state = 'd16;
assign {UB_N,LB_N} = byte_control;
assign sram_address = address;
assign sram_data = command ? 16'bz : write_data;
reg [4:0] state;
reg [4:0] state_next;
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
state <= idle_state;
else
state <= state_next;
end
//save the read data from sram
reg [15:0] sram_data_reg;
reg [15:0] sram_data_reg_next;
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
sram_data_reg <= 'd0;
else
sram_data_reg <= sram_data_reg_next;
end
assign read_data = sram_data_reg;
always@(*) begin
state_next = state;
sram_data_reg_next = sram_data_reg;
CE_N = 1'b1;
OE_N = 1'b1;
WE_N = 1'b1;
finish = 1'b0;
case(state)
idle_state: begin
if(start)
state_next = send_address_state;
end
send_address_state: begin
OE_N = 1'b0;
CE_N = 1'b0;
if(command == 1) //read
begin
state_next = read_state;
end
else //write
begin
state_next = write_state;
WE_N = 1'b0;
end
end
write_state: begin
OE_N = 1'b0;
CE_N = 1'b0;
WE_N = 1'b0;
state_next = finish_state;
end
read_state: begin
OE_N = 1'b0;
CE_N = 1'b0;
sram_data_reg_next = sram_data;
state_next = finish_state;
end
finish_state:begin
finish = 1'b1;
state_next = idle_state;
end
default:
state_next = idle_state;
endcase
end
endmodule