正点原子新起点V2开发板FPGA关于SDRAM代码解读
1. SDRAM 概述
SDRAM(Synchronous Dynamic Random Access Memory)是一种同步动态随机存储器,广泛用于FPGA项目中。通过SDRAM控制模块,可以实现数据读写、刷新等操作。本文对SDRAM的控制模块进行详细解读,分析代码中的命令控制、数据传输、状态管理等内容。
2.代码文件整体关系
以下是关于SDRAM模块的代码关系整理,便于对后续笔记有一个整体了解:
2.1. 顶层文件
- sdram_test:整个SDRAM系统的最顶层文件,用于测试与集成。
2.2. 测试与错误提示模块
- sdram_rw_test:用于写测试数据的模块,负责测试SDRAM的读写操作。
- led_disp:LED显示模块,用于进行错误提示,显示SDRAM操作的状态或错误。
2.3. 整合SDRAM模块(6个文件)
- sdram_top:SDRAM模块的顶层文件,负责协调和整合以下子模块。
- sdram_fifo_ctrl:FIFO控制模块,管理FIFO的读写操作,确保数据正确传输至SDRAM。
- sdram_controller:SDRAM控制器,负责管理对SDRAM的指令发送与数据操作。
- sdram_para:SDRAM模块的参数列表,定义SDRAM的相关配置参数。
- sdram_ctrl:状态控制模块,负责管理SDRAM的状态机。
- sdram_cmd:命令控制模块,负责生成SDRAM的命令信号。
- sdram_data:数据读写模块,处理SDRAM的读写操作与数据传输。
下面关系图可以帮助清晰理解各模块之间的层级与功能:
图脚注提到的状态机可以在我的 SDRAM 学习笔记
随笔中找到,下面再展示一次状态图方便阅读:
(初始化状态机)
(工作状态机)
3. 模块介绍
3.1. 顶层文件
顶层文件 sdram_rw_test.v
分析
//****************************************Copyright (c)***********************************//
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取FPGA & STM32资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: sdram_rw_test
// Last modified Date: 2018/3/18 8:41:06
// Last Version: V1.0
// Descriptions: SDRAM读写测试顶层模块
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2018/3/18 8:41:06
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module sdram_rw_test(
input clk, //FPGA外部时钟,50M
input rst_n, //按键复位,低电平有效
//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_data, //SDRAM 数据
output [ 1:0] sdram_dqm, //SDRAM 数据掩码
//LED
output [ 1:0] led //状态指示灯
);
//parameter
parameter ADDR_MIN = 24'd0; //最小地址
parameter ADDR_MAX = 24'd2048; //最大地址
parameter LEN = 10'd512; //突发长度
//wire define
wire clk_50m; //SDRAM 读写测试时钟
wire clk_100m; //SDRAM 控制器时钟
wire clk_100m_shift; //相位偏移时钟
wire wr_en; //SDRAM 写端口:写使能
wire [15:0] wr_data; //SDRAM 写端口:写入的数据
wire rd_en; //SDRAM 读端口:读使能
wire [15:0] rd_data; //SDRAM 读端口:读出的数据
wire sdram_init_done; //SDRAM 初始化完成信号
wire locked; //PLL输出有效标志
wire sys_rst_n; //系统复位信号
wire error_flag; //读写测试错误标志
//*****************************************************
//** main code
//*****************************************************
//待PLL输出稳定之后,停止系统复位
assign sys_rst_n = rst_n & locked;
//例化PLL, 产生各模块所需要的时钟
pll_clk u_pll_clk(
.inclk0 (clk),
.areset (~rst_n),
.c0 (clk_50m),
.c1 (clk_100m),
.c2 (clk_100m_shift),
.locked (locked)
);
//SDRAM测试模块,对SDRAM进行读写测试
sdram_test # (
.WR_CNT (ADDR_MAX),
.RD_CNT (ADDR_MAX)
)
u_sdram_test(
.clk_50m (clk_50m),
.rst_n (sys_rst_n),
.wr_en (wr_en),
.wr_data (wr_data),
.rd_en (rd_en),
.rd_data (rd_data),
.sdram_init_done (sdram_init_done),
.error_flag (error_flag)
);
//利用LED灯指示SDRAM读写测试的结果
led_disp u_led_disp(
.clk_50m (clk_50m),
.rst_n (sys_rst_n),
.sdram_init_done (sdram_init_done),
.error_flag (error_flag),
.led (led)
);
//SDRAM 控制器顶层模块,封装成FIFO接口
//SDRAM 控制器地址组成: {bank_addr[1:0],row_addr[12:0],col_addr[8:0]}
sdram_top u_sdram_top(
.ref_clk (clk_100m), //sdram 控制器参考时钟
.out_clk (clk_100m_shift), //用于输出的相位偏移时钟
.rst_n (sys_rst_n), //系统复位
//用户写端口
.wr_clk (clk_50m), //写端口FIFO: 写时钟
.wr_en (wr_en), //写端口FIFO: 写使能
.wr_data (wr_data), //写端口FIFO: 写数据
.wr_min_addr (ADDR_MIN), //写SDRAM的起始地址
.wr_max_addr (ADDR_MAX), //写SDRAM的结束地址
.wr_len (LEN), //写SDRAM时的数据突发长度
.wr_load (~sys_rst_n), //写端口复位: 复位写地址,清空写FIFO
//用户读端口
.rd_clk (clk_50m), //读端口FIFO: 读时钟
.rd_en (rd_en), //读端口FIFO: 读使能
.rd_data (rd_data), //读端口FIFO: 读数据
.rd_min_addr (ADDR_MIN), //读SDRAM的起始地址
.rd_max_addr (ADDR_MAX), //读SDRAM的结束地址
.rd_len (LEN), //从SDRAM中读数据时的突发长度
.rd_load (~sys_rst_n), //读端口复位: 复位读地址,清空读FIFO
//用户控制端口
.sdram_read_valid (1'b1), //SDRAM 读使能
.sdram_init_done (sdram_init_done), //SDRAM 初始化完成标志
//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_data (sdram_data), //SDRAM 数据
.sdram_dqm (sdram_dqm) //SDRAM 数据掩码
);
endmodule
这个模块将SDRAM控制器和测试模块组合起来,并通过PLL生成所需的时钟信号,控制SDRAM的工作时序。
主要功能:
- PLL时钟生成:利用PLL生成多个时钟信号,包括SDRAM读写操作所需的
clk_50m
、SDRAM控制器时钟clk_100m
,以及带有相位偏移的clk_100m_shift
。 - 测试模块:实例化
sdram_test
模块,并进行SDRAM的读写测试,测试结果通过LED显示。 - SDRAM控制器集成:通过实例化
SDRAM_top
模块,连接SDRAM芯片的接口,实现SDRAM的FIFO读写操作。 - 错误提示:实例化
led_disp
模块,通过LED指示读写是否出现错误。
关键逻辑:
- 时钟和复位信号:模块利用PLL产生多种时钟,且通过
locked
信号控制系统复位sys_rst_n
。 - SDRAM控制器与读写测试:SDRAM控制器通过FIFO接口连接SDRAM,提供了写操作与读操作的控制信号
wr_en
和rd_en
。 - LED状态显示:当SDRAM完成初始化和读写时,LED根据是否发生错误来显示状态。
3.2. 测试与错误提示模块
测试文件 sdram_test.v
分析
//****************************************Copyright (c)***********************************//
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取FPGA & STM32资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: sdram_test
// Last modified Date: 2018/3/18 8:41:06
// Last Version: V1.0
// Descriptions: SDRAM读写测试: 向SDRAM中写入数据,然后将数据读出,并判断读出的数据是否正确
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2018/3/18 8:41:06
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module sdram_test(
input clk_50m, //时钟
input rst_n, //复位,低有效
output reg wr_en, //SDRAM 写使能
output reg [15:0] wr_data, //SDRAM 写入的数据
output reg rd_en, //SDRAM 读使能
input [15:0] rd_data, //SDRAM 读出的数据
input sdram_init_done, //SDRAM 初始化完成标志
output reg error_flag //SDRAM 读写测试错误标志
);
//parameter
parameter WR_CNT = 24'd1024;
parameter RD_CNT = 24'd1024;
//reg define
reg init_done_d0; //寄存SDRAM初始化完成信号
reg init_done_d1; //寄存SDRAM初始化完成信号
reg [23:0] wr_cnt; //写操作计数器
reg [23:0] rd_cnt; //读操作计数器
reg rd_valid; //读数据有效标志
//*****************************************************
//** main code
//*****************************************************
//同步SDRAM初始化完成信号
always @(posedge clk_50m or negedge rst_n) begin
if(!rst_n) begin
init_done_d0 <= 1'b0;
init_done_d1 <= 1'b0;
end
else begin
init_done_d0 <= sdram_init_done;
init_done_d1 <= init_done_d0;
end
end
//SDRAM初始化完成之后,写操作计数器开始计数
always @(posedge clk_50m or negedge rst_n) begin
if(!rst_n)
wr_cnt <= 24'd0;
else if(init_done_d1 && (wr_cnt <= WR_CNT))
wr_cnt <= wr_cnt + 1'b1;
else
wr_cnt <= wr_cnt;
end
//SDRAM写端口FIFO的写使能、写数据(1~1024)
always @(posedge clk_50m or negedge rst_n) begin
if(!rst_n) begin
wr_en <= 1'b0;
wr_data <= 16'd0;
end
else if((wr_cnt >= 24'd1) && (wr_cnt <= WR_CNT)) begin
wr_en <= 1'b1; //写使能拉高
wr_data <= wr_cnt; //写入数据1~1024
end
else begin
wr_en <= 1'b0;
wr_data <= 16'd0;
end
end
//写入数据完成后,开始读操作
always @(posedge clk_50m or negedge rst_n) begin
if(!rst_n)
rd_en <= 1'b0;
else if(wr_cnt > WR_CNT) //写数据完成
rd_en <= 1'b1; //读使能拉高
end
//对读操作计数
always @(posedge clk_50m or negedge rst_n) begin
if(!rst_n)
rd_cnt <= 24'd0;
else if(rd_en) begin
if(rd_cnt < RD_CNT)
rd_cnt <= rd_cnt + 1'b1;
else
rd_cnt <= 24'd1;
end
end
//第一次读取的数据无效,后续读操作所读取的数据才有效
always @(posedge clk_50m or negedge rst_n) begin
if(!rst_n)
rd_valid <= 1'b0;
else if(rd_cnt == RD_CNT) //等待第一次读操作结束
rd_valid <= 1'b1; //后续读取的数据有效
else
rd_valid <= rd_valid;
end
//读数据有效时,若读取数据错误,给出标志信号
always @(posedge clk_50m or negedge rst_n) begin
if(!rst_n)
error_flag <= 1'b0;
else if(rd_valid && (rd_data != rd_cnt))
error_flag <= 1'b1; //若读取的数据错误,将错误标志位拉高
else
error_flag <= error_flag;
end
endmodule
该模块的主要功能是进行SDRAM的写操作和读操作的计数,并在操作完成后验证数据的一致性。如果数据读写不一致,则通过error_flag
标志位报告错误。
主要功能:
- SDRAM初始化同步:通过两个寄存器
init_done_d0
和init_done_d1
实现SDRAM初始化信号的同步。 - 写操作:通过写操作计数器
wr_cnt
,从1到1024依次向SDRAM写入数据,控制信号wr_en
和数据wr_data
随计数器变化。 - 读操作:当写操作完成后,读操作计数器
rd_cnt
开始计数,并启用rd_en
进行数据读取。 - 错误检测:通过比较读取的数据
rd_data
和rd_cnt
判断是否出错,如果出错,error_flag
被置1。
关键逻辑:
- 写入过程:
wr_cnt
控制写数据的范围(1~1024),写使能信号wr_en
与写数据同步。 - 读出过程:当写操作结束后,开始读操作,
rd_cnt
用于记录读操作次数,并通过rd_valid
标志区分有效数据。 - 错误检测:通过比较读取到的
rd_data
和rd_cnt
,如果不一致则置位error_flag
。
错误检测 led_disp
模块
//****************************************Copyright (c)***********************************//
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取FPGA & STM32资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: led_disp
// Last modified Date: 2018/3/18 8:41:06
// Last Version: V1.0
// Descriptions: LED显示模块,利用LED灯指示SDRAM读写测试的结果
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2018/3/18 8:41:06
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module led_disp(
input clk_50m, //系统时钟
input rst_n, //系统复位
input sdram_init_done,//SDRAM初始化完成
input error_flag, //错误标志信号
output reg [1:0] led //LED灯
);
//parameter ddefine
parameter LED_CNT = 25'd25000000;
//reg define
reg [24:0] led_cnt; //控制LED闪烁周期的计数器
//*****************************************************
//** main code
//*****************************************************
//计数器对50MHz时钟计数,计数周期为0.5s
always @(posedge clk_50m or negedge rst_n) begin
if(!rst_n)
led_cnt <= 25'd0;
else if(led_cnt < LED_CNT)
led_cnt <= led_cnt + 25'd1;
else
led_cnt <= 25'd0;
end
//利用LED灯不同的显示状态指示错误标志的高低
always @(posedge clk_50m or negedge rst_n) begin
if(rst_n == 1'b0)
led[0] <= 1'b0;
else if(!sdram_init_done)begin
if(led_cnt == LED_CNT)
led[0] <= ~led[0]; //SDRAM初始化失败时,LED灯每隔0.5s闪烁一次
else
led[0] <= led[0];
end
else
led[0] <= 1'b1; //错误标志为低时,LED灯常亮
end
always @(posedge clk_50m or negedge rst_n) begin
if(rst_n == 1'b0)
led[1] <= 1'b0;
else if(error_flag) begin
if(led_cnt == LED_CNT)
led[1] <= ~led[1]; //错误标志为高时,LED灯每隔0.5s闪烁一次
else
led[1] <= led[1];
end
else
led[1] <= 1'b1; //错误标志为低时,LED灯常亮
end
endmodule
led_disp
模块负责控制LED灯的状态,以指示SDRAM初始化是否成功以及系统中是否有错误:
-
输入信号:
clk_50m
: 系统时钟,50MHzrst_n
: 系统复位信号,低电平有效sdram_init_done
: SDRAM初始化完成信号error_flag
: 错误标志信号
-
功能:
- 当SDRAM初始化未完成时,LED[0]会以0.5秒的周期闪烁;若初始化成功,LED[0]常亮。
- 当检测到错误标志
error_flag
时,LED[1]以0.5秒周期闪烁;若无错误,LED[1]常亮。
3.3. 整合SDRAM模块(6个文件)
3.3.1整合模块 sdram_top
顶层文件
//****************************************Copyright (c)***********************************//
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取FPGA & STM32资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: sdram_test
// Last modified Date: 2018/3/18 8:41:06
// Last Version: V1.0
// Descriptions: SDRAM 控制器顶层模块
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2018/3/18 8:41:06
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module sdram_top(
input ref_clk, //sdram 控制器参考时钟
input out_clk, //用于输出的相位偏移时钟
input rst_n, //系统复位
//用户写端口
input wr_clk, //写端口FIFO: 写时钟
input wr_en, //写端口FIFO: 写使能
input [15:0] wr_data, //写端口FIFO: 写数据
input [23:0] wr_min_addr, //写SDRAM的起始地址
input [23:0] wr_max_addr, //写SDRAM的结束地址
input [ 9:0] wr_len, //写SDRAM时的数据突发长度
input wr_load, //写端口复位: 复位写地址,清空写FIFO
//用户读端口
input rd_clk, //读端口FIFO: 读时钟
input rd_en, //读端口FIFO: 读使能
output [15:0] rd_data, //读端口FIFO: 读数据
input [23:0] rd_min_addr, //读SDRAM的起始地址
input [23:0] rd_max_addr, //读SDRAM的结束地址
input [ 9:0] rd_len, //从SDRAM中读数据时的突发长度
input rd_load, //读端口复位: 复位读地址,清空读FIFO
//用户控制端口
input sdram_read_valid, //SDRAM 读使能
output sdram_init_done, //SDRAM 初始化完成标志
//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_data, //SDRAM 数据
output [ 1:0] sdram_dqm //SDRAM 数据掩码
);
//wire define
wire sdram_wr_req; //sdram 写请求
wire sdram_wr_ack; //sdram 写响应
wire [23:0] sdram_wr_addr; //sdram 写地址
wire [15:0] sdram_din; //写入sdram中的数据
wire sdram_rd_req; //sdram 读请求
wire sdram_rd_ack; //sdram 读响应
wire [23:0] sdram_rd_addr; //sdram 读地址
wire [15:0] sdram_dout; //从sdram中读出的数据
//*****************************************************
//** main code
//*****************************************************
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), //系统复位
//用户写端口
.clk_write (wr_clk), //写端口FIFO: 写时钟
.wrf_wrreq (wr_en), //写端口FIFO: 写请求
.wrf_din (wr_data), //写端口FIFO: 写数据
.wr_min_addr (wr_min_addr), //写SDRAM的起始地址
.wr_max_addr (wr_max_addr), //写SDRAM的结束地址
.wr_length (wr_len), //写SDRAM时的数据突发长度
.wr_load (wr_load), //写端口复位: 复位写地址,清空写FIFO
//用户读端口
.clk_read (rd_clk), //读端口FIFO: 读时钟
.rdf_rdreq (rd_en), //读端口FIFO: 读请求
.rdf_dout (rd_data), //读端口FIFO: 读数据
.rd_min_addr (rd_min_addr), //读SDRAM的起始地址
.rd_max_addr (rd_max_addr), //读SDRAM的结束地址
.rd_length (rd_len), //从SDRAM中读数据时的突发长度
.rd_load (rd_load), //读端口复位: 复位读地址,清空读FIFO
//用户控制端口
.sdram_read_valid (sdram_read_valid), //sdram 读使能
.sdram_init_done (sdram_init_done), //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_len), //写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_len), //读sdram时数据突发长度
.sdram_dout (sdram_dout), //从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_data (sdram_data) //SDRAM 数据
);
endmodule
sdram_top
是整个SDRAM控制系统的顶层模块,它集成了SDRAM FIFO控制模块和SDRAM控制器模块:
-
输入输出信号:
- 提供与用户的数据写入、读取控制信号以及SDRAM的接口信号。
- 包含数据突发读写地址、数据长度等控制参数,完成SDRAM的整体控制。
-
主要功能:
- 通过集成FIFO控制模块和SDRAM控制器模块,实现对SDRAM的读写操作,并通过
sdram_init_done
信号反馈初始化是否完成。
- 通过集成FIFO控制模块和SDRAM控制器模块,实现对SDRAM的读写操作,并通过
-
SDRAM 读写:
- 用户通过写时钟和读时钟(
wr_clk
、rd_clk
)与FIFO交互,进行SDRAM的读写操作。
- 用户通过写时钟和读时钟(
3.3.2控制FIFO模块sdram_fifo_ctrl
文件
//****************************************Copyright (c)***********************************//
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取FPGA & STM32资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: sdram_fifo_ctrl
// Last modified Date: 2018/3/18 8:41:06
// Last Version: V1.0
// Descriptions: SDRAM 读写端口FIFO控制模块
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2018/3/18 8:41:06
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module sdram_fifo_ctrl(
input clk_ref, //SDRAM控制器时钟
input rst_n, //系统复位
//用户写端口
input clk_write, //写端口FIFO: 写时钟
input wrf_wrreq, //写端口FIFO: 写请求
input [15:0] wrf_din, //写端口FIFO: 写数据
input [23:0] wr_min_addr, //写SDRAM的起始地址
input [23:0] wr_max_addr, //写SDRAM的结束地址
input [ 9:0] wr_length, //写SDRAM时的数据突发长度
input wr_load, //写端口复位: 复位写地址,清空写FIFO
//用户读端口
input clk_read, //读端口FIFO: 读时钟
input rdf_rdreq, //读端口FIFO: 读请求
output [15:0] rdf_dout, //读端口FIFO: 读数据
input [23:0] rd_min_addr, //读SDRAM的起始地址
input [23:0] rd_max_addr, //读SDRAM的结束地址
input [ 9:0] rd_length, //从SDRAM中读数据时的突发长度
input rd_load, //读端口复位: 复位读地址,清空读FIFO
//用户控制端口
input sdram_read_valid, //SDRAM 读使能
input sdram_init_done, //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中读出的数据
);
//reg define
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 read_valid_r1; //sdram读使能寄存器
reg read_valid_r2;
//wire define
wire write_done_flag; //sdram_wr_ack 下降沿标志位
wire read_done_flag; //sdram_rd_ack 下降沿标志位
wire wr_load_flag; //wr_load 上升沿标志位
wire rd_load_flag; //rd_load 上升沿标志位
wire [10:0] wrf_use; //写端口FIFO中的数据量
wire [10:0] rdf_use; //读端口FIFO中的数据量
//*****************************************************
//** main code
//*****************************************************
//检测下降沿
assign write_done_flag = wr_ack_r2 & ~wr_ack_r1;
assign read_done_flag = rd_ack_r2 & ~rd_ack_r1;
//检测上升沿
assign wr_load_flag = ~wr_load_r2 & wr_load_r1;
assign rd_load_flag = ~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读使能信号
always @(posedge clk_ref or negedge rst_n) begin
if(!rst_n) begin
read_valid_r1 <= 1'b0;
read_valid_r2 <= 1'b0;
end
else begin
read_valid_r1 <= sdram_read_valid;
read_valid_r2 <= read_valid_r1;
end
end
//sdram写地址产生模块
always @(posedge clk_ref or negedge rst_n) begin
if(!rst_n)
sdram_wr_addr <= wr_min_addr;
else if(wr_load_flag) //检测到写端口复位信号时,写地址复位
sdram_wr_addr <= wr_min_addr;
else if(write_done_flag) begin //若突发写SDRAM结束,更改写地址
//若未到达写SDRAM的结束地址,则写地址累加
if(sdram_wr_addr < wr_max_addr - wr_length)
sdram_wr_addr <= sdram_wr_addr + wr_length;
else //若已到达写SDRAM的结束地址,则回到写起始地址
sdram_wr_addr <= wr_min_addr;
end
end
//sdram读地址产生模块
always @(posedge clk_ref or negedge rst_n) begin
if(!rst_n)
sdram_rd_addr <= rd_min_addr;
else if(rd_load_flag) //检测到读端口复位信号时,读地址复位
sdram_rd_addr <= rd_min_addr;
else if(read_done_flag) begin //突发读SDRAM结束,更改读地址
//若未到达读SDRAM的结束地址,则读地址累加
if(sdram_rd_addr < rd_max_addr - rd_length)
sdram_rd_addr <= sdram_rd_addr + rd_length;
else //若已到达读SDRAM的结束地址,则回到读起始地址
sdram_rd_addr <= rd_min_addr;
end
end
//sdram 读写请求信号产生模块
always@(posedge clk_ref or negedge rst_n) begin
if(!rst_n) begin
sdram_wr_req <= 0;
sdram_rd_req <= 0;
end
else if(sdram_init_done) begin //SDRAM初始化完成后才能响应读写请求
//优先执行写操作,防止写入SDRAM中的数据丢失
if(wrf_use >= wr_length) begin //若写端口FIFO中的数据量达到了写突发长度
sdram_wr_req <= 1; //发出写sdarm请求
sdram_rd_req <= 0;
end
else if((rdf_use < rd_length) //若读端口FIFO中的数据量小于读突发长度,
&& read_valid_r2) begin //同时sdram读使能信号为高
sdram_wr_req <= 0;
sdram_rd_req <= 1; //发出读sdarm请求
end
else begin
sdram_wr_req <= 0;
sdram_rd_req <= 0;
end
end
else begin
sdram_wr_req <= 0;
sdram_rd_req <= 0;
end
end
//例化写端口FIFO
wrfifo u_wrfifo(
//用户接口
.wrclk (clk_write), //写时钟
.wrreq (wrf_wrreq), //写请求
.data (wrf_din), //写数据
//sdram接口
.rdclk (clk_ref), //读时钟
.rdreq (sdram_wr_ack), //读请求
.q (sdram_din), //读数据
.rdusedw (wrf_use), //FIFO中的数据量
.aclr (~rst_n | wr_load_flag) //异步清零信号
);
//例化读端口FIFO
rdfifo u_rdfifo(
//sdram接口
.wrclk (clk_ref), //写时钟
.wrreq (sdram_rd_ack), //写请求
.data (sdram_dout), //写数据
//用户接口
.rdclk (clk_read), //读时钟
.rdreq (rdf_rdreq), //读请求
.q (rdf_dout), //读数据
.wrusedw (rdf_use), //FIFO中的数据量
.aclr (~rst_n | rd_load_flag) //异步清零信号
);
endmodule
sdram_fifo_ctrl
模块负责管理SDRAM读写端口的FIFO控制逻辑:
-
输入输出信号:
- 接收SDRAM写请求、写数据、写地址等信号,并与SDRAM控制器进行读写交互。
-
功能:
- 检测SDRAM写、读请求并进行相应的地址控制和数据传输。
- 通过内部寄存器和标志位检测写读操作的完成状态。
-
信号控制:
- 检测写/读操作的响应信号,更新状态寄存器,保证写入和读取FIFO中的数据能按要求传输到SDRAM。
3.3.3sdram_controller
模块分析
//****************************************Copyright (c)***********************************//
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取FPGA & STM32资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: sdram_controller
// Last modified Date: 2018/3/18 8:41:06
// Last Version: V1.0
// Descriptions: SDRAM 控制器
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2018/3/18 8:41:06
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
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读出的数据
output sdram_init_done, //SDRAM 初始化完成标志
// FPGA与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_data // SDRAM 数据总线
);
//wire define
wire [4:0] init_state; // SDRAM初始化状态
wire [3:0] work_state; // SDRAM工作状态
wire [9:0] cnt_clk; // 延时计数器
wire sdram_rd_wr; // SDRAM读/写控制信号,低电平为写,高电平为读
//*****************************************************
//** main code
//*****************************************************
// 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_data (sdram_data)
);
endmodule
该模块是顶层模块,整合了多个子模块,管理FPGA与SDRAM之间的交互,提供了一些基本的读写请求信号和硬件接口信号。它的主要功能是:
- 初始化 SDRAM:模块负责在系统启动时完成对SDRAM的初始化,包括预充电、自动刷新和模式寄存器设置。
- 读写操作:在完成初始化后,模块通过内部状态机管理SDRAM的读写请求,并在突发读写期间与SDRAM通信。
- 接口信号处理:包含SDRAM控制器的多种信号接口,如片选信号、地址线、数据总线等。
3.3.3.1 sdram_para
参数文件分析
// 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-3 //读突发终止
`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
- 初始化状态:
定义了 SDRAM 初始化过程中的各个状态,如空闲、预充电、自动刷新、模式寄存器设置等。 - 工作状态:
定义了 SDRAM 工作过程中的各个状态,如空闲、行有效、读操作、写操作、预充电等。 - 延时参数:
定义了不同状态下的延时条件,确保 SDRAM 操作符合时序要求。 - 命令定义:
包含了 SDRAM 的各种命令信号,如空操作、激活、读、写、突发停止、预充电、自动刷新、模式寄存器加载等。
3.3.3.2. sdram_ctrl
状态控制模块分析
//****************************************Copyright (c)***********************************//
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取FPGA & STM32资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: sdram_ctrl
// Last modified Date: 2018/3/18 8:41:06
// Last Version: V1.0
// Descriptions: SDRAM 状态控制模块
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2018/3/18 8:41:06
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
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-256个)
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读/写控制信号,低电平为写,高电平为读
);
`include "sdram_para.v" //包含SDRAM参数定义模块
//parameter define 时钟个数,以100Mhz(10ns)为准
parameter TRP_CLK = 10'd4; //预充电有效周期,至少15ns,
parameter TRC_CLK = 10'd6; //自动刷新周期,至少60ns,
parameter TRSC_CLK = 10'd6; //模式寄存器设置时钟周期,2个TCK周期
parameter TRCD_CLK = 10'd2; //行选通周期,至少15ns,
parameter TCL_CLK = 10'd3; //列潜伏期(cl),3个TCK周期
parameter TWR_CLK = 10'd2; //写入校正,2个TCK周期
parameter CNT_200US = 15'd20_000; //200us计数个数
parameter CNT_REFRESH = 11'd781; //自动刷新计数器
//reg define
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 define
wire done_200us; //上电后200us输入稳定期结束标志位
wire sdram_ref_ack; //SDRAM自动刷新请求应答信号
//*****************************************************
//** main code
//*****************************************************
//SDRAM上电后200us稳定期结束后,将标志信号拉高
assign done_200us = (cnt_200us == CNT_200US);
//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 < CNT_200US)
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 < CNT_REFRESH) // 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 == CNT_REFRESH - 1)
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
SDRAM_Ctrl
模块是SDRAM_Controller
模块的核心之一,负责对SDRAM的读写请求进行控制和状态管理。
1. 初始化状态机 (init_state
)
初始化状态机用于控制 SDRAM 的初始化过程,在上电后200微秒稳定期结束后,执行预充电、自动刷新、模式寄存器设置等操作,最后进入工作状态。
- 状态
I_NOP
:这是默认状态,等待上电后的200微秒计时器完成 (done_200us
) 后,进入I_PRE
状态。 - 状态
I_PRE
:执行预充电操作后,进入I_TRP
状态等待预充电完成。 - 状态
I_TRP
:等待预充电周期 (TRP_CLK
) 结束,当预充电结束后,进入I_AR
状态,开始自动刷新操作。 - 状态
I_AR
:执行自动刷新操作,并在完成刷新后检查是否已进行8次刷新(初始化要求)。如果满足条件,进入I_MRS
状态设置模式寄存器。 - 状态
I_MRS
:设置 SDRAM 的模式寄存器,定义工作模式,然后进入I_TRSC
状态等待模式寄存器设置完成。 - 状态
I_TRSC
:等待模式寄存器设置完成 (TRSC_CLK
),完成后进入I_DONE
状态,标志初始化结束。 - 状态
I_DONE
:表示 SDRAM 初始化完成,SDRAM 进入工作状态,系统可以进行读写操作。
2. 工作状态机 (work_state
)
工作状态机负责控制 SDRAM 的读写和自动刷新操作。它根据外部请求和当前状态,管理工作流程。
-
状态
W_IDLE
:这是空闲状态。当检测到自动刷新请求、写请求或读请求时,工作状态机会相应跳转:- 若收到
sdram_ref_req
刷新请求,跳转到W_AR
状态执行自动刷新操作。 - 若收到写请求 (
sdram_wr_req
),跳转到W_ACTIVE
状态,并设置sdram_rd_wr
为低电平(写操作)。 - 若收到读请求 (
sdram_rd_req
),同样跳转到W_ACTIVE
状态,但设置sdram_rd_wr
为高电平(读操作)。
- 若收到
-
状态
W_ACTIVE
:行有效状态,进入行选通等待状态W_TRCD
。 -
状态
W_TRCD
:行选通等待状态,根据sdram_rd_wr
信号,决定跳转到读操作状态 (W_READ
) 或写操作状态 (W_WRITE
)。 -
读操作流程:
W_READ
:进入潜伏期状态W_CL
。W_CL
:等待潜伏期结束后进入读数据状态W_RD
。W_RD
:读取数据,完成后进入预充电状态W_PRE
。
-
写操作流程:
W_WRITE
:进入写数据状态W_WD
。W_WD
:写入数据,完成后进入写回周期W_TWR
。W_TWR
:完成写回周期后,进入预充电状态W_PRE
。
-
自动刷新操作:
W_AR
:执行自动刷新,等待W_TRFC
状态完成刷新,最后返回空闲状态W_IDLE
。
3. 计数器控制逻辑
计数器用于控制不同操作中的延迟和时序管理,根据状态机的不同状态执行计数。
-
cnt_200us
:上电后的200微秒稳定期计时器。当cnt_200us
计数到CNT_200US
,done_200us
置高,表示200微秒结束,可以进入下一状态。 -
cnt_refresh
:刷新计时器,每 64ms 完成 8192 行的刷新操作(计时器为7812ns)。当cnt_refresh
达到CNT_REFRESH
时,产生自动刷新请求sdram_ref_req
。 -
cnt_clk
:用于计数不同状态下的延时。在不同状态如预充电 (W_PRE
)、行选通等待 (W_TRCD
)、潜伏期 (W_CL
)、读数据 (W_RD
)、写数据 (W_WD
) 等,会通过cnt_rst_n
控制计数器复位或启动。例如,在
I_PRE
状态下,计数器启动计数预充电时钟周期 (TRP_CLK
),等待end_trp
信号结束后,计数器复位并进入下一状态。
3.3.3.3. sdram_cmd
命令控制模块分析
//****************************************Copyright (c)***********************************//
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取FPGA & STM32资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: sdram_cmd
// Last modified Date: 2018/3/18 8:41:06
// Last Version: V1.0
// Descriptions: SDRAM 命令控制模块
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2018/3/18 8:41:06
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
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地址总线
);
`include "sdram_para.v" //包含SDRAM参数定义模块
//parameter define
parameter BURST_LENGTH = 3'b111; //突发长度,这里设置为页突发
parameter ADDRESSING_MODE = 1'b0; //突发传输方式,这里设置为顺序
parameter CAS_LATENCY = 3'b011; //CAS潜伏期(CL)设置,这里设置为3,
parameter WRITE_MODE = 1'b0; //读写方式 A9=0,突发读&突发写
//reg define
reg [ 4:0] sdram_cmd_r; //SDRAM操作指令
//wire define
wire [23:0] sys_addr; //SDRAM读写地址
//*****************************************************
//** main code
//*****************************************************
//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, //预留
WRITE_MODE, //读写方式 A9=0,突发读&突发写
2'b00, //默认,{A8,A7}=00
CAS_LATENCY, //CAS潜伏期设置,这里设置为3,{A6,A5,A4}=011
ADDRESSING_MODE, //突发传输方式,这里设置为顺序,A3=0
BURST_LENGTH //突发长度,这里设置为页突发,
};
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'h0000;
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
这个 sdram_cmd
模块主要负责根据系统状态生成控制 SDRAM 的命令信号。通过时钟信号驱动,模块根据初始化状态 (init_state
) 和工作状态 (work_state
) 来决定何时发送命令,如行激活、读写、预充电等操作。下面详细分析其工作机制:
1. 接口信号
-
输入信号:
clk
和rst_n
: 系统时钟和复位信号,用于时序控制。sys_wraddr
和sys_rdaddr
: 分别为系统写地址和读地址,用于读写操作时指定SDRAM的地址。sdram_wr_burst
和sdram_rd_burst
: 突发写和读时的字节数,用于控制读写操作中的突发传输。init_state
和work_state
: 初始化和工作状态控制信号,用于决定当前 SDRAM 的操作。cnt_clk
: 延时计数器,用于特定时序的延时控制。sdram_rd_wr
: SDRAM的读写控制信号,低电平表示写操作,高电平表示读操作。
-
输出信号:
sdram_cke
,sdram_cs_n
,sdram_ras_n
,sdram_cas_n
,sdram_we_n
: 这些信号分别用于控制SDRAM的时钟有效、片选、行地址选通、列地址选通以及写使能。sdram_ba
: SDRAM的L-Bank地址信号,用于指定SDRAM的Bank选择。sdram_addr
: SDRAM的地址信号总线,用于读写操作时指定SDRAM内存位置。
2. 主要逻辑部分
(1) SDRAM 命令控制
- 模块包含了五个主要命令信号 (
sdram_cke
、sdram_cs_n
、sdram_ras_n
、sdram_cas_n
、sdram_we_n
),这些信号通过sdram_cmd_r
寄存器赋值控制。具体命令定义在sdram_para.v
中,通过状态机切换来控制命令的发送。 - 地址控制方面,通过
sys_addr
来区分读地址 (sys_rdaddr
) 或写地址 (sys_wraddr
),并且在不同的工作状态下使用不同的地址传输。
(2) 状态机控制
-
初始化状态 (init_state):
- 在初始化过程中,根据不同的状态 (
I_NOP
,I_TRP
,I_TRF
,I_TRSC
,I_PRE
,I_AR
,I_MRS
等),模块发送不同的命令。例如:I_NOP
状态下发送CMD_NOP
(无操作指令)。I_PRE
状态发送预充电命令CMD_PRGE
。I_MRS
状态发送模式寄存器设置命令CMD_LMR
,并通过sdram_addr
配置CAS潜伏期、突发长度等参数。
- 在初始化过程中,根据不同的状态 (
-
工作状态 (work_state):
- 初始化完成后,进入工作状态。根据不同的状态执行不同操作:
W_IDLE
,W_TRCD
,W_CL
等空闲状态发送CMD_NOP
。W_ACTIVE
发送行激活命令CMD_ACTIVE
,并根据地址sys_addr
设置sdram_ba
和sdram_addr
。W_READ
和W_WRITE
分别发送读/写命令CMD_READ
和CMD_WRITE
,对应地址根据当前sys_addr
分配。W_RD
和W_WD
状态下检查突发传输结束后发送突发终止命令CMD_B_STOP
。W_PRE
发送预充电命令CMD_PRGE
。
- 初始化完成后,进入工作状态。根据不同的状态执行不同操作:
(3) SDRAM命令生成逻辑
每个时钟周期通过 always @ (posedge clk or negedge rst_n)
块进行更新。状态机的各个状态通过 case 语句进行区分,状态机的切换和相应操作指令的发出主要依赖 init_state
和 work_state
。对于特定状态,生成相应的命令指令并设置相应的地址与控制信号。
3. 参数设置
模块中通过 parameter
设置了几个重要的 SDRAM 工作参数:
BURST_LENGTH
: 页突发长度,设置为3'b111
。CAS_LATENCY
: CAS潜伏期设置为3。WRITE_MODE
: 设置为突发读和突发写。
这些参数的设置影响了SDRAM读写操作的延迟和传输模式,可以根据应用需求进行修改。
3.3.3.4. sdram_data
数据读写模块分析
//****************************************Copyright (c)***********************************//
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取FPGA & STM32资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: sdram_data
// Last modified Date: 2018/3/18 8:41:06
// Last Version: V1.0
// Descriptions: SDRAM 数据读写模块
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2018/3/18 8:41:06
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
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_data //SDRAM数据总线
);
`include "sdram_para.v" //包含SDRAM参数定义模块
//reg define
reg sdram_out_en; //SDRAM数据总线输出使能
reg [15:0] sdram_din_r; //寄存写入SDRAM中的数据
reg [15:0] sdram_dout_r; //寄存从SDRAM中读取的数据
//*****************************************************
//** main code
//*****************************************************
//SDRAM 双向数据线作为输入时保持高阻态
assign sdram_data = 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_data; //寄存从SDRAM中读取的数据
end
endmodule
- 功能:处理 SDRAM 数据线上的双向数据传输。
- 主要信号:
- 输入:
sdram_data_in
:需要写入 SDRAM 的数据。work_state
:当前的工作状态。cnt_clk
:时钟计数器,用于同步操作。
- 输出:
sdram_data_out
:从 SDRAM 中读取的数据。sdram_data
:SDRAM 数据总线(inout)。
- 输入:
- 数据总线控制:
- 当处于写操作或写数据状态时,
sdram_out_en
使能数据输出,sdram_data
总线作为输出;在其他状态下,保持高阻态。 - 从
work_state
中判断当前是读操作或写操作,分别将数据写入或从 SDRAM 中读取。
- 当处于写操作或写数据状态时,
4. 疑问与总结
其实这里在读代码的过程中我发现了关于地址线的一些问题,问题如下图中红字所述,在sdram_rw_test.v
文件我们可以看到最大地址和数据传递关系:
在sdram_cmd.v
文件中
以上是我的疑惑,请求大佬帮忙解答!
本篇博客详细分析了SDRAM代码中各个模块的作用和功能,包括命令控制模块、数据控制模块以及时序控制机制。通过这些模块的协同工作,实现了SDRAM的初始化、读写操作。
标签:SDRAM,FPGA,clk,sdram,rd,V2,wr,input From: https://www.cnblogs.com/LilMonsterOvO/p/18475001声明:本博客笔记中的SDRAM代码来自正点原子,整理用于自己学习与经验交流。