SDRAM(6):2通道的sdram控制器

         移动目标检测时,需要用到 sdram_4port 模块,即把 SDRAM 控制器设计成双通道(两进两出)的结构,本文记录一下这个设计的代码,来源于【正点原子FPGA开发板】,我移植到多个 Altera 型开发板,都能跑通。

1、模块结构

image

2、inst例化

inst
//==========================================================================
//==                        SDRAM
//==========================================================================
sdram_4port_top u_sdram
(
    .ref_clk                (clk_100m               ),  //SDRAM 控制器参考时钟
    .out_clk                (clk_100m_shift         ),  //用于输出的相位偏移时钟
    .rst_n                  (sys_rst_n              ),  //系统复位
    //通道0 -----------------------------------------
    .ch0_min_addr           (24'd0                  ),  //通道0 写SDRAM的起始地址
    .ch0_max_addr           (H_DISP * V_DISP        ),  //通道0 写SDRAM的结束地址
    .ch0_wr_clk             (sys_clk                ),  //通道0 写端口FIFO: 写时钟
    .ch0_wr_req             (ch0_wr_en              ),  //通道0 写端口FIFO: 写使能
    .ch0_wr_data            (ch0_wr_data            ),  //通道0 写端口FIFO: 写数据
    .ch0_rd_clk             (clk_25m                ),  //通道0 读端口FIFO: 读时钟
    .ch0_rd_req             (ch0_rd_en              ),  //通道0 读端口FIFO: 读使能
    .ch0_rd_data            (ch0_rd_data            ),  //通道0 读端口FIFO: 读数据
    //通道1 -----------------------------------------
    .ch1_min_addr           (24'd0                  ),  //通道1 写SDRAM的起始地址
    .ch1_max_addr           (H_DISP * V_DISP        ),  //通道1 写SDRAM的结束地址
    .ch1_wr_clk             (cmos_pclk              ),  //通道1 写端口FIFO: 写时钟
    .ch1_wr_req             (ch1_wr_en              ),  //通道1 写端口FIFO: 写使能
    .ch1_wr_data            (ch1_wr_data            ),  //通道1 写端口FIFO: 写数据                                   
    .ch1_rd_clk             (clk_25m                ),  //通道1 读端口FIFO: 读时钟
    .ch1_rd_req             (ch1_rd_en              ),  //通道1 读端口FIFO: 读使能
    .ch1_rd_data            (ch1_rd_data            ),  //通道1 读端口FIFO: 读数据
    //读写设置 --------------------------------------
    .wr_length              (10'd512                ),  //写SDRAM时的数据突发长度
    .wr_load                (~rst_n                 ),  //写端口复位: 复位写地址,清空写FIFO
    .rd_length              (10'd512                ),  //写SDRAM时的数据突发长度
    .rd_load                (~rst_n                 ),  //读端口复位: 复位读地址,清空读FIFO
    //用户控制端口 ----------------------------------
    .sdram_init_done        (sdram_init_done        ),  //SDRAM 初始化完成标志
    .sdram_pingpang_en      (1'b0                   ),  //SDRAM 乒乓操作使能,图片0视频1
    //SDRAM 芯片端口 --------------------------------
    .sdram_clk              (sdram_clk              ),  //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_ba               (sdram_ba               ),  //SDRAM Bank地址
    .sdram_addr             (sdram_addr             ),  //SDRAM 行/列地址
    .sdram_dq               (sdram_dq               ),  //SDRAM 数据
    .sdram_dqm              (sdram_dqm              )   //SDRAM 数据掩码
);

3、code展示

(1)sdram_para

sdram_para
//=========================================================================================
//==                            SDRAM参数
//=========================================================================================
// SDRAM 初始化过程各个状态
`define     I_NOP           5'd0                           //等待上电200us稳定期结束
`define     I_PRE           5'd1                           //预充电状态
`define     I_TRP           5'd2                           //等待预充电完成 tRP
`define     I_AR            5'd3                           //自动刷新            
`define     I_TRF           5'd4                           //等待自动刷新结束 tRC
`define     I_MRS           5'd5                           //模式寄存器设置
`define     I_TRSC          5'd6                           //等待模式寄存器设置完成 tRSC
`define     I_DONE          5'd7                           //初始化完成
    
// SDRAM 工作过程各个状态   
`define     W_IDLE          4'd0                           //空闲
`define     W_ACTIVE        4'd1                           //行有效
`define     W_TRCD          4'd2                           //行有效等待
`define     W_READ          4'd3                           //读操作
`define     W_CL            4'd4                           //潜伏期
`define     W_RD            4'd5                           //读数据
`define     W_WRITE         4'd6                           //写操作
`define     W_WD            4'd7                           //写数据
`define     W_TWR           4'd8                           //写回
`define     W_PRE           4'd9                           //预充电
`define     W_TRP           4'd10                          //预充电等待
`define     W_AR            4'd11                          //自动刷新
`define     W_TRFC          4'd12                          //自动刷新等待
    
//延时参数  
`define     end_trp         cnt_clk == TRP_CLK             //预充电有效周期结束
`define     end_trfc        cnt_clk == TRC_CLK             //自动刷新周期结束
`define     end_trsc        cnt_clk == TRSC_CLK            //模式寄存器设置时钟周期结束
`define     end_trcd        cnt_clk == TRCD_CLK-1          //行选通周期结束
`define     end_tcl         cnt_clk == TCL_CLK-1           //潜伏期结束
`define     end_rdburst     cnt_clk == sdram_rd_burst-4    //读突发终止
`define     end_tread       cnt_clk == sdram_rd_burst+2    //突发读结束     
`define     end_wrburst     cnt_clk == sdram_wr_burst-1    //写突发终止
`define     end_twrite      cnt_clk == sdram_wr_burst-1    //突发写结束
`define     end_twr         cnt_clk == TWR_CLK             //写回周期结束
    
//SDRAM控制信号命令   
`define     CMD_INIT        5'b01111                       // INITIATE
`define     CMD_NOP         5'b10111                       // NOP COMMAND
`define     CMD_ACTIVE      5'b10011                       // ACTIVE COMMAND
`define     CMD_READ        5'b10101                       // READ COMMADN
`define     CMD_WRITE       5'b10100                       // WRITE COMMAND
`define     CMD_B_STOP      5'b10110                       // BURST STOP
`define     CMD_PRGE        5'b10010                       // PRECHARGE
`define     CMD_A_REF       5'b10001                       // AOTO REFRESH
`define     CMD_LMR         5'b10000                       // LODE MODE REGISTER

(2)sdram_4port_top

sdram_4port_top
//**************************************************************************
// *** 名称 : sdram_4port_top
// *** 作者 : 正点原子
// *** 日期 : 2019/7/18
// *** 描述 : SDRAM 控制器顶层模块
//**************************************************************************

module  sdram_4port_top
//========================< 端口 >==========================================
(
input                       ref_clk                 , //SDRAM 控制器参考时钟
input                       out_clk                 , //用于输出的相位偏移时钟
input                       rst_n                   , //系统复位
//通道0 ---------------------------------------------
input       [23:0]          ch0_min_addr            , //通道0 起始地址
input       [23:0]          ch0_max_addr            , //通道0 结束地址                              
input                       ch0_wr_clk              , //通道0 写端口FIFO0: 写时钟 
input                       ch0_wr_req              , //通道0 写端口FIFO0: 写请求 
input       [15:0]          ch0_wr_data             , //通道0 写端口FIFO0: 写数据
input                       ch0_rd_clk              , //通道0 读端口FIFO0: 读时钟
input                       ch0_rd_req              , //通道0 读端口FIFO0: 读请求
output      [15:0]          ch0_rd_data             , //通道0 读端口FIFO0: 读数据
//通道1 ---------------------------------------------
input       [23:0]          ch1_min_addr            , //通道1 起始地址
input       [23:0]          ch1_max_addr            , //通道1 结束地址           
input                       ch1_wr_clk              , //通道1 写端口FIFO0: 写时钟
input                       ch1_wr_req              , //通道1 写端口FIFO0: 写请求
input       [15:0]          ch1_wr_data             , //通道1 写端口FIFO0: 写数据
input                       ch1_rd_clk              , //通道1 读端口FIFO0: 读时钟
input                       ch1_rd_req              , //通道1 读端口FIFO0: 读请求
output      [15:0]          ch1_rd_data             , //通道1 读端口FIFO0: 读数据
//读写设置 ------------------------------------------
input       [ 9:0]          wr_length               , //写SDRAM时的数据突发长度 
input                       wr_load                 , //写端口复位: 复位写地址,清空写FIFO 
input       [ 9:0]          rd_length               , //从SDRAM中读数据时的突发长度 
input                       rd_load                 , //读端口复位: 复位读地址,清空读FIFO
//用户控制端口 --------------------------------------
output                      sdram_init_done         , //SDRAM 初始化完成标志
input                       sdram_pingpang_en       , //SDRAM 乒乓操作使能,1开0关
//SDRAM芯片接口 -------------------------------------
output                      sdram_clk               , //SDRAM 芯片时钟
output                      sdram_cke               , //SDRAM 时钟有效
output                      sdram_cs_n              , //SDRAM 片选
output                      sdram_ras_n             , //SDRAM 行有效
output                      sdram_cas_n             , //SDRAM 列有效
output                      sdram_we_n              , //SDRAM 写有效
output      [ 1:0]          sdram_ba                , //SDRAM Bank地址
output      [12:0]          sdram_addr              , //SDRAM 行/列地址
inout       [15:0]          sdram_dq                , //SDRAM 数据
output      [ 1:0]          sdram_dqm                 //SDRAM 数据掩码
);
//========================< 端口 >==========================================
//write ---------------------------------------------
wire                        sdram_wr_req            ; //SDRAM 写请求
wire                        sdram_wr_ack            ; //SDRAM 写响应
wire        [23:0]          sdram_wr_addr           ; //SDRAM 写地址
wire        [15:0]          sdram_din               ; //写入SDRAM中的数据
//read ----------------------------------------------
wire                        sdram_rd_req            ; //SDRAM 读请求
wire                        sdram_rd_ack            ; //SDRAM 读响应
wire        [23:0]          sdram_rd_addr           ; //SDRAM 读地址
wire        [15:0]          sdram_dout              ; //从SDRAM中读出的数据
//==========================================================================
//==                        信号设置
//========================================================================== 
assign  sdram_clk = out_clk; //将相位偏移时钟输出给sdram芯片
assign  sdram_dqm = 2'b00;   //读写过程中均不屏蔽数据线       
//==========================================================================
//==                        SDRAM 读写端口FIFO控制模块
//==========================================================================
sdram_fifo_ctrl u_sdram_fifo_ctrl
(
    .clk_ref                (ref_clk                ), //SDRAM控制器时钟
    .rst_n                  (rst_n                  ), //系统复位
    //通道0 -----------------------------------------
    .ch0_min_addr           (ch0_min_addr           ), //通道0 起始地址
    .ch0_max_addr           (ch0_max_addr           ), //通道0 结束地址                                                      
    .ch0_wr_clk             (ch0_wr_clk             ), //通道0 写端口FIFO0: 写时钟 
    .ch0_wr_req             (ch0_wr_req             ), //通道0 写端口FIFO0: 写请求 
    .ch0_wr_data            (ch0_wr_data            ), //通道0 写端口FIFO0: 写数据
    .ch0_rd_clk             (ch0_rd_clk             ), //通道0 读端口FIFO0: 读时钟
    .ch0_rd_req             (ch0_rd_req             ), //通道0 读端口FIFO0: 读请求
    .ch0_rd_data            (ch0_rd_data            ), //通道0 读端口FIFO0: 读数据
    //通道1 ----------------------------------------- 
    .ch1_min_addr           (ch1_min_addr           ), //通道1 起始地址
    .ch1_max_addr           (ch1_max_addr           ), //通道1 结束地址           
    .ch1_wr_clk             (ch1_wr_clk             ), //通道1 写端口FIFO0: 写时钟
    .ch1_wr_req             (ch1_wr_req             ), //通道1 写端口FIFO0: 写请求
    .ch1_wr_data            (ch1_wr_data            ), //通道1 写端口FIFO0: 写数据
    .ch1_rd_clk             (ch1_rd_clk             ), //通道1 读端口FIFO0: 读时钟
    .ch1_rd_req             (ch1_rd_req             ), //通道1 读端口FIFO0: 读请求
    .ch1_rd_data            (ch1_rd_data            ), //通道1 读端口FIFO0: 读数据
    //读写设置 --------------------------------------
    .wr_length              (wr_length              ), //写SDRAM时的数据突发长度
    .wr_load                (wr_load                ), //写端口复位: 复位写地址,清空写FIFO
    .rd_length              (rd_length              ), //从SDRAM中读数据时的突发长度
    .rd_load                (rd_load                ), //读端口复位: 复位读地址,清空读FIFO
    //用户控制端口 ----------------------------------
    .sdram_init_done        (sdram_init_done        ), //SDRAM 初始化完成标志
    .sdram_pingpang_en      (sdram_pingpang_en      ), //SDRAM 乒乓操作使能
    //SDRAM 控制器写端口 ----------------------------
    .sdram_wr_req           (sdram_wr_req           ), //SDRAM 写请求
    .sdram_wr_ack           (sdram_wr_ack           ), //SDRAM 写响应
    .sdram_wr_addr          (sdram_wr_addr          ), //SDRAM 写地址
    .sdram_din              (sdram_din              ), //写入SDRAM中的数据
    //SDRAM 控制器读端口 ----------------------------
    .sdram_rd_req           (sdram_rd_req           ), //SDRAM 读请求
    .sdram_rd_ack           (sdram_rd_ack           ), //SDRAM 读响应
    .sdram_rd_addr          (sdram_rd_addr          ), //SDRAM 读地址
    .sdram_dout             (sdram_dout             )  //从SDRAM中读出的数据
);
//==========================================================================
//==                        SDRAM控制器
//==========================================================================
sdram_controller u_sdram_controller
(
    .clk                    (ref_clk                ), //SDRAM 控制器时钟
    .rst_n                  (rst_n                  ), //系统复位
    //SDRAM 控制器写端口 ----------------------------
    .sdram_wr_req           (sdram_wr_req           ), //SDRAM 写请求
    .sdram_wr_ack           (sdram_wr_ack           ), //SDRAM 写响应
    .sdram_wr_addr          (sdram_wr_addr          ), //SDRAM 写地址
    .sdram_wr_burst         (wr_length              ), //写sdram时数据突发长度
    .sdram_din              (sdram_din              ), //写入sdram中的数据
    //SDRAM 控制器读端口 ----------------------------
    .sdram_rd_req           (sdram_rd_req           ), //SDRAM 读请求
    .sdram_rd_ack           (sdram_rd_ack           ), //SDRAM 读响应
    .sdram_rd_addr          (sdram_rd_addr          ), //SDRAM 读地址
    .sdram_rd_burst         (rd_length              ), //读SDRAM时数据突发长度
    .sdram_dout             (sdram_dout             ), //从SDRAM中读出的数据
    //SDRAM 控制器初始化 ----------------------------
    .sdram_init_done        (sdram_init_done        ), //SDRAM 初始化完成标志
    //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_ba               (sdram_ba               ), //SDRAM Bank地址
    .sdram_addr             (sdram_addr             ), //SDRAM 行/列地址
    .sdram_dq               (sdram_dq               )  //SDRAM 数据   
);

    
endmodule

(3)sdram_fifo_ctrl

sdram_fifo_ctrl
//**************************************************************************
// *** 名称 : sdram_fifo_ctrl
// *** 作者 : 正点原子
// *** 日期 : 2019/7/18
// *** 描述 : SDRAM 读写端口FIFO控制模块
//**************************************************************************

module sdram_fifo_ctrl
(
input                       clk_ref                 , //SDRAM控制器时钟
input                       rst_n                   , //系统复位                             
//通道0 ---------------------------------------------
input       [23:0]          ch0_min_addr            , //通道0 起始地址
input       [23:0]          ch0_max_addr            , //通道0 结束地址                    
input                       ch0_wr_clk              , //通道0 写端口FIFO0: 写时钟 
input                       ch0_wr_req              , //通道0 写端口FIFO0: 写请求 
input       [15:0]          ch0_wr_data             , //通道0 写端口FIFO0: 写数据
input                       ch0_rd_clk              , //通道0 读端口FIFO0: 读时钟
input                       ch0_rd_req              , //通道0 读端口FIFO0: 读请求
output      [15:0]          ch0_rd_data             , //通道0 读端口FIFO0: 读数据
//通道1 ---------------------------------------------
input       [23:0]          ch1_min_addr            , //通道1 起始地址
input       [23:0]          ch1_max_addr            , //通道1 结束地址           
input                       ch1_wr_clk              , //通道1 写端口FIFO0: 写时钟
input                       ch1_wr_req              , //通道1 写端口FIFO0: 写请求
input       [15:0]          ch1_wr_data             , //通道1 写端口FIFO0: 写数据
input                       ch1_rd_clk              , //通道1 读端口FIFO0: 读时钟
input                       ch1_rd_req              , //通道1 读端口FIFO0: 读请求
output      [15:0]          ch1_rd_data             , //通道1 读端口FIFO0: 读数据
//读写设置 ------------------------------------------
input       [ 9:0]          wr_length               , //写SDRAM时的数据突发长度 
input                       wr_load                 , //写端口复位: 复位写地址,清空写FIFO 
input       [ 9:0]          rd_length               , //从SDRAM中读数据时的突发长度 
input                       rd_load                 , //读端口复位: 复位读地址,清空读FIFO
//用户控制端口 --------------------------------------
input                       sdram_init_done         , //SDRAM 初始化完成标志
input                       sdram_pingpang_en       , //SDRAM 乒乓操作使能
//SDRAM 控制器写端口 --------------------------------
output  reg                 sdram_wr_req            , //SDRAM 写请求
input                       sdram_wr_ack            , //SDRAM 写响应
output  reg     [23:0]      sdram_wr_addr           , //SDRAM 写地址
output          [15:0]      sdram_din               , //写入SDRAM中的数据 
//SDRAM 控制器读端口 --------------------------------
output  reg                 sdram_rd_req            , //SDRAM 读请求
input                       sdram_rd_ack            , //SDRAM 读响应
output  reg     [23:0]      sdram_rd_addr           , //SDRAM 读地址 
input           [15:0]      sdram_dout                //从SDRAM中读出的数据 
);
//========================< 参数 >==========================================
localparam IDLE             = 4'd0                  ; //空闲状态
localparam SDRAM_DONE       = 4'd1                  ; //SDRAM初始化完成状态
localparam WR_KEEP          = 4'd2                  ; //读FIFO保持状态
localparam RD_KEEP          = 4'd3                  ; //写FIFO保持状态
//========================< 信号 >==========================================
reg                         wr_ack_r1               ; //sdram写响应寄存器      
reg                         wr_ack_r2               ;
reg                         rd_ack_r1               ; //sdram读响应寄存器      
reg                         rd_ack_r2               ;
reg                         wr_load_r1              ; //写端口复位寄存器      
reg                         wr_load_r2              ;
reg                         rd_load_r1              ; //读端口复位寄存器      
reg                         rd_load_r2              ;
reg                         ch0_sw_bank_en          ;
reg                         ch1_sw_bank_en          ;
reg                         ch0_rw_bank_flag        ;
reg                         ch1_rw_bank_flag        ;
reg                         sw_wrFIFO               ; //写FIFO切换信号
reg                         sw_rdFIFO               ; //读FIFO切换信号
reg     [22:0]              ch0_rd_addr             ; //读FIFO0地址
reg     [22:0]              ch1_rd_addr             ; //读FIFO1地址
reg     [22:0]              ch0_wr_addr             ; //写FIFO0地址
reg     [22:0]              ch1_wr_addr             ; //写FIFO1地址
reg     [ 3:0]              state                   ; //读写FIFO控制状态
reg     [12:0]              rd_cnt                  ;
wire                        wr_done                 ;  //sdram_wr_ack 下降沿标志位      
wire                        rd_done                 ;  //sdram_rd_ack 下降沿标志位    
wire                        wr_load_pos             ;  //wr_load      上升沿标志位      
wire                        rd_load_pos             ;  //rd_load      上升沿标志位      
wire    [10:0]              wrFIFO_use0             ;  //写端口FIFO0中的数据量
wire    [10:0]              rdFIFO_use0             ;  //读端口FIFO0中的数据量
wire    [10:0]              wrFIFO_use1             ;  //写端口FIFO1中的数据量
wire    [10:0]              rdFIFO_use1             ;  //读端口FIFO1中的数据量
wire    [15:0]              sdram_dout0             ;  //读端口FIFO0中读出数据
wire    [15:0]              sdram_dout1             ;  //读端口FIFO1中读出数据
wire    [15:0]              sdram_din0              ;
wire    [15:0]              sdram_din1              ;
wire                        sdram_wr_ack0           ;
wire                        sdram_wr_ack1           ;
wire                        sdram_rd_ack0           ;
wire                        sdram_rd_ack1           ;
//==========================================================================
//==                        代码
//========================================================================== 
//检测下降沿
assign wr_done = wr_ack_r2 & ~wr_ack_r1;  
assign rd_done = rd_ack_r2 & ~rd_ack_r1;

//写端口FIFO0中读请求切换
assign sdram_wr_ack0 = sw_wrFIFO ? 1'b0 : sdram_wr_ack;

//写端口FIFO1中读请求切换
assign sdram_wr_ack1 = sw_wrFIFO ? sdram_wr_ack : 1'b0;

//读端口FIFO0中写请求切换
assign sdram_rd_ack0 = sw_rdFIFO ? 1'b0 : sdram_rd_ack;

//读端口FIFO1中写请求切换
assign sdram_rd_ack1 = sw_rdFIFO ? sdram_rd_ack : 1'b0;

//写端口FIFO中写数据切换,即选择哪个数据写道SDRAM中
assign sdram_din     = sw_wrFIFO ? sdram_din1 : sdram_din0;

//读端口FIFO中写数据切换,即选择读FIFO1来接收SDRAM中数据
assign sdram_dout0   = sw_rdFIFO ? 1'b0 : sdram_dout;

//读端口FIFO中写数据切换,即选择读FIFO0来接收SDRAM中数据
assign sdram_dout1   = sw_rdFIFO ? sdram_dout : 1'b0;

//检测上升沿
assign wr_load_pos   = ~wr_load_r2 & wr_load_r1;
assign rd_load_pos   = ~rd_load_r2 & rd_load_r1;

//寄存sdram写响应信号,用于捕获sdram_wr_ack下降沿
always @(posedge clk_ref or negedge rst_n) begin
    if(!rst_n) begin
        wr_ack_r1 <= 1'b0;
        wr_ack_r2 <= 1'b0;
    end
    else begin
        wr_ack_r1 <= sdram_wr_ack;
        wr_ack_r2 <= wr_ack_r1;     
    end
end 

//寄存sdram读响应信号,用于捕获sdram_rd_ack下降沿
always @(posedge clk_ref or negedge rst_n) begin
    if(!rst_n) begin
        rd_ack_r1 <= 1'b0;
        rd_ack_r2 <= 1'b0;
    end
    else begin
        rd_ack_r1 <= sdram_rd_ack;
        rd_ack_r2 <= rd_ack_r1;
    end
end 

//同步写端口复位信号,用于捕获wr_load上升沿
always @(posedge clk_ref or negedge rst_n) begin
    if(!rst_n) begin
        wr_load_r1 <= 1'b0;
        wr_load_r2 <= 1'b0;
    end
    else begin
        wr_load_r1 <= wr_load;
        wr_load_r2 <= wr_load_r1;
    end
end

//同步读端口复位信号,同时用于捕获rd_load上升沿
always @(posedge clk_ref or negedge rst_n) begin
    if(!rst_n) begin
        rd_load_r1 <= 1'b0;
        rd_load_r2 <= 1'b0;
    end
    else begin
        rd_load_r1 <= rd_load;
        rd_load_r2 <= rd_load_r1;
    end
end

//sdram写地址0产生模块
always @(posedge clk_ref or negedge rst_n) begin
    if(!rst_n)begin
        ch0_wr_addr <= 23'd0;
        ch0_sw_bank_en <= 1'b0;
        ch0_rw_bank_flag <= 1'b0;
        
    end 
    else if(wr_load_pos)begin              //检测到写端口复位信号时,写地址复位
        ch0_wr_addr <= ch0_min_addr;
        ch0_rw_bank_flag    <= 0;
        ch0_sw_bank_en <= 0;
    end                                     //若突发写SDRAM结束更改写地址
    else if(wr_done && !sw_wrFIFO) begin    
        if(sdram_pingpang_en) begin         //SDRAM 读写乒乓使能
                                            //若未到达写SDRAM的结束地址写地址累加 
            if(ch0_wr_addr[21:0] < ch0_max_addr - wr_length)
                ch0_wr_addr <= ch0_wr_addr + wr_length;
            else begin                      //切换BANK
                ch0_rw_bank_flag    <= ~ch0_rw_bank_flag;   
                ch0_sw_bank_en <= 1'b1;        //拉高切换BANK使能信号
            end            
        end                                 //乒乓操作不使能时
                                            //判断是否到达结束地址                     
        else if(ch0_wr_addr < ch0_max_addr - wr_length)
                                            //没达结束地址,地址累加一个突发长度
            ch0_wr_addr <= ch0_wr_addr + wr_length;
        else                                //若已到达结束地址,则回到写起始地址
            ch0_wr_addr <= ch0_min_addr;
    end    
    else if(ch0_sw_bank_en) begin              //如果bank切换使能信号有效
        ch0_sw_bank_en <= 1'b0;                //将使能信号置0,方便下次使用
        if(ch0_rw_bank_flag == 1'b0)                //根据bank标志信号切换BANK
            ch0_wr_addr <= {1'b0,ch0_min_addr[21:0]};
        else
            ch0_wr_addr <= {1'b1,ch0_min_addr[21:0]};     
   end
end

//sdram写地址1产生模块
always @(posedge clk_ref or negedge rst_n) begin
    if(!rst_n)begin
        ch1_wr_addr <= 23'd0;
        ch1_sw_bank_en <= 1'b0;
        ch1_rw_bank_flag <= 1'b0;
    end 
    else if(wr_load_pos)begin              //检测到写端口复位信号时,写地址复位
        ch1_rw_bank_flag    <= 0;
        ch1_sw_bank_en <= 0;
        ch1_wr_addr <= ch1_min_addr;
    end                                     //若突发写SDRAM结束,更改写地址
    else if(wr_done && sw_wrFIFO) begin
        if(sdram_pingpang_en) begin         //判断若SDRAM 读写乒乓使能
                                            //若未到达写SDRAM的结束地址写地址累加 
        if(ch1_wr_addr[21:0] < ch1_max_addr - wr_length)
                ch1_wr_addr <= ch1_wr_addr + wr_length;
            else begin                      //切换BANK
                ch1_rw_bank_flag    <= ~ch1_rw_bank_flag;   
                ch1_sw_bank_en <= 1'b1;        //拉高切换BANK使能信号
            end            
        end                                 //乒乓操作不使能
                                            //未到达写SDRAM的结束地址写地址累加
        else if(ch1_wr_addr < ch1_max_addr - wr_length)
            ch1_wr_addr <= ch1_wr_addr + wr_length;
            else                            //到达写SDRAM的结束地址回到写起始地址
            ch1_wr_addr <= ch1_min_addr;
    end
    else if(ch1_sw_bank_en) begin              //如果bank切换使能信号有效
        ch1_sw_bank_en <= 1'b0;                //将使能信号置0,方便下次使用
        if(ch1_rw_bank_flag == 1'b0)                //切换BANK
            ch1_wr_addr <= {1'b0,ch1_min_addr[21:0]};
        else
            ch1_wr_addr <= {1'b1,ch1_min_addr[21:0]};     
    end
end

//sdram读地址0产生模块
always @(posedge clk_ref or negedge rst_n) begin
    if(!rst_n)
        ch0_rd_addr <= 23'd0;   
    else if(rd_load_pos)                     //检测到写端口复位信号时,写地址复位
        ch0_rd_addr <= ch0_min_addr;         //若突发读SDRAM结束,更改读地址
    else if(rd_done && !sw_rdFIFO ) begin
        if(sdram_pingpang_en) begin          //判断若SDRAM 读写乒乓使能  
                                             //若未到达SDRAM的结束地址则地址累加 
            if(ch0_rd_addr[21:0] < ch0_max_addr - rd_length)
                ch0_rd_addr <= ch0_rd_addr + rd_length;                                                
            else begin                       //到达读SDRAM的结束地址,回到读起始                     
                if(ch0_rw_bank_flag == 1'b0)         //根据rw_bank_flag的值切换读BANK地址
                    ch0_rd_addr <= {1'b1,ch0_min_addr[21:0]};
                else
                    ch0_rd_addr <= {1'b0,ch0_min_addr[21:0]};    
            end    
        end                                  //若乒乓操作未使能
                                             //未到达SDRAM的结束地址地址累加
        else if(ch0_rd_addr < ch0_max_addr - rd_length)
            ch0_rd_addr <= ch0_rd_addr + rd_length;
        else                                 //若到达SDRAM的结束地址回到起始地址
            ch0_rd_addr <= ch0_min_addr;
    end
end

//sdram读地址1产生模块
always @(posedge clk_ref or negedge rst_n) begin
    if(!rst_n)
        ch1_rd_addr <= 23'd0;   
    else if(rd_load_pos)                     //检测到复位信号时地址复位
        ch1_rd_addr <= ch1_min_addr;    
                                             //判断若突发读SDRAM结束
    else if(rd_done && sw_rdFIFO) begin
        if(sdram_pingpang_en) begin          //若SDRAM 读写乒乓使能 
                                             //若未到达SDRAM的结束地址则地址累加        
            if(ch1_rd_addr[21:0] < ch1_max_addr - rd_length)
                ch1_rd_addr <= ch1_rd_addr + rd_length;                                                
            else begin                       //到达读SDRAM的结束地址                    
                if(ch1_rw_bank_flag == 1'b0)         //根据rw_bank_flag的值切换BANK地址
                    ch1_rd_addr <= {1'b1,ch1_min_addr[21:0]};
                else
                    ch1_rd_addr <= {1'b0,ch1_min_addr[21:0]};    
            end    
        end                                  //如果乒乓操作没有使能
                                             //未到达SDRAM的结束地址地址累加
        else if(ch1_rd_addr < ch1_max_addr - rd_length)
            ch1_rd_addr <= ch1_rd_addr + rd_length;
        else                                 //若已到达SDRAM的结束地址回到起始地址
            ch1_rd_addr <= ch1_min_addr;
    end
end

//读写端四个FIFO的判断逻辑       
always@(posedge clk_ref or negedge rst_n) begin
    if(!rst_n) begin                     
        sdram_wr_req  <= 0;
        sdram_wr_addr <= 0;
        sw_wrFIFO     <= 0;
        
        sdram_rd_req  <= 0;
        sw_rdFIFO     <= 0;
        sdram_rd_addr <= 0;
        
        state <= IDLE;
    end
    else begin
        case(state)
            IDLE:begin
                if(sdram_init_done)
                    state <=  SDRAM_DONE;
             end
             SDRAM_DONE:begin
                if(wrFIFO_use0 >= wr_length) begin          //进入写端FIFO0的读状态状态
                    sdram_wr_req  <= 1;
                    sdram_wr_addr <= {1'b0,ch0_wr_addr};
                    sw_wrFIFO     <= 0;
                    
                    sdram_rd_req  <= 0;
                    sdram_rd_addr <= {1'b0,ch0_rd_addr};
                    sw_rdFIFO     <= 0;
                    
                    state <= WR_KEEP;
                end
        
                else if(wrFIFO_use1 >= wr_length) begin     //进入写端FIFO1的读状态状态
                    sdram_wr_req  <= 1;
                    sdram_wr_addr <= {1'b1,ch1_wr_addr};
                    sw_wrFIFO     <= 1;
                    
                    sdram_rd_req  <= 0;
                    sdram_rd_addr <= {1'b1,ch0_rd_addr};
                    sw_rdFIFO     <= 0;
                    
                    state <= WR_KEEP;
                end
                else if((rdFIFO_use0 < rd_length)) begin    //进入读端FIFO0的写状态状态
                    sdram_wr_req  <= 0;
                    sdram_wr_addr <= {1'b0,ch0_wr_addr};
                    sw_wrFIFO     <= 0;
                  
                    sdram_rd_req  <= 1;
                    sdram_rd_addr <= {1'b0,ch0_rd_addr};
                    sw_rdFIFO     <= 0;
                    
                    state <= RD_KEEP;
                end
                else if((rdFIFO_use1 < rd_length)) begin    //进入读端FIFO1的写状态状态
                    sdram_wr_req  <= 0;
                    sdram_wr_addr <= {1'b1,ch0_wr_addr};
                    sw_wrFIFO     <= 0;
                   
                    sdram_rd_req  <= 1;
                    sdram_rd_addr <= {1'b1,ch1_rd_addr};
                    sw_rdFIFO     <= 1;
                    
                    state <= RD_KEEP;
                end  
            end
            WR_KEEP:begin
                if(wr_done) begin                           //保持写状态
                    sdram_wr_req  <= 0;
                    sdram_wr_addr <= ch0_wr_addr;
                    sw_wrFIFO     <= 0;
                    
                    state <= SDRAM_DONE;
                end    
            end
            RD_KEEP:begin
                if(rd_done) begin                           //保持读状态
                    sdram_rd_req  <= 0;
                    sdram_rd_addr <= ch0_rd_addr;
                    sw_rdFIFO     <= 0;
                    
                    state <= SDRAM_DONE;
                end    
            end   
            default:state <= IDLE;                          //默认停在空闲状态
        endcase    
    end
end
//==========================================================================
//==                        作用:写端口FIFO
//==                        类型:异步时钟
//==                        深度:2048
//==                        模式:Standard
//==========================================================================
wrFIFO  u_wrFIFO_0
(
    .wrclk                  (ch0_wr_clk             ),  //写时钟
    .wrreq                  (ch0_wr_req             ),  //写请求
    .data                   (ch0_wr_data            ),  //写数据
    .rdclk                  (clk_ref                ),  //读时钟
    .rdreq                  (sdram_wr_ack0          ),  //读请求
    .q                      (sdram_din0             ),  //读数据
    .rdusedw                (wrFIFO_use0            ),  //FIFO中的数据量
    .aclr                   (~rst_n | wr_load_pos   )   //异步清零信号
);
    
wrFIFO  u_wrFIFO_1
(
    .wrclk                  (ch1_wr_clk             ),  //写时钟
    .wrreq                  (ch1_wr_req             ),  //写请求
    .data                   (ch1_wr_data            ),  //写数据 
    .rdclk                  (clk_ref                ),  //读时钟
    .rdreq                  (sdram_wr_ack1          ),  //读请求
    .q                      (sdram_din1             ),  //读数据
    .rdusedw                (wrFIFO_use1            ),  //FIFO中的数据量
    .aclr                   (~rst_n | wr_load_pos   )   //异步清零信号
);      
//==========================================================================
//==                        作用:读端口FIFO
//==                        类型:异步时钟
//==                        深度:2048
//==                        模式:Standard
//==========================================================================
rdFIFO  u_rdFIFO_0
(
    .wrclk                  (clk_ref                ), //写时钟
    .wrreq                  (sdram_rd_ack0          ), //写请求
    .data                   (sdram_dout0            ), //写数据
    .rdclk                  (ch0_rd_clk             ), //读时钟
    .rdreq                  (ch0_rd_req             ), //读请求
    .q                      (ch0_rd_data            ), //读数据
    .wrusedw                (rdFIFO_use0            ), //FIFO中的数据量
    .aclr                   (~rst_n | rd_load_pos   )  //异步清零信号   
); 

rdFIFO  u_rdFIFO_1
(
    .wrclk                  (clk_ref                ), //写时钟
    .wrreq                  (sdram_rd_ack1          ), //写请求
    .data                   (sdram_dout1            ), //写数据
    .rdclk                  (ch1_rd_clk             ), //读时钟
    .rdreq                  (ch1_rd_req             ), //读请求
    .q                      (ch1_rd_data            ), //读数据
    .wrusedw                (rdFIFO_use1            ), //FIFO中的数据量
    .aclr                   (~rst_n | rd_load_pos   )  //异步清零信号   
);



   
endmodule 

(4)sdram_controller

sdram_controller
//**************************************************************************
// *** 名称 : sdram_controller
// *** 作者 : 正点原子
// *** 日期 : 2018/3/18
// *** 描述 : SDRAM控制器
//**************************************************************************

module sdram_controller
//========================< 端口 >==========================================
(
input                       clk                     , //SDRAM控制器时钟,100MHz
input                       rst_n                   , //系统复位信号,低电平有效
//SDRAM 控制器写端口 --------------------------------
input                       sdram_wr_req            , //写SDRAM请求信号
output                      sdram_wr_ack            , //写SDRAM响应信号
input       [23:0]          sdram_wr_addr           , //SDRAM写操作的地址
input       [ 9:0]          sdram_wr_burst          , //写SDRAM时数据突发长度
input       [15:0]          sdram_din               , //写入SDRAM的数据
//SDRAM 控制器读端口 --------------------------------
input                       sdram_rd_req            , //读SDRAM请求信号
output                      sdram_rd_ack            , //读SDRAM响应信号
input       [23:0]          sdram_rd_addr           , //SDRAM写操作的地址
input       [ 9:0]          sdram_rd_burst          , //读SDRAM时数据突发长度
output      [15:0]          sdram_dout              , //从SDRAM读出的数据
//SDRAM 初始化 --------------------------------------
output                      sdram_init_done         , //SDRAM 初始化完成标志
//SDRAM 芯片接口 ------------------------------------
output                      sdram_cke               , //SDRAM 时钟有效信号
output                      sdram_cs_n              , //SDRAM 片选信号
output                      sdram_ras_n             , //SDRAM 行地址选通脉冲
output                      sdram_cas_n             , //SDRAM 列地址选通脉冲
output                      sdram_we_n              , //SDRAM 写允许位
output      [ 1:0]          sdram_ba                , //SDRAM L-Bank地址线
output      [12:0]          sdram_addr              , //SDRAM 地址总线
inout       [15:0]          sdram_dq                  //SDRAM 数据总线
);
//========================< 信号 >==========================================
wire        [ 4:0]          init_state              ; //SDRAM初始化状态
wire        [ 3:0]          work_state              ; //SDRAM工作状态
wire        [ 9:0]          cnt_clk                 ; //延时计数器
wire                        sdram_rd_wr             ; //SDRAM读/写控制信号,低电平为写,高电平为读
//==========================================================================
//==                        SDRAM 状态控制模块
//==========================================================================                    
sdram_ctrl u_sdram_ctrl
(           
    .clk                    (clk                    ),                      
    .rst_n                  (rst_n                  ),
    .sdram_wr_req           (sdram_wr_req           ), 
    .sdram_rd_req           (sdram_rd_req           ),
    .sdram_wr_ack           (sdram_wr_ack           ),
    .sdram_rd_ack           (sdram_rd_ack           ),                      
    .sdram_wr_burst         (sdram_wr_burst         ),
    .sdram_rd_burst         (sdram_rd_burst         ),
    .sdram_init_done        (sdram_init_done        ),
    .init_state             (init_state             ),
    .work_state             (work_state             ),
    .cnt_clk                (cnt_clk                ),
    .sdram_rd_wr            (sdram_rd_wr            )
);
//==========================================================================
//==                        SDRAM 命令控制模块
//==========================================================================
sdram_cmd u_sdram_cmd
(               
    .clk                    (clk                    ),
    .rst_n                  (rst_n                  ),
    .sys_wraddr             (sdram_wr_addr          ),          
    .sys_rdaddr             (sdram_rd_addr          ),
    .sdram_wr_burst         (sdram_wr_burst         ),
    .sdram_rd_burst         (sdram_rd_burst         ),
    .init_state             (init_state             ),  
    .work_state             (work_state             ),
    .cnt_clk                (cnt_clk                ),
    .sdram_rd_wr            (sdram_rd_wr            ),
    .sdram_cke              (sdram_cke              ),
    .sdram_cs_n             (sdram_cs_n             ),
    .sdram_ras_n            (sdram_ras_n            ),
    .sdram_cas_n            (sdram_cas_n            ),
    .sdram_we_n             (sdram_we_n             ),
    .sdram_ba               (sdram_ba               ),
    .sdram_addr             (sdram_addr             )
);
//==========================================================================
//==                        SDRAM 数据读写模块
//==========================================================================
sdram_data u_sdram_data
(   
    .clk                    (clk                    ),
    .rst_n                  (rst_n                  ),
    .sdram_data_in          (sdram_din              ),
    .sdram_data_out         (sdram_dout             ),
    .work_state             (work_state             ),
    .cnt_clk                (cnt_clk                ),
    .sdram_dq               (sdram_dq               )
);


endmodule 

(5)sdram_ctrl

sdram_ctrl
//**************************************************************************
// *** 名称 : sdram_ctrl
// *** 作者 : 正点原子
// *** 日期 : 2018/3/18
// *** 描述 : SDRAM 状态控制模块
//**************************************************************************
`include "sdram_para.v" //SDRAM参数定义

module sdram_ctrl
//========================< 端口 >==========================================
(
input                       clk                     , //系统时钟
input                       rst_n                   , //复位信号,低电平有效
input                       sdram_wr_req            , //写SDRAM请求信号
input                       sdram_rd_req            , //读SDRAM请求信号
output                      sdram_wr_ack            , //写SDRAM响应信号
output                      sdram_rd_ack            , //读SDRAM响应信号
input       [ 9:0]          sdram_wr_burst          , //突发写SDRAM字节数(1-512个)
input       [ 9:0]          sdram_rd_burst          , //突发读SDRAM字节数(1-512个)
output                      sdram_init_done         , //SDRAM系统初始化完毕信号
output reg  [ 4:0]          init_state              , //SDRAM初始化状态
output reg  [ 3:0]          work_state              , //SDRAM工作状态
output reg  [ 9:0]          cnt_clk                 , //时钟计数器
output reg                  sdram_rd_wr               //SDRAM读/写控制信号,低电平为写,高电平为读
);
//========================< 参数 >==========================================              
parameter TRP_CLK           = 10'd4                 ; //预充电有效周期
parameter TRC_CLK           = 10'd6                 ; //自动刷新周期
parameter TRSC_CLK          = 10'd6                 ; //模式寄存器设置时钟周期
parameter TRCD_CLK          = 10'd2                 ; //行选通周期
parameter TCL_CLK           = 10'd3                 ; //列潜伏期
parameter TWR_CLK           = 10'd2                 ; //写入校正
//========================< 信号 >==========================================                          
reg         [14:0]          cnt_200us               ; //SDRAM 上电稳定期200us计数器
reg         [10:0]          cnt_refresh             ; //刷新计数寄存器
reg                         sdram_ref_req           ; //SDRAM 自动刷新请求信号
reg                         cnt_rst_n               ; //延时计数器复位信号,低有效   
reg         [ 3:0]          init_ar_cnt             ; //初始化过程自动刷新计数器                         
wire                        done_200us              ; //上电后200us输入稳定期结束标志位
wire                        sdram_ref_ack           ; //SDRAM自动刷新请求应答信号 
//==========================================================================
//==                        代码
//==========================================================================
//SDRAM上电后200us稳定期结束后,将标志信号拉高
assign done_200us = (cnt_200us == 15'd20_000);

//SDRAM初始化完成标志 
assign sdram_init_done = (init_state == `I_DONE);

//SDRAM 自动刷新应答信号
assign sdram_ref_ack = (work_state == `W_AR);

//写SDRAM响应信号
assign sdram_wr_ack = ((work_state == `W_TRCD) & ~sdram_rd_wr) | 
                      ( work_state == `W_WRITE)|
                      ((work_state == `W_WD) & (cnt_clk < sdram_wr_burst - 2'd2));
                      
//读SDRAM响应信号
assign sdram_rd_ack = (work_state == `W_RD) & 
                      (cnt_clk >= 10'd1) & (cnt_clk < sdram_rd_burst + 2'd1);
                      
//上电后计时200us,等待SDRAM状态稳定
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) 
        cnt_200us <= 15'd0;
    else if(cnt_200us < 15'd20_000) 
        cnt_200us <= cnt_200us + 1'b1;
    else
        cnt_200us <= cnt_200us;
end
 
//刷新计数器循环计数7812ns (60ms内完成全部8192行刷新操作)
always @ (posedge clk or negedge rst_n)
    if(!rst_n) 
        cnt_refresh <= 11'd0;
    else if(cnt_refresh < 11'd781)      // 64ms/8192 =7812ns
        cnt_refresh <= cnt_refresh + 1'b1;  
    else 
        cnt_refresh <= 11'd0;   

//SDRAM 刷新请求
always @ (posedge clk or negedge rst_n)
    if(!rst_n) 
        sdram_ref_req <= 1'b0;
    else if(cnt_refresh == 11'd780) 
        sdram_ref_req <= 1'b1;          //刷新计数器计时达7812ns时产生刷新请求
    else if(sdram_ref_ack) 
        sdram_ref_req <= 1'b0;          //收到刷新请求响应信号后取消刷新请求 

//延时计数器对时钟计数
always @ (posedge clk or negedge rst_n) 
    if(!rst_n) 
        cnt_clk <= 10'd0;
    else if(!cnt_rst_n)                 //在cnt_rst_n为低电平时延时计数器清零
        cnt_clk <= 10'd0;
    else 
        cnt_clk <= cnt_clk + 1'b1;
        
//初始化过程中对自动刷新操作计数
always @ (posedge clk or negedge rst_n) 
    if(!rst_n) 
        init_ar_cnt <= 4'd0;
    else if(init_state == `I_NOP) 
        init_ar_cnt <= 4'd0;
    else if(init_state == `I_AR)
        init_ar_cnt <= init_ar_cnt + 1'b1;
    else
        init_ar_cnt <= init_ar_cnt;
    
//SDRAM的初始化状态机
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) 
        init_state <= `I_NOP;
    else 
        case (init_state)
                                        //上电复位后200us结束则进入下一状态
            `I_NOP:  init_state <= done_200us  ? `I_PRE : `I_NOP;
                                        //预充电状态
            `I_PRE:  init_state <= `I_TRP;
                                        //预充电等待,TRP_CLK个时钟周期
            `I_TRP:  init_state <= (`end_trp)  ? `I_AR  : `I_TRP;
                                        //自动刷新
            `I_AR :  init_state <= `I_TRF;  
                                        //等待自动刷新结束,TRC_CLK个时钟周期
            `I_TRF:  init_state <= (`end_trfc) ? 
                                        //连续8次自动刷新操作
                                   ((init_ar_cnt == 4'd8) ? `I_MRS : `I_AR) : `I_TRF;
                                        //模式寄存器设置
            `I_MRS:  init_state <= `I_TRSC; 
                                        //等待模式寄存器设置完成,TRSC_CLK个时钟周期
            `I_TRSC: init_state <= (`end_trsc) ? `I_DONE : `I_TRSC;
                                        //SDRAM的初始化设置完成标志
            `I_DONE: init_state <= `I_DONE;
            default: init_state <= `I_NOP;
        endcase
end

//SDRAM的工作状态机,工作包括读、写以及自动刷新操作
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) 
        work_state <= `W_IDLE;          //空闲状态
    else
        case(work_state)
                                        //定时自动刷新请求,跳转到自动刷新状态
            `W_IDLE: if(sdram_ref_req & sdram_init_done) begin
                         work_state <= `W_AR;       
                         sdram_rd_wr <= 1'b1;
                     end                
                                        //写SDRAM请求,跳转到行有效状态
                     else if(sdram_wr_req & sdram_init_done) begin
                         work_state <= `W_ACTIVE;
                         sdram_rd_wr <= 1'b0;   
                     end                
                                        //读SDRAM请求,跳转到行有效状态
                     else if(sdram_rd_req && sdram_init_done) begin
                         work_state <= `W_ACTIVE;
                         sdram_rd_wr <= 1'b1;   
                     end                
                                        //无操作请求,保持空闲状态
                     else begin 
                         work_state <= `W_IDLE;
                         sdram_rd_wr <= 1'b1;
                     end
                     
            `W_ACTIVE:                  //行有效,跳转到行有效等待状态
                         work_state <= `W_TRCD;
            `W_TRCD: if(`end_trcd)      //行有效等待结束,判断当前是读还是写
                         if(sdram_rd_wr)//读:进入读操作状态
                             work_state <= `W_READ;
                         else           //写:进入写操作状态
                             work_state <= `W_WRITE;
                     else 
                         work_state <= `W_TRCD;
                         
            `W_READ:                    //读操作,跳转到潜伏期
                         work_state <= `W_CL;   
            `W_CL:                      //潜伏期:等待潜伏期结束,跳转到读数据状态
                         work_state <= (`end_tcl) ? `W_RD:`W_CL;                                            
            `W_RD:                      //读数据:等待读数据结束,跳转到预充电状态
                         work_state <= (`end_tread) ? `W_PRE:`W_RD;
                         
            `W_WRITE:                   //写操作:跳转到写数据状态
                         work_state <= `W_WD;
            `W_WD:                      //写数据:等待写数据结束,跳转到写回周期状态
                         work_state <= (`end_twrite) ? `W_TWR:`W_WD;                         
            `W_TWR:                     //写回周期:写回周期结束,跳转到预充电状态
                         work_state <= (`end_twr) ? `W_PRE:`W_TWR;
                         
            `W_PRE:                     //预充电:跳转到预充电等待状态
                         work_state <= `W_TRP;
            `W_TRP:                 //预充电等待:预充电等待结束,进入空闲状态
                         work_state <= (`end_trp) ? `W_IDLE:`W_TRP;
                         
            `W_AR:                      //自动刷新操作,跳转到自动刷新等待
                         work_state <= `W_TRFC;             
            `W_TRFC:                    //自动刷新等待:自动刷新等待结束,进入空闲状态
                         work_state <= (`end_trfc) ? `W_IDLE:`W_TRFC;
            default:     work_state <= `W_IDLE;
        endcase
end

//计数器控制逻辑
always @ (*) begin
    case (init_state)
        `I_NOP:  cnt_rst_n <= 1'b0;     //延时计数器清零(cnt_rst_n低电平复位)
                                        
        `I_PRE:  cnt_rst_n <= 1'b1;     //预充电:延时计数器启动(cnt_rst_n高电平启动)
                                        //等待预充电延时计数结束后,清零计数器
        `I_TRP:  cnt_rst_n <= (`end_trp) ? 1'b0 : 1'b1;
                                        //自动刷新:延时计数器启动
        `I_AR:
                 cnt_rst_n <= 1'b1;
                                        //等待自动刷新延时计数结束后,清零计数器
        `I_TRF:
                 cnt_rst_n <= (`end_trfc) ? 1'b0 : 1'b1;    
                                        
        `I_MRS:  cnt_rst_n <= 1'b1;     //模式寄存器设置:延时计数器启动
                                        //等待模式寄存器设置延时计数结束后,清零计数器
        `I_TRSC: cnt_rst_n <= (`end_trsc) ? 1'b0:1'b1;
                                        
        `I_DONE: begin                  //初始化完成后,判断工作状态
            case (work_state)
                `W_IDLE:    cnt_rst_n <= 1'b0;
                                        //行有效:延时计数器启动
                `W_ACTIVE:  cnt_rst_n <= 1'b1;
                                        //行有效延时计数结束后,清零计数器
                `W_TRCD:    cnt_rst_n <= (`end_trcd)   ? 1'b0 : 1'b1;
                                        //潜伏期延时计数结束后,清零计数器
                `W_CL:      cnt_rst_n <= (`end_tcl)    ? 1'b0 : 1'b1;
                                        //读数据延时计数结束后,清零计数器
                `W_RD:      cnt_rst_n <= (`end_tread)  ? 1'b0 : 1'b1;
                                        //写数据延时计数结束后,清零计数器
                `W_WD:      cnt_rst_n <= (`end_twrite) ? 1'b0 : 1'b1;
                                        //写回周期延时计数结束后,清零计数器
                `W_TWR:     cnt_rst_n <= (`end_twr)    ? 1'b0 : 1'b1;
                                        //预充电等待延时计数结束后,清零计数器
                `W_TRP: cnt_rst_n <= (`end_trp) ? 1'b0 : 1'b1;
                                        //自动刷新等待延时计数结束后,清零计数器
                `W_TRFC:    cnt_rst_n <= (`end_trfc)   ? 1'b0 : 1'b1;
                default:    cnt_rst_n <= 1'b0;
            endcase
        end
        default: cnt_rst_n <= 1'b0;
    endcase
end


endmodule 

(6)sdram_cmd

sdram_ctrl
//**************************************************************************
// *** 名称 : sdram_ctrl
// *** 作者 : 正点原子
// *** 日期 : 2018/3/18
// *** 描述 : SDRAM 命令控制模块,地址为13位,可修改
//**************************************************************************
`include "sdram_para.v" //SDRAM参数定义

module sdram_cmd
//========================< 端口 >==========================================
(
input                       clk                     , //系统时钟
input                       rst_n                   , //低电平复位信号
input       [23:0]          sys_wraddr              , //写SDRAM时地址
input       [23:0]          sys_rdaddr              , //读SDRAM时地址
input       [ 9:0]          sdram_wr_burst          , //突发写SDRAM字节数
input       [ 9:0]          sdram_rd_burst          , //突发读SDRAM字节数
input       [ 4:0]          init_state              , //SDRAM初始化状态
input       [ 3:0]          work_state              , //SDRAM工作状态
input       [ 9:0]          cnt_clk                 , //延时计数器   
input                       sdram_rd_wr             , //SDRAM读/写控制信号,低电平为写
output                      sdram_cke               , //SDRAM时钟有效信号
output                      sdram_cs_n              , //SDRAM片选信号
output                      sdram_ras_n             , //SDRAM行地址选通脉冲
output                      sdram_cas_n             , //SDRAM列地址选通脉冲
output                      sdram_we_n              , //SDRAM写允许位
output reg  [ 1:0]          sdram_ba                , //SDRAM的L-Bank地址线
output reg  [12:0]          sdram_addr                //SDRAM地址总线
);
//========================< 信号 >==========================================
reg         [ 4:0]          sdram_cmd_r             ; //SDRAM操作指令
wire        [23:0]          sys_addr                ; //SDRAM读写地址   
//==========================================================================
//==                        代码
//==========================================================================
//SDRAM 控制信号线赋值
assign {sdram_cke,sdram_cs_n,sdram_ras_n,sdram_cas_n,sdram_we_n} = sdram_cmd_r;

//SDRAM 读/写地址总线控制
assign sys_addr = sdram_rd_wr ? sys_rdaddr : sys_wraddr;
    
//SDRAM 操作指令控制
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) begin
            sdram_cmd_r <= `CMD_INIT;
            sdram_ba    <= 2'b11;
            sdram_addr  <= 13'h1fff;
    end
    else
        case(init_state)
                                        //初始化过程中,以下状态不执行任何指令
            `I_NOP,`I_TRP,`I_TRF,`I_TRSC: begin
                    sdram_cmd_r <= `CMD_NOP;
                    sdram_ba    <= 2'b11;
                    sdram_addr  <= 13'h1fff;    
                end
            `I_PRE: begin               //预充电指令
                    sdram_cmd_r <= `CMD_PRGE;
                    sdram_ba    <= 2'b11;
                    sdram_addr  <= 13'h1fff;
                end 
            `I_AR: begin
                                        //自动刷新指令
                    sdram_cmd_r <= `CMD_A_REF;
                    sdram_ba    <= 2'b11;
                    sdram_addr  <= 13'h1fff;                        
                end                 
            `I_MRS: begin               //模式寄存器设置指令
                    sdram_cmd_r <= `CMD_LMR;
                    sdram_ba    <= 2'b00;
                    sdram_addr  <= {    //利用地址线设置模式寄存器,可根据实际需要进行修改
                        3'b000,         //预留
                        1'b0,           //读写方式 A9=0,突发读&突发写
                        2'b00,          //默认,{A8,A7}=00
                        3'b011,         //CAS潜伏期设置,这里设置为3,{A6,A5,A4}=011
                        1'b0,           //突发传输方式,这里设置为顺序,A3=0
                        3'b111          //突发长度,这里设置为页突发,{A2,A1,A0}=011
                    };
                end 
            `I_DONE:                    //SDRAM初始化完成
                    case(work_state)    //以下工作状态不执行任何指令
                        `W_IDLE,`W_TRCD,`W_CL,`W_TWR,`W_TRP,`W_TRFC: begin
                                sdram_cmd_r <= `CMD_NOP;
                                sdram_ba    <= 2'b11;
                                sdram_addr  <= 13'h1fff;
                            end
                        `W_ACTIVE: begin//行有效指令
                                sdram_cmd_r <= `CMD_ACTIVE;
                                sdram_ba    <= sys_addr[23:22];
                                sdram_addr  <= sys_addr[21:9];
                            end
                        `W_READ: begin  //读操作指令
                                sdram_cmd_r <= `CMD_READ;
                                sdram_ba    <= sys_addr[23:22];
                                sdram_addr  <= {4'b0000,sys_addr[8:0]};
                            end
                        `W_RD: begin    //突发传输终止指令
                                if(`end_rdburst) 
                                    sdram_cmd_r <= `CMD_B_STOP;
                                else begin
                                    sdram_cmd_r <= `CMD_NOP;
                                    sdram_ba    <= 2'b11;
                                    sdram_addr  <= 13'h1fff;
                                end
                            end                             
                        `W_WRITE: begin //写操作指令
                                sdram_cmd_r <= `CMD_WRITE;
                                sdram_ba    <= sys_addr[23:22];
                                sdram_addr  <= {4'b0000,sys_addr[8:0]};
                            end     
                        `W_WD: begin    //突发传输终止指令
                                if(`end_wrburst) 
                                    sdram_cmd_r <= `CMD_B_STOP;
                                else begin
                                    sdram_cmd_r <= `CMD_NOP;
                                    sdram_ba    <= 2'b11;
                                    sdram_addr  <= 13'h1fff;
                                end
                            end
                        `W_PRE:begin    //预充电指令
                                sdram_cmd_r <= `CMD_PRGE;
                                sdram_ba    <= sys_addr[23:22];
                                sdram_addr  <= 13'h0400;
                            end             
                        `W_AR: begin    //自动刷新指令
                                sdram_cmd_r <= `CMD_A_REF;
                                sdram_ba    <= 2'b11;
                                sdram_addr  <= 13'h1fff;
                            end
                        default: begin
                                sdram_cmd_r <= `CMD_NOP;
                                sdram_ba    <= 2'b11;
                                sdram_addr  <= 13'h1fff;
                            end
                    endcase
            default: begin
                    sdram_cmd_r <= `CMD_NOP;
                    sdram_ba    <= 2'b11;
                    sdram_addr  <= 13'h1fff;
                end
        endcase
end


endmodule 

(7)sdram_data

sdram_data
//**************************************************************************
// *** 名称 : sdram_data
// *** 作者 : 正点原子
// *** 日期 : 2018/3/18
// *** 描述 : SDRAM 数据读写模块
//**************************************************************************
`include "sdram_para.v" //SDRAM参数定义

module sdram_data
//========================< 端口 >==========================================
(
input                       clk                     , //系统时钟
input                       rst_n                   , //低电平复位信号
input   [15:0]              sdram_data_in           , //写入SDRAM中的数据
output  [15:0]              sdram_data_out          , //从SDRAM中读取的数据
input   [ 3:0]              work_state              , //SDRAM工作状态寄存器
input   [ 9:0]              cnt_clk                 , //时钟计数
inout   [15:0]              sdram_dq                  //SDRAM数据总线
);
//========================< 信号 >==========================================
reg                         sdram_out_en            ; //SDRAM数据总线输出使能
reg     [15:0]              sdram_din_r             ; //寄存写入SDRAM中的数据
reg     [15:0]              sdram_dout_r            ; //寄存从SDRAM中读取的数据
//==========================================================================
//==                        代码
//==========================================================================
//SDRAM 双向数据线作为输入时保持高阻态
assign sdram_dq = sdram_out_en ? sdram_din_r : 16'hzzzz;

//输出SDRAM中读取的数据
assign sdram_data_out = sdram_dout_r;

//SDRAM 数据总线输出使能
always @ (posedge clk or negedge rst_n) begin 
    if(!rst_n) 
       sdram_out_en <= 1'b0;
   else if((work_state == `W_WRITE) | (work_state == `W_WD)) 
       sdram_out_en <= 1'b1;            //向SDRAM中写数据时,输出使能拉高
   else 
       sdram_out_en <= 1'b0;
end

//将待写入数据送到SDRAM数据总线上
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) 
        sdram_din_r <= 16'd0;
    else if((work_state == `W_WRITE) | (work_state == `W_WD))
        sdram_din_r <= sdram_data_in;   //寄存写入SDRAM中的数据
end

//读数据时,寄存SDRAM数据线上的数据
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) 
        sdram_dout_r <= 16'd0;
    else if(work_state == `W_RD) 
        sdram_dout_r <= sdram_dq;     //寄存从SDRAM中读取的数据
end


endmodule 

4、双目视觉

基于此 4port 的 sdram 控制器,很容易就能实现双目视觉。

(1)cmos裁剪

确定屏幕总大小,例如640*480,那么单目的分辨率就是 320*480,在cmos摄像头出来的画面处进行剪辑,如下所示:

//**************************************************************************
// *** 名称 : cmos_capture.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2019-08-10
// *** 描述 : 摄像头原始数据转RGB565数据,裁剪后输出
//**************************************************************************

module cmos_capture
//========================< 参数 >==========================================
#(
parameter CMOS_H_PIXEL      = 12'd640               ,   //CMOS输出宽度
parameter CMOS_V_PIXEL      = 12'd480               ,   //CMOS输出高度
parameter H_DISP            = 12'd480               ,   //图像宽度
parameter V_DISP            = 12'd272                   //图像高度
)
//========================< 端口 >==========================================
(
input   wire                clk_24m                 ,   //24Mhz
input   wire                cmos_pclk               ,   //cmos数据像素时钟
input   wire                rst_n                   ,   //复位,低电平有效
//cmos ----------------------------------------------
input   wire                cmos_href               ,   //CMOS 行同步
input   wire                cmos_vsync              ,   //CMOS 场同步
input   wire    [ 7:0]      cmos_data               ,   //CMOS 像素数据
//RGB -----------------------------------------------
output  wire                RGB_vld                 ,   //RGB数据使能
output  wire    [15:0]      RGB_data                ,   //RGB数据
output  reg     [ 7:0]      FPS_rate                    //帧率
);
//========================< 参数 >==========================================
parameter WAIT              = 10                             ;  //等待10帧
parameter TIME_1S           = 24_000000                      ;  //1s时间
//------------------------------------------------------------        
parameter H_START           = CMOS_H_PIXEL[11:2]-H_DISP[11:2];  //裁剪后的宽度起始
parameter H_STOP            = H_START + H_DISP               ;  //裁剪后的宽度结束
parameter V_START           = CMOS_V_PIXEL[11:2]-V_DISP[11:2];  //裁剪后的高度起始
parameter V_STOP            = V_START + V_DISP               ;  //裁剪后的高度结束 
//========================< 信号 >==========================================
reg                         cmos_vsync_r1           ;
reg                         cmos_vsync_r2           ;
reg                         cmos_href_r1            ;
reg                         cmos_href_r2            ;
wire                        cmos_vsync_pos          ;
reg     [ 3:0]              frame_cnt               ;
wire                        frame_vld               ;
reg                         byte_flag               ;
//---------------------------------------------------
reg     [11:0]              cnt_h                   ;
wire                        add_cnt_h               ;
wire                        end_cnt_h               ;
reg     [11:0]              cnt_v                   ;
wire                        add_cnt_v               ;
wire                        end_cnt_v               ;
reg                         RGB565_vld              ;
reg     [15:0]              RGB565_data             ;
//---------------------------------------------------
wire                        frame_vsync             ;
wire                        frame_hsync             ;
reg                         frame_vsync_r           ;
wire                        frame_vsync_pos         ;
reg     [24:0]              cnt_1s                  ;
wire                        add_cnt_1s              ;
wire                        end_cnt_1s              ;
reg     [ 7:0]              cnt_FPS                 ;
//==========================================================================
//==    打拍,以供后面程序使用
//==========================================================================
always @(posedge cmos_pclk or negedge rst_n) begin
    if(!rst_n) begin
        cmos_vsync_r1 <= 1'b0;
        cmos_vsync_r2 <= 1'b0;
        cmos_href_r1  <= 1'b0;
        cmos_href_r2  <= 1'b0;
    end
    else begin
        cmos_vsync_r1 <= cmos_vsync;
        cmos_vsync_r2 <= cmos_vsync_r1;
        cmos_href_r1  <= cmos_href;
        cmos_href_r2  <= cmos_href_r1;
    end
end
//==========================================================================
//==    前10帧图像数据不稳定,丢弃掉
//==========================================================================
//vsync上升沿
//---------------------------------------------------
assign cmos_vsync_pos = (~cmos_vsync_r1 & cmos_vsync);

//帧有效信号,去除前10帧
//---------------------------------------------------
always @(posedge cmos_pclk or negedge rst_n) begin
    if(!rst_n) begin
        frame_cnt <= 'd0;
    end
    else if(cmos_vsync_pos && frame_vld==1'b0) begin
        frame_cnt <= frame_cnt + 1'b1;
    end
end

assign frame_vld = (frame_cnt >= WAIT) ? 1'b1 : 1'b0;
//==========================================================================
//==    两个原始数据拼成一个RGB565像素
//==========================================================================
//字节指示
//---------------------------------------------------
always  @(posedge cmos_pclk or negedge rst_n) begin
    if(!rst_n) begin
        byte_flag <= 1'b0;
    end
    else if(cmos_href) begin
        byte_flag <= ~byte_flag;
    end
    else begin
        byte_flag <= 1'b0;
    end
end

//RGB_data
//---------------------------------------------------
always  @(posedge cmos_pclk or negedge rst_n) begin
    if(!rst_n) begin
        RGB565_data <= 'h0;
    end
    else if(byte_flag == 1'b0) begin                    //first byte
        RGB565_data <= {cmos_data, RGB565_data[7:0]};
    end
    else if(byte_flag == 1'b1) begin                    //second byte
        RGB565_data <= {RGB565_data[15:8], cmos_data};
    end
end

//RGB_vld
//---------------------------------------------------
always  @(posedge cmos_pclk or negedge rst_n) begin
    if(!rst_n) begin
        RGB565_vld <= 1'b0;
    end
    else if(frame_vld && byte_flag) begin
        RGB565_vld <= 1'b1;
    end
    else begin
        RGB565_vld <= 1'b0;
    end
end
//==========================================================================
//==    分辨率裁剪
//==========================================================================
//行计数
//---------------------------------------------------
always @(posedge cmos_pclk or negedge rst_n) begin
    if(!rst_n)
        cnt_h <= 'd0;
    else if(add_cnt_h) begin
        if(end_cnt_h)
            cnt_h <= 'd0;
        else
            cnt_h <= cnt_h + 1'b1;
    end
end

assign add_cnt_h = RGB565_vld;
assign end_cnt_h = add_cnt_h && cnt_h== CMOS_H_PIXEL-1;

//场计数
//---------------------------------------------------
always @(posedge cmos_pclk or negedge rst_n) begin 
    if(!rst_n)
        cnt_v <= 'd0;
    else if(add_cnt_v) begin
        if(end_cnt_v)
            cnt_v <= 'd0;
        else
            cnt_v <= cnt_v + 1'b1;
    end
end

assign add_cnt_v = end_cnt_h;
assign end_cnt_v = add_cnt_v && cnt_v== CMOS_V_PIXEL-1;

//裁剪后的数据:适配显示屏
//---------------------------------------------------
assign RGB_data = RGB565_data;
assign RGB_vld  = RGB565_vld && (cnt_h >= H_START) && (cnt_h < H_STOP)
                             && (cnt_v >= V_START) && (cnt_v < V_STOP);
//==========================================================================
//==   帧率计算,不能用pclk时钟,需重新捕捉vsync_pos
//==========================================================================
//输出行场有效信号
//---------------------------------------------------
assign frame_vsync = frame_vld ? cmos_vsync_r2 : 1'b0;
assign frame_hsync = frame_vld ? cmos_href_r2  : 1'b0;

//vsync上升沿
//---------------------------------------------------
always @(posedge clk_24m or negedge rst_n) begin
    if(!rst_n)
        frame_vsync_r <= 1'b0;
    else
        frame_vsync_r <= frame_vsync;
end

assign frame_vsync_pos = (~frame_vsync_r & frame_vsync);

//1s时间
//---------------------------------------------------
always @(posedge clk_24m or negedge rst_n) begin
    if(!rst_n)
        cnt_1s <= 'd0;
    else if(add_cnt_1s) begin
        if(end_cnt_1s)
            cnt_1s <= 'd0;
        else
            cnt_1s <= cnt_1s + 1'b1;
    end
end

assign add_cnt_1s = frame_vld;
assign end_cnt_1s = add_cnt_1s && cnt_1s== TIME_1S-1;

//1s时间内的vsync次数
//---------------------------------------------------
always @(posedge clk_24m or negedge rst_n) begin
    if(!rst_n)
        cnt_FPS <= 'd0;
    else if(end_cnt_1s) begin
        cnt_FPS <= 'd0;
    end
    else if(frame_vld && frame_vsync_pos)begin
        cnt_FPS <= cnt_FPS + 'd1;
    end
end

//实时更新帧率值
//---------------------------------------------------
always @(posedge clk_24m or negedge rst_n) begin
    if(!rst_n) begin
        FPS_rate <= 'd0;
    end
    else if(end_cnt_1s) begin
        FPS_rate <= cnt_FPS;
    end
end



endmodule 

(2)VGA拼接

经过 4port 的 sdram 缓存后,就要进行输出前的图像拼接,将两个画面拼接成一帧,一起输出到显示屏。代码如下所示:

//**************************************************************************
// *** 名称 : VGA_driver.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2019-08-10
// *** 描述 : VGA显示屏控制器,分辨率640x480
//            各家VGA电路不同,有些VGA输出信号是不需要的,那不接引脚就行
//**************************************************************************
module VGA_driver
//========================< 端口 >==========================================
(
//system --------------------------------------------
input   wire                clk                     ,   //时钟
input   wire                rst_n                   ,   //复位,低电平有效
//input ---------------------------------------------
output  wire                ch0_VGA_req             ,   //通道0 输出数据请求
input   wire    [15:0]      ch0_VGA_din             ,   //通道0 得到图像数据
//--------------------------
output  wire                ch1_VGA_req             ,   //通道1 输出数据请求
input   wire    [15:0]      ch1_VGA_din             ,   //通道1 得到图像数据
//output --------------------------------------------
output  wire                VGA_clk                 ,   //VGA数据时钟
output  wire                VGA_blank               ,   //VGA背光控制
output  wire                VGA_hsync               ,   //VGA行同步
output  wire                VGA_vsync               ,   //VGA场同步
output  wire    [15:0]      VGA_data                ,   //VGA数据输出
output  wire                VGA_de                      //VGA数据使能
);
//========================< 参数 >==========================================
//640x480 @60 25Mhz ---------------------------------  
parameter H_SYNC            = 16'd96                ;   //行同步信号
parameter H_BACK            = 16'd48                ;   //行显示后沿
parameter H_DISP            = 16'd640               ;   //行有效数据
parameter H_FRONT           = 16'd16                ;   //行显示前沿
parameter H_TOTAL           = 16'd800               ;   //行扫描周期
//---------------------------------------------------
parameter V_SYNC            = 16'd2                 ;   //场同步信号
parameter V_BACK            = 16'd33                ;   //场显示后沿
parameter V_DISP            = 16'd480               ;   //场有效数据   
parameter V_FRONT           = 16'd10                ;   //场显示前沿
parameter V_TOTAL           = 16'd525               ;   //场扫描周期
//========================< 信号 >==========================================
reg     [15:0]              cnt_h                   ;
wire                        add_cnt_h               ;
wire                        end_cnt_h               ;
reg     [15:0]              cnt_v                   ;
wire                        add_cnt_v               ;
wire                        end_cnt_v               ;
//---------------------------------------------------
reg                         ch0_VGA_de              ;
reg                         ch1_VGA_de              ;
//==========================================================================
//==    行、场计数
//==========================================================================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt_h <= 'd0;
    else if(add_cnt_h) begin
        if(end_cnt_h)
            cnt_h <= 'd0;
        else
            cnt_h <= cnt_h + 1'b1;
    end
end

assign add_cnt_h = 'd1;
assign end_cnt_h = add_cnt_h && cnt_h==H_TOTAL-1;

always @(posedge clk or negedge rst_n) begin 
    if(!rst_n)
        cnt_v <= 'd0;
    else if(add_cnt_v) begin
        if(end_cnt_v)
            cnt_v <= 'd0;
        else
            cnt_v <= cnt_v + 1'b1;
    end
end

assign add_cnt_v = end_cnt_h;
assign end_cnt_v = add_cnt_v && cnt_v==V_TOTAL-1;
//==========================================================================
//==    VGA控制信号
//==========================================================================
//VGA时钟
assign VGA_clk = clk;

//VGA背光控制
assign VGA_blank = rst_n;

//VGA行同步
assign VGA_hsync = (cnt_h < H_SYNC) ? 1'b0 : 1'b1;

//VGA场同步
assign VGA_vsync = (cnt_v < V_SYNC) ? 1'b0 : 1'b1;

//VGA请求,左半边
assign ch0_VGA_req = (cnt_h >= H_SYNC + H_BACK - 1) && (cnt_h < H_SYNC + H_BACK + H_DISP/2 - 1) &&
                     (cnt_v >= V_SYNC + V_BACK    ) && (cnt_v < V_SYNC + V_BACK + V_DISP      )
                     ? 1'b1 : 1'b0;
//VGA请求,右半边                  
assign ch1_VGA_req = (cnt_h >= H_SYNC + H_BACK + H_DISP/2 - 1) && (cnt_h < H_SYNC + H_BACK + H_DISP - 1) &&
                     (cnt_v >= V_SYNC + V_BACK               ) && (cnt_v < V_SYNC + V_BACK + V_DISP    )
                     ? 1'b1 : 1'b0;
//==========================================================================
//==    VGA数据输出
//==========================================================================
//VGA打拍对齐
always @(posedge clk) begin
    ch0_VGA_de <= ch0_VGA_req;
    ch1_VGA_de <= ch1_VGA_req;
end

//VGA数据输出
assign VGA_data = ch0_VGA_de ? ch0_VGA_din :
                  ch1_VGA_de ? ch1_VGA_din : 16'b0;

//VGA数据使能
assign VGA_de = (cnt_h >= H_SYNC + H_BACK) && (cnt_h < H_SYNC + H_BACK + H_DISP) &&
                (cnt_v >= V_SYNC + V_BACK) && (cnt_v < V_SYNC + V_BACK + V_DISP)
                ? 1'b1 : 1'b0;



endmodule

 

posted @ 2026-01-11 13:49  咸鱼IC  阅读(5)  评论(0)    收藏  举报