FPGA综合项目——SDRAM控制器

FPGA综合项目——SDRAM控制器FPGA综合项目——SDRAM控制器目录整体框架串口接收模块接收模块测试仿真串口发送模块发送模块测试仿真整体框架串口接收模块接收模块测试仿真串口发送模块发送模块测试仿真

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

FPGA综合项目——SDRAM控制器

1.整体框架

在这里插入图片描述
本项目的目的,通过串口来对SDRAM进行读写。

首先是串口通信模块,常见的串行通信方式。

再者就是通信处理模块,具体的通信设置,发送什么命令是写?什么命令是读?发的什么数据?等等。

其次就是SDRAM模块,包括初始化、自动刷新、读、写等。(重点)

最后就是fifo的使用,因为SDRAM的读写是远远快于串口通讯的速度的,因此我们需要对写和读的数据进行缓存,不然就会丢失数据。

2.串口接收模块

串口通信我就不多说了,直接看时序图吧:
在这里插入图片描述
①当rx输入有下降沿,传输就开始了

②传输的速率看波特率,根据波特率计算每个数据发送所需要的时间(时间计算在代码注释中)

③连停止位一共传输9位数据,传完结束,完成一整次的传输

读完时序我们来思考一下我们需要怎么实现这个时序:

首先,下降沿开始传输——那么我们就要做一个监沿器来识别下降沿,这里还要额外考虑一个问题,那就是rx信号是由电脑发过来的,和FPGA是不一样的时钟,因此我们要先做异步处理,再对rx进行监沿。所以,这里需要2个D触发器进行异步处理,再加一个D触发器构成监沿器。

其次,根据波特率算出来的时间,用一个计数器计一个数据的时间,再用一个计数器来计数据个数。

接着,将输入给到输出,注意,这里应当用中间取值法,避免边沿的跳变。

最后,是代码:

module rx
(
	input		clk,
	input		rst_n,
	input		rx,
	output reg [7:0]rx_data,
	output reg	rx_over
);

//参数
parameter	_9600	= 	5208;  //9600波特率
parameter	_8bit	=	9;     //加上起始位共9位
parameter	MIDDLE		=	_9600/2 - 1; //中间取值
//内部信号
reg		rx_ff0;
reg		rx_ff1;
reg		rx_ff2;
wire		pose;
wire		nege;
reg		flag_rx;
reg [3:0]	cnt_8bit;
reg		bit_cnt_flag;
reg [12:0]	cnt_9600;
//功能块
always@(posedge clk )begin
		rx_ff0 <= rx;
		rx_ff1 <= rx_ff0;
		rx_ff2 <= rx_ff1;
end

assign pose = rx_ff2 == 0 && rx_ff1 == 1;
assign nege = rx_ff2 == 1 && rx_ff1 == 0;

always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		flag_rx <= 0;
	else if(nege)
		flag_rx <= 1;
	else if(cnt_8bit == 0 && cnt_9600 == _9600-1 )
		flag_rx <= 0;
end

always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		cnt_9600 <= 0;
	else if(cnt_9600 == _9600-1)
		cnt_9600 <= 0;
	else if(flag_rx)
		cnt_9600 <= cnt_9600 + 1;
	else
		cnt_9600 <= 0;
end

always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		bit_cnt_flag <= 0;
	else if(cnt_9600 == MIDDLE)
		bit_cnt_flag <= 1;
	else
		bit_cnt_flag <= 0;
end

always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		cnt_8bit <= 0;
	else if(bit_cnt_flag && cnt_8bit == _8bit - 1 )
		cnt_8bit <= 0;
	else if(bit_cnt_flag)
		cnt_8bit <= cnt_8bit + 1;
end

always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		rx_over <= 0;
	else if(bit_cnt_flag && cnt_8bit == _8bit - 1)
		rx_over <= 1;
	else 
		rx_over <= 0;
end

always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		rx_data <= 0;
	else if(bit_cnt_flag && cnt_8bit >= 1  )
		rx_data <= {rx_ff1,rx_data[7:1]};
end

endmodule

3.接收模块测试仿真

测试模块主要的作用是编写测试的输入,以方便进行仿真。基本的测试编写可见测试文件编写以及modelsim仿真

测试文件的功能在于,一位一位的给出rx信号,通过modelsim对源文件进行验证仿真;

①先用$ readmemh(’’ ./x.txt’’,mem) 命令将txt文本的数据以十六进制读到寄存器mem中

②再一位一位拆分输入的数据

③按照’’ 0 ,8个数据, 1’’ 这样的格式给rx赋值

④注意一个bit持续的时间

以下是测试代码:

`timescale 1 ns/1 ns
module tb_rx();

//输入信号
reg          clk  ;
reg          rst_n;
reg          rx;

//输出信号
wire      rx_over;
wire[7:0] rx_data;

//8位寄存器  4个
reg [7:0]   mem_a[3:0];

//参数
parameter CYCLE    = 20;

//待测试的模块例化
    rx u1
	(
	    .clk          (clk     ),  
	    .rst_n        (rst_n   ),  
	    .rx      	  (rx      ),  
	    .rx_data      (rx_data ),  
            .rx_over      (rx_over ) 
	);                

//生成50M时钟
initial begin
    clk = 0;
    forever
    #(CYCLE/2)
    clk=~clk;
end

//产生复位信号
initial begin
    rst_n = 1;
    #2;
    rst_n = 0;
    #2
    rst_n = 1;
end

//把文件里的数据写入mem_a存储器中
//此文件写好放在sim的文件夹中
initial $readmemh("./rx.txt",mem_a); 

initial begin
   rx  = 0; 
   #20
   rx  = 1; 
   #20
   byte();	//task
end

//task1 , 将txt文件的4个数存到寄存器a中
task    byte();
        integer i;
        for(i =0;i<4;i = i+1)begin
            bit(mem_a[i]);    
        end
endtask

//task2 , 将txt的每一个数据分为8位,一位一位取值
task    bit(input   [7:0]   data_inter );
        integer i;
        for(i =0;i<10;i = i+1)begin
            case(i)
                    0:  rx <=  1'b0         ;
                    1:  rx <=  data_inter[0];
                    2:  rx <=  data_inter[1];
                    3:  rx <=  data_inter[2];
                    4:  rx <=  data_inter[3];
                    5:  rx <=  data_inter[4];
                    6:  rx <=  data_inter[5];
                    7:  rx <=  data_inter[6];
                    8:  rx <=  data_inter[7];
    	      default:  rx <=  1;		    
            endcase
            #104160;	//一个bit数据持续的时间 5208个时钟
        end
endtask

endmodule

仿真结果:
在这里插入图片描述
在这里插入图片描述

4.串口发送模块

发送的时序和rx相仿,只不过多了一个触发信号trigger,时序图如下:
在这里插入图片描述
在这里插入图片描述
①同样使用两个计数器,分别计数一个bit的时间和bit的个数

②使用一个寄存器存下输入进来的发送数据

③发送标志flag_tx的有效区域

④tx的发送 使用一个case语句完成,且在flag_tx下完成,初值为1

代码如下:

module tx
(
	input		clk,
	input		rst_n,
	input [7:0]	tx_data,
	input		trig,
	output reg	tx
);

//参数
parameter		_9600 	= 	5208;
parameter		_8bit	=	10;
//内部信
reg [7:0]		reg_tx_data;
reg			flag_tx;
reg [12:0]		cnt_9600;
reg [3:0]		cnt_8bit;

//将输入数据存入寄存器
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		reg_tx_data <= 0;
	else if(trig && !flag_tx)
		reg_tx_data <= tx_data;
		
end
//传输有效区域信号
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		flag_tx <= 0;
	else if(trig && !flag_tx)
		flag_tx <= 1;
	else if(flag_tx && cnt_8bit == _8bit-1 && cnt_9600 == _9600-1)
		flag_tx <=0;
end
//一位数据传输时间计数
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		cnt_9600 <= 0;
	else if(flag_tx)begin
	     if(cnt_9600 == _9600-1)
		cnt_9600 <= 0;
	     else
		cnt_9600 <= cnt_9600 + 1;
	end
	
end
//数据个数计数
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		cnt_8bit <= 0;
	else if(flag_tx && cnt_9600 == _9600-1)begin
		if(cnt_8bit == _8bit-1)
		cnt_8bit <= 0;
		else
		cnt_8bit <= cnt_8bit + 1;
	end
end
//tx
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		tx <= 0;
	else if(flag_tx)
		case(cnt_8bit)
			0:	tx <= 0;
			1:	tx <= reg_tx_data[0];
			2:	tx <= reg_tx_data[1];
			3:	tx <= reg_tx_data[2];
			4:	tx <= reg_tx_data[3];
			5:	tx <= reg_tx_data[4];
			6:	tx <= reg_tx_data[5];
			7:	tx <= reg_tx_data[6];
			8:	tx <= reg_tx_data[7];
			default:tx <= 1;
		endcase
	else
		tx <= 1;
end
endmodule

5.发送模块测试仿真

发送模块的仿真测试文件就比rx的简单很多了,关键在于延迟的计算,如果延迟不对,那么波特率就不是9600,那么数据就会乱套;代码如下:

`timescale 1 ns/1 ns

module tb_tx();

//输入信号
reg[7:0]  tx_data  ;
reg       trig;
reg       clk  ;
reg       rst_n;

//输出信号
wire      tx;

//参数
parameter CYCLE    = 20;

//待测试的模块例化
tx u2
(
    .clk          (clk           ), 
    .rst_n        (rst_n         ),
    .trig   	  (trig    	 ),
    .tx_data      (tx_data       ),
    .tx           (tx            )
);


//生成本地时钟50M
initial begin
    clk = 0;
    forever
    #(CYCLE/2)
    clk=~clk;
end

//产生复位信号
initial begin
    rst_n = 1;
    #2;
    rst_n = 0;
    #2
    rst_n = 1;
end

initial begin
    tx_data <= 8'd0;
    trig <= 0;
    #20;
    tx_data <= 8'b01010101;
    trig <= 1'b1;
    #937440;	//一共9个比特 ,因此是937440(5208*20*9)的时间
    trig <= 1'b0;
    #2;
    tx_data <= 8'b10101010;
    trig <= 1'b1;
    #937440;
    trig <= 1'b0;
end

endmodule

仿真图如下:
在这里插入图片描述

6.SDRAM基础学习

对SDRAM的基础学习,只要弄清楚我下面的几个问题,就基本上可以了。

①SDRAM是什么?

Synchronous Dynamic Random Access Memory,同步动态随机存储器,说白了,就是用来存数据用的外设(具体看此链接的6.2 DRAM 小结)。那么怎么存呢?看下一个问题。

②SDRAM有什么?

既然作为存东西的外设,那么必定有存放数据的结构,而这个就够就叫做bank,简单的说,一个bank就是一个存储阵列,利用行地址和列地址来进行排号标记,而一个SDRAM一般都有4个bank,它的容量就取决于bank的容量以及bank的数量。如图:
在这里插入图片描述
③SDRAM我们要懂什么?

首先,要明白他存储的原理,也就是为什么要刷新:存储电容会漏电,所以必须一定的间隔进行充电,这个过程就叫刷新。

其次,所用SDRAM的引脚以及数据手册要熟悉,引脚的作用,寄存器的配置都要从数据手册上获得信息。

最后就是各个功能的时序,也要从数据手册找出。以下以ISSI的SDRAM芯片为例讲解,这是此芯片的引脚:

在这里插入图片描述

我们进而查看命令所对应的引脚:
在这里插入图片描述此表是命令列表,并详细说明了如何通过信号组合来形式命令。当然还要去读一些命令所要求的时序,由于手册太长,在这不可能一一列举,我们直接从项目出发,用到什么说什么,看下一节具体操作即可,此节只需了解以下SDRAM的基本原理。

7.SDRAM顶层模块

作为SDRAM的顶层模块,在写的时候是要一步一步更新的,在每个模块调试时,都会加入其顶层模块进行仿真;

8.SDRAM初始化模块设计与仿真测试

初始化SDRAM,我们要参照数据手册的初始化的时序图,如下:
在这里插入图片描述
图中的时间没有具体说明,因此我们还要在数据手册上查找相应的时间:
在这里插入图片描述
在这里插入图片描述
由此,我们时钟是20ns一个周期,所以trc取3个周期、trp取1个周期、tmrd取2个周期。那么总体的时序如下:

①由时序图可知,电源上电需要缓冲100us,我们这保守起见,给他200us延迟
在这里插入图片描述

②在200us过后,执行红色框框中的命令PRECARGE(bank充电),找到此命令对应的四个引脚的高低电平

在这里插入图片描述

③接着经过一个周期T,给到自动刷新命令

在这里插入图片描述
④接着4T后,再给一次自动刷新命令

⑤又4T后,进行模式寄存器命令完成初始化过程,而配置寄存器模式需要看相应的sdram地址,下图是地址对应的配置信息:
在这里插入图片描述
而我们设置的sdram_addr为0000_0011_0010

代码及注释如下:

module sdram_initial
(
    input                   clk,
    input                   rst_n,
    output reg [3:0]        cmd_reg,    //用来寄存命令的信号
    output reg [11:0]       sdram_addr, //时序图中的A0-A11
    output wire             init_end   //初始化结束信号
);

//参数,包括延迟200us、4个指令对应的引脚高低电平
localparam  DELAY_200US = 10_000;
localparam  NOP         = 4'b0111;
localparam  PRECHARGE   = 4'b0010;
localparam  AUTOREFLASH = 4'b0001;
localparam  MODESET     = 4'b0000;

//内部信号
reg [13:0]      cnt0;
reg [3:0]       cnt1;
wire            flag_200us;

//200us计数
assign  flag_200us = (cnt0 >= DELAY_200US-1)? 1'b1:1'b0;

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt0 <= 0;
    end
    else if(!flag_200us)begin
            cnt0 <= cnt0 + 1;
    end
end

//周期个数计数
assign init_end = (cnt1>='d10)?1:0;

always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        cnt1 <= 0;
    end
    else if(flag_200us && !init_end)begin
        if(cnt1==11-1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1;
    end
end

//命令操作
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        cmd_reg <= NOP;
    end
    else if(flag_200us)begin
        case(cnt1)
            0: cmd_reg <= PRECHARGE;
            1: cmd_reg <= AUTOREFLASH;
            5: cmd_reg <= AUTOREFLASH;
            9: cmd_reg <= MODESET;
            default: cmd_reg <= NOP;
        endcase
    end
end

//sdram_addr
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        sdram_addr <= 0;
    end
    else if(flag_200us)begin
        case(cnt1)
                9:  sdram_addr <= 12'b0000_0011_0010;
          default:  sdram_addr <= 12'b0100_0000_0000;
        endcase
    end
end

endmodule

再完成顶层模块的编写:

module sdram_top
(
    input                   clk,
    input                   rst_n,

    output wire             sdram_clk,
    output wire             sdram_cke,
    output wire             sdram_cs,
    output wire             sdram_cas,
    output wire             sdram_ras,
    output wire             sdram_we,
    output wire [1 :0 ]     sdram_bank,
    output wire [11:0 ]     sdram_addr,
    output wire [1 :0 ]     sdram_dqm,
    inout       [15:0 ]     sdram_dq
);

//参数
//状态机采用独热码定义
localparam      IDLE        = 5'b0_0001;
localparam      HANDLE      = 5'b0_0010;
localparam      AUTOREFRESH = 5'b0_0100;
localparam      WRITE       = 5'b0_1000;
localparam      READ        = 5'b1_0000;

localparam      CMD_NOP     = 4'b0111  ;

//内部信号 
wire            init_end;
wire [3:0]      init_cmd;
wire [11:0]     init_addr;


//例化各模块
sdram_initial u1_init
(
   .clk             (clk),
   .rst_n           (rst_n),
   .cmd_reg         (init_cmd),
   .sdram_addr      (init_addr), 
   .init_end        (init_end)
);

assign  sdram_cke 	= 1'b1;
assign  sdram_clk  	= ~clk;
assign  sdram_dqm 	= 2'b00;
assign  sdram_addr	= init_addr;

assign  {sdram_cs,sdram_ras,sdram_cas,sdram_we} = init_cmd;

endmodule

至此,初始化完成、顶层完成后,接下来就是对初始化模块的测试了,我们对顶层模块做测试,每加一个模块,我们就测试一次。测试我们需要用到一个插件:
在这里插入图片描述
完成之后呢,编写测试文件:

`timescale 1 ns/1 ns

module sdram_top_tb();

//输入信号
reg                 clk  ;
reg                 rst_n;

//输出信号
wire            sdram_clk    ; 
wire            sdram_cke    ;
wire            sdram_cs     ;
wire            sdram_cas    ;
wire            sdram_ras    ;
wire            sdram_we     ;
wire [1:0]      sdram_bank   ;
wire [11:0]     sdram_addr   ;
wire [1:0]      sdram_dqm    ;
wire [15:0]     sdram_dq     ;

//参数
parameter CYCLE    = 20;
parameter RST_TIME = 3 ;

//待测试的模块例化
sdram_top u1_sdram_top
(
    .clk                 (clk           ), 
    .rst_n               (rst_n         ),
    .sdram_clk           (sdram_clk     ),
    .sdram_cke           (sdram_cke     ),
    .sdram_cs            (sdram_cs      ),
    .sdram_cas           (sdram_cas     ),
    .sdram_ras           (sdram_ras     ),
    .sdram_we            (sdram_we      ),
    .sdram_bank          (sdram_bank    ),
    .sdram_addr          (sdram_addr    ),
    .sdram_dqm           (sdram_dqm     ),   
    .sdram_dq            (sdram_dq      )

);
//插件例化,相当于是一个sdram硬件
sdram_model_plus u2_plus
(   .Dq             (sdram_dq), 
    .Addr           (sdram_addr), 
    .Ba             (sdram_bank), 
    .Clk            (sdram_clk), 
    .Cke            (sdram_cke), 
    .Cs_n           (sdram_cs), 
    .Ras_n          (sdram_ras), 
    .Cas_n          (sdram_cas), 
    .We_n           (sdram_we), 
    .Dqm            (sdram_dqm),
    .Debug          (1'b1)    
);

//重定义具体sdram的参数,因为插件的和实际的并不一样
defparam         u2_plus.addr_bits = 12;
defparam         u2_plus.data_bits = 16;
defparam         u2_plus.col_bits  = 9;
defparam         u2_plus.mem_sizes = 2*1024*1024; //2M 一个bank

//  生成本地时钟50M
initial begin
    clk = 0;
    forever
    #(CYCLE/2)
    clk=~clk;
end

//产生复位信号
initial begin
    rst_n = 1;
    #2;
    rst_n = 0;
    #(CYCLE*RST_TIME);
    rst_n = 1;
end

endmodule

仿真结果:充电范围所有bank、自动刷新一次、自动刷新两次、模式设置为输出延迟3T、突发长度4、连续突发类型;
在这里插入图片描述

9.自动刷新模块设计与测试

那么刷新的频率怎么算呢?首先数据手册上写这4096/64ms,意思就是64ms内对4096行进行刷新,也就是64000/4096 约为15us一次刷新。

4096行是怎么来的呢?我们的行地址不是有12位嘛,所以2^12为4096行。

自动刷新的操作:每15us发送请求进行刷新操作

同样的,自动刷新模块照样需要我们取查数据手册的自动刷新时序图,如下所示:

在这里插入图片描述
同样的,trc取3个时钟周期、trp取1个时钟周期,时序图中的顺序是:

①在初始化完成后,进行自动刷新(刷新完成信号)。先预充电,地址为A10为1,选中全部bank

②过一个时钟周期,进行自动刷新

③图上是进行两次自动刷新,实际上一次就行

④刷新模块的作用:每15us对全部bank(A10置1)进行一次刷新

⑤从这里开始引入了仲裁模块(直接写在顶层模块中了HANDLE状态),其他执行模块与仲裁模块之间有三线连接,分别是请求、使能、结束标志;仲裁模块通过自身判断再给出使能信号,给出使能信号后状态跳转到其他执行状态,完成执行任务后再跳回仲裁状态
在这里插入图片描述

④刷新模块代码如下:

module autorefresh
(
    input               clk,
    input               rst_n,
    input               init_end,
    input               ref_en,
    output wire         ref_req,
    output wire         ref_end,
    output reg [3:0]    autorefresh_cmd,
    output wire [11:0]  sdram_addr

);

//计数参数
localparam      DELAY_15US  = 750;
//命令参数
localparam      NOP         = 4'b0111;
localparam      PRECHARGE   = 4'b0010;
localparam      AUTOREFLASH = 4'b0001;
localparam      MODESET     = 4'b0000;

//内部信号
reg [9:0]               cnt0;
reg [3:0]               cnt1;
reg                     flag_ref; //刷新进行中标志

//功能
//15us计数器
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)
        cnt0 <= 0;
    else if(init_end)begin
        if(cnt0== DELAY_15US-1)
            cnt0 <= 0;
        else
            cnt0 <= cnt0 + 1;
    end
end

//刷新请求,每15us一次
assign  ref_req = (cnt0 >= DELAY_15US-1)? 1'b1 : 1'b0;

//flag_ref
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        flag_ref <= 0;
    end
    else if(ref_en) begin
        flag_ref <= 1;
    end
    else if(ref_end) begin
        flag_ref <=0;
    end
end

//时钟T个数计数器
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        cnt1 <= 0;
    end
    else if(flag_ref)begin
        if(cnt1== 4-1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1;
    end
end

//给出命令
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        autorefresh_cmd <= NOP;
    end
    else case(cnt1)
                1:      autorefresh_cmd <= PRECHARGE;
                2:      autorefresh_cmd <= AUTOREFLASH;
          default:      autorefresh_cmd <= NOP;
    endcase
end

//sdram_addr,A10置1选中所有bank
assign sdram_addr = 12'b0100_0000_0000;

//ref_end
assign ref_end = (cnt1>='d3)? 1 : 0;

endmodule

接着就是更新加入仲裁机制后的顶层模块:

module sdram_top
(
    input                   clk,
    input                   rst_n,

    output wire             sdram_clk,
    output wire             sdram_cke,
    output wire             sdram_cs,
    output wire             sdram_cas,
    output wire             sdram_ras,
    output wire             sdram_we,
    output wire [1 :0 ]     sdram_bank,
    output wire [11:0 ]     sdram_addr,
    output wire [1 :0 ]     sdram_dqm,
    inout       [15:0 ]     sdram_dq
);


//状态机采用独热码定义
localparam      IDLE        = 5'b0_0001;
localparam      HANDLE      = 5'b0_0010;
localparam      AUTOREFRESH = 5'b0_0100;
localparam      WRITE       = 5'b0_1000;
localparam      READ        = 5'b1_0000;
//空命令
localparam      CMD_NOP     = 4'b0111  ;

//内部信号 
//初始化
wire            init_end;
wire [3:0]      init_cmd;
wire [11:0]     init_addr;
//状态机
reg [4:0]	state;
//自动刷新
wire		ref_req;
reg		ref_en;
wire		ref_end;
wire [3:0]	ref_cmd;
wire [11:0]	ref_addr;


//例化模块
sdram_initial u1_init
(
   .clk             (clk),
   .rst_n           (rst_n),
   .cmd_reg         (init_cmd),
   .sdram_addr      (init_addr), 
   .init_end        (init_end)
);

autorefresh u2_autorefresh
(
    .clk		(clk),
    .rst_n		(rst_n),
    .init_end		(init_end),
    .ref_en		(ref_en),
    .ref_req		(ref_req),
    .ref_end		(ref_end),
    .autorefresh_cmd	(ref_cmd),
    .sdram_addr		(ref_addr)	

);

//功能
//仲裁模块状态机
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		state <= IDLE;
	else case(state)
		IDLE:
		       	if(init_end)
				state <= HANDLE;
		       	else
		       		state <= IDLE;	       
	      HANDLE:   if(ref_en)
		      		state <= AUTOREFRESH;
			else 
				state <= HANDLE;
	 AUTOREFRESH:	
		 	if(ref_end)
				state <= HANDLE;
			else
				state <= AUTOREFRESH;
	     default:	
		     		state <=IDLE;
	endcase
	
end

//刷新使能
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		ref_en <= 0;
	else if(ref_req && state == HANDLE)
		ref_en <= 1;
	else
		ref_en <= 0;
end

assign  sdram_cke 	= 1'b1;
assign  sdram_clk  	= ~clk;
assign  sdram_dqm 	= 2'b00;
assign  sdram_addr	=  (state == IDLE)?init_addr:ref_addr;
assign  {sdram_cs,sdram_ras,sdram_cas,sdram_we} = (state == IDLE)? init_cmd:ref_cmd;

endmodule

下面进行仿真测试,测试文件不用修改;
仿真结果如下:

在这里插入图片描述

10.写模块设计与测试

写模块的设计主要考虑三个方面:第一,写模块自身的时序。第二,写模块与top模块状态机的连接(特别是从写状态跳出到仲裁状态)。第三,和自动刷新模块的连接。

①首先,从SDRAM本身的时序来说,如下第一个图,写有两种模式,一种是WRITE,另一种是WRITEA,前者可以连续写更加高效,所以我们选择用WRITE。(粗线为自动跳转)因为WRITEA会自动跳转到充电状态,所以效率不高。

在这里插入图片描述
那么写时序从数据手册读出时序图如下:先激活,再写,突发长度为4。我们写模块的功能就是写两行(测试用),同时要兼顾自动刷新!
在这里插入图片描述
写模块的状态机如下:
在这里插入图片描述
② 写模块与top模块的连接,通过一个flag_wr_end信号,使得top的状态机从写状态跳到处理(仲裁)状态,来完成自动刷新的动作,这里需要对刷新模块的刷新使能进行修改,由原来的组合逻辑改为时序逻辑产生,修改后的刷新模块如下:

module autorefresh
(
    input               clk,
    input               rst_n,
    input               init_end,
    input               ref_en,
    output reg          ref_req,
    output wire         ref_end,
    output reg [3:0]    autorefresh_cmd,
    output wire [11:0]  sdram_addr

);

//计数参数
localparam      DELAY_15US  = 750;
//命令参数
localparam      NOP         = 4'b0111;
localparam      PRECHARGE   = 4'b0010;
localparam      AUTOREFLASH = 4'b0001;
localparam      MODESET     = 4'b0000;

//内部信号
reg [9:0]               cnt0;
reg [3:0]               cnt1;
reg                     flag_ref; //刷新进行中标志

//功能
//15us计数器
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)
        cnt0 <= 0;
    else if(init_end)begin
        if(cnt0== DELAY_15US-1)
            cnt0 <= 0;
        else
            cnt0 <= cnt0 + 1;
    end
end

//刷新请求,每15us一次 ,直到刷新使能来之前,都为1
always  @(posedge clk or negedge rst_n) begin
        if(rst_n == 1'b0)
                ref_req <=      1'b0;
        else if(ref_en == 1'b1)
                ref_req <=      1'b0;
        else if(cnt0 >= DELAY_15US-1)
                ref_req <=      1'b1;
end
//flag_ref
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        flag_ref <= 0;
    end
    else if(ref_en) begin
        flag_ref <= 1;
    end
    else if(ref_end) begin
        flag_ref <=0;
    end
end

//时钟T个数计数器
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        cnt1 <= 0;
    end
    else if(flag_ref)begin
        if(cnt1== 4-1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1;
    end
end

//给出命令
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        autorefresh_cmd <= NOP;
    end
    else case(cnt1)
                1:      autorefresh_cmd <= PRECHARGE;
                2:      autorefresh_cmd <= AUTOREFLASH;
          default:      autorefresh_cmd <= NOP;
    endcase
end

//sdram_addr,A10置1选中所有bank
assign sdram_addr = 12'b0100_0000_0000;

//ref_end
assign ref_end = (cnt1>='d3)? 1 : 0;

endmodule

③写模块与刷新模块的连接,通过刷新请求和刷新结束标志连接,当刷新请求来了,写模块完成当前的突发后停止,进入REQ状态,并使top的状态跳转至处理状态,以便进行刷新。

④写模块代码如下,有详细注释

module sdram_write
(
input                   clk,        //输入,50M时钟
input                   rst_n,      //输入,异步复位
input                   wr_en,      //输入,写使能
input                   ref_req,    //输入,刷新请求,用来中断写
input                   wr_trigger, //输入,写触发信号
input                   ref_end,    //输入,刷新结束,可以继续写
output reg              wr_req,     //输出,写请求
output reg              flag_wr_end,//输出,top状态机跳出写状态信号 
output reg [3:0]        wr_cmd,     //输出,写命令
output wire [1:0]       bank_addr,  //输出,bank地址
output reg [11:0]       wr_addr,    //输出,写地址
output reg [15:0]       wr_data     //输出,写数据,此代码在本模块内产生
);
//状态参数
localparam          IDLE    = 5'b0_0001;
localparam          REQ     = 5'b0_0010;
localparam          ACTIVE  = 5'b0_0100;
localparam          WRITE   = 5'b0_1000;
localparam          PRE     = 5'b1_0000;
//命令参数
localparam  CMD_NOP         = 4'b0111;
localparam  CMD_PRECHARGE   = 4'b0010;
localparam  CMD_AUTOREFLASH = 4'b0001;
localparam  CMD_ACTIVE      = 4'b0011;
localparam  CMD_WRITE       = 4'b0100;
//内部信号
reg                 flag_wr;        //写区域
reg [4:0]           state_wr;       //写时序状态
reg                 flag_ac_end;    //激活状态计数,计数完成跳转下一状态
reg                 flag_pre_end;   //充电状态计数,计数完成跳转下一状态
reg                 change_row;     //换行,一行写满(512个数据),要换行,再激活新的一行
reg                 wr_data_end;    //数据写完信号
reg [1:0]           burst_cnt;      //突发计数器,一次写入多少数据的控制
reg [1:0]           burst_cnt_ff;   //打一拍
reg [3:0]           active_cnt;     //激活计数器
reg [3:0]           pre_cnt;        //充电计数器
reg [6:0]           col_cnt;        //数据计数器,满512换行,归零
reg [11:0]          row_addr;       //行地址
wire [8:0]          col_addr;       //列地址
//功能
//状态机
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
state_wr <= IDLE;
end
else  begin
case(state_wr)
IDLE:
if(wr_trigger)
state_wr <= REQ;
else
state_wr <= IDLE;
REQ:
if(wr_en)
state_wr <= ACTIVE;
else
state_wr <= REQ;
ACTIVE:
if(flag_ac_end)
state_wr <= WRITE;
else
state_wr <= ACTIVE;
WRITE:
if(wr_data_end)
state_wr <= PRE;
else if(ref_req && burst_cnt_ff == 3 && flag_wr )
state_wr <= PRE;
else if(change_row && flag_wr)
state_wr <= PRE;
PRE:
if(ref_req && flag_wr)
state_wr <= REQ;
else if(flag_pre_end && flag_wr)
state_wr <= ACTIVE;
else if(wr_data_end)
state_wr <= IDLE;
default:
state_wr <= IDLE;
endcase
end
end
//burst_cnt_ff 打一拍
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
burst_cnt_ff <= 0;
end
else 
burst_cnt_ff <= burst_cnt;
end
//数据输出
always  @(*)begin
case(burst_cnt_ff)
0:  wr_data = 'd1;
1:  wr_data = 'd2;
2:  wr_data = 'd3;
3:  wr_data = 'd4;
endcase
end
//命令
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_cmd <= CMD_NOP;
end
else case(state_wr)
ACTIVE:
if(active_cnt == 'd0)
wr_cmd <= CMD_ACTIVE;
else
wr_cmd <= CMD_NOP;
WRITE:
if(burst_cnt == 'd0)
wr_cmd <= CMD_WRITE;
else
wr_cmd <= CMD_NOP;
PRE:
if(pre_cnt == 'd0)
wr_cmd <= CMD_PRECHARGE;
else
wr_cmd <= CMD_NOP;
default:
wr_cmd <= CMD_NOP;
endcase
end
//写地址
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_addr <= 0;
end
else case(state_wr)
ACTIVE:
if(active_cnt == 'd0)
wr_addr <= row_addr;
WRITE:
wr_addr <= {3'b000,col_addr};
PRE:
if(pre_cnt == 'd0)
wr_addr <= {12'b0100_0000_0000};
default:
wr_addr <= 0;                           
endcase
end
//bank地址
assign bank_addr = 2'b00;
//flag_wr
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_wr <= 0;
end
else if(wr_trigger && !flag_wr)
flag_wr <= 1;
else if(wr_data_end)
flag_wr <= 0;
end
//act计数器,计数激活所需要的时间
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
active_cnt <= 0;
end
else if(state_wr == ACTIVE)
active_cnt <= active_cnt + 1;
else
active_cnt <= 0;
end
//刷新计数
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
pre_cnt <= 0;
end
else if(state_wr == PRE) begin
pre_cnt <= pre_cnt + 1;
end
else
pre_cnt <= 0;
end
//col计数器,列计数
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
col_cnt <= 0;
end
else if(col_addr == 'd511) 
col_cnt <= 0;
else if(burst_cnt =='d3)
col_cnt <= col_cnt + 1;  
end
//产生flag_ac_end信号
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_ac_end <= 0;
end
else if(active_cnt >= 'd3) begin
flag_ac_end <= 1;
end
else
flag_ac_end <= 0;
end
//产生flag_pre_end信号
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_pre_end <= 0;
end
else if(pre_cnt >= 'd3) 
flag_pre_end <= 1;
else
flag_pre_end <= 0;
end
//产生flag_wr_end信号(写一次结束)
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_wr_end <= 0;
end
else if(state_wr == PRE && ref_req  ) 
flag_wr_end <= 1;
else if(state_wr == PRE && wr_data_end)
flag_wr_end <= 1;
else
flag_wr_end <= 0;
end
//写所有结束标志
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_data_end <= 0;
end
else if(row_addr == 'd1 && col_addr == 'd511 ) begin
wr_data_end <= 1;
end
else
wr_data_end <= 0;
end
//突发长度计数
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
burst_cnt <= 0;
end
else if(state_wr == WRITE) begin
burst_cnt <= burst_cnt +1;
end
else
burst_cnt <= 0;
end
//col_addr 列地址计算
assign col_addr = {col_cnt,burst_cnt};
//row_addr  行地址
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
row_addr <= 0;
end
else if(change_row)
row_addr <= row_addr + 1;
end
//换行请求
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
change_row <= 0;
end
else if(col_addr == 'd510 )
change_row <= 1;
else
change_row <= 0;
end
//写请求
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_req <= 0;
end
else if(state_wr == REQ && ref_end)
wr_req <= 1;
else
wr_req <= 0;
end
endmodule

在top模块中加入写模块的例化,在测试文件中写好wr_trigger的波形,便继续仿真:

module sdram_top
(
input                   clk,
input                   rst_n,
input		    wr_trigger,
output wire             sdram_clk,
output wire             sdram_cke,
output wire             sdram_cs,
output wire             sdram_cas,
output wire             sdram_ras,
output wire             sdram_we,
output wire [1 :0 ]     sdram_bank,
output reg [11:0 ]      sdram_addr,
output wire [1 :0 ]     sdram_dqm,
inout       [15:0 ]     sdram_dq
);
//状态机采用独热码定义
localparam      IDLE        = 5'b0_0001;
localparam      HANDLE      = 5'b0_0010;
localparam      AUTOREFRESH = 5'b0_0100;
localparam      WRITE       = 5'b0_1000;
localparam      READ        = 5'b1_0000;
//空命令
localparam      CMD_NOP     = 4'b0111  ;
//内部信号 
//初始化
wire            init_end;
wire [3:0]      init_cmd;
wire [11:0]     init_addr;
//状态机
reg [4:0]	state;
//自动刷新
wire		ref_req;
reg		ref_en;
wire		ref_end;
wire [3:0]	ref_cmd;
wire [11:0]	ref_addr;
//写模块
reg		wr_en;
wire		wr_req;
wire [3:0]	wr_cmd;
wire [11:0]	wr_addr;
wire [1:0]	wr_bank_addr;
wire		flag_wr_end;
wire [15:0]	wr_data;
//命令赋值信号
reg [3:0]	sdram_cmd;
assign  {sdram_cs,sdram_ras,sdram_cas,sdram_we} = sdram_cmd;
//例化模块
sdram_initial u1_init
(
.clk             (clk),
.rst_n           (rst_n),
.cmd_reg         (init_cmd),
.sdram_addr      (init_addr), 
.init_end        (init_end)
);
autorefresh u2_autorefresh
(
.clk		(clk),
.rst_n		(rst_n),
.init_end		(init_end),
.ref_en		(ref_en),
.ref_req		(ref_req),
.ref_end		(ref_end),
.autorefresh_cmd	(ref_cmd),
.sdram_addr		(ref_addr)	
);
sdram_write u3
(
.clk		(clk),        
.rst_n		(rst_n),      
.wr_en		(wr_en),      
.ref_req		(ref_req),    
.wr_trigger		(wr_trigger), 
.ref_end		(ref_end),    
.wr_req		(wr_req),     
.flag_wr_end	(flag_wr_end),
.wr_cmd		(wr_cmd),     
.bank_addr		(wr_bank_addr),  
.wr_addr		(wr_addr),    
.wr_data            (wr_data)
);
//功能
//仲裁模块状态机
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
state <= IDLE;
else case(state)
IDLE:
if(init_end)
state <= HANDLE;
else
state <= IDLE;	       
HANDLE:   if(ref_en)
state <= AUTOREFRESH;
else if(wr_en)
state <= WRITE;
else 
state <= HANDLE;
AUTOREFRESH:	
if(ref_end)
state <= HANDLE;
else
state <= AUTOREFRESH;
WRITE:	
if(flag_wr_end)
state <= HANDLE;
else
state <= WRITE;
default:	
state <=IDLE;
endcase
end
always@(*)begin
case(state)
IDLE:
begin
sdram_cmd  <= init_cmd;
sdram_addr <= init_addr;
end
AUTOREFRESH:
begin
sdram_cmd  <= ref_cmd;
sdram_addr <= ref_addr;
end
WRITE:
begin
sdram_cmd  <= wr_cmd;
sdram_addr <= wr_addr;
end
default:
begin
sdram_cmd  <= CMD_NOP;
sdram_addr <= 0;
end
endcase
end
//刷新使能
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
ref_en <= 0;
else if(ref_req && state == HANDLE)
ref_en <= 1;
else
ref_en <= 0;
end
//写使能
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
wr_en <= 0;
else if(wr_req && state == HANDLE && !ref_req)
wr_en <= 1;
else
wr_en <= 0;
end
assign  sdram_cke 	= 1'b1;
assign  sdram_clk  	= ~clk;
assign  sdram_dqm 	= 2'b00;
assign  sdram_dq 	= (state == WRITE)?wr_data:{16{1'bz}};
assign  sdram_bank 	= (state == WRITE)?wr_bank_addr:0;
endmodule

至此,写模块设计完成,在整个设计过程中呢,博主写错了一个状态机的名字,一个小小的问题找了一晚上的错,最终解决,也以此告诫大家一定义要细心!仿真结果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

11.读模块设计与仿真测试

我们完成了写模块的设计后,读模块就简单的要命了。打开写好的写模块,将所有的写命令全部改成读命令即可。接着补充top模块中的代码,将读模块例化,补全内部信号。

而读模块与写模块不一样的地方在于时序有一个小差别:读命令后两个时钟周期才将数据读到,也就是会有2个时钟周期的延迟;

还有一个就是读和写的优先级,这里优先写(先判断写在到读)

在这里插入图片描述
读模块代码如下:

module sdram_read
(
input                   clk,        //输入,50M时钟
input                   rst_n,      //输入,异步复位
input                   rd_en,      //输入,读使能
input                   ref_req,    //输入,刷新请求,用来中断读
input                   rd_trigger, //输入,读触发信号
input                   ref_end,    //输入,刷新结束,可以继续读
output reg              rd_req,     //输出,读请求
output reg              flag_rd_end,//输出,top状态机跳出读状态信号 
output reg [3:0]        rd_cmd,     //输出,读命令
output wire [1:0]       bank_addr,  //输出,bank地址
output reg [11:0]       rd_addr    //输出,读地址
);
//状态参数
localparam          IDLE    = 5'b0_0001;
localparam          REQ     = 5'b0_0010;
localparam          ACTIVE  = 5'b0_0100;
localparam          READ    = 5'b0_1000;
localparam          PRE     = 5'b1_0000;
//命令参数
localparam  CMD_NOP         = 4'b0111;
localparam  CMD_PRECHARGE   = 4'b0010;
localparam  CMD_AUTOREFLASH = 4'b0001;
localparam  CMD_ACTIVE      = 4'b0011;
localparam  CMD_READ        = 4'b0101;
//内部信号
reg                 flag_rd;        //读区域
reg [4:0]           state_rd;       //读时序状态
reg                 flag_ac_end;    //激活状态计数,计数完成跳转下一状态
reg                 flag_pre_end;   //充电状态计数,计数完成跳转下一状态
reg                 change_row;     //读完要换行,再激活新的一行
reg                 rd_data_end;    //数据写完信号
reg [1:0]           burst_cnt;      //突发计数器,一次读多少数据的控制
reg [1:0]           burst_cnt_ff;   //打一拍
reg [3:0]           active_cnt;     //激活计数器
reg [3:0]           pre_cnt;        //充电计数器
reg [6:0]           col_cnt;        //数据计数器,满512换行,归零
reg [11:0]          row_addr;       //行地址
wire [8:0]          col_addr;       //列地址
//功能
//状态机
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
state_rd <= IDLE;
end
else  begin
case(state_rd)
IDLE:
if(rd_trigger)
state_rd <= REQ;
else
state_rd <= IDLE;
REQ:
if(rd_en)
state_rd <= ACTIVE;
else
state_rd <= REQ;
ACTIVE:
if(flag_ac_end)
state_rd <= READ;
else
state_rd <= ACTIVE;
READ:
if(rd_data_end)
state_rd <= PRE;
else if(ref_req && burst_cnt_ff == 'd3 && flag_rd )
state_rd <= PRE;
else if(change_row && flag_rd)
state_rd <= PRE;
PRE:
if(ref_req && flag_rd)
state_rd <= REQ;
else if(flag_pre_end && flag_rd)
state_rd <= ACTIVE;
else if(rd_data_end)
state_rd <= IDLE;
default:
state_rd <= IDLE;
endcase
end
end
//burst_cnt_ff 打一拍
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
burst_cnt_ff <= 0;
end
else begin
burst_cnt_ff <= burst_cnt;
end
end
//命令
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_cmd <= CMD_NOP;
end
else case(state_rd)
ACTIVE:
if(active_cnt == 'd0)
rd_cmd <= CMD_ACTIVE;
else
rd_cmd <= CMD_NOP;
READ:
if(burst_cnt == 'd0)
rd_cmd <= CMD_READ;
else
rd_cmd <= CMD_NOP;
PRE:
if(pre_cnt == 'd0)
rd_cmd <= CMD_PRECHARGE;
else
rd_cmd <= CMD_NOP;
default:
rd_cmd <= CMD_NOP;
endcase
end
//读地址
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_addr <= 0;
end
else case(state_rd)
ACTIVE:
if(active_cnt == 'd0)
rd_addr <= row_addr;
READ:
rd_addr <= {3'b000,col_addr};
PRE:
if(pre_cnt == 'd0)
rd_addr <= {12'b0100_0000_0000};
default:
rd_addr <= 0;                           
endcase
end
//bank地址
assign bank_addr = 2'b00;
//flag_rd
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_rd <= 0;
end
else if(rd_trigger && !flag_rd)
flag_rd <= 1;
else if(rd_data_end)
flag_rd <= 0;
end
//act计数器,计数激活所需要的时间
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
active_cnt <= 0;
end
else if(state_rd == ACTIVE)
active_cnt <= active_cnt + 1;
else
active_cnt <= 0;
end
//刷新计数
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
pre_cnt <= 0;
end
else if(state_rd == PRE) begin
pre_cnt <= pre_cnt + 1;
end
else
pre_cnt <= 0;
end
//col计数器,列计数
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
col_cnt <= 0;
end
else if(col_addr == 'd511) 
col_cnt <= 0;
else if(burst_cnt =='d3)
col_cnt <= col_cnt + 1;  
end
//产生flag_ac_end信号
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_ac_end <= 0;
end
else if(active_cnt >= 'd3) begin
flag_ac_end <= 1;
end
else
flag_ac_end <= 0;
end
//产生flag_pre_end信号
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_pre_end <= 0;
end
else if(pre_cnt >= 'd3) begin
flag_pre_end <= 1;
end
else
flag_pre_end <= 0;
end
//产生flag_rd_end信号
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_rd_end <= 0;
end
else if(state_rd == PRE && ref_req ) 
flag_rd_end <= 1;
else if(state_rd == PRE && rd_data_end)
flag_rd_end <= 1;
else
flag_rd_end <= 0;
end
//突发长度计数
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
burst_cnt <= 0;
end
else if(state_rd == READ) begin
burst_cnt <= burst_cnt +1;
end
else
burst_cnt <= 0;
end
//读结束标志
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_data_end <= 0;
end
else if(row_addr == 'd1 && col_addr == 'd511 ) begin
rd_data_end <= 1;
end
else
rd_data_end <= 0;
end
//col_addr
assign col_addr = {col_cnt,burst_cnt};
//row_addr
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)
row_addr <= 0;
else if(change_row)
row_addr <= row_addr + 1;
end
//换行请求
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
change_row <= 0;
end
else if(col_addr == 'd510 )
change_row <= 1;
else
change_row <= 0;
end
//读请求
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_req <= 0;
end
else if(state_rd == REQ && ref_end)
rd_req <= 1;
else
rd_req <= 0;
end
endmodule

为了方便测试,在测试文件中多引入了读触发信号,写触发在205000ns,而读触发在325000ns。

`timescale 1 ns/1 ns
module sdram_top_tb();
//输入信号
reg                 clk  ;
reg                 rst_n;
reg		    wr_trigger;
reg		    rd_trigger;	
//输出信号
wire            sdram_clk    ; 
wire            sdram_cke    ;
wire            sdram_cs     ;
wire            sdram_cas    ;
wire            sdram_ras    ;
wire            sdram_we     ;
wire [1:0]      sdram_bank   ;
wire [11:0]     sdram_addr   ;
wire [1:0]      sdram_dqm    ;
wire [15:0]     sdram_dq     ;
//参数
parameter CYCLE    = 20;
parameter RST_TIME = 3 ;
//待测试的模块例化
sdram_top u1_sdram_top
(
.clk                 (clk           ), 
.rst_n               (rst_n         ),
.wr_trigger		 (wr_trigger	),
.rd_trigger		 (rd_trigger	),
.sdram_clk           (sdram_clk     ),
.sdram_cke           (sdram_cke     ),
.sdram_cs            (sdram_cs      ),
.sdram_cas           (sdram_cas     ),
.sdram_ras           (sdram_ras     ),
.sdram_we            (sdram_we      ),
.sdram_bank          (sdram_bank    ),
.sdram_addr          (sdram_addr    ),
.sdram_dqm           (sdram_dqm     ),   
.sdram_dq            (sdram_dq      )
);
//插件例化,相当于是一个sdram硬件
sdram_model_plus u2_plus
(   .Dq             (sdram_dq), 
.Addr           (sdram_addr), 
.Ba             (sdram_bank), 
.Clk            (sdram_clk), 
.Cke            (sdram_cke), 
.Cs_n           (sdram_cs), 
.Ras_n          (sdram_ras), 
.Cas_n          (sdram_cas), 
.We_n           (sdram_we), 
.Dqm            (sdram_dqm),
.Debug          (1'b1)    
);
//重定义具体sdram的参数,因为插件的和实际的并不一样
defparam         u2_plus.addr_bits = 12;
defparam         u2_plus.data_bits = 16;
defparam         u2_plus.col_bits  = 9;
defparam         u2_plus.mem_sizes = 2*1024*1024; //2M 一个bank
//  生成本地时钟50M
initial begin
clk = 0;
forever
#(CYCLE/2)
clk=~clk;
end
//产生复位信号
initial begin
rst_n = 1;
#2;
rst_n = 0;
#(CYCLE*RST_TIME);
rst_n = 1;
end
initial begin
wr_trigger <= 0;
#205000
wr_trigger <= 1;
#20
wr_trigger <= 0;
end
initial begin
rd_trigger <= 0;
#325000
rd_trigger <= 1;
#20
rd_trigger <= 0;
end
endmodule

仿真结果如图:
在这里插入图片描述
初始化后,由于要等此次刷新结束后才开始写,所以205000ns时并不能立即写,要延后到下一个刷新结束,激活第0行开始写数据8411。

在这里插入图片描述
写满一行时还没到15us一次的刷新,因此进行激活第1行操作,继续写。

在这里插入图片描述
230000ns又要刷新了,跳出写,进行刷新命令。

在这里插入图片描述
写完后,中间无命令的一段时间每15us进行一次刷新。

在这里插入图片描述
320000us后开始读操作,数据8411,读数正常。

在这里插入图片描述
读完一行开始换行,需要激活下一行。因为上述的延迟2周期的数据,出现上图情况,也是正常情况。

在这里插入图片描述
读数据过程中遇到刷新命令,中断读而进行刷新,完成刷新后继续读。

在这里插入图片描述

数据读完后没有其他命令,则进行每15us的自动刷新操作。至此仿真结束,没有问题。整个SDRAM的功能模块也写完,接下来编写完成整个控制器系统。

12.通信处理模块

在这里插入图片描述
在写完SDRAM模块后呢,其他的代码就简单很多啦,先写一个串口通信的通信处理模块,其作用是将rx模块发送过来的8位数据写到wfifo中,并给出读写的触发信号到sdram模块。代码及注释如下:

		module ctrl_rxtx
(
input               clk,
input               rst_n,
input               flag_uart,
input [7:0]         uart_data,
output wire         wr_trigger,
output wire         rd_trigger,
output wire         wfifo_wr_en,
output wire [7:0]   wfifo_wr_data
);
//参数
localparam      RX_NUM_END = 'd4;
//内部信号
reg [2:0]       rx_num; //接收数据的个数,此数据是有rx模块给出的8位数据,一共接收五个
reg [7:0]       cmd_reg;//数据寄存器
//功能
//接收数据计数
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_num <= 0;
end
else if(flag_uart && rx_num == 'd0 && uart_data == 8'haa)
rx_num <= 0;
else if(flag_uart && rx_num == RX_NUM_END)
rx_num <= 0;
else if(flag_uart)
rx_num <= rx_num + 1;
end
//数据寄存,命令也是从数据这出来的
always  @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
cmd_reg <= 8'h00;
end
else if(rx_num == 'd0 && flag_uart)
cmd_reg <= uart_data;
end
//接受到第四个数据且为命令指示数据55时,给出写触发信号
assign wr_trigger  = (rx_num == RX_NUM_END && cmd_reg == 8'h55)?flag_uart:0;
//没有接收新数据且为命令指示数据aa时,给出读触发信号
assign rd_trigger  = (rx_num == 'd0 && uart_data == 8'haa)?flag_uart:0;
//写到wfifo的使能,有数据过来时,就写到wfifo中
assign wfifo_wr_en = (rx_num >= 'd1)?flag_uart:0;
//写入wfifo的数据
assign wfifo_wr_data  = uart_data;
endmodule

13.顶层模块top

按照第一节的图,在每个模块之间多加了关于读写fifo的接口,在top模块中连线如下:
在这里插入图片描述
首先我们要生成一个fifo的ip,调用其FIFO的.v文件,具体的创建参照FPGA实例03——FIFO的IP核创建及16位输入转8位输出

具体的fifo IP文件如下,我们只需要例化出一个写fifo和一个读fifo即可,此fifo为16深度,数据8位,同步fifo(读写都是相同的时钟)

//synopsys translate_off
`timescale 1 ps / 1 ps
// synopsys translate_on
module fifo_ip (
clock,
data,
rdreq,
wrreq,
empty,
q);
input	  clock;
input	[7:0]  data;
input	  rdreq;
input	  wrreq;
output	  empty;
output	[7:0]  q;
wire  sub_wire0;
wire [7:0] sub_wire1;
wire  empty = sub_wire0;
wire [7:0] q = sub_wire1[7:0];
scfifo	scfifo_component (
.clock (clock),
.data (data),
.rdreq (rdreq),
.wrreq (wrreq),
.empty (sub_wire0),
.q (sub_wire1),
.aclr (),
.almost_empty (),
.almost_full (),
.full (),
.sclr (),
.usedw ());
defparam
scfifo_component.add_ram_output_register = "OFF",
scfifo_component.intended_device_family = "Cyclone IV E",
scfifo_component.lpm_numwords = 16,
scfifo_component.lpm_showahead = "OFF",
scfifo_component.lpm_type = "scfifo",
scfifo_component.lpm_width = 8,
scfifo_component.lpm_widthu = 4,
scfifo_component.overflow_checking = "ON",
scfifo_component.underflow_checking = "ON",
scfifo_component.use_eab = "ON";
endmodule

接着我们需要在sdram的读和写模块中加入fifo的使能和数据接口:

①由写模块给写使能到写fifo中,使得缓存在写fifo中的数据像sdram的写模块传输,注意数据要与写突发对齐,不然数据必定出错

②由读模块给读使能到读fifo中,使得读fifo读取sdram的数据,注意数据要与读突发对齐,不然数据必定出错

③在各个模块直接接入写fifo以及读fifo的接口(有点繁杂)

④将sdram模块本来的写2行操作改为写4个数据

⑤分析整个控制器的数据流:

在这里插入图片描述

最后的顶层代码如下:

			module top
(
input                   clk,
input                   rst_n,
input                   rx,
output                  tx,
output wire             sdram_clk,
output wire             sdram_cke,
output wire             sdram_cs,
output wire             sdram_cas,
output wire             sdram_ras,
output wire             sdram_we,
output wire [1 :0 ]     sdram_bank,
output wire  [11:0 ]    sdram_addr,
output wire [1 :0 ]     sdram_dqm,
inout       [15:0 ]     sdram_dq
);
//内部信号,中间连接点
wire [7:0]      data;      //rx出来的8位数据,先给到通信模块,再给到wfifo,在给到SDRAM模块,再到rfifo,最后输出
wire            tx_trigger;
wire            sdram_wr_trigger;
wire            sdram_rd_trigger;
wire            wfifo_wr_en;
wire [7:0]      wfifo_wr_data;
wire            wfifo_rd_en;
wire [7:0]      wfifo_rd_data;
wire [7:0]      rfifo_wr_data;
wire            rfifo_wr_en;
wire [7:0]      rfifo_rd_data;
wire            rfifo_rd_en;
wire            rfifo_empty;
uart_rx u1_rx
(
.clk                (clk),          
.rst_n              (rst_n),
.rx_uart            (rx),
.data               (data),
.rx_over            (tx_trigger)
);
ctrl_rxtx u2_control
(
.clk                (clk),
.rst_n              (rst_n),
.flag_uart          (tx_trigger),
.uart_data          (data),
.wr_trigger         (sdram_wr_trigger),
.rd_trigger         (sdram_rd_trigger),
.wfifo_wr_en        (wfifo_wr_en),
.wfifo_wr_data      (wfifo_wr_data)
);
ip_fifo u3_wfifo
(
.clock               (clk),
.data                (wfifo_wr_data),
.rdreq               (wfifo_rd_en),
.wrreq               (wfifo_wr_en),
.empty               (           ),
.q                   (wfifo_rd_data)
);
ip_fifo u4_rfifo
(
.clock               (clk),
.data                (rfifo_wr_data),
.rdreq               (rfifo_rd_en),
.wrreq               (rfifo_wr_en),
.empty               (rfifo_empty),
.q                   (rfifo_rd_data)
);
uart_tx u5_tx
(
.clk                  (clk),
.rst_n                (rst_n),
.tx                   (tx), 
.rfifo_empty          (rfifo_empty),
.rfifo_rd_data        (rfifo_rd_data),               
.rfifo_rd_en          (rfifo_rd_en)
);
sdram_top u6_sdram
(
.clk                (clk),
.rst_n              (rst_n),
.wr_trigger         (sdram_wr_trigger),
.rd_trigger         (sdram_rd_trigger),
.wfifo_rd_data      (wfifo_rd_data),
.wfifo_rd_en        (wfifo_rd_en  ),
.rfifo_wr_en        (rfifo_wr_en ),
.rfifo_wr_data      (rfifo_wr_data),
.sdram_clk          (sdram_clk  ),
.sdram_cke          (sdram_cke  ),
.sdram_cs           (sdram_cs   ),
.sdram_cas          (sdram_cas  ),
.sdram_ras          (sdram_ras  ),
.sdram_we           (sdram_we   ),
.sdram_bank         (sdram_bank ),
.sdram_addr         (sdram_addr ),
.sdram_dqm          (sdram_dqm  ),
.sdram_dq           (sdram_dq   )
);                      
endmodule               

14.上板!

根据我们的fpga板子的原理图,对应设置好管脚,最后进行上版实验,当然我这里是最终的结果,并不是一次就实现了功能,其中修修改改了好多细节的地方,所以说时序的设计要想的非常的全面,不然就得慢慢调试,这个过程太苦了!下面是最终的功能:发送55表示写入,再写入4个数据,aa表示读出
在这里插入图片描述
板级调试结束!

至此,整个项目结束。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/160074.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • EOS智能合约授权限制和数据存储

    EOS智能合约授权限制和数据存储

  • 电信光纤友华PT921G,烽火HG220光猫激活成功教程关闭自带路由改桥接拨号教程「建议收藏」

    电信光纤友华PT921G,烽火HG220光猫激活成功教程关闭自带路由改桥接拨号教程「建议收藏」电信光纤友华PT921G光猫激活成功教程关闭自带路由改桥接拨号教程电信光猫质量烂就算了,最受不了它自带的路由还做了手脚,导致VPN用不了。不让看AV就算了,打个外服游戏总可以吧?不知道为啥,网上关于光猫改桥接的教程基本没有,搜出来的也说得很不清楚,是和谐了还是什么原因不得而知。本人也是自己自己试出来的,其实修改难度并不大,只不过那个界面搞的特奇葩特不友好罢了。废话不多说,步骤如下:

  • vue 时间戳转换成yyyy-MM-dd hh:mm[通俗易懂]

    vue 时间戳转换成yyyy-MM-dd hh:mm[通俗易懂]data的就是文件直接引入exportfunctionformatDate(date,fmt){if(/(y+)/.test(fmt)){fmt=fmt.replace(RegExp.$1,(date.getFullYear()+”).substr(4-RegExp.$1.length));}leto={‘M…

    2022年10月20日
  • docker离线安装部署 linux_docker官方中文文档

    docker离线安装部署 linux_docker官方中文文档linux下离线安装docker一、基础环境1、操作系统:centos7.32、docker版本:18.06.1官方下载地址(打不开可能很慢)4、官方参考文档:二、docker安装1、解压tar-xvfdocker-18.06.1-ce.tgz2、将解压出来的docker文件内容移动到/usr/bin/目录下mvdocker/*/usr/bin/3、将docker注册为servic…

  • 树莓派 网络设置_树莓派4b教程

    树莓派 网络设置_树莓派4b教程概览你想做的第一件事一定是把你的树莓派连接到因特网上。在这节课里,你将会学到如何:使用网线连接到以太网在Raspbian和Occidentalis上使用无线网卡找到树莓派的IP地址使用有线网络最快的把树莓派接入到因特网的方法是使用一根以太网线把树莓派连接到你家的路由器上。当你把网线连入树莓派的时候,你就会看到网络LED灯开始闪烁了。对于大多数的家庭网络来说,你就不需要再做任何进一步的配置了。但为了…

  • 前端VsCode使用插件

    前端VsCode使用插件vscode前端插件

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号