FPGA基础入门【9】开发板外部存储器DDR2访问

上一篇教程介绍了NEXYS4 开发板中SPI flash的使用方式,这一篇介绍另一种板载存储DDR2

板载DDR2

NEXYS 4开发板上包含有一块DDR2 SDRAM,编号为MT47H64M16HR-25:H,MT47H是产品号,64M16是配置,HR是包装,-25是速度等级,:H是版本号,文档链接:DDR2 datasheet

虽然文档里说是MT47H的DDR2芯片,但在相应位置看到的芯片却是Mira P2R1GE4KGF - G8E,看网上说的,这两种芯片似乎没啥区别,可以用同种方式使用。

参数表如下:
parameter

DDR2细节

在文档中的DDR2状态图如下
state diagram
DDR2内部结构如下
DDR2
引脚功能如下(有些引脚没有在架构中显示)

  • A[12:0],输入地址,在ACTIVATE指令中作为行地址,在READ/WRITE指令中[9:0]作为列地址,并且[10]作为预充电指示位
  • BA[2:0],bank地址,我们用的DDR2中有16个bank,需要有4位,最后一位来自控制寄存器
  • CK, CK#,一对差分时钟信号
  • CKE,时钟使能
  • CS#,片选
  • LDM, UDM, DM,输入数据mask
  • ODT,终端匹配
  • RAS#, CAS#, WE#,指令输入
  • DQ[15:0],数据输入输出
  • DQS, DQS#,差分数据同步信号
  • LDQS, LDQS#,差分低位数据同步信号
  • UDQS, UDQS#,差分高位数据同步信号
  • V_DD, V_DDQ, V_DDL, V_REF, V_SS, V_SSDL, V_SSQ是各种电源或者接地
  • NC, NF, NU, RFU都是无用引脚

输入命令的形式如下表,H是高电平,L是低电平,X不关心
command table
从这些信息中可以得到大致的DDR2读写流程:

  1. 充电完成,准备状态
  2. 激活ACTIVATE
  3. 激活bank
  4. 读写操作,可重复burst读写
  5. 预充电,回到准备状态

调用方式

NEXYS 4的文档给了我们两种途径来实现DDR2的调用,一种相对容易的方式是使用Digilent提供的DDR-to-SRAM适配模块
,另一种更基础的方式是使用Xilinx提供的Memory Interface Generator (MIG) Wizard,其实Digilent提供的适配模块也是以MIG为核心,既然这个教程是为了了解DDR,这篇就直接使用MIG了。

在这里我们需要第一次调用Xilinx的IP,所谓IP是自动生成打包好的模块,Xilinx把一些常用的功能做成了可以通过GUI配置的选项,这样可以省去大量的设计时间,使用者不需要知道所有细节知识,比如此处对DDR2的配置细节。

使用的IP名为MIG,具体可以参考此文档:UG586

MIG IP生成和调用

根据UG586的介绍开始MIG的配置(由于版本原因,与文档稍有不同),大致参照Digilent提供的DDR-to-SRAM适配模块
中的parameter setting表格。

先新建一个名为ddr2的新工程文件。第一步到左侧的Flow Navigator中找到IP Catalog
IP catalog
搜索MIG找到它,双击开始生成配置
MIG IP
系统自动读取工程配置
MIG1
这里的控制接口我们使用初始的,不使用AXI4
MIG2
引脚兼容就不需要了,只要我们现在用的芯片就够了
MIG3
这里自然是选择DDR2 SDRAM
MIG4
来到这一步,先把时钟周期从默认的3000ps改成3077ps,下面的文字建议我们使用默认配置,但我们不接受它的建议,我们接受NEXYS 4文档中的建议,让默认系统时钟100MHz能够方便的生成DDR控制器所需的时钟。

存储器系列自然是MT47H64M16HR-25E,其他就不用管了
MIG5
suggestion
到这一步选择输入时钟周期为10000ps也就是100MHz。前面之所以用3077ps而不用3000ps就是因为,3000ps无法在这个列表中找到100MHz
MIG6
时钟配置时系统时钟system clock和参考时钟reference clock都选择No Buffer,因为这么选择可以自己调配时钟,不然MIG IP的配置会要求你给它调配真实引脚。系统时钟要100MHz,参考时钟要200MHz,之后我们再调用一个时钟生成器IP给这两个接口调配时钟。

我们不在IP内部调配ChipScope,不需要选择debug signal。
MIG7
ODT是50欧姆
MIG8
引脚配置我们使用Digilent提供的DDR-to-SRAM适配模块
中提供的UCF文件,避免手打的失误。需要选择第二个选项
MIG9
进入该界面选择Read XDC/UCF
MIG10
跳转到下载好的ucf文件路径
MIG11
点击校验Validate,确定没问题后进入下一步。

这里要配置系统复位信号sys_rst,初始校准完成信号init_calib_complete和误码信号tg_compare_error。这个误码信号实际上在生成后找不到,traffic generator是一个用来生成读写数据的测试模块,在MIG的示例工程中使用,但我们并没有使用到。

这里把复位信号连到第一个拨动开关J15,初始校准完成信号连到第一个LED H17,误码信号连到第二个LED K15。
MIG13
后面几步都同意,之后就点击Generate开始编译生成
MIG14
MIG15

时钟生成器IP配置

在IP Catalog中搜索Clocking Wizard找到时钟生成器IP,按照下面的配置后同样点击Generate生成
CLK1
CLK2
CLK3

逻辑设计

数据接口

在配置MIG的时候我们使用的是初始数据接口,而不是AXI4。从MIG的文档中找到相应的接口介绍如下

  • app_addr[26:0],数据地址,从高位到低位分别是bank 4位,行地址13位和列地址10位
  • app_cmd[2:0],用户指令,3’b001为读,3’b000为写
  • app_en,指令使能,当app_addr和app_cmd都准备好后,将其拉高来送出指令
  • app_rdy,指令接收信号,此信号升高表示送入的指令已经被接受。如果迟迟不升高,有可能DDR在初始化,或者FIFO已满无法在读写,或者正在处理其他的读操作
  • app_wdf_data[127:0],写数据,它会先经过app_wdf_mask,将指定部分覆盖为全1后送出
  • app_wdf_mask[15:0],写数据mask,16位的mask,每一位对应数据中的8位,当mask的[0]为高时,app_wdf_data[7:0]送入DDR时会变成全1;当mask的[15]为高时,app_wdf_data[127:120]送入DDR时会变成全1
  • app_wdf_end,写数据末端信号,当输入的数据是最后一个时,将此信号拉高,表示数据已送完
  • app_wdf_wren,写使能,当数据准备好时,将此信号拉高
  • app_wdf_rdy,写数据准备,此信号升高表示FIFO已经准备好接收新数据
  • app_rd_data[127:0],读数据
  • app_rd_data_end,读数据末端信号,最后一个读出的信号
  • app_rd_data_valid,读数据准备,此信号升高表示读数据准备,可以接受数据
  • app_sr_req,保留引脚,应该拉低
  • app_ref_req,拉高该信号表示送一个refresh刷新指令给DRAM,必须是一个单时钟脉冲
  • app_zq_req,拉高该信号表示送一个ZQ校准指令(用来校准ODT电阻值)给DRAM,必须是一个单时钟脉冲
  • app_sr_active,保留输出引脚,无视
  • app_ref_ack,该信号升高表示refresh刷新指令被确认
  • app_zq_ack,该信号升高表示ZQ校准指令被确认
  • ui_clk,IP输出给用户使用的时钟,一般是送给SDRAM的二分之一或者四分之一,根据IP的配置决定
  • ui_clk_sync_rst,IP输出给用户使用的复位信号,和上面的时钟同步

综合上面的介绍,官方给的读写示范是这样的:
MIG RW

状态机设计

计划写8个数再读8个数,循环往复,因此根据上面的接口定义设计状态机如下
FSM

顶层代码

顶层代码ddr2.v拆解如下

端口定义和资源定义,和之前一样,定义dont_touch让ChipScope可以方便找到这些信号,但对系统复杂度有影响,真实工程中,最后应该把这些去掉

module ddr2(
    // Reference Clock Ports
    input  clk,
    input  rst,
    
    // Memory interface ports
    output [12:0]                     ddr2_addr,
    output [2:0]                      ddr2_ba,
    output                            ddr2_cas_n,
    output [0:0]                      ddr2_ck_n,
    output [0:0]                      ddr2_ck_p,
    output [0:0]                      ddr2_cke,
    output                            ddr2_ras_n,
    output                            ddr2_we_n,
    inout [15:0]                      ddr2_dq,
    inout [1:0]                       ddr2_dqs_n,
    inout [1:0]                       ddr2_dqs_p,
    output                            init_calib_complete,
    output [0:0]                      ddr2_cs_n,
    output [1:0]                      ddr2_dm,
    output [0:0]                      ddr2_odt
);

// App commands
parameter WRITE = 3'b000;
parameter READ  = 3'b001;

// State machine
parameter IDLE    = 4'd1;
parameter CMD     = 4'd2;
parameter WR_DATA = 4'd3;
parameter WR_END  = 4'd4;
parameter RD_WAIT = 4'd5;
parameter RD_DATA = 4'd6;
parameter ENDING  = 4'd7;

(* dont_touch = "true" *)reg [3:0] state;
(* dont_touch = "true" *)reg [3:0] next_state;

// Definition for APP interface
wire clk_ref_i;

(* dont_touch = "true" *)reg  [26:0]                      app_addr;
(* dont_touch = "true" *)reg  [2:0]                       app_cmd;
(* dont_touch = "true" *)reg                              app_en;
(* dont_touch = "true" *)reg  [127:0]                     app_wdf_data;
reg  [15:0]                      app_wdf_mask;
(* dont_touch = "true" *)reg                              app_wdf_end;
(* dont_touch = "true" *)reg                              app_wdf_wren;
wire [127:0]                     app_rd_data;
wire                             app_rd_data_end;
wire                             app_rd_data_valid;
wire                             app_rdy;
wire                             app_wdf_rdy;
reg                              app_sr_req;
reg                              app_ref_req;
reg                              app_zq_req;
wire                             app_sr_active;
wire                             app_ref_ack;
wire                             app_zq_ack;
wire                             ui_clk;
wire                             ui_clk_sync_rst;

// Control of the data
(* dont_touch = "true" *)reg [26:0]                       addr_start;
(* dont_touch = "true" *)reg                              wrh_rdl;
(* dont_touch = "true" *)reg [2:0]                        command;
reg [2:0]                        command_d;
(* dont_touch = "true" *)reg [15:0]                       data_start;
(* dont_touch = "true" *)reg [7:0]                        write_length;
(* dont_touch = "true" *)reg [7:0]                        write_count;
(* dont_touch = "true" *)reg [7:0]                        read_length;
(* dont_touch = "true" *)reg [7:0]                        read_count;
reg [127:0]                      data_from_RAM;

状态机部分,首先把次要信号拉低,再用状态机控制其他读写信号。具体可以参考上面设计的状态机

// State machine
always @ (posedge ui_clk or posedge ui_clk_sync_rst) begin
    if(ui_clk_sync_rst | (~init_calib_complete)) begin
        state <= IDLE;
    end
    else begin
        state <= next_state;
    end
end

always @(posedge ui_clk) begin
    app_wdf_mask <= 16'h0000;
    app_sr_req <= 1'b0;
    app_ref_req <= 1'b0;
    app_zq_req <= 1'b0;
end

always @(posedge ui_clk) begin
    case(state)
        IDLE: begin
            app_addr <= 27'h0;
            app_cmd <= READ;
            app_en <= 1'b0;
            app_wdf_data <= {8{16'h0}};
            app_wdf_end <= 1'b0;
            app_wdf_wren <= 1'b0;
            next_state <= CMD;
            data_from_RAM <= 128'd0;
            write_count <= 8'd0;
            read_count <= 8'd0;
        end
        CMD: begin
            if(app_rdy) begin
                next_state <= (wrh_rdl) ? WR_DATA : RD_DATA;
                app_addr <= addr_start;
                app_wdf_data <= {8{data_start}};
                app_cmd <= command;
                app_en <= 1'b1;
            end
            else begin
                next_state <= CMD;  // Keep here until it's ready
            end
        end
        WR_DATA: begin
            app_wdf_wren <= 1'b1;
            if(app_wdf_rdy) begin
                app_wdf_data <= {8{app_wdf_data[15:0] + 16'h0001}}; // change the data only when the FIFO is available
            end
            
            if(write_count < write_length) begin
                write_count <= write_count + 8'd1;
                next_state <= WR_DATA;
            end
            else begin
                next_state <= WR_END;
            end
        end
        WR_END: begin
            app_en <= 1'b0;
            if(app_wdf_rdy) begin
                app_wdf_data <= {8{app_wdf_data[15:0] + 16'h0001}}; // write the last data
            end
            app_wdf_end <= 1'b1;    // assert write end
            app_wdf_wren <= 1'b0;
            next_state <= ENDING;
        end
        RD_DATA: begin
            if(app_rd_data_valid && (read_count < read_length)) begin
                next_state <= RD_DATA;
                data_from_RAM <= app_rd_data;
                read_count <= read_count + 8'd1;
            end
            else if(read_count == read_length) begin
                next_state <= ENDING;  // wait until read data valid
            end
        end
        ENDING: begin
            app_en <= 1'b0;
            read_count <= 8'd0;
            write_count <= 8'd0;
            app_wdf_end <= 1'b0;
            next_state <= IDLE;
        end
    endcase
end

写个简单的控制逻辑

// address and data setting for test
always @(posedge ui_clk) begin
    addr_start <= 27'h0000d00;
    data_start <= 16'h0005;
    write_length <= 8'd8;
    read_length <= 8'd8;
end

always @(posedge ui_clk or posedge ui_clk_sync_rst) begin
    if(ui_clk_sync_rst) begin
        command <= WRITE;
        command_d <= WRITE;
        wrh_rdl <= 1'b1;
    end
    else if(state == ENDING)begin
        command_d <= (command[0]) ? WRITE : READ; // change every time reach ending
        wrh_rdl <= (command_d[0]) ? 1'b0 : 1'b1;
        command <= command_d;
    end
end

调用时钟生成器和MIG IP

wire locked;

clk_wiz_0 clk_wiz(
    // Clock out ports
    .clk_out1(sys_clk_i),     // output clk_out1, 100MHz
    .clk_out2(clk_ref_i),     // output clk_out2, 200MHz
    // Status and control signals
    .reset(rst),           // input reset
    .locked(locked),       // output locked
   // Clock in ports
    .clk_in1(clk)      // input clk_in1, 100MHz
);


mig_7series_0 MIG (
    // Memory interface ports
    .ddr2_addr                      (ddr2_addr),            // output [12:0]                     ddr2_addr
    .ddr2_ba                        (ddr2_ba),              // output [2:0]                      ddr2_ba
    .ddr2_cas_n                     (ddr2_cas_n),           // output                            ddr2_cas_n
    .ddr2_ck_n                      (ddr2_ck_n),            // output [0:0]                      ddr2_ck_n
    .ddr2_ck_p                      (ddr2_ck_p),            // output [0:0]                      ddr2_ck_p
    .ddr2_cke                       (ddr2_cke),             // output [0:0]                      ddr2_cke
    .ddr2_ras_n                     (ddr2_ras_n),           // output                            ddr2_ras_n
    .ddr2_we_n                      (ddr2_we_n),            // output                            ddr2_we_n
    .ddr2_dq                        (ddr2_dq),              // inout [15:0]                      ddr2_dq
    .ddr2_dqs_n                     (ddr2_dqs_n),           // inout [1:0]                       ddr2_dqs_n
    .ddr2_dqs_p                     (ddr2_dqs_p),           // inout [1:0]                       ddr2_dqs_p
    .init_calib_complete            (init_calib_complete),  // output                            init_calib_complete
	.ddr2_cs_n                      (ddr2_cs_n),            // output [0:0]                      ddr2_cs_n
    .ddr2_dm                        (ddr2_dm),              // output [1:0]                      ddr2_dm
    .ddr2_odt                       (ddr2_odt),             // output [0:0]                      ddr2_odt

    // Application interface ports
    .app_addr                       (app_addr),             // input [26:0]                      app_addr
    .app_cmd                        (app_cmd),              // input [2:0]                       app_cmd
    .app_en                         (app_en),               // input                             app_en
    .app_wdf_data                   (app_wdf_data),         // input [127:0]                     app_wdf_data
    .app_wdf_end                    (app_wdf_end),          // input                             app_wdf_end
    .app_wdf_wren                   (app_wdf_wren),         // input                             app_wdf_wren
    .app_rd_data                    (app_rd_data),          // output [127:0]                    app_rd_data
    .app_rd_data_end                (app_rd_data_end),      // output                            app_rd_data_end
    .app_rd_data_valid              (app_rd_data_valid),    // output                            app_rd_data_valid
    .app_rdy                        (app_rdy),              // output                            app_rdy
    .app_wdf_rdy                    (app_wdf_rdy),          // output                            app_wdf_rdy
    .app_sr_req                     (app_sr_req),           // input                             app_sr_req
    .app_ref_req                    (app_ref_req),          // input                             app_ref_req
    .app_zq_req                     (app_zq_req),           // input                             app_zq_req
    .app_sr_active                  (app_sr_active),        // output                            app_sr_active
    .app_ref_ack                    (app_ref_ack),          // output                            app_ref_ack
    .app_zq_ack                     (app_zq_ack),           // output                            app_zq_ack
    .ui_clk                         (ui_clk),               // output                            ui_clk
    .ui_clk_sync_rst                (ui_clk_sync_rst),      // output                            ui_clk_sync_rst
    .app_wdf_mask                   (app_wdf_mask),         // input [15:0]                      app_wdf_mask

    // System Clock Ports
    .sys_clk_i                      (sys_clk_i),

    // Reference Clock Ports
    .clk_ref_i                      (clk_ref_i),  // input  clk_ref_i
    .sys_rst                        (rst)     // input  sys_rst
);

endmodule

模拟仿真

Xilinx的IP在仿真时需要将IP编译后生成的netlist文件拷贝到仿真根目录,或者在编译时直接引用到netlist所在路径,还是比较方便的。

这里墙裂吐槽批评一下Intel Altera!不把IP整合到一个文件里,还在后面加上一个哈希值,每次稍微改点东西都要生成一个新的哈希值,生成一个新文件,对使用git之类的version control时候很不友好。

除了MIG和时钟生成器两个IP的netlist之外,还需要一个DDR2的仿真模型,这个模型可以从网上下载,但更方便的办法是从IP生成的文件中找。Xilinx为了仿真方便在生成的文件中自带了一个DDR2的模型,如果IP生成的路径是默认的,那么你可以在这个路径中找到ddr2_model.v和ddr2_model_parameters.vh两个文件:

[your_project].srcs\sources_1\ip\mig_7series_0\mig_7series_0\example_design\sim

可以把这俩拷贝到仿真根目录后直接调用使用,很方便

Testbench代码tb_ddr2.v如下:

`timescale 1ns/1ns

module tb_ddr2;

reg clock;
reg reset;
wire led;

wire [12:0]                     ddr2_addr;
wire [2:0]                      ddr2_ba;
wire                            ddr2_cas_n;
wire [0:0]                      ddr2_ck_n;
wire [0:0]                      ddr2_ck_p;
wire [0:0]                      ddr2_cke;
wire                            ddr2_ras_n;
wire                            ddr2_we_n;
wire [15:0]                     ddr2_dq;
wire [1:0]                      ddr2_dqs_n;
wire [1:0]                      ddr2_dqs_p;
wire [0:0]                      ddr2_cs_n;
wire [1:0]                      ddr2_dm;
wire [0:0]                      ddr2_odt;

initial begin
    clock = 1'b0;
    reset = 1'b0;
    // Reset for 1us
    #100 
    reset = 1'b1;
    #1000
    reset = 1'b0;
end

// Generate 100MHz clock signal
always #5 clock <= ~clock;

定义信号以及生成必要的100MHz时钟以及复位

ddr2 ddr2_top(
    // Reference Clock Ports
    .clk                              (clock),
    .rst                              (reset),
    .init_calib_complete              (led),
    
    // Memory interface ports
    .ddr2_addr                        (ddr2_addr ),
    .ddr2_ba                          (ddr2_ba   ),
    .ddr2_cas_n                       (ddr2_cas_n),
    .ddr2_ck_n                        (ddr2_ck_n ),
    .ddr2_ck_p                        (ddr2_ck_p ),
    .ddr2_cke                         (ddr2_cke  ),
    .ddr2_ras_n                       (ddr2_ras_n),
    .ddr2_we_n                        (ddr2_we_n ),
    .ddr2_dq                          (ddr2_dq   ),
    .ddr2_dqs_n                       (ddr2_dqs_n),
    .ddr2_dqs_p                       (ddr2_dqs_p),
    .ddr2_cs_n                        (ddr2_cs_n ),
    .ddr2_dm                          (ddr2_dm   ),
    .ddr2_odt                         (ddr2_odt  )
);

调用工程的顶层代码

ddr2_model  ddr2_model(
    .ck          (ddr2_ck_p),
    .ck_n        (ddr2_ck_n),
    .cke         (ddr2_cke),
    .cs_n        (ddr2_cs_n),
    .ras_n       (ddr2_ras_n),
    .cas_n       (ddr2_cas_n),
    .we_n        (ddr2_we_n),
    .dm_rdqs     (ddr2_dm),
    .ba          (ddr2_ba),
    .addr        (ddr2_addr),
    .dq          (ddr2_dq),
    .dqs         (ddr2_dqs_p),
    .dqs_n       (ddr2_dqs_n),
    .rdqs_n      (),
    .odt         (ddr2_odt)
);

endmodule

调用刚刚从IP生成文件中找到的DDR2模型

写一个仿真脚本sim.do

vlib work
vlog ../src/ddr2.v ./tb_ddr2.v ./ddr2_model.v ./clk_wiz_0_sim_netlist.v ./mig_7series_0_sim_netlist.v ./glbl.v
vsim -L xpm -L secureip -L unisims_ver -L unimacro_ver -L unifast_ver -L simprims_ver work.tb_ddr2 work.glbl -voptargs=+acc +notimingchecks
log -depth 7 /tb_ddr2/*
do wave.do
run 2ms

和之前一样,使用Xilinx的IP需要调用初始化文件glbl.v,仿真命令也需要把glbl.v一起仿真进去,具体位置可以参考上一篇教程,也可以直接下载附带工程文件。

ModelSim中把路径改为仿真根目录后执行do sim.do即可开始

仿真中DDR的校准需要很长时间,大概1.3ms左右init_calib_complete信号才会拉高,要耐心等待,这里我们跑2ms,可以看到如下波形
SIM

编译烧写

在前面配置IP时我们已经新建了一个工程文件ddr2,现在可以add source加入上面的顶层代码ddr2.v

引脚定义

下一步加入约束constraint文件ddr2.xdc,同样这是用标准模板取自己需要部分修改出来的(NEXYS 4 DDR Master XDC

## This file is a general .xdc for the Nexys4 DDR Rev. C
## To use it in a project:
## - uncomment the lines corresponding to used pins
## - rename the used ports (in each line, after get_ports) according to the top level signal names in the project

## Clock signal
set_property -dict { PACKAGE_PIN E3    IOSTANDARD LVCMOS33 } [get_ports { clk }]; #IO_L12P_T1_MRCC_35 Sch=clk100mhz
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports {clk}];

##Switches

set_property -dict { PACKAGE_PIN J15   IOSTANDARD LVCMOS33 } [get_ports { rst }]; #IO_L24N_T3_RS0_15 Sch=sw[0]

## LEDs

set_property -dict {PACKAGE_PIN H17 IOSTANDARD LVCMOS33} [get_ports init_calib_complete]

这个文件只定义了clk, rst和init_calib_complete三个信号,其他和DDR2相连的引脚都在MIG配置时生成好了。在IP生成目录中,你可以找到这个文件

[your_project].srcs\sources_1\ip\mig_7series_0\mig_7series_0\user_design\constraints\mig_7series_0.xdc

在编译时这个也作为约束文件被编译进去了,摘取其中的一部分看看:

...

# PadFunction: IO_L23P_T3_34 
set_property SLEW FAST [get_ports {ddr2_dq[0]}]
set_property IN_TERM UNTUNED_SPLIT_50 [get_ports {ddr2_dq[0]}]
set_property IOSTANDARD SSTL18_II [get_ports {ddr2_dq[0]}]
set_property PACKAGE_PIN R7 [get_ports {ddr2_dq[0]}]

# PadFunction: IO_L20N_T3_34 
set_property SLEW FAST [get_ports {ddr2_dq[1]}]
set_property IN_TERM UNTUNED_SPLIT_50 [get_ports {ddr2_dq[1]}]
set_property IOSTANDARD SSTL18_II [get_ports {ddr2_dq[1]}]
set_property PACKAGE_PIN V6 [get_ports {ddr2_dq[1]}]

# PadFunction: IO_L24P_T3_34 
set_property SLEW FAST [get_ports {ddr2_dq[2]}]
set_property IN_TERM UNTUNED_SPLIT_50 [get_ports {ddr2_dq[2]}]
set_property IOSTANDARD SSTL18_II [get_ports {ddr2_dq[2]}]
set_property PACKAGE_PIN R8 [get_ports {ddr2_dq[2]}]

...

ChipScope配置

具体配置方法可以参照之前几个教程,这里我们再synthesis完成后Set Up Debug,并将之前定义了(* dont_touch = “true” *)的信号全部加入进来了

成果

编译综合后生成bitstream,USB线连接开发板,进入hardware manager烧写进芯片后,把连接到reset的开关拨到0,观察到LED0亮起了绿灯,这代表着DDR和我们的MIG校准连接已经完成
result_led

进入ChipScope,不需要设置trigger,点击立即抓取(上面一排小按钮中双右箭头那个),抓取结果如下
result_chipscope

总结

这篇教程从DDR的芯片介绍开始一步步到最终实现DDR读写,这离最终应用还有一些控制逻辑方面的距离,但至少可以帮助我们理解这个接口的原理。之后具体将所有接口综合应用起来的教程中我们会用更简化的方式来实现这些接口

下一篇是局域网接口RJ45的使用

  • 5
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值