weiqi7777

FPGA驱动sram

0
阅读(2642) 评论(0)

准备把de2-115上面的存储器外设都给驱动一下,首先就先从简单的sram开始。

Sram的驱动比较简单,和FPGA内部的ram差不多,只不过不是由时钟来控制读写,而是由控制信号来控制读写,读写都很快,基本上一个时钟就可以读取和写入数据,当然这时钟不能太快,不能超过芯片规定的最小时间。

首先肯定是先看datasheet。

clip_image002

首先看看sram的结构框图。

有20位地址总线,对地址进行译码,得到存储器的单元,然后根据控制电路,判断各个控制信号,从而判定是读还是写操作。读的话,数据线16位就是输出,输出数据。如果是写的话,数据线就是输入,从外部接收数据。

这里数据线高8位和低8位是分开的,因为这里有一个位屏蔽功能,可以屏蔽高8位数据线或者是屏蔽低8位数据线。

具体看芯片的真值表就知道了。

clip_image004

从真值表就可以看出芯片的功能了。

clip_image005

上图是管脚说明。

下面就是关键的时序图了。因为要驱动它的话,是肯定要按照规定的时序来的。

首先是读时序。

clip_image007

考虑通用的方式,即CE和OE来控制。而不是直接将OE和CE使能。

先解读下时序:

在地址数据发送后,要过10ns时间后,但是OE拉低6.5ns时间,并且CE拉低10ns时间,数据数据才是有效数据,当然要读数据的话,需要将OE和CE拉低,同时不要忘记WE要拉高,表示是读数据。

第二次读数据,要10ns之后,所以这个10ns就是读取数据的最小时间,才能发送地址。然后OE和CE要满足上图的保持时间。

总体来说,这个时序图还是很简单的。

然后是写的时序,

clip_image009

这时序图也是很简单的。写时候,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仿真。

clip_image011

先观察黄线左边的部分,这部分是写数据。就看写的数据和sram_data是不是一样的,发现是一样的。说明功能正确。

然后观察黄线右边的部分,这部分是读数据。就看读的数据是不是和sram_data_reg一样的。发现sram_data_reg和sram_data值一样,这个肯定是当然的,然后有和read_data一样的。说明功能正确。

然后来看看时序部分:

先看写数据。

clip_image013

这个时钟周期是20ns。

Start为高,表明操作开始。这个时候地址数据已经送出,CE,OE,WE持续20ns的高电平,然后持续40ns的低电平,最后才拉高,是符合写的时序的,而且余量还很多。

在看读数据:

clip_image015

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