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

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

浙公网安备 33010602011771号