【至简设计案例系列】基于FPGA的SDRAM控制器设计(三)读写
本文为明德扬原创及录用文章,转载请注明出处!
作者:小周
SDRAM控制器设计的主要功能是能对SDRAM进行读写操作,本工程实现了SDRAM的初始化、自动刷新、读、写等功能。
初始化功能和刷新功能在前一章的分享中已经进行了比较详细的描述,感兴趣的同学可以搜索学习下,这里不再赘述。今天我们主要讨论SDRAM读写的功能以及实现。
一、原理功能
1、读写突发模式
在初始化里的模式寄存器配置中我们将读写的突发长度(BL)设置为4,列选通潜伏期(CL)设为3,突发类型为顺序模式(每一次突发只给出起始地址即可)。对于预充电选择(Auto Precharge)自动预充电,即外部不需要发送预充电命令
2、读、写时序图
图表1写时序图
图表2读时序图
3、时序图解读
由时序图可知,读写模块采用线性序列机设计比较简单。在每次读写突发之前都需要发送ACTIVE命令,同时给出bank地址和row地址,2拍后发出读或写命令,同时将A10拉高并给出col地址。对于写操作没有潜伏期,由于DQ是双向端口,所以需要一个三态门控制DQ总线方向,当写操作时为输出方向,读操作时为输入方向。对于读操作,发出读命令后会有CL拍的潜伏期,本实验里CL=3,即发出读命令后3拍,数据才会出现在DQ总线上。
二、FPGA实现
1、模块架构
注:信号方向请看箭头
2、模块架构解读
要完整实现SDRAM控制器必须要完成初始化,刷新,读写这四部分功能。所以模块划分大体依照次为指导。由于SDRAM控制器工作时钟为100MHz,且要输出一个频率相同相位相差180°的时钟给SDRAM,所以要有一个锁相环模块。刷新需要计时刷新间隔,所以要加入一个刷新定时器模块,由于初始化,刷新,读,写等模块都要输出sdr_cke,sdr_cs_n,sdr_cas_n,sdr_ras_n,sdr_we_n,sdr_ba,sdr_a等信号到SDRAM,所以需要一个选择模块。将sdr_cke,sdr_cs_n,sdr_cas_n,sdr_ras_n,sdr_we_n,sdr_ba,sdr_a等信号组合成bus信号输入到选择模块。
新加入的信号说明,其他信号在前两篇中已经详细说明,读者可以参考。
信号 | 功能 | 说明 |
---|---|---|
顶层 | ||
local_data | 写突发数据输入 | 外部输入 |
local_addr | 地址总线 | 外部输入 |
local_q | 读突发数据输出 | 输出 |
local_wrreq | 写请求 | 外部输入 |
local_rdreq | 读请求 | 外部输入 |
local_reday | 可以进行读写操作信号 | 输出 |
local_rdata_vaild | 输出数据有效信号 | 输出 |
写模块 | ||
wr_en | 写使能 | 仲裁模块输出到写模块 |
wr_done | 写完成 | 写模块输出到仲裁模块 |
wr_bus | 写操作总线 | 写模块输出到选择模块 |
sdr_dq | SDRAM数据总线 | 双向端口,对于写模块是输出,读模块是输入 |
读模块 | ||
rd_bus | 读操作总线 | 读模块输出到选择模块 |
rd_en | 读使能 | 仲裁模块输出到读模块 |
rd_done | 读完成 | 读模块输出到仲裁模块 |
新加入了写模块和读模块。
写模块主要是完成一次写突发操作,将local_data信号写入到SDRAM中的指定地址local_addr中。local_addr主要由bank地址,行地址和列地址组合而成。在读和写模块代码中均有体现,可以参考。
读模块主要完成一次读突发操作,将SDRAM中指定的地址中的数据读出来,并赋值给local_q信号。
由于加入了写模块和读模块,所以仲裁模块也要做出相应的修改,当收到刷新请求时,即rt_flag信号后,在结束本次读突发或者写突发后,拉高刷新使能(ref_en)信号,当收到写请求且没有刷新请求时,拉高写使能(wr_en)信号,当收到读请求且没有刷新请求和写请求时,拉高读使能(rd_en)信号。
3、顶层模块参考代码
module sdram_top(
clk ,
sys_rst_n ,
//其他信号,举例dout
local_addr,
local_data,
local_q,
local_rdreq,
local_wrreq,
local_ready,
local_rdata_valid,
init_done,
sdr_cke,
sdr_cs_n,
sdr_ras_n,
sdr_cas_n,
sdr_we_n,
sdr_ba,
sdr_a,
sdr_dq,
sdr_dqm,
sdr_clk
);
input clk;
input sys_rst_n;
input [24:0] local_addr;
input [63:0] local_data;
output [63:0] local_q;
input local_rdreq;
input local_wrreq;
output local_ready;
output local_rdata_valid;
output init_done;
output sdr_cke;
output sdr_cs_n;
output sdr_ras_n;
output sdr_cas_n;
output sdr_we_n;
output [1:0] sdr_ba;
output [12:0] sdr_a;
inout [15:0] sdr_dq;
output [1:0] sdr_dqm;
output sdr_clk;
wire phy_clk;
wire rst_n;
wire rt_flag;
wire rt_clear;
wire rt_en;
wire ref_en;
wire ref_done;
wire wr_done;
wire wr_en;
wire rd_en;
wire rd_done;
wire [1:0] sel_sm;
wire [19:0] sdr_bus;
wire [19:0] init_bus;
wire [19:0] ref_bus;
wire [19:0] wr_bus;
wire [19:0] rd_bus;
assign {sdr_cke, sdr_cs_n, sdr_ras_n, sdr_cas_n, sdr_we_n, sdr_ba, sdr_a} = sdr_bus;
assign sdr_dqm = 2'b00;
sdram_init sdram_init_inst(
.clk (phy_clk) ,
.rst_n (rst_n) ,
//其他信号,举例dout
.init_done (init_done) ,
.init_bus (init_bus)
);
arbitrate arbitrate_inst(
.clk(phy_clk),
.rst_n(rst_n),
.rt_flag(rt_flag),
.rt_en(rt_en),
.rt_clear(rt_clear),
.ref_done(ref_done),
.ref_en(ref_en),
.init_done(init_done),
.sel_sm(sel_sm),
.local_rdata_valid(local_rdata_valid),
.local_ready(local_ready),
.local_wrreq(local_wrreq),
.local_rdreq(local_rdreq),
.wr_done(wr_done),
.wr_en(wr_en),
.rd_en(rd_en),
.rd_done(rd_done)
);
ref_timer ref_timer_inst(
.clk(phy_clk),
.rst_n(rst_n),
.rt_en(rt_en),
.rt_clear(rt_clear),
.rt_flag(rt_flag)
);
sdram_ref sdram_ref_inst(
.clk(phy_clk),
.rst_n(rst_n),
.ref_en(ref_en),
.ref_done(ref_done),
.ref_bus(ref_bus)
);
sdram_write sdram_write_inst(
.clk(phy_clk),
.rst_n(rst_n),
.wr_en(wr_en),
.wr_done(wr_done),
.wr_bus(wr_bus),
.sdr_dq(sdr_dq),
.local_data(local_data),
.local_addr(local_addr)
);
sdram_read sdram_read_inst(
.clk(phy_clk),
.rst_n(rst_n),
.rd_en(rd_en),
.rd_done(rd_done),
.rd_bus(rd_bus),
.sdr_dq(sdr_dq),
.local_q(local_q),
.local_addr(local_addr)
);
sdram_mux sdram_mux_inst(
.clk(phy_clk),
.rst_n(rst_n),
.init_bus(init_bus),
.ref_bus(ref_bus),
.wr_bus(wr_bus),
.rd_bus(rd_bus),
.sdr_bus(sdr_bus),
.sel_sm(sel_sm)
);
my_pll PLL(
.areset (~sys_rst_n) ,
.inclk0 (clk) ,
.c0 (phy_clk) ,
.c1 (sdr_clk) ,
.locked (rst_n)
);
endmodule
4、模块功能
PLL模块,初始化模块,刷新模块在前面两篇文章中已经讨论过,这里不再描述。
(1)写模块
主要完成写突发,采用线性序列机设计,当检测到wr_en为高电平时,计数器开始计时,并发出开ACT命令,并将row地址赋值给sdr_a。两拍之后,发出写命令,并将A10拉高,然后开始将数据赋值给sdr_dq信号。然后计数到8-1时将写完成信号wr_done拉高。
可以对照时序图阅读代码,其代码如下:
module sdram_write(clk, rst_n, wr_en, wr_done, wr_bus, sdr_dq, local_data, local_addr);
input clk;
input rst_n;
input wr_en;
output reg wr_done;
output [19:0] wr_bus;
inout [15:0] sdr_dq;
input [63:0] local_data;
input [24:0] local_addr;
parameter CNT_MAX = 8;
parameter NOP = 4'b0111;
parameter ACT = 4'b0011;
parameter WR = 4'b0100;
reg [3:0] cnt;
reg [3:0] sdr_cmd;
reg [1:0] sdr_ba;
reg [12:0] sdr_a;
reg [15:0] temp;
reg out_en;
wire [9:0] col;
wire [12:0] row;
wire [1:0] ba;
wire sdr_cke;
wire add_cnt;
wire end_cnt;
assign {ba, row, col} = local_addr;
assign sdr_dq = out_en ? temp : 16'dz;
assign sdr_cke = 1'b1;
assign wr_bus = {sdr_cke, sdr_cmd, sdr_ba, sdr_a};
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
assign add_cnt = wr_en;
assign end_cnt = add_cnt && cnt==CNT_MAX - 1 ;
always @ (posedge clk or negedge rst_n)begin
if(!rst_n)begin
sdr_cmd <= NOP;
end
else if(add_cnt && cnt == 1 - 1)begin
sdr_cmd <= ACT;
end
else if(add_cnt && cnt == 3 - 1)begin
sdr_cmd <= WR;
end
else begin
sdr_cmd <= NOP;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sdr_ba <= 2'd0;
end
else begin
sdr_ba <= ba;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sdr_a <= 13'd0;
end
else if(add_cnt && cnt == 1 - 1)begin
sdr_a <= row;
end
else if(add_cnt && cnt == 3 - 1)begin
sdr_a <= {2'd0, 1'b1, col};
end
else begin
sdr_a <= 13'd0;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
temp <= 16'd0;
end
else if(add_cnt && cnt == 4 - 1)begin
temp <= local_data[15:0];
end
else if(add_cnt && cnt == 5 - 1)begin
temp <= local_data[31:16];
end
else if(add_cnt && cnt == 6 - 1)begin
temp <= local_data[47:32];
end
else if(add_cnt && cnt == 7 - 1)begin
temp <= local_data[63:48];
end
else begin
temp <= 16'd0;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
out_en <= 1'b0;
end
else if(add_cnt && cnt == 4 - 1)begin
out_en <= 1'b1;
end
else if(add_cnt && cnt == 8 - 1)begin
out_en <= 1'b0;
end
else begin
out_en <= out_en;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wr_done <= 1'b0;
end
else if(add_cnt && cnt == 1 - 1)begin
wr_done <= 1'b0;
end
else if(add_cnt && cnt == 8 - 1)begin
wr_done <= 1'b1;
end
else begin
wr_done <= wr_done;
end
end
endmodule
(2)读模块
读模块主要完成读突发,对于读模块来说,sdr_dq信号是输入信号,且不是同一时钟域信号,所以要加两级同步寄存器。当检测到读使能rd_en为高时,计数器开始计数,并发出ACT命令,同时给出row地址,两拍之后发出读命令,并将A10拉高,由于加入了两级同步寄存器且读潜伏期为3,所以5拍之后才可以采集数据,即计数到9-1时采集数据,4拍之后数据采集完成,下一拍将读完成信号rd_done拉高。
可以对照时序图阅读代码,代码如下
module sdram_read(clk, rst_n, rd_en, rd_done, rd_bus, sdr_dq, local_q, local_addr);
input clk;
input rst_n;
input rd_en;
output reg rd_done;
output [19:0] rd_bus;
input [15:0] sdr_dq;
output reg [63:0] local_q;
input [24:0] local_addr;
parameter CNT_MAX = 14;
parameter NOP = 4'b0111;
parameter ACT = 4'b0011;
parameter RD = 4'b0101;
reg [3:0] cnt;
reg [3:0] sdr_cmd;
reg [1:0] sdr_ba;
reg [12:0] sdr_a;
reg [15:0] temp0, temp1;
wire [9:0] col;
wire [12:0] row;
wire [1:0] ba;
wire sdr_cke;
wire add_cnt;
wire end_cnt;
assign {ba, row, col} = local_addr;
assign sdr_cke = 1'b1;
assign rd_bus = {sdr_cke, sdr_cmd, sdr_ba, sdr_a};
always @(posedge clk)begin
temp0 <= sdr_dq;
temp1 <= temp0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
assign add_cnt = rd_en;
assign end_cnt = add_cnt && cnt==CNT_MAX - 1 ;
always @ (posedge clk or negedge rst_n)begin
if(!rst_n)begin
sdr_cmd <= NOP;
end
else if(add_cnt && cnt == 1 - 1)begin
sdr_cmd <= ACT;
end
else if(add_cnt && cnt == 3 - 1)begin
sdr_cmd <= RD;
end
else begin
sdr_cmd <= NOP;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sdr_ba <= 2'd0;
end
else begin
sdr_ba <= ba;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sdr_a <= 13'd0;
end
else if(add_cnt && cnt == 1 - 1)begin
sdr_a <= row;
end
else if(add_cnt && cnt == 3 - 1)begin
sdr_a <= {2'd0, 1'b1, col};
end
else begin
sdr_a <= 13'd0;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
local_q <= 64'd0;
end
else if(add_cnt && cnt == 9 - 1)begin
local_q[15:0] <= temp1;
end
else if(add_cnt && cnt == 10 - 1)begin
local_q[31:16] <= temp1;
end
else if(add_cnt && cnt == 11 - 1)begin
local_q[47:32] <= temp1;
end
else if(add_cnt && cnt == 12 - 1)begin
local_q[63:48] <= temp1;
end
else begin
local_q <= local_q;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rd_done <= 1'b0;
end
else if(add_cnt && cnt == 1 - 1)begin
rd_done <= 1'b0;
end
else if(add_cnt && cnt == 13 - 1)begin
rd_done <= 1'b1;
end
else begin
rd_done <= rd_done;
end
end
endmodule
(3)仲裁模块
由于加入了写模块和读模块,所以仲裁模块也要做出相应的修改,当收到刷新请求时,即rt_flag信号后,在结束本次读突发或者写突发后,拉高刷新使能(ref_en)信号,当收到写请求且没有刷新请求时,拉高写使能(wr_en)信号,当收到读请求且没有刷新请求和写请求时,拉高读使能(rd_en)信号。
主要代码如下:
module arbitrate(clk, rst_n, rt_flag, rt_en, rt_clear, ref_done,ref_en, init_done, sel_sm, local_rdata_valid,
local_ready,local_wrreq, local_rdreq, wr_done, wr_en, rd_en, rd_done);
input clk, rst_n;
input rt_flag;
output reg rt_en,rt_clear;
input ref_done;
output reg ref_en;
input init_done;
output reg [1:0]sel_sm;
output reglocal_rdata_valid;
output reglocal_ready;
input local_wrreq,local_rdreq;
input wr_done;
output reg wr_en;
output reg rd_en;
input rd_done;
localparam SM_INIT =2'd0;
localparam SM_REF = 2'd1;
localparam SM_WR = 2'd2;
localparam SM_RD = 2'd3;
always @(posedge clkor negedge rst_n)begin
if(!rst_n)begin
rt_en<= 0;
end
elseif(init_done)begin
rt_en<= 1;
end
else begin
rt_en<= rt_en;
end
end
always @(posedge clkor negedge rst_n)begin
if(!rst_n)begin
rt_clear<= 0;
end
elseif(ref_en)begin
rt_clear<= 1;
end
elseif(ref_done)begin
rt_clear<= 0;
end
else begin
rt_clear<= rt_clear;
end
end
always @(posedge clkor negedge rst_n)begin
if(!rst_n)begin
ref_en<= 0;
end
elseif(rt_flag)begin
ref_en<= 1;
end
elseif(ref_done)begin
ref_en<= 0;
end
else begin
ref_en<= ref_en;
end
end
always @(posedge clkor negedge rst_n)begin
if(!rst_n)begin
sel_sm<= SM_INIT;
end
elseif(!init_done)begin
sel_sm<= SM_INIT;
end
elseif(rt_flag)begin
sel_sm<= SM_REF;
end
elseif(local_wrreq && !rt_flag)begin
sel_sm<= SM_WR;
end
elseif(local_rdreq && !rt_flag)begin
sel_sm<= SM_RD;
end
else begin
sel_sm<= sel_sm;
end
end
always @(posedge clkor negedge rst_n)begin
if(!rst_n)begin
wr_en<= 0;
end
elseif(local_wrreq && !rt_flag)begin
wr_en<= 1;
end
elseif(wr_done)begin
wr_en<= 0;
end
else begin
wr_en<= wr_en;
end
end
always @(posedge clkor negedge rst_n)begin
if(!rst_n)begin
rd_en<= 0;
end
elseif(local_rdreq && !local_wrreq && !rt_flag)begin
rd_en<= 1;
end
elseif(rd_done)begin
rd_en<= 0;
end
else begin
rd_en<= rd_en;
end
end
always @(posedge clkor negedge rst_n)begin
if(!rst_n)begin
local_ready<= 1;
end
elseif(wr_en || rd_en)begin
local_ready<= 0;
end
else begin
local_ready<= 1;
end
end
always @(posedge clkor negedge rst_n)begin
if(!rst_n)begin
local_rdata_valid<= 0;
end
elseif(rd_done)begin
local_rdata_valid<= 1;
end
else begin
local_rdata_valid<= 0;
end
end
endmodule
(5)仿真验证
向SDRAM中写入64’h1122334455667788,然后读出来。由图可知SDRAM正确读出了数据。(波形显示的是十六进制,报告是十进制)
上一篇: URL对象连接url并获取值
下一篇: gulp --- 前端自动化构建工具