FPGA综合项目——SDRAM控制器

article/2025/10/26 11:56:22

FPGA综合项目——SDRAM控制器

目录

  • 1.整体框架
  • 2.串口接收模块
  • 3.接收模块测试仿真
  • 4.串口发送模块
  • 5.发送模块测试仿真
  • 6.SDRAM基础学习
  • 7.SDRAM顶层模块
  • 8.SDRAM初始化模块设计与仿真测试
  • 9.自动刷新模块设计与测试
  • 10.写模块设计与测试
  • 11.读模块设计与仿真测试
  • 12.通信处理模块
  • 13.顶层模块top
  • 14.上板!
  • 15.后记:资源使用情况

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 )beginrx_ff0 <= rx;rx_ff1 <= rx_ff0;rx_ff2 <= rx_ff1;
endassign pose = rx_ff2 == 0 && rx_ff1 == 1;
assign nege = rx_ff2 == 1 && rx_ff1 == 0;always@(posedge clk or negedge rst_n)beginif(!rst_n)flag_rx <= 0;else if(nege)flag_rx <= 1;else if(cnt_8bit == 0 && cnt_9600 == _9600-1 )flag_rx <= 0;
endalways@(posedge clk or negedge rst_n)beginif(!rst_n)cnt_9600 <= 0;else if(cnt_9600 == _9600-1)cnt_9600 <= 0;else if(flag_rx)cnt_9600 <= cnt_9600 + 1;elsecnt_9600 <= 0;
endalways@(posedge clk or negedge rst_n)beginif(!rst_n)bit_cnt_flag <= 0;else if(cnt_9600 == MIDDLE)bit_cnt_flag <= 1;elsebit_cnt_flag <= 0;
endalways@(posedge clk or negedge rst_n)beginif(!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;
endalways@(posedge clk or negedge rst_n)beginif(!rst_n)rx_over <= 0;else if(bit_cnt_flag && cnt_8bit == _8bit - 1)rx_over <= 1;else rx_over <= 0;
endalways@(posedge clk or negedge rst_n)beginif(!rst_n)rx_data <= 0;else if(bit_cnt_flag && cnt_8bit >= 1  )rx_data <= {rx_ff1,rx_data[7:1]};
endendmodule

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 beginclk = 0;forever#(CYCLE/2)clk=~clk;
end//产生复位信号
initial beginrst_n = 1;#2;rst_n = 0;#2rst_n = 1;
end//把文件里的数据写入mem_a存储器中
//此文件写好放在sim的文件夹中
initial $readmemh("./rx.txt",mem_a); initial beginrx  = 0; #20rx  = 1; #20byte();	//task
end//task1 , 将txt文件的4个数存到寄存器a中
task    byte();integer i;for(i =0;i<4;i = i+1)beginbit(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)begincase(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
endtaskendmodule

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

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)beginif(!rst_n)reg_tx_data <= 0;else if(trig && !flag_tx)reg_tx_data <= tx_data;end
//传输有效区域信号
always@(posedge clk or negedge rst_n)beginif(!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)beginif(!rst_n)cnt_9600 <= 0;else if(flag_tx)beginif(cnt_9600 == _9600-1)cnt_9600 <= 0;elsecnt_9600 <= cnt_9600 + 1;endend
//数据个数计数
always@(posedge clk or negedge rst_n)beginif(!rst_n)cnt_8bit <= 0;else if(flag_tx && cnt_9600 == _9600-1)beginif(cnt_8bit == _8bit-1)cnt_8bit <= 0;elsecnt_8bit <= cnt_8bit + 1;end
end
//tx
always@(posedge clk or negedge rst_n)beginif(!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;endcaseelsetx <= 1;
end
endmodule

5.发送模块测试仿真

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

`timescale 1 ns/1 nsmodule 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 beginclk = 0;forever#(CYCLE/2)clk=~clk;
end//产生复位信号
initial beginrst_n = 1;#2;rst_n = 0;#2rst_n = 1;
endinitial begintx_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;
endendmodule

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

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-A11output 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)beginif(!rst_n)begincnt0 <= 0;endelse if(!flag_200us)begincnt0 <= cnt0 + 1;end
end//周期个数计数
assign init_end = (cnt1>='d10)?1:0;always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt1 <= 0;endelse if(flag_200us && !init_end)beginif(cnt1==11-1)cnt1 <= 0;elsecnt1 <= cnt1 + 1;end
end//命令操作
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)begincmd_reg <= NOP;endelse if(flag_200us)begincase(cnt1)0: cmd_reg <= PRECHARGE;1: cmd_reg <= AUTOREFLASH;5: cmd_reg <= AUTOREFLASH;9: cmd_reg <= MODESET;default: cmd_reg <= NOP;endcaseend
end//sdram_addr
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginsdram_addr <= 0;endelse if(flag_200us)begincase(cnt1)9:  sdram_addr <= 12'b0000_0011_0010;default:  sdram_addr <= 12'b0100_0000_0000;endcaseend
endendmodule

再完成顶层模块的编写:

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 nsmodule 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 beginclk = 0;forever#(CYCLE/2)clk=~clk;
end//产生复位信号
initial beginrst_n = 1;#2;rst_n = 0;#(CYCLE*RST_TIME);rst_n = 1;
endendmodule

仿真结果:充电范围所有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)beginif(!rst_n)cnt0 <= 0;else if(init_end)beginif(cnt0== DELAY_15US-1)cnt0 <= 0;elsecnt0 <= 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)beginif(rst_n==1'b0)beginflag_ref <= 0;endelse if(ref_en) beginflag_ref <= 1;endelse if(ref_end) beginflag_ref <=0;end
end//时钟T个数计数器
always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt1 <= 0;endelse if(flag_ref)beginif(cnt1== 4-1)cnt1 <= 0;elsecnt1 <= cnt1 + 1;end
end//给出命令
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginautorefresh_cmd <= NOP;endelse 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)beginif(!rst_n)state <= IDLE;else case(state)IDLE:if(init_end)state <= HANDLE;elsestate <= IDLE;	       HANDLE:   if(ref_en)state <= AUTOREFRESH;else state <= HANDLE;AUTOREFRESH:	if(ref_end)state <= HANDLE;elsestate <= AUTOREFRESH;default:	state <=IDLE;endcaseend//刷新使能
always@(posedge clk or negedge rst_n)beginif(!rst_n)ref_en <= 0;else if(ref_req && state == HANDLE)ref_en <= 1;elseref_en <= 0;
endassign  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)beginif(!rst_n)cnt0 <= 0;else if(init_end)beginif(cnt0== DELAY_15US-1)cnt0 <= 0;elsecnt0 <= cnt0 + 1;end
end//刷新请求,每15us一次 ,直到刷新使能来之前,都为1
always  @(posedge clk or negedge rst_n) beginif(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)beginif(rst_n==1'b0)beginflag_ref <= 0;endelse if(ref_en) beginflag_ref <= 1;endelse if(ref_end) beginflag_ref <=0;end
end//时钟T个数计数器
always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt1 <= 0;endelse if(flag_ref)beginif(cnt1== 4-1)cnt1 <= 0;elsecnt1 <= cnt1 + 1;end
end//给出命令
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginautorefresh_cmd <= NOP;endelse 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)beginif(rst_n==1'b0)beginstate_wr <= IDLE;endelse  begincase(state_wr)IDLE:if(wr_trigger)state_wr <= REQ;elsestate_wr <= IDLE;REQ:if(wr_en)state_wr <= ACTIVE;elsestate_wr <= REQ;ACTIVE:if(flag_ac_end)state_wr <= WRITE;elsestate_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;endcaseend
end//burst_cnt_ff 打一拍
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginburst_cnt_ff <= 0;endelse burst_cnt_ff <= burst_cnt;
end//数据输出
always  @(*)begincase(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)beginif(rst_n==1'b0)beginwr_cmd <= CMD_NOP;endelse case(state_wr)ACTIVE:if(active_cnt == 'd0)wr_cmd <= CMD_ACTIVE;elsewr_cmd <= CMD_NOP;WRITE:if(burst_cnt == 'd0)wr_cmd <= CMD_WRITE;elsewr_cmd <= CMD_NOP;PRE:if(pre_cnt == 'd0)wr_cmd <= CMD_PRECHARGE;elsewr_cmd <= CMD_NOP;default:wr_cmd <= CMD_NOP;endcase
end//写地址
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginwr_addr <= 0;endelse 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)beginif(rst_n==1'b0)beginflag_wr <= 0;endelse 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)beginif(rst_n==1'b0)beginactive_cnt <= 0;endelse if(state_wr == ACTIVE)active_cnt <= active_cnt + 1;elseactive_cnt <= 0;
end//刷新计数
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginpre_cnt <= 0;endelse if(state_wr == PRE) beginpre_cnt <= pre_cnt + 1;endelsepre_cnt <= 0;
end//col计数器,列计数
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)begincol_cnt <= 0;endelse 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)beginif(rst_n==1'b0)beginflag_ac_end <= 0;endelse if(active_cnt >= 'd3) beginflag_ac_end <= 1;endelseflag_ac_end <= 0;
end
//产生flag_pre_end信号
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginflag_pre_end <= 0;endelse if(pre_cnt >= 'd3) flag_pre_end <= 1;elseflag_pre_end <= 0;
end//产生flag_wr_end信号(写一次结束)
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginflag_wr_end <= 0;endelse if(state_wr == PRE && ref_req  ) flag_wr_end <= 1;else if(state_wr == PRE && wr_data_end)flag_wr_end <= 1;elseflag_wr_end <= 0;
end//写所有结束标志
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginwr_data_end <= 0;endelse if(row_addr == 'd1 && col_addr == 'd511 ) beginwr_data_end <= 1;endelsewr_data_end <= 0;
end//突发长度计数
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginburst_cnt <= 0;endelse if(state_wr == WRITE) beginburst_cnt <= burst_cnt +1;endelseburst_cnt <= 0;
end//col_addr 列地址计算
assign col_addr = {col_cnt,burst_cnt};//row_addr  行地址
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginrow_addr <= 0;endelse if(change_row)row_addr <= row_addr + 1;
end//换行请求
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginchange_row <= 0;endelse if(col_addr == 'd510 )change_row <= 1;elsechange_row <= 0;
end//写请求
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginwr_req <= 0;endelse if(state_wr == REQ && ref_end)wr_req <= 1;elsewr_req <= 0;
endendmodule

在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)beginif(!rst_n)state <= IDLE;else case(state)IDLE:if(init_end)state <= HANDLE;elsestate <= IDLE;	       HANDLE:   if(ref_en)state <= AUTOREFRESH;else if(wr_en)state <= WRITE;else state <= HANDLE;AUTOREFRESH:	if(ref_end)state <= HANDLE;elsestate <= AUTOREFRESH;WRITE:	if(flag_wr_end)state <= HANDLE;elsestate <= WRITE;default:	state <=IDLE;endcaseendalways@(*)begincase(state)IDLE:beginsdram_cmd  <= init_cmd;sdram_addr <= init_addr;endAUTOREFRESH:beginsdram_cmd  <= ref_cmd;sdram_addr <= ref_addr;endWRITE:beginsdram_cmd  <= wr_cmd;sdram_addr <= wr_addr;enddefault:beginsdram_cmd  <= CMD_NOP;sdram_addr <= 0;endendcaseend//刷新使能
always@(posedge clk or negedge rst_n)beginif(!rst_n)ref_en <= 0;else if(ref_req && state == HANDLE)ref_en <= 1;elseref_en <= 0;
end
//写使能
always@(posedge clk or negedge rst_n)beginif(!rst_n)wr_en <= 0;else if(wr_req && state == HANDLE && !ref_req)wr_en <= 1;elsewr_en <= 0;
endassign  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)beginif(rst_n==1'b0)beginstate_rd <= IDLE;endelse  begincase(state_rd)IDLE:if(rd_trigger)state_rd <= REQ;elsestate_rd <= IDLE;REQ:if(rd_en)state_rd <= ACTIVE;elsestate_rd <= REQ;ACTIVE:if(flag_ac_end)state_rd <= READ;elsestate_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;endcaseend
end//burst_cnt_ff 打一拍
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginburst_cnt_ff <= 0;endelse beginburst_cnt_ff <= burst_cnt;end
end//命令
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginrd_cmd <= CMD_NOP;endelse case(state_rd)ACTIVE:if(active_cnt == 'd0)rd_cmd <= CMD_ACTIVE;elserd_cmd <= CMD_NOP;READ:if(burst_cnt == 'd0)rd_cmd <= CMD_READ;elserd_cmd <= CMD_NOP;PRE:if(pre_cnt == 'd0)rd_cmd <= CMD_PRECHARGE;elserd_cmd <= CMD_NOP;default:rd_cmd <= CMD_NOP;endcase
end//读地址
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginrd_addr <= 0;endelse 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)beginif(rst_n==1'b0)beginflag_rd <= 0;endelse 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)beginif(rst_n==1'b0)beginactive_cnt <= 0;endelse if(state_rd == ACTIVE)active_cnt <= active_cnt + 1;elseactive_cnt <= 0;
end//刷新计数
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginpre_cnt <= 0;endelse if(state_rd == PRE) beginpre_cnt <= pre_cnt + 1;endelsepre_cnt <= 0;
end//col计数器,列计数
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)begincol_cnt <= 0;endelse 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)beginif(rst_n==1'b0)beginflag_ac_end <= 0;endelse if(active_cnt >= 'd3) beginflag_ac_end <= 1;endelseflag_ac_end <= 0;
end//产生flag_pre_end信号
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginflag_pre_end <= 0;endelse if(pre_cnt >= 'd3) beginflag_pre_end <= 1;endelseflag_pre_end <= 0;
end//产生flag_rd_end信号
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginflag_rd_end <= 0;endelse if(state_rd == PRE && ref_req ) flag_rd_end <= 1;else if(state_rd == PRE && rd_data_end)flag_rd_end <= 1;elseflag_rd_end <= 0;
end//突发长度计数
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginburst_cnt <= 0;endelse if(state_rd == READ) beginburst_cnt <= burst_cnt +1;endelseburst_cnt <= 0;
end//读结束标志
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginrd_data_end <= 0;endelse if(row_addr == 'd1 && col_addr == 'd511 ) beginrd_data_end <= 1;endelserd_data_end <= 0;
end//col_addr
assign col_addr = {col_cnt,burst_cnt};//row_addr
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)row_addr <= 0;else if(change_row)row_addr <= row_addr + 1;
end//换行请求
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginchange_row <= 0;endelse if(col_addr == 'd510 )change_row <= 1;elsechange_row <= 0;
end//读请求
always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginrd_req <= 0;endelse if(state_rd == REQ && ref_end)rd_req <= 1;elserd_req <= 0;
endendmodule

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

`timescale 1 ns/1 nsmodule 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 beginclk = 0;forever#(CYCLE/2)clk=~clk;
end//产生复位信号
initial beginrst_n = 1;#2;rst_n = 0;#(CYCLE*RST_TIME);rst_n = 1;
endinitial beginwr_trigger <= 0;#205000wr_trigger <= 1;#20wr_trigger <= 0;
endinitial beginrd_trigger <= 0;#325000rd_trigger <= 1;#20rd_trigger <= 0;
endendmodule

仿真结果如图:
在这里插入图片描述
初始化后,由于要等此次刷新结束后才开始写,所以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)beginif(rst_n==1'b0)beginrx_num <= 0;endelse 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)beginif(rst_n==1'b0)begincmd_reg <= 8'h00;endelse 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 ());defparamscfifo_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表示读出
在这里插入图片描述
板级调试结束!

至此,整个项目结束。

15.后记:资源使用情况

在这里插入图片描述
使用的基本逻辑单元为333个,其中组合逻辑用了319个,时序逻辑用了191个,也就是说,存在既有组合逻辑也有时序逻辑的LE;

我们再来看一个表

在这里插入图片描述

纯组合逻辑:142个基本逻辑单元

纯时序逻辑:14个基本逻辑单元

组合时序混合:177个基本逻辑单元

由于过长的组合逻辑(多个LUT串联)会有较大的延时,而时序逻辑能够把较长的组合逻辑链分割成较短的组合逻辑链,有效地缩短关键路径和次关键路径的长度,进而提高该FPGA设计的整体时序性能,所以组合逻辑与时序逻辑的使用比例(组/时)可以作为评价一个FPGA设计时序性能的辅助参数

这里的的组/时 = 319 / 191 = 1.67


http://chatgpt.dhexx.cn/article/NqiOgijs.shtml

相关文章

细说SDRAM控制器

SDRAM的基本概念 SDRAM凭借其极高的性价比&#xff0c;广泛应用于高速数据存储、实时图像处理等设计当中&#xff0c;但是相对于SRAM、FIFO等其他存储器件&#xff0c;SDRAM的控制相对复杂。虽说是复杂&#xff0c;但也不代表没办法实现&#xff0c;仔细梳理一下&#xff0c;发…

SDRAM控制器

1.SDRAM简介 简介&#xff1a;SDRAM为同步动态随机存储内存&#xff0c;同步指的是时钟与外部输入的时钟保持一致&#xff0c;也就是与外部共用一个时钟&#xff1b;动态指的是每个时间段内&#xff0c;都要进行一次刷新操作&#xff0c;否则里面的数据会丢失&#xff0c;这也…

FPGA学习历程(四):SDRAM 控制器(初始化与刷新)

目录 一、数据手册相关信息1.1 命令真值表1.2 时间参数1.3 模式寄存器配置 二、初始化模块2.1 模块时序图2.2 模块源码2.2.1 sdram_init.v2.2.2 sdram_top.v2.2.3 tb_sdram_top.v 2.3 Modelsim仿真 三、刷新模块3.1 模块时序图3.2 模块源码3.2.1 sdram_aref.v3.2.2 sdram_top.v…

手把手带你实现SDRAM控制器(带Verilog代码)

上篇博客&#xff0c;我们了解了SDRAM的控制命令以及寻址方式&#xff0c;SDRAM芯片需要配合专门的控制电路使用才能发挥功能&#xff0c;这一节我们将一步步分析&#xff0c;使用Verilog搭建一个SDRAM驱动控制器。 目录 学习目标 问题分析 初始化模块 信息收集 模块接口确…

SDRAM 控制器(一)

1、基础知识 SDRAM&#xff08;synchronous Dynamic Random &#xff09;&#xff0c;同步动态随机存储器&#xff0c;同步指内存工作需要同步时钟&#xff0c;内存的命令的发送和数据的接收都以它为标准。动态是指需要不断地刷新来保证数据不丢失&#xff08;电容存储&#xf…

操作系统面试题(二十一):什么是DMA

DMA DMA&#xff08;Direct Memory Access 直接内存访问&#xff09; DMA意味着CPU授予I/O模块权限不涉及在不涉及CPU的情况下依然可以读取/写入内存&#xff0c;即DMA不需要CPUde支持 DMAC&#xff08;DMA 控制器&#xff09; 控制直接内存访问的过程 DMA的优点&#xff1a…

操作系统面试题:虚拟内存是什么,解决了什么问题,如何映射?

虚拟内存是什么&#xff1f; 虚拟内存别称虚拟存储器&#xff08;Virtual Memory&#xff09;。电脑中所运行的程序均需经由内存执行&#xff0c;若执行的程序占用内存很大或很多&#xff0c;则会导致内存消耗殆尽。为解决该问题&#xff0c;Windows中运用了虚拟内存技术&…

Linux面试题(34道)

1、Linux中各个目录的作用 1&#xff09;/ 根目录 2&#xff09;/boot 引导程序&#xff0c;内核等存放的目录 3&#xff09;/sbin 超级用户可以使用的命令的存放目录 4&#xff09;/bin 普通用户可以使用的命令的存放目录 5&#xff09;/lib 根目录下的所程序的共享库目录 6&…

【游戏客户端面试题干货】-- 2021年度最新游戏客户端面试干货(操作系统篇)

【游戏客户端面试题干货】-- 2021年度最新游戏客户端面试干货(操作系统篇&#xff09; 大家好&#xff0c;我是Lampard~~ 经过一番艰苦奋战之后&#xff0c;我终于是进入了心仪的公司。 今天给大家分享一下我在之前精心准备的一套面试知识。 今天和大家分享的是操作系统相关的面…

操作系统和网络(一):计算机网络常见面试题

计算机网络常见面试题总结 1. OSI &#xff0c; TCP/IP &#xff0c;五层协议的体系结构 OSI分层&#xff08;7层&#xff09; &#xff1a;物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。 TCP/IP分层&#xff08;4层&#xff09; &#xff1a;网络接…

【Java实习生】每日面试题打卡——操作系统篇

临近秋招&#xff0c;备战暑期实习&#xff0c;祝大家每天进步亿点点&#xff01;Day15本篇总结的是 操作系统 相关的面试题&#xff0c;后续会每日更新~ 1、请分别简单说一说进程和线程以及它们的区别? 根本区别&#xff1a;进程是操作系统资源分配的基本单位&#xff0c;而…

操作系统面试题(一)

请你说一下进程与线程的概念&#xff0c;以及为什么要有进程线程&#xff0c;其中有什么区别&#xff0c;他们各自又是怎么同步的 参考回答&#xff1a; 基本概念&#xff1a; 进程是对运行时程序的封装&#xff0c;是系统进行资源调度和分配的的基本单位&#xff0c;实现了…

操作系统面试常问——for考研复试面试

关于操作系统的一些面试常问问题 前言&#xff1a; 本人22考研党已上岸&#xff0c;发一些复试准备整理的资料作为对考研准备的一个收尾。由于近几年基本都是线上复试&#xff0c;线上的话会更加注重概念的考察&#xff0c;本人在复试准备期间搜集了面试题&#xff0c;整理了…

操作系统面试题(三)

请你来说一说协程 参考回答&#xff1a; 1、概念&#xff1a; 协程&#xff0c;又称微线程&#xff0c;纤程&#xff0c;英文名Coroutine。协程看上去也是子程序&#xff0c;但执行过程中&#xff0c;在子程序内部可中断&#xff0c;然后转而执行别的子程序&#xff0c;在适…

操作系统面试题(十四):什么是虚拟内存?

虚拟内存&#xff08;virtual Memory&#xff09; 日常生活中&#xff0c;当我们使用电脑的时候&#xff0c;尤其是windows电脑&#xff0c;经常会打开许多软件&#xff0c;这些软件占用的内存已经远远大于计算机的物理内存。之所以会这样&#xff0c;就是因为虚拟内存的存在 …

操作系统面试题:设备管理

管理什么设备&#xff1f; 除cpu,内存外的所有设备 怎么管理设备&#xff1f; 通过将物理设备映射成逻辑设备 为什么要把物理设备映射成逻辑设备&#xff1f; 物理设备是I/O系统中实际安装的设备&#xff0c;物理名通常是字符串或者ID 而逻辑设备的逻辑名比较好记 如何理解…

操作系统面试问题汇总(超详细)

操作系统的组成 1、驱动程序是最底层的、直接控制和监视各类硬件的部分&#xff0c;它们的职责是隐藏硬件的具体细节&#xff0c;并向其他部分提供一个抽象的、通用的接口。 2、内核是操作系统之最内核部分&#xff0c;通常运行在最高特权级&#xff0c;负责提供基础性、结构性…

操作系统面试题(二)

请你讲述一下互斥锁&#xff08;mutex&#xff09;机制&#xff0c;以及互斥锁和读写锁的区别 参考回答&#xff1a; 1、互斥锁和读写锁区别&#xff1a; 互斥锁&#xff1a;mutex&#xff0c;用于保证在任何时刻&#xff0c;都只能有一个线程访问该对象。当获取锁操作失败时…

几率大的杂乱+操作系统面试题(含答案)

其他面试题类型汇总&#xff1a; Java校招极大几率出的面试题&#xff08;含答案&#xff09;----汇总 几率大的网络安全面试题&#xff08;含答案&#xff09; 几率大的多线程面试题&#xff08;含答案&#xff09; 几率大的源码底层原理&#xff0c;杂食面试题&#xff08;含…

操作系统常见面试题

文章目录 进程和线程进程和线程的区别协程与线程的区别进程与线程的切换流程什么是虚拟地址空间为什么虚拟地址空间切换后感觉程序变慢进程间通信方式线程&#xff08;进程&#xff09;间同步方式线程的分类协程&#xff08;纤程&#xff09;线程和协程的区别 进程&#xff08;…