SDRAM控制器——仲裁模块的实现

前面一文中,我们已经对SDRAM的上电初始化、自动刷新以及突发读写进行了学习。
本文跟着大佬学习SDRAM中的仲裁模块


仲裁机制

仲裁(arbit):在FPGA中,当多个source源同时发出请求,容易导致操作冲突,因此我我们需要根据相应的优先级来响应哪一个source,这个过程就叫仲裁。

对于SDRAM控制器来说,其中包含上电初始化、自动刷新以及突发读写模块,由于SDRAM在一个clk中只执行一个操作,因此当4个模块中的两个及以上模块同时发出操作命令的时候,就会出现操作冲突,从而导致SDRAM工作出错,因此这里就需要引入仲裁机制,仲裁机制根据优先级对4个模块发出来的操作请求统一管理。

四个模块的仲裁总框图如下:
在这里插入图片描述

仲裁模块的输入信号

仲裁模块的输入信号主要就是4个模块的输出信号。
其中四个小模块的使能信号是由仲裁模块发出的,初始化模块由于上电后自动工作,因此仲裁模块实质是控制3个模块。
可看到仲裁模块发出刷新使能、读写使能信号,根据这三个使能信号,来控制小模块的独立工作,避免操作冲突。
在这里插入图片描述
除了四个模块的输出信号作为仲裁模块的输入信号,以及三个使能信号作为仲裁模块的输出之外,仲裁模块的端口信号中还包含时钟复位信号以及SDRAM芯片物理接口:

module  sdram_arbit
(
    input   wire            arbit_clk		,   //系统时钟
    input   wire            arbit_rst_n		,   //复位信号
//sdram_init	
    input   wire    [3:0]   init_cmd    	,   //初始化阶段命令
    input   wire            init_end    	,   //初始化结束标志
    input   wire    [1:0]   init_bank		,   //初始化阶段Bank地址
    input   wire    [12:0]  init_addr   	,   //初始化阶段数据地址
//sdram_auto_ref	
    input   wire            atref_req		,   //自刷新请求
    input   wire            atref_end		,   //自刷新结束
    input   wire    [3:0]   atref_cmd		,   //自刷新阶段命令
    input   wire    [1:0]   atref_bank		,   //自动刷新阶段Bank地址
    input   wire    [12:0]  atref_addr		,   //自刷新阶段数据地址
//sdram_write
    input   wire            wr_req     		,   //写数据请求
    input   wire    [1:0]   wr_bank			,   //写阶段Bank地址
    input   wire            wr_end     		,   //一次写结束信号
    input   wire    [3:0]   wr_cmd     		,   //写阶段命令
    input   wire    [12:0]  wr_addr    		,   //写阶段数据地址
    input   wire			wr_sdram_en		,   //写数据有效
    input   wire    [15:0]  wr_sdram_data	,   //要写入sdram的数据
//sdram_read
    input   wire            rd_req      	,   //读数据请求
    input   wire            rd_end      	,   //一次读结束
    input   wire    [3:0]   rd_cmd      	,   //读阶段命令
    input   wire    [12:0]  rd_addr     	,   //读阶段数据地址
    input   wire    [1:0]   rd_bank			,   //读阶段Bank地址
//输出控制逻辑	
    output  reg             atref_en    	,   //自刷新使能
    output  reg             wr_en       	,   //写数据使能
    output  reg             rd_en       	,   //读数据使能	
//sdram接口	
    output  wire            sdram_cke   	,   //SDRAM时钟使能
    output  wire            sdram_cs_n  	,   //SDRAM片选信号
    output  wire            sdram_ras_n 	,   //SDRAM行地址选通
    output  wire            sdram_cas_n 	,   //SDRAM列地址选通
    output  wire            sdram_we_n  	,   //SDRAM写使能
    output  reg     [1:0]   sdram_bank		,   //SDRAM Bank地址
    output  reg     [12:0]  sdram_addr  	,   //SDRAM地址总线
    inout   wire    [15:0]  sdram_dq    	    //SDRAM数据总线
);

最终给出仲裁模块的输入输出端口:

在这里插入图片描述

分析仲裁模块的工作状态

首先我们确定优先级:
上电初始化——自动刷新——写操作——读操作(先写在读,防止数据被覆盖)

根据上面的分析,得到如下的状态图:

仲裁模块:负责发出自动刷新、读写模块的使能信号 (注意优先级)
四个模块:负责发出相应操作的请求信号

这里的状态不复杂且大佬博文中也有讲解,很清晰,不再赘述
在这里插入图片描述

仲裁模块的verilog实现

module  sdram_arbit
(
    input   wire            arbit_clk		,   //系统时钟
    input   wire            arbit_rst_n		,   //复位信号
//sdram_init	
    input   wire    [3:0]   init_cmd    	,   //初始化阶段命令
    input   wire            init_end    	,   //初始化结束标志
    input   wire    [1:0]   init_bank		,   //初始化阶段Bank地址
    input   wire    [12:0]  init_addr   	,   //初始化阶段数据地址
//sdram_auto_ref	
    input   wire            atref_req		,   //自刷新请求
    input   wire            atref_end		,   //自刷新结束
    input   wire    [3:0]   atref_cmd		,   //自刷新阶段命令
    input   wire    [1:0]   atref_bank		,   //自动刷新阶段Bank地址
    input   wire    [12:0]  atref_addr		,   //自刷新阶段数据地址
//sdram_write
    input   wire            wr_req     		,   //写数据请求
    input   wire    [1:0]   wr_bank			,   //写阶段Bank地址
    input   wire            wr_end     		,   //一次写结束信号
    input   wire    [3:0]   wr_cmd     		,   //写阶段命令
    input   wire    [12:0]  wr_addr    		,   //写阶段数据地址
    input   wire			wr_sdram_en		,   //写数据有效
    input   wire    [15:0]  wr_sdram_data	,   //要写入sdram的数据
//sdram_read
    input   wire            rd_req      	,   //读数据请求
    input   wire            rd_end      	,   //一次读结束
    input   wire    [3:0]   rd_cmd      	,   //读阶段命令
    input   wire    [12:0]  rd_addr     	,   //读阶段数据地址
    input   wire    [1:0]   rd_bank			,   //读阶段Bank地址
//输出控制逻辑	
    output  reg             atref_en    	,   //自刷新使能
    output  reg             wr_en       	,   //写数据使能
    output  reg             rd_en       	,   //读数据使能	
//sdram接口	
    output  wire            sdram_cke   	,   //SDRAM时钟使能
    output  wire            sdram_cs_n  	,   //SDRAM片选信号
    output  wire            sdram_ras_n 	,   //SDRAM行地址选通
    output  wire            sdram_cas_n 	,   //SDRAM列地址选通
    output  wire            sdram_we_n  	,   //SDRAM写使能
    output  reg     [1:0]   sdram_bank		,   //SDRAM Bank地址
    output  reg     [12:0]  sdram_addr  	,   //SDRAM地址总线
    inout   wire    [15:0]  sdram_dq    	    //SDRAM数据总线
);


//对状态进行格雷码编码
localparam	INIT	= 5'b0_0001   ,   	//初始状态
            ARBIT	= 5'b0_0010   ,   	//仲裁状态
            ATREF	= 5'b0_0100   ,   	//自动刷新状态
            WRITE	= 5'b0_1000   ,   	//写状态
            READ	= 5'b1_0000   ;   	//读状态
				
//命令定义			
localparam	NOP   = 4'b0111     ;   	//空操作指令


reg     [3:0]   sdram_cmd   ;   		//写入SDRAM命令
reg     [4:0]   state	;   		
reg     [4:0]   state_next	;   	


//SDRAM时钟使能
assign  sdram_cke = 1'b1;

//wr_sdram_data:写入 SDRAM 的数据
assign sdram_dq = (wr_sdram_en == 1'b1) ? wr_sdram_data : 16'bz;

//片选信号,行地址选通信号,列地址选通信号,写使能信号组成sdram命令
assign  {sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n} = sdram_cmd;


//第一段状态机,状态寄存器
always@(posedge arbit_clk or negedge arbit_rst_n)begin
	if(!arbit_rst_n)
		state <= INIT;	
	else
		state <= state_next;
end

//第二段状态机,组合逻辑描述状态转移
always@(*)begin
	state_next= INIT;
	case(state)
        INIT:   state_next = (init_end) ? ARBIT : INIT ;

        ARBIT: //注意优先级
			if(atref_req) //自动刷新
                state_next = ATREF;
			else if(wr_req)  //写
                state_next = WRITE;
			else if(rd_req)  //读
                state_next = READ;
			else
                state_next = ARBIT;
					 
        ATREF:  state_next = (atref_end) ? ARBIT : ATREF ; 

        WRITE:  state_next = (wr_end) ? ARBIT : WRITE ; 

        READ:   state_next = (rd_end) ? ARBIT : READ ; 

        default:state_next = INIT;
    endcase
end

//第三段状态机,时序逻辑描述输出
always@(*)begin
    case(state) 
        INIT: begin
            sdram_cmd <= init_cmd;
            sdram_bank <= init_bank;
            sdram_addr <= init_addr;
        end
        ATREF: begin
            sdram_cmd <= atref_cmd;
            sdram_bank <= atref_bank;
            sdram_addr <= atref_addr;
        end
        WRITE: begin
            sdram_cmd <= wr_cmd;
            sdram_bank <= wr_bank;
            sdram_addr <= wr_addr;
        end
        READ: begin
            sdram_cmd <= rd_cmd;
            sdram_bank <= rd_bank;
            sdram_addr <= rd_addr;
        end
        default: begin
            sdram_cmd <= NOP;
            sdram_bank <= 2'b11; //全部拉高
            sdram_addr <= 13'h1fff;
        end
    endcase
end


//atref_en:自动刷新使能
always@(posedge arbit_clk or negedge arbit_rst_n)begin
    if(!arbit_rst_n)
        atref_en <= 1'b0;
    else if((state == ARBIT) && (atref_req)) //仲裁状态且有自动刷新请求
        atref_en <= 1'b1;
    else if(atref_end)
        atref_en <= 1'b0;
end

//wr_en:写数据使能
always@(posedge arbit_clk or negedge arbit_rst_n)begin
    if(!arbit_rst_n)
        wr_en <= 1'b0;
    else if((state == ARBIT) && (!atref_req) && (wr_req)) //仲裁状态且自动刷新请求无效,写请求有效
        wr_en <= 1'b1;
    else if(wr_end)
        wr_en <= 1'b0;
end

//rd_en:读数据使能
always@(posedge arbit_clk or negedge arbit_rst_n)begin
    if(!arbit_rst_n)
        rd_en <= 1'b0;  //仲裁状态且自动刷新请求无效,写请求无效,读请求有效
    else if((state == ARBIT) && (!atref_req) && (!wr_req) && (rd_req))
        rd_en <= 1'b1;
    else if(rd_end)
        rd_en <= 1'b0;
end


endmodule

sdram控制器

最终我们即可将四个模块以及仲裁模块例化到顶层模块中,该顶层模块为sdram_ctrl。

//顶层模块——sdram控制器,包含仲裁模块、初始化、自动刷新、读写模块
module  sdram_ctrl
(
    input   wire            sdram_clk		,   //sdram时钟
    input   wire            sdram_rst_n		,   //sdram复位信号	
	output  wire            init_end        ,   //SDRAM 初始化完成标志
//SDRAM写端口
    input   wire            sdram_wr_req    ,   //写SDRAM请求信号
    input   wire    [23:0]  sdram_wr_addr   ,   //SDRAM写操作的地址
    input   wire    [9:0]   wr_burst_len    ,   //写sdram时数据突发长度
    input   wire    [15:0]  sdram_data_in   ,   //写入SDRAM的数据
    output  wire            sdram_wr_ack    ,   //写SDRAM响应信号
//SDRAM读端口
    input   wire            sdram_rd_req    ,   //读SDRAM请求信号
    input   wire    [23:0]  sdram_rd_addr   ,   //SDRAM读操作的地址
    input   wire    [9:0]   rd_burst_len    ,   //读sdram时数据突发长度
    output  wire    [15:0]  sdram_data_out  ,   //从SDRAM读出的数据
    output  wire            sdram_rd_ack    ,   //读SDRAM响应信号
//FPGA与SDRAM硬件接口
    output  wire            sdram_cke       ,   // SDRAM 时钟有效信号
    output  wire            sdram_cs_n      ,   // SDRAM 片选信号
    output  wire            sdram_ras_n     ,   // SDRAM 行地址选通
    output  wire            sdram_cas_n     ,   // SDRAM 列地址选通
    output  wire            sdram_we_n      ,   // SDRAM 写使能
    output  wire    [1:0]   sdram_bank		,   // SDRAM Bank地址
    output  wire    [12:0]  sdram_addr      ,   // SDRAM 地址总线
    inout   wire    [15:0]  sdram_dq            // SDRAM 数据总线
);


//sdram_init
wire    [3:0]   init_cmd    ;   //初始化阶段写入sdram的指令
wire    [1:0]   init_bank	;   //初始化阶段Bank地址
wire    [12:0]  init_addr   ;   //初始化阶段地址数据,辅助预充电操作
//sdram_a_ref
wire            atref_req    ;   //自动刷新请求
wire            atref_end    ;   //自动刷新结束标志
wire    [3:0]   atref_cmd    ;   //自动刷新阶段写入sdram的指令
wire    [1:0]   atref_bank	 ;   //自动刷新阶段Bank地址
wire    [12:0]  atref_addr   ;   //地址数据,辅助预充电操作
wire            atref_en     ;   //自动刷新使能
//sdram_write
wire            wr_en       	;   //写使能
wire            wr_end      	;   //一次写结束信号
wire    [3:0]   wr_sdram_cmd	;   //写阶段命令
wire    [1:0]   wr_sdram_bank	;   //写数据阶段Bank地址
wire    [12:0]  wr_sdram_addr  	;   //写阶段数据地址
wire            wr_sdram_en 	;   //SDRAM写使能
wire    [15:0]  wr_sdram_data	;   //写入SDRAM的数据
//sdram_read
wire            rd_en       	;   //读使能
wire            rd_end      	;   //一次突发读结束
wire    [3:0]   rd_sdram_cmd	;   //读数据阶段写入sdram的指令
wire    [1:0]   rd_sdram_bank	;   //读阶段Bank地址
wire    [12:0]  rd_sdram_addr   ;   //读阶段数据地址


//实例化sdram初始化模块
sdram_init  sdram_init_inst(
    .init_clk    (sdram_clk    ),  //系统时钟,频率100MHz
    .init_rst_n  (sdram_rst_n  ),  //复位信号,低电平有效

    .init_cmd    (init_cmd   ),  //初始化阶段写入sdram的指令
    .init_bank	 (init_bank	 ),  //初始化阶段Bank地址
    .init_addr   (init_addr  ),  //初始化阶段地址数据,辅助预充电操作
    .init_end    (init_end   )   //初始化结束信号
);

//实例化仲裁模块
sdram_arbit sdram_arbit_inst
(
    .arbit_clk   	(sdram_clk		),  
    .arbit_rst_n 	(sdram_rst_n	),  
//初始化	
    .init_cmd    	(init_cmd       ),  //初始化阶段命令
    .init_end    	(init_end       ),  //初始化结束标志
    .init_bank	 	(init_bank		),  //初始化阶段Bank地址
    .init_addr   	(init_addr      ),  //初始化阶段数据地址
//自动刷新	
    .atref_req   	(atref_req      ),  //自刷新请求
    .atref_end   	(atref_end      ),  //自刷新结束
    .atref_cmd   	(atref_cmd      ),  //自刷新阶段命令
    .atref_bank	 	(atref_bank	 	),  //自动刷新阶段Bank地址
    .atref_addr  	(atref_addr     ),  //自刷新阶段数据地址
	.atref_en	 	(atref_en		),  //自刷新使能
//写
    .wr_req      	(sdram_wr_req   ),  //写数据请求
    .wr_end      	(wr_end         ),  //一次写结束信号
    .wr_en       	(wr_en          ),  //写数据使能	
    .wr_cmd      	(wr_sdram_cmd	),  //写阶段命令
    .wr_bank	 	(wr_sdram_bank	),  //写阶段Bank地址
    .wr_addr     	(wr_sdram_addr	),  //写阶段数据地址
	.wr_sdram_en 	(wr_sdram_en	),	 //写数据有效	
	.wr_sdram_data	(wr_sdram_data 	),  //要写入sdram的数据
//读     
    .rd_req      	(sdram_rd_req   ),  //读数据请求
    .rd_end      	(rd_end         ),  //一次读结束
    .rd_en       	(rd_en          ),  //读数据使能
		
    .rd_cmd      	(rd_sdram_cmd	),  //读阶段命令
    .rd_addr     	(rd_sdram_addr	),  //读阶段数据地址
    .rd_bank	 	(rd_sdram_bank	),  //读阶段Bank地址				 
//SDRAM物理接口	
    .sdram_cke   	(sdram_cke      ),  //SDRAM时钟使能
    .sdram_cs_n  	(sdram_cs_n     ),  //SDRAM片选信号
    .sdram_ras_n 	(sdram_ras_n    ),  //SDRAM行地址选通
    .sdram_cas_n 	(sdram_cas_n    ),  //SDRAM列地址选通
    .sdram_we_n  	(sdram_we_n     ),  //SDRAM写使能
    .sdram_bank	 	(sdram_bank	 	),  //SDRAM Bank地址
    .sdram_addr  	(sdram_addr     ),  //SDRAM地址总线
    .sdram_dq    	(sdram_dq       )   //SDRAM数据总线
);

//实例化自动刷新模块
sdram_atref sdram_atref_inst
(
    .atref_clk		(sdram_clk   	),  
    .atref_rst_n   	(sdram_rst_n	),
    .init_end		(init_end  		),  //初始化结束信号
    .atref_en		(atref_en		),  //自动刷新使能

    .atref_req    	(atref_req 	 	),  //自动刷新请求
    .atref_cmd    	(atref_cmd 	 	),  //自动刷新阶段写入sdram的指令
    .atref_bank		(atref_bank		),  //自动刷新阶段Bank地址
    .atref_addr   	(atref_addr 	),  //地址数据,辅助预充电操作
    .atref_end    	(atref_end  	)   //自动刷新结束标志
);

//实例化写模块
sdram_write sdram_write_inst
(
    .wr_clk        	(sdram_clk		),  
    .wr_rst_n      	(sdram_rst_n	),  
    .init_end       (init_end       ),  //初始化结束信号
    .wr_en          (wr_en          ),  //写使能

    .wr_addr        (sdram_wr_addr  ),  //写SDRAM地址
    .wr_data        (sdram_data_in  ),  //待写入SDRAM的数据(写FIFO传入)
    .wr_burst_len   (wr_burst_len   ),  //写突发SDRAM字节数

    .wr_ack         (sdram_wr_ack   ),  //写SDRAM响应信号
    .wr_end         (wr_end         ),  //一次突发写结束
    .wr_sdram_cmd	(wr_sdram_cmd	),  //写数据阶段写入sdram的指令
    .wr_sdram_bank	(wr_sdram_bank	),  //写数据阶段Bank地址
    .wr_sdram_addr	(wr_sdram_addr	),  //地址数据,辅助预充电操作
    .wr_sdram_en    (wr_sdram_en	),  //数据总线输出使能
    .wr_sdram_data  (wr_sdram_data	)   //写入SDRAM的数据
);

//实例化读模块
sdram_read  sdram_read_inst
(
    .rd_clk        	(sdram_clk		),  
    .rd_rst_n      	(sdram_rst_n	),  
    .init_end       (init_end       ),  //初始化结束信号
    .rd_en          (rd_en          ),  //读使能

    .rd_addr        (sdram_rd_addr  ),  //读SDRAM地址
    .rd_data        (sdram_dq       ),  //自SDRAM中读出的数据
    .rd_burst_len   (rd_burst_len   ),  //读突发SDRAM字节数

    .rd_ack         (sdram_rd_ack   ),  //读SDRAM响应信号
    .rd_end         (rd_end         ),  //一次突发读结束
    .rd_sdram_cmd	(rd_sdram_cmd	),  //读数据阶段写入sdram的指令
    .rd_sdram_bank	(rd_sdram_bank	),  //读数据阶段Bank地址
    .rd_sdram_addr	(rd_sdram_addr	),  //地址数据,辅助预充电操作
    .rd_sdram_data  (sdram_data_out )   //SDRAM读出的数据
);

endmodule

生成的RTL图如下:
在这里插入图片描述

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fighting_FPGA

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值