本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子、信息、通信类专业学生、初入职场小白及打算进阶提升的职业开发者都可以有系统性学习的机会。
系统性的掌握技术开发以及相关要求,对个人就业以及职业发展都有着潜在的帮助,希望对大家有所帮助。后续会陆续更新 Xilinx 的 Vivado、ISE 及相关操作软件的开发的相关内容,学习FPGA设计方法及设计思想的同时,实操结合各类操作软件,会让你在技术学习道路上无比的顺畅,告别技术学习小BUG卡破脑壳,告别目前忽悠性的培训诱导,真正的去学习去实战应用,这种快乐试试你就会懂的。话不多说,上货。
高级设计:SDR SDRAM 驱动设计
作者:郝旭帅 校对:陆辉
本篇实现基于叁芯智能科技的SANXIN -B01 FPGA开发板,以下为配套的教程,如有入手开发板,可以登录官方淘宝店购买,还有配套的学习视频。
FPGA入门进阶板卡推荐 | SANXIN B02 FPGA开发板(Xilinx)
FPGA入门进阶板卡推荐 | SANXIN B01 FPGA开发板(Intel)
SANXIN-B01开发板verilog教程V3电子版
FPGA就业班,课程内容丰富,系统性学习FPGA,高薪就业,线上线下同步,7月15号开班!
本系列的技术文档、源工程代码可以登录叁芯智能科技官方技术论坛下载。
论坛网址:www.sxznfpga.com
随机访问存储器(RAM)分为静态RAM(SRAM)和动态RAM(DRAM)。由于动态存储器存储单元的结构非常简单,所以它能达到的集成度远高于静态存储器。但是动态存储器的存取速度不如静态存储器快。
RAM的动态存储单元是利用电容可以存储电荷的原理制成的。由于存储单元的机构能够做得很简单,所以在大容量、高集成度的RAM中得到了普遍的应用。但是由于电容的容量很小,而漏电流又不可能绝对等于零,所以电荷保存的时间有限。为了及时补充漏掉的电荷以避免存储的信号丢失,必须定时地给电容补充电荷,通常将这种操作称为刷新。
行列地址线被选中后,数据线(data_bit)直接和电容相连接。当写入时,数据线给电容充放电;读取时,电容将数据线拉高或者置低。
SDRAM 的全称即同步动态随机存储器(Synchronous Dynamic Random Access Memory);这里的同步是指其时钟频率与对应控制器的系统时钟频率相同,并且内部命令的发送与数据传输都是以该时钟为基准;动态是指存储阵列需要不断的刷新来保证数据不丢失。
SDR SDRAM中的SDR是指单数据速率,即每一根数据线上,每个时钟只传输一个bit的数据。SDR SDRAM的时钟频率可以达到100MHz以上,按照100MHz的速率计算,一片16位数据宽度的SDR SDRAM的读写数据带宽可以达到1.6Gbit/s。
SANXIN – B01的开发板上有一个容量为256Mbit(16M x 16bit)的SDR SDRAM(H57V2562GTR)。其内部存储时,分为了4个独立的区域(BANK),每个bank为4Mx16bit的存储空间;每个bank在存储时,按照二维的方式进行存储,利用行列来进行确定,有8192行(13bit地址线),有512列(9bit地址线),8192 x 512为4M的存储量。
在进行指定某个地址时,共需要2位bank地址,13位行地址,9位列地址,合计共24位地址。但是在SDR SDRAM的指定某个地址时,行地址和列地址不是同时给出,SDR SDRAM采用行列地址线复用,所以地址线合计为2(bank 地址)+13(行、列地址复用)。
SDR SDRAM需要时钟端和时钟使能端。SDR SDRAM所有的操作都依靠于此时钟;当时钟使能端无效时,SDR SDRAM自动忽略时钟上升沿。
SDR SDRAM拥有四个命令控制线,分别为CS、RAS、CAS、WE。组成的命令表如下:
在写入数据时,有时会出现不想对某8bit进行写入,就可以采用DQM进行控制。
SDR SDRAM的内部机构为:
由于SDR SDRAM为DRAM,内部的存储都是靠电容进行保存数据,电容的保持数据的时间为64ms,SDR SDRAM每次只能够刷新一行,为了不丢失任何数据,所以要保证64ms内,将所有的行都要刷新一遍。
SDR SDRAM支持读写的长度为1、2、4、8和一行(整页)。
具体的SDR SDRAM的介绍可以查看手册。下面只介绍几个相对重要的时序图。
在SDR SDRAM正常使用之前,需要进行初始化。初始化的时序图如下:
在PRECHARGE时,A10为高,表示选中所有的bank;A10为低,表示选中BA0、BA1所指定的bank。初始化中,A10置高。
在LOAD MOOE REGISTER中,采用地址线进行配置模式寄存器。说明如下:
在模式配置中,利用CL(CAS Latency)表示列选通潜伏期,利用BL(Burst Length)表示突发长度。
SDR SDRAM中有内部的刷新控制器和刷新的行计数器,外部控制器只需要保证在64ms之内进行8192次刷新即可。
在进行PRECHARGE时,A10要为高电平。
SDR SDRAM中,我们可以在任意位置进行写入。写入的时序图如下:
SDR SDRAM中,我们可以在任意位置进行读出。读出的时序图如下:
在各个时序中的时序参数如下:
设计一个突发长度为2,列选通潜伏期为2的SDR SDRAM的控制器。
该控制器共有四部分功能,初始化、刷新、写和读。四部分的执行控制采用一个模块来控制。
SDR SDRAM必须要进行初始化,初始化只用执行一次。然后启动一个计时器,等计时器达到后,进行刷新。在刷新的间隔中,根据读写的要求进行读写。
四个模块都会对SDR SDRAM的命令线和地址线进行控制,所以输出时,采用多路选择器对齐进行选择输出。
四个模块按照对应的时序图进行编写代码即可。
该控制器命名为sdr_drive。
pll_sdr(锁相环模块):产生驱动所需要的100MHz的时钟(0度相位)、SDR SDRAM所需要的100MHz的时钟(270度相位)、以及PLL锁定信号当作系统复位使用。
timer(刷新计时器):当启动计时器后,开始计时,当计时到规定时间后,输出刷新请求,计数器直接清零计数计数。当控制器响应后,输出清除信号后,刷新请求拉低。
refresh(刷新模块)、init(初始化模块)、sdr_write(写模块)、sdr_read(读模块):当启动模块后,按照规定的时序进行输出即可,然后输出完成信号。
sdr_ctrl(控制模块):控制各个模块协调工作。
mux4_1(四选一多路选择器模块):选择对应的bus总线作为输出。
*_bus的组成为:高四位为sdr_cs_n、sdr_ras_n、sdr_cas_n、sdr_we_n。然后是bank的两位,后续为13位的sdr_addr。
将驱动中用到各种参数定义在该文件中。
`define SDR_ADDR_WIDTH 13
`define SDR_COL_ADDR_WIDTH 9
`define SDR_REFRESH_TIME 64_000_000
`define ADDR_WIDTH 2 + `SDR_ADDR_WIDTH + `SDR_COL_ADDR_WIDTH
`define BUS_WIDTH 4 + 2 + `SDR_ADDR_WIDTH
`define CMD_INH 4'b1000
`define NOP 4'b0111
`define ACT 4'b0011
`define RD 4'b0101
`define WR 4'b0100
`define BT 4'b0110
`define PREC 4'b0010
`define REFR 4'b0001
`define LMR 4'b0000
`define PU_DELAY 20_000
`define Trp 3
`define Trfc 7
`define Tmrd 3
`define Trcd 3
`define Twr 3
`define Tcl 2
`define CODE 13'b000_0_00_010_0_001
`define REFRESH_TIME (`SDR_REFRESH_TIME/(2**`SDR_ADDR_WIDTH))/10
该模块为IP core,输出0相位的100MHz(系统时钟)和270相位的100MHz(SDR的时钟)。系统设计中,信号在上升沿输出;对于外部器件(相位调整为270),能够较好的满足建立和保持时间。
该模块负责将SDR SDRAM进行初始化。上电延迟(PU_DELAY)设置为200us;预充电时间(Trp)设置为3个时钟周期(30ns);自刷新时间(Trfc)设置为7个时钟周期(70ns);模式寄存器应用时间(Tmrd)设置为3个时钟周期(30ns);突发长度为2;列选通潜伏期为3。
按照对应的初始化的时序图,做出如下设计。
本模块采用状态机的方式设计实现。
设计代码为:
`include "../rtl/sdr_drive_head.v"
module init (
input wire clk,
input wire rst_n,
input wire init_en,
output reg init_done,
output wire [`BUS_WIDTH - 1 : 0] init_bus
);
localparam IDLE = 7'b000_0001;
localparam PUD = 7'b000_0010;
localparam PRECHARGE = 7'b000_0100;
localparam AUTOREFR1 = 7'b000_1000;
localparam AUTOREFR2 = 7'b001_0000;
localparam LMR_STATE = 7'b010_0000;
localparam INITDONE = 7'b100_0000;
reg [6:0] c_state;
reg [6:0] n_state;
wire [1:0] sdr_bank;
reg [3:0] sdr_cmd;
reg [`SDR_ADDR_WIDTH - 1 : 0] sdr_addr;
reg [14:0] cnt;
assign sdr_bank = 2'b00;
assign init_bus = {sdr_cmd,sdr_bank,sdr_addr};
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
c_state <= IDLE;
else
c_state <= n_state;
end
always @ * begin
case (c_state)
IDLE : begin
if (init_en == 1'b1)
n_state = PUD;
else
n_state = IDLE;
end
PUD : begin
if (cnt == `PU_DELAY - 1'b1)
n_state = PRECHARGE;
else
n_state = PUD;
end
PRECHARGE : begin
if (cnt == `Trp - 1'b1)
n_state = AUTOREFR1;
else
n_state = PRECHARGE;
end
AUTOREFR1 : begin
if (cnt == `Trfc - 1'b1)
n_state = AUTOREFR2;
else
n_state = AUTOREFR1;
end
AUTOREFR2 : begin
if (cnt == `Trfc - 1'b1)
n_state = LMR_STATE;
else
n_state = AUTOREFR2;
end
LMR_STATE : begin
if (cnt == `Tmrd - 1'b1)
n_state = INITDONE;
else
n_state = LMR_STATE;
end
INITDONE : begin
n_state = INITDONE;
end
default : n_state = IDLE;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
sdr_cmd <= `NOP;
else
case (c_state)
IDLE : sdr_cmd <= `NOP;
PUD : begin
if (cnt == `PU_DELAY - 1'b1)
sdr_cmd <= `PREC;
else
sdr_cmd <= `NOP;
end
PRECHARGE : begin
if (cnt == `Trp - 1'b1)
sdr_cmd <= `REFR;
else
sdr_cmd <= `NOP;
end
AUTOREFR1 : begin
if (cnt == `Trfc - 1'b1)
sdr_cmd <= `REFR;
else
sdr_cmd <= `NOP;
end
AUTOREFR2 : begin
if (cnt == `Trfc - 1'b1)
sdr_cmd <= `LMR;
else
sdr_cmd <= `NOP;
end
LMR_STATE : sdr_cmd <= `NOP;
INITDONE : sdr_cmd <= `NOP;
default : sdr_cmd <= `NOP;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
cnt <= 15'd0;
else
case (c_state)
IDLE : cnt <= 16'd0;
PUD : begin
if (cnt < `PU_DELAY - 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= 16'd0;
end
PRECHARGE : begin
if (cnt < `Trp - 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= 16'd0;
end
AUTOREFR1 : begin
if (cnt < `Trfc - 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= 16'd0;
end
AUTOREFR2 : begin
if (cnt < `Trfc - 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= 16'd0;
end
LMR_STATE : begin
if (cnt < `Tmrd - 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= 16'd0;
end
INITDONE : cnt <= 16'd0;
default : cnt <= 16'd0;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
init_done <= 1'b0;
else
if (c_state == LMR_STATE && cnt == `Tmrd - 1'b1)
init_done <= 1'b1;
else
init_done <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
sdr_addr <= 0;
else
if (c_state == PUD && cnt == `PU_DELAY - 1'b1)
sdr_addr[10] <= 1'b1;
else
if (c_state == AUTOREFR2 && cnt == `Trfc - 1'b1)
sdr_addr <= `CODE;
else
sdr_addr <= 0;
end
endmodule
SDR SDRAM内部构造为DRAM,需要不间断的刷新,要求64ms刷新一遍。每次刷新为一行,开发板上的SDR SDRAM共有8192行,平均需要7812.5ns刷新一次,我们选择7810刷新一次。
到达规定的刷新时间时,控制器有可能正在进行其他的操作。在设计时,达到时间后,发出刷新请求,当外部执行刷新后,将次请求清除。发出刷新请求的同时,计数器重新归零计数。
`include "../rtl/sdr_drive_head.v"
module timer (
input wire clk,
input wire rst_n,
input wire time_en,
input wire req_clr,
output reg refresh_req
);
reg [9:0] cnt;
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
cnt <= 10'd0;
else
if (time_en == 1'b1 && cnt < `REFRESH_TIME - 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= 10'd0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
refresh_req <= 1'b0;
else
if (cnt == `REFRESH_TIME - 1'b1)
refresh_req <= 1'b1;
else
if (req_clr == 1'b1)
refresh_req <= 1'b0;
else
refresh_req <= refresh_req;
end
endmodule
该模块负责刷新,按照对应的时序图进行控制即可。
该模块利用状态机的方式实现。状态转移图如下:
设计代码为:
`include "../rtl/sdr_drive_head.v"
module refresh (
input wire clk,
input wire rst_n,
input wire refresh_en,
output reg refresh_done,
output wire [`BUS_WIDTH - 1 : 0] refresh_bus
);
localparam IDLE = 5'b0_0001;
localparam PRECHARGE = 5'b0_0010;
localparam AUTOREFR1 = 5'b0_0100;
localparam AUTOREFR2 = 5'b0_1000;
localparam REFRDONE = 5'b1_0000;
reg [4:0] c_state;
reg [4:0] n_state;
wire [1:0] sdr_bank;
reg [3:0] sdr_cmd;
reg [`SDR_ADDR_WIDTH - 1 : 0] sdr_addr;
reg [3:0] cnt;
assign sdr_bank = 2'b00;
assign refresh_bus = {sdr_cmd,sdr_bank,sdr_addr};
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
c_state <= IDLE;
else
c_state <= n_state;
end
always @ * begin
case (c_state)
IDLE : begin
if (refresh_en == 1'b1)
n_state = PRECHARGE;
else
n_state = IDLE;
end
PRECHARGE : begin
if (cnt == `Trp - 1'b1)
n_state = AUTOREFR1;
else
n_state = PRECHARGE;
end
AUTOREFR1 : begin
if (cnt == `Trfc - 1'b1)
n_state = AUTOREFR2;
else
n_state = AUTOREFR1;
end
AUTOREFR2 : begin
if (cnt == `Trfc - 1'b1)
n_state = REFRDONE;
else
n_state = AUTOREFR2;
end
REFRDONE : begin
n_state = IDLE;
end
default : n_state = IDLE;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
sdr_cmd <= `NOP;
else
case (c_state)
IDLE : begin
if (refresh_en == 1'b1)
sdr_cmd <= `PREC;
else
sdr_cmd <= `NOP;
end
PRECHARGE : begin
if (cnt == `Trp - 1'b1)
sdr_cmd <= `REFR;
else
sdr_cmd <= `NOP;
end
AUTOREFR1 : begin
if (cnt == `Trfc - 1'b1)
sdr_cmd <= `REFR;
else
sdr_cmd <= `NOP;
end
AUTOREFR2 : sdr_cmd <= `NOP;
REFRDONE : sdr_cmd <= `NOP;
default : sdr_cmd <= `NOP;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
cnt <= 4'd0;
else
case (c_state)
IDLE : cnt <= 4'd0;
PRECHARGE : begin
if (cnt < `Trp - 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= 4'd0;
end
AUTOREFR1 : begin
if (cnt < `Trfc - 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= 4'd0;
end
AUTOREFR2 : begin
if (cnt < `Trfc - 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= 4'd0;
end
REFRDONE : cnt <= 4'd0;
default : cnt <= 4'd0;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
refresh_done <= 1'b0;
else
if (c_state == AUTOREFR2 && cnt == `Trfc - 1'b1)
refresh_done <= 1'b1;
else
refresh_done <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
sdr_addr <= 0;
else
if (c_state == IDLE && refresh_en == 1'b1)
sdr_addr[10] <= 1'b1;
else
sdr_addr <= 0;
end
endmodule
该模块负责将外部的数据写入到规定的地址中去。在SDR SDRAM中,每操作(读写)一次,都会引起该存储位的漏电,每次结束时,可以进行预充电。SDR SDRAM提供了自动预充电的机制,在读写命令时,sdr_addr[10]=1,即可自动预充电。在设计时,应该要为自动预充电预留出足够的时间。
根据对应的写入时序图,利用状态机完成此设计。
设计代码如下:
`include "../rtl/sdr_drive_head.v"
module sdr_write (
input wire clk,
input wire rst_n,
input wire write_en,
input wire [`ADDR_WIDTH - 1 : 0] wr_addr,
input wire [31:0] wr_data,
output reg [15:0] odq,
output wire [`BUS_WIDTH - 1 : 0] wr_bus,
output reg wr_done
);
localparam IDLE = 4'b0001;
localparam ACT_STATE = 4'b0010;
localparam WR1 = 4'b0100;
localparam WR2 = 4'b1000;
reg [3:0] c_state;
reg [3:0] n_state;
wire [1:0] sdr_bank;
reg [3:0] sdr_cmd;
reg [`SDR_ADDR_WIDTH - 1 : 0] sdr_addr;
reg [14:0] cnt;
assign sdr_bank = wr_addr[23:22];
assign wr_bus = {sdr_cmd,sdr_bank,sdr_addr};
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
c_state <= IDLE;
else
c_state <= n_state;
end
always @ * begin
case (c_state)
IDLE : begin
if (write_en == 1'b1)
n_state = ACT_STATE;
else
n_state = IDLE;
end
ACT_STATE : begin
if (cnt == `Trcd - 1'b1)
n_state = WR1;
else
n_state = ACT_STATE;
end
WR1 : n_state = WR2;
WR2 : begin
if (cnt == `Twr + `Trp - 1'b1)
n_state = IDLE;
else
n_state = WR2;
end
default : n_state = IDLE;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
sdr_cmd <= `NOP;
else
if (c_state == IDLE && write_en == 1'b1)
sdr_cmd <= `ACT;
else
if (c_state == ACT_STATE && cnt == `Trcd - 1'b1)
sdr_cmd <= `WR;
else
sdr_cmd <= `NOP;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
sdr_addr <= 0;
else
if (c_state == IDLE && write_en == 1'b1)
sdr_addr <= wr_addr[21:9];
else
if (c_state == ACT_STATE && cnt == `Trcd - 1'b1) begin
sdr_addr[10] <= 1'b1;
sdr_addr[8:0] <= wr_addr[8:0];
end
else
sdr_addr <= 0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
cnt <= 4'd0;
else
if (c_state == ACT_STATE && cnt < `Trcd - 1'b1)
cnt <= cnt + 1'b1;
else
if (c_state == WR2 && cnt < `Twr + `Trp - 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= 4'd0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
odq <= 16'd0;
else
if (c_state == ACT_STATE && cnt == `Trcd - 1'b1)
odq <= wr_data[15:0];
else
if (c_state == WR1)
odq <= wr_data[31:16];
else
odq <= 16'd0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
wr_done <= 1'b0;
else
if (c_state == WR2 && cnt < `Twr + `Trp - 1'b1)
wr_done <= 1'b1;
else
wr_done <= 1'b0;
end
endmodule
该模块负责从指定的地址中,将数据读出。
按照对应的读时序图即可实现功能,本模块采用状态机方式实现,状态转移图如下:
设计代码为:
module sdr_read (
input wire clk,
input wire rst_n,
input wire read_en,
input wire [`ADDR_WIDTH - 1 : 0] rd_addr,
input wire [15:0] sdr_dq,
output reg [31:0] rd_data,
output reg rd_done,
output wire [`BUS_WIDTH - 1 : 0] rd_bus
);
localparam IDLE = 5'b00001;
localparam ACT_STATE = 5'b00010;
localparam READ_STATE = 5'b00100;
localparam RD1 = 5'b01000;
localparam RD2 = 5'b10000;
reg [4:0] c_state;
reg [4:0] n_state;
wire [1:0] sdr_bank;
reg [3:0] sdr_cmd;
reg [`SDR_ADDR_WIDTH - 1 : 0] sdr_addr;
reg [3:0] cnt;
assign sdr_bank = rd_addr[23:22];
assign rd_bus = {sdr_cmd,sdr_bank,sdr_addr};
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
c_state <= IDLE;
else
c_state <= n_state;
end
always @ * begin
case (c_state)
IDLE : begin
if (read_en == 1'b1)
n_state = ACT_STATE;
else
n_state = IDLE;
end
ACT_STATE : begin
if (cnt == `Trcd - 1'b1)
n_state = READ_STATE;
else
n_state = ACT_STATE;
end
READ_STATE : begin
if (cnt == `Tcl)
n_state = RD1;
else
n_state = READ_STATE;
end
RD1 : n_state = RD2;
RD2 : begin
if (cnt == `Trp - 1'b1)
n_state = IDLE;
else
n_state = RD2;
end
default : n_state = IDLE;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
sdr_cmd <= `NOP;
else
if (c_state == IDLE && read_en == 1'b1)
sdr_cmd <= `ACT;
else
if (c_state == ACT_STATE && cnt == `Trcd - 1'b1)
sdr_cmd <= `RD;
else
sdr_cmd <= `NOP;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
sdr_addr <= 0;
else
if (c_state == IDLE && read_en == 1'b1)
sdr_addr <= rd_addr[21:9];
else
if (c_state == ACT_STATE && cnt == `Trcd - 1'b1) begin
sdr_addr[10] <= 1'b1;
sdr_addr[8:0] <= rd_addr[8:0];
end
else
sdr_addr <= 0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
cnt <= 4'd0;
else
case (c_state)
IDLE : cnt <= 4'd0;
ACT_STATE : begin
if (cnt < `Trcd - 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= 4'd0;
end
READ_STATE: begin
if (cnt < `Tcl)
cnt <= cnt + 1'b1;
else
cnt <= 4'd0;
end
RD1 : cnt <= 4'd0;
RD2 : begin
if (cnt < `Trp - 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= 4'd0;
end
default : cnt <= 4'd0;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rd_data <= 32'd0;
else
if (c_state == READ_STATE && cnt == `Tcl)
rd_data[15:0] <= sdr_dq;
else
if (c_state == RD1)
rd_data[31:16] <= sdr_dq;
else
rd_data <= rd_data;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rd_done <= 1'b0;
else
if (c_state == RD2 && cnt < `Trp - 1'b1)
rd_done <= 1'b1;
else
rd_done <= 1'b0;
end
endmodule
该模块负责选择出对应的bus,然后将对应位作为输出即可。
设计代码为:
module mux4_1 (
input wire [`BUS_WIDTH - 1 : 0] init_bus,
input wire [`BUS_WIDTH - 1 : 0] refresh_bus,
input wire [`BUS_WIDTH - 1 : 0] wr_bus,
input wire [`BUS_WIDTH - 1 : 0] rd_bus,
input wire [1:0] mux_sel,
output wire [1 : 0] sdr_bank,
output wire [`ADDR_WIDTH - 1 : 0] sdr_addr,
output wire sdr_cs_n,
output wire sdr_ras_n,
output wire sdr_cas_n,
output wire sdr_we_n
);
reg [`BUS_WIDTH - 1 : 0] sdr_bus;
assign sdr_cs_n = sdr_bus[18];
assign sdr_ras_n = sdr_bus[17];
assign sdr_cas_n = sdr_bus[16];
assign sdr_we_n = sdr_bus[15];
assign sdr_bank = sdr_bus[14:13];
assign sdr_addr = sdr_bus[12:0];
always @ * begin
case (mux_sel)
2'b00 : sdr_bus = init_bus;
2'b01 : sdr_bus = refresh_bus;
2'b10 : sdr_bus = wr_bus;
2'b11 : sdr_bus = rd_bus;
default : sdr_bus = init_bus;
endcase
end
endmodule
该模块负责调度整个控制器,利用状态机实现。
设计代码为:
`include "../rtl/sdr_drive_head.v"
module sdr_ctrl (
input wire clk,
input wire rst_n,
input wire wr_en,
input wire rd_en,
input wire [`ADDR_WIDTH - 1 : 0] addr,
input wire [31:0] wdata,
output reg [31:0] rdata,
output reg rd_valid,
output wire sdr_busy,
output reg [1:0] mux_sel,
output reg init_en,
input wire init_done,
output reg time_en,
input wire refresh_req,
output reg req_clr,
output reg refresh_en,
input wire refresh_done,
output reg out_en,
output reg write_en,
output reg [`ADDR_WIDTH - 1 : 0] wr_addr,
output reg [31:0] wr_data,
input wire wr_done,
output reg read_en,
output reg [`ADDR_WIDTH - 1 : 0] rd_addr,
input wire [31:0] rd_data,
input wire rd_done
);
localparam IDLE = 6'b000_001;
localparam INIT_STATE = 6'b000_010;
localparam REFRESH_STATE = 6'b000_100;
localparam NO_BUSY = 6'b001_000;
localparam WR_STATE = 6'b010_000;
localparam RD_STATE = 6'b100_000;
reg [5:0] c_state;
reg [5:0] n_state;
reg wren;
reg wren_clr;
reg rden;
reg rden_clr;
reg [`ADDR_WIDTH - 1 : 0] addrr;
reg [31:0] wdatar;
reg busy;
assign sdr_busy = busy | rd_en | wr_en;
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
wren <= 1'b0;
else
if (wr_en == 1'b1)
wren <= 1'b1;
else
if (wren_clr == 1'b1)
wren <= 1'b0;
else
wren <= wren;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rden <= 1'b0;
else
if (rd_en == 1'b1)
rden <= 1'b1;
else
if (rden_clr == 1'b1)
rden <= 1'b0;
else
rden <= rden;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
wdatar <= 32'd0;
else
if (wr_en == 1'b1)
wdatar <= wdata;
else
wdatar <= wdatar;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
addrr <= 0;
else
if (wr_en == 1'b1 || rd_en == 1'b1)
addrr <= addr;
else
addrr <= addrr;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
c_state <= IDLE;
else
c_state <= n_state;
end
always @ * begin
case (c_state)
IDLE : n_state = INIT_STATE;
INIT_STATE : begin
if (init_done == 1'b1)
n_state = REFRESH_STATE;
else
n_state = INIT_STATE;
end
REFRESH_STATE : begin
if (refresh_done == 1'b1)
n_state = NO_BUSY;
else
n_state = REFRESH_STATE;
end
NO_BUSY : begin
if (refresh_req == 1'b1)
n_state = REFRESH_STATE;
else
if (wren == 1'b1)
n_state = WR_STATE;
else
if (rden == 1'b1)
n_state = RD_STATE;
else
n_state = NO_BUSY;
end
WR_STATE : begin
if (wr_done == 1'b1)
n_state = NO_BUSY;
else
n_state = WR_STATE;
end
RD_STATE : begin
if (rd_done == 1'b1)
n_state = NO_BUSY;
else
n_state = RD_STATE;
end
default : n_state = IDLE;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
busy <= 1'b1;
else
if (c_state == NO_BUSY && wren == 1'b0 && rden == 1'b0 && refresh_req == 1'b0)
busy <= rd_en | wr_en;
else
busy <= 1'b1;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
init_en <= 1'b0;
else
if (c_state == IDLE)
init_en <= 1'b1;
else
init_en <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
time_en <= 1'b0;
else
if (c_state == INIT_STATE && init_done == 1'b1)
time_en <= 1'b1;
else
time_en <= time_en;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
refresh_en <= 1'b0;
else
if (c_state == INIT_STATE && init_done == 1'b1)
refresh_en <= 1'b1;
else
if (c_state == NO_BUSY && refresh_req == 1'b1)
refresh_en <= 1'b1;
else
refresh_en <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
req_clr <= 1'b0;
else
if (c_state == NO_BUSY && refresh_req == 1'b1)
req_clr <= 1'b1;
else
req_clr <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
write_en <= 1'b0;
else
if (c_state == NO_BUSY && refresh_req == 1'b0 && wren == 1'b1)
write_en <= 1'b1;
else
write_en <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
out_en <= 1'b0;
else
if (c_state == NO_BUSY && refresh_req == 1'b0 && wren == 1'b1)
out_en <= 1'b1;
else
if (c_state == WR_STATE && wr_done == 1'b1)
out_en <= 1'b0;
else
out_en <= out_en;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
wr_addr <= 0;
else
if (c_state == NO_BUSY && refresh_req == 1'b0 && wren == 1'b1)
wr_addr <= addrr;
else
wr_addr <= wr_addr;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
wr_data <= 0;
else
if (c_state == NO_BUSY && refresh_req == 1'b0 && wren == 1'b1)
wr_data <= wdatar;
else
wr_data <= wr_data;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
wren_clr <= 1'b0;
else
if (c_state == NO_BUSY && refresh_req == 1'b0 && wren == 1'b1)
wren_clr <= 1'b1;
else
wren_clr <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rden_clr <= 1'b0;
else
if (c_state == NO_BUSY && refresh_req == 1'b0 && wren == 1'b0 && rden == 1'b1)
rden_clr <= 1'b1;
else
rden_clr <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
read_en <= 1'b0;
else
if (c_state == NO_BUSY && refresh_req == 1'b0 && wren == 1'b0 && rden == 1'b1)
read_en <= 1'b1;
else
read_en <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rd_addr <= 0;
else
if (c_state == NO_BUSY && refresh_req == 1'b0 && wren == 1'b0 && rden == 1'b1)
rd_addr <= addrr;
else
rd_addr <= rd_addr;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rdata <= 32'd0;
else
if (c_state == RD_STATE && rd_done == 1'b1)
rdata <= rd_data;
else
rdata <= rdata;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rd_valid <= 1'b0;
else
if (c_state == RD_STATE && rd_done == 1'b1)
rd_valid <= 1'b1;
else
rd_valid <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
mux_sel <= 2'b00;
else
case (c_state)
IDLE : mux_sel <= 2'b00;
INIT_STATE : begin
if (init_done == 1'b1)
mux_sel <= 2'b01;
else
mux_sel <= mux_sel;
end
REFRESH_STATE: mux_sel <= mux_sel;
NO_BUSY : begin
if (refresh_req == 1'b1)
mux_sel <= 2'b01;
else
if (wren == 1'b1)
mux_sel <= 2'b10;
else
if (rden == 1'b1)
mux_sel <= 2'b11;
else
mux_sel <= mux_sel;
end
RD_STATE : mux_sel <= mux_sel;
WR_STATE : mux_sel <= mux_sel;
default : mux_sel <= 2'b00;
endcase
end
endmodule
为了防止在进行刷新的起始部分丢失读写命令,所以在设计时,加入了缓存结构,只要有读写命令时,都会进行保存。在读写执行时,才会清除此命令。
为了能够仿真此设计,需要用到SDR SDRAM的仿真模型。仿真模型在msim的sdr_sim_module中,将其修改为行线为13bit,列为9bit,每个bank有4194304个存储空间。
在仿真时,在第二个bank,第五行,第10列,写入一个随机值。然后读取出来。
仿真代码为:
`timescale 1ns/1ps
module sdr_drive_tb;
reg clk;
reg rst_n;
wire sys_clk;
wire sys_rst_n;
wire sdr_busy;
reg wr_en;
reg rd_en;
reg [23:0] addr;
reg [31:0] wdata;
wire [31:0] rdata;
wire rd_valid;
wire sdr_clk;
wire sdr_cke;
wire sdr_cs_n;
wire sdr_ras_n;
wire sdr_cas_n;
wire sdr_we_n;
wire [15:0] sdr_dq;
wire [1:0] sdr_bank;
wire [1:0] sdr_dqm;
wire [12:0] sdr_addr;
sdr_drive sdr_drive_inst(
.clk (clk),
.rst_n (rst_n),
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
// local
.sdr_busy (sdr_busy),
.wr_en (wr_en),
.rd_en (rd_en),
.addr (addr),
.wdata (wdata),
.rdata (rdata),
.rd_valid (rd_valid),
// sdr
.sdr_clk (sdr_clk),
.sdr_cke (sdr_cke),
.sdr_cs_n (sdr_cs_n),
.sdr_ras_n (sdr_ras_n),
.sdr_cas_n (sdr_cas_n),
.sdr_we_n (sdr_we_n),
.sdr_bank (sdr_bank),
.sdr_addr (sdr_addr),
.sdr_dqm (sdr_dqm),
.sdr_dq (sdr_dq)
);
mt48lc32m16a2 mt48lc32m16a2_inst(
.Dq (sdr_dq),
.Addr (sdr_addr),
.Ba (sdr_bank),
.Clk (sdr_clk),
.Cke (sdr_cke),
.Cs_n (sdr_cs_n),
.Ras_n (sdr_ras_n),
.Cas_n (sdr_cas_n),
.We_n (sdr_we_n),
.Dqm (sdr_dqm)
);
initial clk = 1'b0;
always # 10 clk = ~clk;
initial begin
rst_n = 1'b0;
wr_en = 1'b0;
rd_en = 1'b0;
addr = {2'b01, 13'd5,9'd10};
wdata = 32'd0;
# 201
rst_n = 1'b1;
@ (negedge sdr_busy);
@ (posedge sys_clk);
# 2;
wr_en = 1'b1;
wdata = $random;
@ (posedge sys_clk);
# 2;
wr_en = 1'b0;
# 2000;
@ (negedge sdr_busy);
@ (posedge sys_clk);
# 2;
rd_en = 1'b1;
@ (posedge sys_clk);
# 2;
rd_en = 1'b0;
# 2000;
$stop;
end
endmodule
这设置激励时,将tb文件和仿真模型文件同时加入添加文件中。
在modelsim的报告界面会显示出具体的配置信息以及读写信息。
从打印的报告中可以看出,在初始化时,列选通潜伏期为2,突发长度为2。在后续的读写时,在指定的位置,写入了13604,后续的一个位置为4629;在读出时,也正确的读出了数据。
报告打印出写入数据,即认为写入成功;报告打印出读出数据,只能证明控制器将数据读出,并不表示控制器能把数据接收到。
通过控制输出的rdata以及对应的rd_valid信号,确定读出成功。在rdata中显示为16进制,16进制的1215为十进制的4629;16进制的3524的为十进制的13604。证明读数据接收正确。
编写控制器的上游模块(sdr_drive_test_crtl),控制写入和读出。在固定的地址中addr = {2'b01, 13'd128, 9'd20},写入一个固定的数字wdata = 32'h5a5aa5a5,然后读出,进行验证。
读者在进行验证时,可以采样其他的地址或者数据进行验证,且可以进行多次尝试,保证设计正确。
该模块采用状态机设计实现。
设计代码为:
`include "../rtl/sdr_drive_head.v"
module sdr_drive_test_ctrl (
input wire clk,
input wire rst_n,
input wire sdr_busy,
output reg wr_en,
output reg rd_en,
output wire [31:0] wdata,
input wire rd_valid,
input wire [31:0] rdata,
output wire [`ADDR_WIDTH - 1 : 0] addr
);
localparam IDLE = 4'b0001;
localparam WR_STATE = 4'b0010;
localparam RD_STATE = 4'b0100;
localparam TEST_DONE = 4'b1000;
reg [3:0] c_state;
reg [3:0] n_state;
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
c_state <= IDLE;
else
c_state <= n_state;
end
always @ * begin
case (c_state)
IDLE : begin
if (sdr_busy == 1'b0)
n_state = WR_STATE;
else
n_state = IDLE;
end
WR_STATE : begin
if (sdr_busy == 1'b0)
n_state = RD_STATE;
else
n_state = WR_STATE;
end
RD_STATE : begin
if (rd_valid == 1'b1 && rdata == 32'h5a5aa5a5)
n_state = TEST_DONE;
else
n_state = RD_STATE;
end
TEST_DONE : n_state = TEST_DONE;
default : n_state = IDLE;
endcase
end
assign wdata = 32'h5a5aa5a5;
assign addr = {2'b01, 13'd128, 9'd20};
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
wr_en <= 1'b0;
else
if (c_state == IDLE && sdr_busy == 1'b0)
wr_en <= 1'b1;
else
wr_en <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rd_en <= 1'b0;
else
if (c_state == WR_STATE && sdr_busy == 1'b0)
rd_en <= 1'b1;
else
rd_en <= 1'b0;
end
endmodule
编写测试顶层,模块命名为sdr_drive_test,并且设置为顶层。
此模块负责例化sdr_drive和sdr_drive_test_ctrl,完成连接功能,以此测试。
代码为:
`include "../rtl/sdr_drive_head.v"
module sdr_drive_test (
input wire clk,
input wire rst_n,
// sdr
output wire sdr_clk,
output wire sdr_cke,
output wire sdr_cs_n,
output wire sdr_ras_n,
output wire sdr_cas_n,
output wire sdr_we_n,
output wire [1:0] sdr_bank,
output wire [`SDR_ADDR_WIDTH - 1 : 0] sdr_addr,
output wire [1:0] sdr_dqm,
inout wire [15:0] sdr_dq
);
wire sys_clk;
wire sys_rst_n;
// local
wire sdr_busy;
wire wr_en;
wire rd_en;
wire [`ADDR_WIDTH - 1 : 0] addr;
wire [31:0] wdata;
wire [31:0] rdata;
wire rd_valid;
sdr_drive_test_ctrl sdr_drive_test_ctrl_inst(
.clk (sys_clk),
.rst_n (sys_rst_n),
.sdr_busy (sdr_busy),
.wr_en (wr_en),
.rd_en (rd_en),
.wdata (wdata),
.rd_valid (rd_valid),
.rdata (rdata),
.addr (addr)
);
sdr_drive sdr_drive_inst(
.clk (clk),
.rst_n (rst_n),
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
// local
.sdr_busy (sdr_busy),
.wr_en (wr_en),
.rd_en (rd_en),
.addr (addr),
.wdata (wdata),
.rdata (rdata),
.rd_valid (rd_valid),
// sdr
.sdr_clk (sdr_clk),
.sdr_cke (sdr_cke),
.sdr_cs_n (sdr_cs_n),
.sdr_ras_n (sdr_ras_n),
.sdr_cas_n (sdr_cas_n),
.sdr_we_n (sdr_we_n),
.sdr_bank (sdr_bank),
.sdr_addr (sdr_addr),
.sdr_dqm (sdr_dqm),
.sdr_dq (sdr_dq)
);
endmodule
经过综合分析后,进行分配管脚。在分配管脚后,需要将双功能管脚中的NCEO设置为普通用户IO。如果不设置,将会出现如下错误:
右击器件名称,选择DEVICE。
选择device and pin option。
选择dual – purpose pins。
将nceo设置为 use as regular IO。
点击OK,进行编译即可。
连接上开发板,启动逻辑分析仪。
将采样时钟选择为,sys_clk(PLL的c0)。采样深度选择为1K。
添加观测信号如下,将wr_en的上升沿设置为触发条件。
经过保存,重新形成配置文件后,进行下板测试。
下板后,按下复位。等待波形触发。
通过逻辑分析仪,就可以看出可以正确的写入和读出数据。
读者也可以进行尝试一次性写入多个数据,然后进行读出,进行验证设计的正确性。
往期精选
FPGA技术江湖广发江湖帖
无广告纯净模式,给技术交流一片净土,从初学小白到行业精英业界大佬等,从军工领域到民用企业等,从通信、图像处理到人工智能等各个方向应有尽有,QQ微信双选,FPGA技术江湖打造最纯净最专业的技术交流学习平台。
FPGA技术江湖微信交流群
加群主微信,备注职业+方向+名字进群
FPGA技术江湖QQ交流群
备注地区+职业+方向+名字进群