设计SDRAM
SDRAM写入10个字节的数据并且读出来,通过串口调试助手传给电脑。
sdram_write 写模块
sdram_read 读模块
sdram_init 初始化模块
sdram_aref 自动刷新模块 : 电容会放电,需要刷新
sdram_arbit 仲裁模块 : 判断处理优先级
sdram_init 初始化模块
init_end 结束信号
init_addr 行列地址
init_ba bank地址
init_cmd 控制命令
sdram_aref 自动刷新模块
sdram_write 写模块
wr_addr: 写地址
wr_data: 写数据
wr_brust_len: 突发长度
wr_cmd: 写控制命令
wr_ba:bank地址
wr_data:写入数据
wr_addr: 地址总线
wr_end:结束信号
写响应信号 wr_ack
sdram_read 读模块
读响应信号 rd_ack
rd_addr: 读地址
init_end:初始化结束信号
rd_brust_len: 突发长度
rd_data读出数据
sdram_arbit 仲裁模块
sdram_dq: 数据
系统上电后要进行初始化,初始化完成信号init_end,仲裁模块是为了避免各个操作之间的冲突。发送请求信号aref_req,仲裁模块作出判断,使能信号,aref_en信号,如果有效,传入地址和命令,完成自动刷新后,传入自动刷新结束信号。
如果要进行数据写入也要进行请求,写使能信号后,传入地址和命令,数据。 写入完成。
读操作也是如此,发送请求信号,使能,反馈地址,数据,命令,读出完成。
仲裁模块每次只能执行一个操作。 传出时钟使能信号。 片选信号,行选通,列选通,写使能,bank地址,地址总线,数据端口输入输出
1 sdram_ctrl 控制模块
包括上述子模块。
2 fifo控制模块
作用:
使用fifo进行数据缓存,进行跨时钟域处理,同时为sdram控制模块提供sdram读写地址和读写请求信号
信号:
输入数据:
写时钟 写请求 写开始地址 写寄书地址 突发长度 复位信号 有效信号
读和写类似
初始化结束信号 写响应和 读响应信号 读出的数据
输出:
sdram_wr_req 写数据请求信号 写地址 写数据
读请求 读地址 读数据 rd_fifo_rd_data
对读出数据进行计数 rd_fifo_num
3 例化这两个模块,生成顶层模块 sdram_top
还需要时钟信号 串口发送 串口接收 模块
sdram 读出数据时钟频率高通过串口进行输出,这里需要fifo作为跨时钟域处理。
fifo_read 模块
clk_gen 模块
uart_tx 模块 uart_rx 模块
4 绘制该工程顶层模块 uart_sdram
无操作命令: 防止其他操作写入
SDRAm_init初始化模块设计
sdram初始化至少两次自动刷新
上电后 必须进行初始化。
初始化命令: 预充电命令+自动刷新命令+寄存器配置命令
上电后有效时钟信号,等待大于等于100us,等待过程中写入无操作NOP指令。 等待结束后,写入预充电指令A10给入高电平对all bank 进行预充电。 预充电写入后要进行等待tRP,同时写入NOP指令。 时间结束后,写入自动刷新指令,也需要一段时间等待,tRFC,同时写入NOP指令。等待时间结束后,随后开始第二次自动刷新。 开始配置模式寄存器,同时使用地址端口辅助模式寄存器配置。 需要等待tMRD时间,同时写入NOP。 等待时间结束后,初始化完成。
tRP tRFC tMRD 等待时间。
上电之后的等待时间 可以延长提高芯片适配度
代码编写
初始化 适合状态机来表示。
状态参数
IDLE : NOP
PR
PR_T
ARF
ARF_T
ARF
ARF_T
MRS
MRS_T
END
module sdram_init(
input wire sys_clk,
input wire sys_rst_n,
output wire [3:0] init_cmd,
output wire [1:0] init_ba,
output wire [12:0] init_addr,
output wire init_end
)
reg [2:0] init_state;
reg [14:0] cnt_200us;
wire wait_end;
reg [2:0] cnt_clk;
reg cnt_clk_rst;
reg trp_end;
reg trfc_end;
reg tmrd_end;
reg [3:0] cnt_aref;
//格雷码表示状态
parameter INIT_IDLE = 3'b000
INIT_PRE = 3'b001,
INIT_TRP = 3'b011,
INIT_AR = 3'b010,
INIT_TRF = 3'b110,
INIT_MRS = 3'b111,
INIT_TMRD = 3'b101,
INIT_END = 3'b100;
// 10ns 完成200us
parameter WAIT_MAX= 15'd20000;
parameter TRP = 3'd2,
TRF = 3'd7,
TMRD = 3d'3; // 2个周期 7个周期 3个周期
//四个命令 空命令 预充电 自动刷新 模式寄存器配置
parameter NOP = 4'b0111,
P_CHARGE =4'b0010,
AUTO_REF =4'b0001,
M_REG_SET = 4'b0000;
//对状态变量赋值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
init_state <= INIT_IDLE;
//状态跳转
case(init_state)
INIT_IDLE:
if(wait_end == 1'b1)
init_state <= INIT_PRE;
else
inite_state <= init_state;
INIT_PRE :
inite_state <= INIT_TRP;
//使用cnt_clk计数,trp_end 拉高
INIT_TRP :
if(trp_end== 1'b1)
init_state <= INIT_AR;
else
inite_state <= init_state;
INIT_AR :
inite_state <= INIT_TRP;
//至少两次自动刷新,结束信号判断和次数判断 8次自动刷新
INIT_TRF :
if(trfc_end == 1'b1)
if(cnt_aref == 4'd8)
init_state <= INIT_MRS;
else
init_state <= INIT_AR;
else
init_state <= init_state;
INIT_MRS :
inite_state <= INIT_TMRD;
INIT_TMRD :
if(tmrd_end == 1'b1)
init_state <= INIT_END;
else
init_state <= init_state;
INIT_END:
init_state <=INIT_END;
default:init_state <= INIT_IDLE;
endcase
// 200us计数器赋值 上电后的等待时间
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_200us <= 15'd0;
else if(cnt_200us == WAIT_MAX)
cnt_200us <= WAIT_MAX;
else
cnt_200us <= cnt_200us + 1'b1;
// 使得等待结束信号。只拉高一个时钟周期
assign wait_end = (cnt_200us == WAIT_MAX -1'b1) ?1'b1:1'b0;
//cnt_clk 计数器的赋值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_clk <= 3'd0;
else if(cnt_clk_rst == 1'b1)
cnt_clk <= 3'd0;
else
cnt_clk<= cnt_clk +1'b1;
//复位信号的赋值 组合逻辑赋值 case 语句状态跳转条件
always@(*)
begin
case(init_state)
INIT_IDLE: cnt_clk_rst <= 1'b1;
INIT_TRP: cnt_clk_rst <= (trp_end == 1'b1) ? 1'b1:1'b0;
INIT_TRF :cnt_clk_rst <= (trfc_end ==1'b1)?1'b1:1'b0;
INIT_TMRD:cnt_clk_rst <= (tmrd_end==1'b1)?1'b1:1'b0;
INIT_END: cnt_clk_rst <=1'b1;
default: cnt_clk_rst <= 1'b0;
endcase
end
// tmrd_end trfc_end trp_end 进行赋值 组合逻辑,定义参数进行判断
assign trp_end = (cnt_clk == TRP)? 1'b1:1'b0;
assign trfc_end = (cnt_clk == TRF)? 1'b1:1'b0;
assign tmrd_end = (cnt_clk == TMRD)? 1'b1:1'b0;
//自动刷新次数计数器 cnt_aref ,当状态机处于初始状态进行清零
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_aref <= 4'd0;
else if(init_state == INIT_IDLE)
cnt_aref <= 4'd0;
else if(init_state == INIT_AR)
cnt_aref <= cnt_aref + 1'b1;
else
cnt_aref <= cnt_aref;
// 以上所有中间变量完成。以下为四路输出信号
//命令和地址是同时起作用,故同时进行赋值 这几个命令定义为参数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
init_cmd <= NOP;
init_ba <= 2'b11;
init_addr <= 13'h1fff;
end
//结合状态机进行赋值
case(init_state)
INIT_IDLE,INIT_TRP,INIT_TRP,INIT_TMRD,INIT_END:
begin
init_cmd <= NOP;
init_ba <= 2'b11;
init_addr <= 13'h1fff;
end
INIT_PRE:
begin
init_cmd <= P_CHARGE;
init_ba <= 2'b11;
init_addr <= 13'h1fff;
end
INIT_AR:
begin
init_cmd <= AUTO_REF;
init_ba <= 2'b11;
init_addr <= 13'h1fff;
end
//模式寄存器下地址总线和bank地址都要改一下
// 地址总线高三位为0 ,第三位为突发长度选择
INIT_MRS:
begin
init_cmd <= M_REG_SET;
init_ba <= 2'b00;
init_addr <= {3'b0,1'b0,2'b00,3'b011'1b0,3'b11)
end
default;
begin
init_cmd <= NOP;
init_ba <= 2'b11;
init_addr <= 13'h1fff;
end
endcase
// 结束信号 init_end 用组合逻辑赋值
assign init_end = (init_state == INIT_END)?1'b1:1'b0;
endmodule
cnt_200us 为上电后的等待时间
预充电 寄存器配置 自动刷新等的等待时间通过 cnt_clk来统计,通过tmrd_end等的信号来辅助判断。
wait_end 充电后等待时间结束的判断信号
模式寄存器的bank地址为全0
用来写模式寄存器的地址总线为:
四路输出信号分析:
init_cmd:
init_ba: bank地址初值给2'b11。寄存器配置时给定2'b00,
因为在配置时候bank地址要给定00.
init_addr:地址总线,预充电时要高电平,直接13h1fff。然后模式寄存器配置的时候给定 需要的值。
SDRAM初始化的 验证, 进行全编译检查错误:
完整代码如下:
module sdram_init(
input wire sys_clk,
input wire sys_rst_n,
output reg [3:0] init_cmd,
output reg [1:0] init_ba,
output reg [12:0] init_addr,
output wire init_end
);
reg [2:0] init_state;
reg [14:0] cnt_200us;
wire wait_end;
reg [2:0] cnt_clk;
reg cnt_clk_rst;
wire trp_end;
wire trfc_end;
wire tmrd_end;
reg [3:0] cnt_aref;
//格雷码表示状态
parameter INIT_IDLE = 3'b000,
INIT_PRE = 3'b001,
INIT_TRP = 3'b011,
INIT_AR = 3'b010,
INIT_TRF = 3'b110,
INIT_MRS = 3'b111,
INIT_TMRD = 3'b101,
INIT_END = 3'b100;
// 10ns 完成200us
parameter WAIT_MAX= 15'd20000;
parameter TRP = 3'd2,
TRF = 3'd7,
TMRD = 3'd3;
// 2个周期 7个周期 3个周期
//四个命令 空命令 预充电 自动刷新 模式寄存器配置
parameter NOP = 4'b0111,
P_CHARGE =4'b0010,
AUTO_REF =4'b0001,
M_REG_SET = 4'b0000;
//对状态变量赋值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
init_state <= INIT_IDLE;
//状态跳转
else
case(init_state)
INIT_IDLE:
if(wait_end == 1'b1)
init_state <= INIT_PRE;
else
init_state <= init_state;
INIT_PRE :
init_state <= INIT_TRP;
//使用cnt_clk计数,trp_end 拉高
INIT_TRP :
if(trp_end== 1'b1)
init_state <= INIT_AR;
else
init_state <= init_state;
INIT_AR :
init_state <= INIT_TRF;
//至少两次自动刷新,结束信号判断和次数判断 8次自动刷新
INIT_TRF :
if(trfc_end == 1'b1)
if(cnt_aref == 4'd8)
init_state <= INIT_MRS;
else
init_state <= INIT_AR;
else
init_state <= init_state;
INIT_MRS :
init_state <= INIT_TMRD;
INIT_TMRD :
if(tmrd_end == 1'b1)
init_state <= INIT_END;
else
init_state <= init_state;
INIT_END:
init_state <=INIT_END;
default:init_state <= INIT_IDLE;
endcase
// 200us计数器赋值 上电后的等待时间
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_200us <= 15'd0;
else if(cnt_200us == WAIT_MAX)
cnt_200us <= WAIT_MAX;
else
cnt_200us <= cnt_200us + 1'b1;
// 使得等待结束信号。只拉高一个时钟周期
assign wait_end = (cnt_200us == WAIT_MAX -1'b1) ?1'b1:1'b0;
//cnt_clk 计数器的赋值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_clk <= 3'd0;
else if(cnt_clk_rst == 1'b1)
cnt_clk <= 3'd0;
else
cnt_clk<= cnt_clk +1'b1;
//复位信号的赋值 组合逻辑赋值 case 语句状态跳转条件
always@(*)
begin
case(init_state)
INIT_IDLE: cnt_clk_rst <= 1'b1;
INIT_TRP: cnt_clk_rst <= (trp_end == 1'b1) ? 1'b1:1'b0;
INIT_TRF :cnt_clk_rst <= (trfc_end ==1'b1)?1'b1:1'b0;
INIT_TMRD:cnt_clk_rst <= (tmrd_end==1'b1)?1'b1:1'b0;
INIT_END: cnt_clk_rst <=1'b1;
default: cnt_clk_rst <= 1'b0;
endcase
end
// tmrd_end trfc_end trp_end 进行赋值 组合逻辑,定义参数进行判断
assign trp_end = ((init_state <= INIT_TRP)&&(cnt_clk == TRP))? 1'b1:1'b0;
assign trfc_end = ((init_state <= INIT_TRF)&&(cnt_clk == TRF))? 1'b1:1'b0;
assign tmrd_end =((init_state <= INIT_TMRD)&& (cnt_clk == TMRD))? 1'b1:1'b0;
//自动刷新次数计数器 cnt_aref ,当状态机处于初始状态进行清零
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_aref <= 4'd0;
else if(init_state == INIT_IDLE)
cnt_aref <= 4'd0;
else if(init_state == INIT_AR)
cnt_aref <= cnt_aref + 1'b1;
else
cnt_aref <= cnt_aref;
// 以上所有中间变量完成。以下为四路输出信号
//命令和地址是同时起作用,故同时进行赋值 这几个命令定义为参数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
init_cmd <= NOP;
init_ba <= 2'b11;
init_addr <= 13'h1fff;
end
//结合状态机进行赋值
else
case(init_state)
INIT_IDLE,INIT_TRP,INIT_TRP,INIT_TMRD,INIT_END:
begin
init_cmd <= NOP;
init_ba <= 2'b11;
init_addr <= 13'h1fff;
end
INIT_PRE:
begin
init_cmd <= P_CHARGE;
init_ba <= 2'b11;
init_addr <= 13'h1fff;
end
INIT_AR:
begin
init_cmd <= AUTO_REF;
init_ba <= 2'b11;
init_addr <= 13'h1fff;
end
//模式寄存器下地址总线和bank地址都要改一下
// 地址总线高三位为0 ,第三位为突发长度选择
INIT_MRS:
begin
init_cmd <= M_REG_SET;
init_ba <= 2'b00;
init_addr <= {3'b0,1'b0,2'b00,3'b011,1'b0,3'b11};
end
default:
begin
init_cmd <= NOP;
init_ba <= 2'b11;
init_addr <= 13'h1fff;
end
endcase
// 结束信号 init_end 用组合逻辑赋值
assign init_end = (init_state == INIT_END)?1'b1:1'b0;
endmodule
时钟生成模块 clk_gen
clk50 串口用
clk100m sdram控制使用的信号
clk100m 有偏移 sdram芯片使用的信号
时钟信号生成: clk_100 是sdram工作时钟
clk_100_shift:经过相位偏移的时钟,传入直接传出了。
需要两个时钟信号
调用ip 核:
输入时钟频率选择 50MHZ:
使用复位信号同时使用锁定信号:
输出100Mhz,并且不使用相位偏移
输出频率100mhz,暂且使用30度的时钟偏移。
选择生成例化模板:clk_gen_inst.v
复制例化模板
.areset ( areset_sig ),
.inclk0 ( inclk0_sig ),
.c0 ( c0_sig ),
.c1 ( c1_sig ),
.c2 ( c2_sig ),
.locked ( locked_sig )
);
关于sdram复位信号:
assign rst_n = sys_rst_n & locked;
新生成的复位信号 rst_n
locked锁定信号。
锁定信号有效时候才可以输出有效时钟信号。
clk50 串口用
clk100m sdram控制使用的信号
clk100m 有偏移 sdram芯片使用的信号
仿真程序:
`imescale 1ns/1ns
module tb_sdtam_init();
wire clk_50m;
wire clk_100m;
wire clk_100m_shift;
wire locked;
wire rst_n;
wire [3:0] init_cmd;
wire [1:0] init_ba;
wire [12:0] init_addr;
wire init_end;
reg sys_clk;
reg sys_rst_n;
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#30
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk
assign rst_n = sys_rst_n & locked;
//pll锁相环 复位信号为高电平有效,
dram_init dram_init_inst(
.sys_clk(sys_clk),
.sys_rst_n(rst_n),
.init_cmd(init_cmd),
.init_ba(init_ba),
.init_addr(init_addr),
.init_end(init_end)
);
//仿真需要用到sdram仿真模型。 这个模块我没有 可以自己在网上找找
clk_gen clk_gen_inst (
.areset ( sys_clk ),
.inclk0 ( ~sys_rst_n ),
.c0 ( clk_50m ),
.c1 ( clk_100m ),
.c2 ( clk_100m_shift ),
.locked ( locked )
);
endmodule
回到工程,添加仿真代码:
仿真模型一并添加。
全编译 检查问题
进行仿真设置:添加ip核等
然后开始功能仿真
发现问题:
赋值条件不充分: 改为:
assign trfc_end = ((init_state <= INIT_TRF)&&(cnt_clk == TRF))? 1'b1:1'b0;
assign tmrd_end =((init_state <= INIT_TMRD)&& (cnt_clk == TMRD))? 1'b1:1'b0;