前言
本章主要讲述根据《DDR & DDR2 SDRAM Controller IP Cores User’s Guide 》数据手册,配合ddr2的demo仿真,学习DDR2的IP核时序控制。
器件:Lattice ECP3
环境:Win10 + Diamond3.13 + ModelSim SE-64 10.5
一、下载DDR2的UG数据手册
方法1:通过Diamond联网下载
方法2:前往lattice官网下载数据手册IPUG35
《DDR & DDR2 SDRAM Controller IP Cores User’s Guide 》
https://www.latticesemi.com/Search.aspx?&lcid=9&q=IPUG35&t=480
二、分析数据手册
接下来让我们开始研究《DDR & DDR2 SDRAM Controller IP Cores User’s Guide 》
2.1用户命令CMD
2.2DDR初始化
系统复位后,只初始化一次,在上电完成,时钟稳定后,
用户等待200us后产生一个init_start高电平信号给CORE,当CORE完成初始化时序,会发送一个时钟周期的init_done高电平信号,当init_done信号出现高电平,init_start信号拉低。
如果我们不对MR或者EMR寄存器进行配置,则CORE会按照默认配置进行初始化。
DEMO解析--DDR初始化代码 |
//********************************************************************/ // Init_start generation // - in simulation mode, wait for 128 clock cycles // - in synthesis mode, wait for +200us from reset_n deassertion //********************************************************************/ always@(posedge k_clk or negedge reset_n) begin if (reset_n == 1'b0) begin init_cnt <= 16'h0; init_srvcd <= 1'b0; init_start_hit <= 1'b0; init_start <= 1'b0; end else begin init_cnt <= init_cnt + 1; `ifdef RTL_SIM if (init_cnt[6] && !init_srvcd) begin // to save the simulation run time `else if (init_cnt[15] && init_cnt[13] && !init_srvcd) begin `endif init_srvcd <= 1'b1; init_start_hit <= 1'b1; end else init_start_hit <= 1'b0; if (init_start_hit) init_start <= 1'b1; if (init_done) init_start <= 1'b0; end end assign init_start = init_start_req && pll_lock && dll_lock; |
2.3DDR的自刷新模式
该模式一般在IP核设置为使能,不需要写代码,直接使用,了解即可。
内存控制器自带自刷新模式,一旦使能,ext_auto_ref被用户拉高,来强制CORE产生一连串的刷新指令,当CORE产生刷新指令后,ext_auto_ref_acklag拉高一个时钟周期,当ext_auto_ref_acklag信号出现高电平,ext_auto_ref信号拉低。
2.4DDR的命令和地址控制
一旦内存初始化完成,IP核就会等待用户命令来访问内存。用户需要向IP核提供命令和地址以及控制信号。命令和地址是按照以下描述的过程传递给IP核:
- 当IP核输出的cmd_rdy信号拉高一个时钟周期,代表其已准备好接收用户命令。
- 当IP核输出的cmd_rdy信号拉高时,如果用户逻辑也拉高了cmd_valid信号,IP核输就会将cmd输入作为有效的用户命令进行处理。
如果cmd_valid没有被拉高,cmd和addr输入将变为无效,IP核将忽略它们。
- 当cmd_rdy信号为低时,cmd、addr、cmd_valid将变为无效,IP核将会忽略它们。
- 当cmd_rdy信号再次拉高,则准备执行下一个命令。
DEMO解析--DDR的命令和地址控制代码 |
1.在初始化完成后,IP核发出cmd_rdy信号。 2.在初始化完成后,一直拉高cmd_valid信号。 3.在cmd_rdy拉低时,就应该准备好addr 、burst_count的信号。 4.每出现一次cmd_rdy,状态机就跳转一次。 PS:此处的CMD=6(LOAD_MR)在实际的工程中是不需要我们去写的,一旦配置好DDR2的IP核,就会自动产生,实际的工程中我们只需要去控制写命令和读命令,DEMO工程中并没有配置DDR2的IP核,而是调用该IP的一些源文件,与实际工程有所不同。 |
// ============================================================================== // state assignments & defines // ============================================================================== `define IDLE 0 `define CONF_MR 1 `define CONF_EMR 2 `define WRITE_CMD 3 `define READ_CMD 4
// ============================================================================== // Demo configuration generation // ============================================================================== assign BL_MODE = switch[0]; assign ODT_SEL = switch[2:1]; assign CMD_BRST = switch[5:3]; assign DATA_MODE = switch[6]; // 1: PRBS, 0: Sequential assign DEMO_MODE = switch[7]; // 1: Non-stop, 0: Single Wr/Rd assign MR_DATA = BL_MODE ? 16'h0643 : 16'h0642; // 1: BL8, 0: BL4 assign EMR_DATA = (ODT_SEL == 2'b11) ? 16'h2004 : // 75-ohm (ODT_SEL == 2'b10) ? 16'h2040 : // 150-ohm (ODT_SEL == 2'b01) ? 16'h2044 : // 50-ohm 16'h2000 ; // disabled assign data_mask = {`DSIZE/8{1'b0}}; `ifdef BRST_CNT_EN assign burst_count = (CMD_BRST == 3'b100) ? 5'd16 : (CMD_BRST == 3'b011) ? 5'd8 : (CMD_BRST == 3'b010) ? 5'd4 : (CMD_BRST == 3'b001) ? 5'd2 : (CMD_BRST == 3'b000) ? 5'd1 : 5'd0; // 32-command burst in all other cases `endif
//*******************************************************************************/ // Address and Command Generation //*******************************************************************************/ assign cmd_gone = cmd_rdy && cmd_valid;
// ============================================================================== // Command generation state machine // ============================================================================== always @(posedge k_clk or negedge reset_n) begin if (reset_n == 1'b0) begin cmdgen <= `IDLE; cmd <= 4'h6; cmd_valid <= 1'b0; addr <= {`ADDR_WIDTH{1'b0}}; end else begin case (cmdgen) `IDLE : begin // :0 if (init_done) begin cmdgen <= `CONF_MR; cmd_valid <= 1'b1; end else begin cmdgen <= `IDLE; end end `CONF_MR : begin // :1 cmd <= 4'h6; // LOAD_MR command for MR addr <= MR_DATA; if (cmd_gone) begin cmdgen <= `CONF_EMR; end else cmdgen <= `CONF_MR; end `CONF_EMR : begin // :2 addr <= EMR_DATA; // LOAD_MR command for EMR if (cmd_gone) begin cmdgen <= `WRITE_CMD; end else cmdgen <= `CONF_EMR; end `WRITE_CMD : begin // :3 cmd <= 4'h2; // WRITE command addr <= addr_wr; if (cmd_gone) begin cmdgen <= `READ_CMD; end else begin cmdgen <= `WRITE_CMD; end end `READ_CMD : begin // :4 cmd <= 4'h1; // READ command addr <= addr_rd; if (cmd_gone) begin if (DEMO_MODE) begin cmdgen <= `WRITE_CMD; end else begin cmdgen <= `IDLE; // Run only one Write-Read cycle cmd_valid <= 1'b0; end end else begin cmdgen <= `READ_CMD; end end endcase end end
|
2.5DDR的写数据控制
1.在IP核接收到写命令之后,当它准备好接收要写入内存的数据时,IP核会拉高data_rdy信号。
2.一旦data_rdy被拉高,在data_rdy信号拉高后的一到两个时钟周期内(由WrRqDDelay决定),写数据线上必须接收到有效数据。
实际的工程中,写数据延迟是通过IP核配置的。
DEMO解析--Write Data产生代码 |
1.当data_rdy为高时,就发送数据。(时序逻辑,会延后1拍) |
// ============================================================================== // state assignments & defines // ==============================================================================
`define WAIT_RDY 1
//********************************************************************/ // // Data Generation and Result Chcker // //********************************************************************/ always @(posedge k_clk or negedge reset_n) begin if (reset_n == 1'b0) begin datagen <= `IDLE; write_data <= {`DSIZE{1'b0}}; end else begin case (datagen) `IDLE : begin if (init_done) datagen <= `WAIT_RDY; else datagen <= `IDLE; end
`WAIT_RDY : begin if (data_rdy) begin write_data <= curr_data; datagen <= `WAIT_RDY; end else begin datagen <= `WAIT_RDY; end end endcase end end assign gen_data = (datagen == `WAIT_RDY) && data_rdy; assign idle_data = (datagen == `IDLE);
|
2.6DDR的读数据控制
1.当IP核接收读命令后,IP核就会产生read_data_valid信号,此时有效读取数据已经在read_data总线上。
与写数据不同,读数据是没有读数据延迟。
EMO解析--Read Data代码 |
1.read_data没什么好讲的,按照时序正常接收就行。 |
// ============================================================================== // Sequential expected data generator // ============================================================================== always @(posedge k_clk or negedge reset_n) begin if (reset_n == 1'b0) begin chk_seq_data32 <= 32'h1; read_data_valid_d <= 1'b0; read_data_valid_d2 <= 1'b0; read_data_valid_d3 <= 1'b0; end else begin read_data_valid_d <= read_data_valid; read_data_valid_d2 <= read_data_valid_d; read_data_valid_d3 <= read_data_valid_d2; if (read_data_valid_d) begin chk_seq_data32 <= chk_seq_data32 +1; end end end assign expect_data = (DATA_MODE == 1'b1) ? chk_rndm_data : chk_seq_data; always @(posedge k_clk or negedge reset_n) begin if (reset_n == 1'b0) begin read_data_d <= {`DSIZE{1'b0}}; read_data_2d <= {`DSIZE{1'b0}}; expect_data_d <= {`DSIZE{1'b0}}; end else begin read_data_d <= read_data; read_data_2d <= read_data_d; expect_data_d <= expect_data; end end |