在模式配置中,利用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,即可自动预充电。在设计时,应该要为自动预充电预留出足够的时间。
根据对应的写入时序图,利用状态机完成此设计。
设计代码如下:
该模块负责从指定的地址中,将数据读出。
按照对应的读时序图即可实现功能,本模块采用状态机方式实现,状态转移图如下:
设计代码为:
该模块负责选择出对应的bus,然后将对应位作为输出即可。
设计代码为:
该模块负责调度整个控制器,利用状态机实现。
设计代码为:
为了防止在进行刷新的起始部分丢失读写命令,所以在设计时,加入了缓存结构,只要有读写命令时,都会进行保存。在读写执行时,才会清除此命令。
为了能够仿真此设计,需要用到SDR SDRAM的仿真模型。仿真模型在msim的sdr_sim_module中,将其修改为行线为13bit,列为9bit,每个bank有4194304个存储空间。
在仿真时,在第二个bank,第五行,第10列,写入一个随机值。然后读取出来。
仿真代码为:
这设置激励时,将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,然后读出,进行验证。
读者在进行验证时,可以采样其他的地址或者数据进行验证,且可以进行多次尝试,保证设计正确。
该模块采用状态机设计实现。
设计代码为:
编写测试顶层,模块命名为sdr_drive_test,并且设置为顶层。
此模块负责例化sdr_drive和sdr_drive_test_ctrl,完成连接功能,以此测试。
代码为:
经过综合分析后,进行分配管脚。在分配管脚后,需要将双功能管脚中的NCEO设置为普通用户IO。如果不设置,将会出现如下错误:
右击器件名称,选择DEVICE。
选择device and pin option。
选择dual – purpose pins。
将nceo设置为 use as regular IO。
点击OK,进行编译即可。
连接上开发板,启动逻辑分析仪。
将采样时钟选择为,sys_clk(PLL的c0)。采样深度选择为1K。
添加观测信号如下,将wr_en的上升沿设置为触发条件。
经过保存,重新形成配置文件后,进行下板测试。
下板后,按下复位。等待波形触发。