Waxwing Spartan 6 FPGA Development Board

Simple LPDDR Interfacing on Waxwing using Xilinx MIG 6

195 views March 9, 2018 vijayalakshmi 4

Introduction

The purpose of this article is to help readers understand how to use LPDDR memory available on Waxwing using Xilinx MIG 6 IP core easily. The MIG 6 IP core provides users with two interface options: User Interface (a wrapper over Native memory interface) and the AXI4 Interface.  This article will demonstrate how to write to the LPDDR memory on Waxwing using simple Verilog code and then read back the data. The design will compare the data written to LPDDR and the data read back from it. The verification results and LPDDR calibration results are indicated using onboard LEDs on Waxwing.

Prerequisites

To follow this article, you would require following:

Let’s get started

The following steps will walk you through the process of creating simple LPDDR project for Waxwing using Xilinx ISE.

Step 1:

Open Xilinx ISE Design Suite and select “New Project” from “File” menu. “New Project” dialog will open. Type in a project name and select a convenient project location. In this article, author has used “waxwing_lpddr” as a project name. Click “Next” to proceed.

Step 2:

Specify the device and project settings (Family: Spartan 6, Device: XC6SLX45, Package: CSG324 and Speed grade: -2) as shown below in the “Project Settings” tab. Click “Next” and “Finish”. A new ISE project will open with the selected settings.

Step 3:

Under the “Design” tab, right click and select “New Source”. “New Source Wizard” dialog will open. Select “IP (CORE Generator & Architecture Wizard)”, type in “File Name” as “mem” and click “Next” to proceed. Select “MIG 6” IP core, click “Next” and “Finish”. “Xilinx Memory Interface Generator” wizard will open. Click “Next” three consecutive times, select “LPDDR” as “Memory Type” and de-select “Enable AXI interface for All MCBs”. Click “Next” to proceed.

Step 4:

Select the “Frequency” as “10000 ps (100MHz)” and “MT46H32M16XXXX-5” as Memory Part. Click “Next” twice and select port configuration as “One 128-bit bidirectional port”, under “Configuration Selection” tab. Leave the other options as default. Click “Next” twice and choose “System Clock” as “Single-Ended”. Accept the license by clicking on the “Next” twice. Click “Next” and “Generate”. The MIG 6 IP core is generated.

Step 5:

Create a Verilog file with .v extension and copy paste the following code in “waxwing_lpddr.v” to run simple LPDDR project with the user interface.

The RTL code uses MIG 6 IP core and the input clock for the MIG is directly provided from the 100MHz onboard oscillator on Waxwing.

To interface with memory controller IP core, a simple FSM is implemented with six states. The states are IDLE, WR_CMD, WRITE, RD_CMD, READ and PARK. Initially, the FSM is in the IDLE state. The state machine waits for the calibration to complete in the IDLE state. When the calibration completes successfully (led_calib goes HIGH), the state machine changes to WR_CMD state.

In WR_CMD state, the state machine waits for the MIG to be ready to accept commands. When the c3_p0_cmd_full signal gets de-asserted, the core is ready to accept the write command. The state machine writes the command to the MIG IP core and changes its state to WRITE. In WRITE state, the state machine de-asserts command enable (c3_p0_cmd_en) and waits for the MIG to become ready to accept new data to be written to memory. When the c3_p0_wr_full signal is de-asserted by the MIG core, the write enable signal (c3_p0_wr_en) is asserted and the data is captured into the write FIFO of MIG 6 core. The state machine goes to RD_CMD state to read back the data from MIG.

In RD_CMD state, the state machine again waits for the MIG to become ready to accept commands. When the c3_p0_cmd_full signal is de-asserted by the MIG IP core, it indicates that the core is ready to accept the command for reading. The state machine de-asserts the write enable (c3_p0_wr_en) signal, writes the command to the MIG IP core and changes its state to READ. In READ state, the state machine de-asserts the command enable (c3_p0_cmd_en) signal and waits for the read empty signal from the MIG 6 IP core to go low. If the read empty signal is low, it indicates that data, as requested by the design from LPDDR, is now available in the MIG core’s read FIFOto be read from it. So, when c3_p0_rd_empty gets de-asserted by the MIG, the state machine asserts the read enable signal (c3_p0_rd_en) and data is captured from the MIG IP core’s FIFO to data_read_from_memory register. The state machine changes to PARK state where the data written to the memory is compared with the data read back from memory.

In the PARK state, if the data written to the memory and the data read back from it match, thenled_pass goes HIGH otherwise led_fail goes HIGH.

module waxwing_lpddr(
   input         clk_in,
   input         reset,
   inout  [15:0] mcb3_dram_dq,
   output [12:0] mcb3_dram_a,
   output [1:0]  mcb3_dram_ba,
   output        mcb3_dram_ras_n,
   output        mcb3_dram_cas_n,
   output        mcb3_dram_we_n,
   output        mcb3_dram_cke,
   output        mcb3_dram_dm,
   inout         mcb3_dram_udqs,
   inout         mcb3_rzq,
   output        mcb3_dram_udm,
   inout         mcb3_dram_dqs,
   output        mcb3_dram_ck,
   output        mcb3_dram_ck_n,
   output        led_calib,
   output  reg   led_pass,
   output  reg   led_fail
   );
 
 wire                c3_clk0;
 wire                c3_rst0;
 // Command Signals
 wire                c3_p0_cmd_clk;
 reg                 c3_p0_cmd_en;
 reg    [2:0]        c3_p0_cmd_instr;
 reg    [5:0]        c3_p0_cmd_bl;
 reg    [29:0]       c3_p0_cmd_byte_addr;
 wire                c3_p0_cmd_empty;
 wire                c3_p0_cmd_full;
 // Write Signals
 wire                c3_p0_wr_clk;
 reg                 c3_p0_wr_en;
 wire   [15:0]       c3_p0_wr_mask;
 reg    [127:0]      c3_p0_wr_data;
 wire                c3_p0_wr_full;
 wire                c3_p0_wr_empty;
 wire   [6:0]        c3_p0_wr_count;
 wire                c3_p0_wr_underrun;
 wire                c3_p0_wr_error;
 // Read Signals
 wire                c3_p0_rd_clk;
 reg                 c3_p0_rd_en;
 wire   [127:0]      c3_p0_rd_data;
 wire                c3_p0_rd_full;
 wire                c3_p0_rd_empty;
 wire   [6:0]        c3_p0_rd_count;
 wire                c3_p0_rd_overflow;
 wire                c3_p0_rd_error;
 
 reg  [2:0]  state;
 reg  [127:0]data_read_from_memory = 128'd0;
 wire [127:0]data_to_write = {32'hcafebabe, 32'h12345678,
                              32'hAA55AA55, 32'h55AA55AA};
 
assign c3_p0_cmd_clk = c3_clk0;
assign c3_p0_wr_clk  = c3_clk0; 
assign c3_p0_rd_clk  = c3_clk0; 
 
// Instantiation of memory
 mem u_mem (
   .c3_sys_clk         (clk_in),
   .c3_sys_rst_i       (reset),

   .mcb3_dram_dq       (mcb3_dram_dq), 
   .mcb3_dram_a        (mcb3_dram_a), 
   .mcb3_dram_ba       (mcb3_dram_ba),
   .mcb3_dram_ras_n    (mcb3_dram_ras_n), 
   .mcb3_dram_cas_n    (mcb3_dram_cas_n), 
   .mcb3_dram_we_n     (mcb3_dram_we_n), 
   .mcb3_dram_cke      (mcb3_dram_cke), 
   .mcb3_dram_ck       (mcb3_dram_ck), 
   .mcb3_dram_ck_n     (mcb3_dram_ck_n), 
   .mcb3_dram_dqs      (mcb3_dram_dqs), 
   .mcb3_dram_udqs     (mcb3_dram_udqs), // for X16 parts 
   .mcb3_dram_udm      (mcb3_dram_udm), // for X16 parts
   .mcb3_dram_dm       (mcb3_dram_dm),
   .c3_clk0            (c3_clk0),
   .c3_rst0            (c3_rst0),
   .c3_calib_done      (led_calib),
   .mcb3_rzq           (mcb3_rzq), 
   .c3_p0_cmd_clk      (c3_p0_cmd_clk),
   .c3_p0_cmd_en       (c3_p0_cmd_en),
   .c3_p0_cmd_instr    (c3_p0_cmd_instr),
   .c3_p0_cmd_bl       (c3_p0_cmd_bl),
   .c3_p0_cmd_byte_addr(c3_p0_cmd_byte_addr),
   .c3_p0_cmd_empty    (c3_p0_cmd_empty),
   .c3_p0_cmd_full     (c3_p0_cmd_full),
   .c3_p0_wr_clk       (c3_p0_wr_clk),
   .c3_p0_wr_en        (c3_p0_wr_en),
   .c3_p0_wr_mask      (c3_p0_wr_mask),
   .c3_p0_wr_data      (c3_p0_wr_data),
   .c3_p0_wr_full      (c3_p0_wr_full),
   .c3_p0_wr_empty     (c3_p0_wr_empty),
   .c3_p0_wr_count     (c3_p0_wr_count), 
   .c3_p0_wr_underrun  (c3_p0_wr_underrun),
   .c3_p0_wr_error     (c3_p0_wr_error),
   .c3_p0_rd_clk       (c3_p0_rd_clk),
   .c3_p0_rd_en        (c3_p0_rd_en),
   .c3_p0_rd_data      (c3_p0_rd_data),
   .c3_p0_rd_full      (c3_p0_rd_full),
   .c3_p0_rd_empty     (c3_p0_rd_empty),
   .c3_p0_rd_count     (c3_p0_rd_count), 
   .c3_p0_rd_overflow  (c3_p0_rd_overflow),
   .c3_p0_rd_error     (c3_p0_rd_error)
   );

localparam IDLE    = 3'h0,
           WR_CMD  = 3'h1,
           WRITE   = 3'h2,
           RD_CMD  = 3'h3,
           READ    = 3'h4,
           PARK    = 3'h5; 
assign c3_p0_wr_mask = 16'h0000;
//****************** State Machine start here ****************************//
[email protected](posedge c3_clk0)
begin
   if(c3_rst0) begin
      c3_p0_cmd_byte_addr <= 0;
      c3_p0_cmd_bl    <= 1; 
      state           <= IDLE;
      c3_p0_cmd_instr <= 3'b000;
      c3_p0_cmd_en    <= 1'b0;
      c3_p0_wr_en     <= 1'b0;
      c3_p0_rd_en     <= 1'b0;
      led_pass        <= 1'b0;
      led_fail        <= 1'b0;
   end
   else begin
      case(state)
         IDLE : begin
           if(led_calib) begin
              state <= WR_CMD;
           end
         end
 
         WR_CMD : begin
            if(~c3_p0_cmd_full) begin
               c3_p0_cmd_byte_addr <= 0;
               c3_p0_cmd_bl    <= 1;
               c3_p0_cmd_instr <= 3'b000;
               c3_p0_cmd_en    <= 1'b1;
               state           <= WRITE;
            end
         end
 
         WRITE : begin
            c3_p0_cmd_en     <= 1'b0; 
            if(~c3_p0_wr_full) begin
               c3_p0_wr_en   <= 1'b1;
               c3_p0_wr_data <= data_to_write;
               state         <= RD_CMD;
            end
         end


         RD_CMD : begin
            if(~c3_p0_cmd_full) begin
               c3_p0_wr_en         <= 1'b0;
               c3_p0_cmd_byte_addr <= 0;
               c3_p0_cmd_bl        <= 1;
               c3_p0_cmd_instr     <= 3'b001;
               c3_p0_cmd_en        <= 1'b1; 
               state               <= READ;
            end
         end
 
         READ : begin
            c3_p0_cmd_en   <= 1'b0;
            if(~c3_p0_rd_empty) begin
               c3_p0_rd_en <= 1'b1;
               data_read_from_memory <= c3_p0_rd_data;
               state       <= PARK;
            end
         end
 
         PARK : begin
            c3_p0_rd_en <= 1'b0;
            if (data_to_write == data_read_from_memory) begin
               led_pass <= 1;
           end else if (data_to_write != data_read_from_memory) begin
               led_fail <= 1;
           end
        end
 
        default: state <= IDLE;
      endcase
   end
end
 
endmodule

Step 6:

Create a constraints file and copy-paste the following constraints.

CONFIG VCCAUX = "3.3" ;

NET "clk_in" LOC = V10 | IOSTANDARD = LVCMOS33 | PERIOD = 100MHz;
NET "reset" LOC = A11 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST | PULLDOWN;

###################################################################################################################################################
#                                                              LPDDR MT46H32M16XXXX-5                                                             #
###################################################################################################################################################
NET "mcb3_dram_a[0]"   LOC = "J7" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_a[1]"   LOC = "J6" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_a[2]"   LOC = "H5" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_a[3]"   LOC = "L7" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_a[4]"   LOC = "F3" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_a[5]"   LOC = "H4" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_a[6]"   LOC = "H3" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_a[7]"   LOC = "H6" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_a[8]"   LOC = "D2" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_a[9]"   LOC = "D1" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_a[10]"  LOC = "F4" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_a[11]"  LOC = "D3" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_a[12]"  LOC = "G6" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dq[0]"  LOC = "L2" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dq[1]"  LOC = "L1" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dq[2]"  LOC = "K2" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dq[3]"  LOC = "K1" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dq[4]"  LOC = "H2" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dq[5]"  LOC = "H1" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dq[6]"  LOC = "J3" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dq[7]"  LOC = "J1" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dq[8]"  LOC = "M3" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dq[9]"  LOC = "M1" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dq[10]" LOC = "N2" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dq[11]" LOC = "N1" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dq[12]" LOC = "T2" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dq[13]" LOC = "T1" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dq[14]" LOC = "U2" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dq[15]" LOC = "U1" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_ba[0]"  LOC = "F2" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_ba[1]"  LOC = "F1" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_ras_n"  LOC = "L5" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_cas_n"  LOC = "K5" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_we_n"   LOC = "E3" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_ck"     LOC = "G3" | IOSTANDARD = DIFF_MOBILE_DDR; 
NET "mcb3_dram_ck_n"   LOC = "G1" | IOSTANDARD = DIFF_MOBILE_DDR;
NET "mcb3_dram_cke"    LOC = "H7" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dm"     LOC = "K3" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_dqs"    LOC = "L4" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_udm"    LOC = "K4" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_dram_udqs"   LOC = "P2" | IOSTANDARD = MOBILE_DDR;
NET "mcb3_rzq"         LOC = "N4" | IOSTANDARD = MOBILE_DDR;

NET "led_fail"   LOC = V5 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST ;
NET "led_pass"   LOC = V7 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST ;
NET "led_calib"  LOC = U7 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST ;

Step 7:

Synthesize the design and generate bitstream by clicking “Generate Bitstream” in ISE. Once the bitstream is generated successfully, power up Waxwing and program it using the Xilinx Platform Cable USB II JTAG hardware.

FPGA pins U7V7 and V5 which are connected to onboard LEDs D1, D2 and D3 on Waxwing are used for led_calibled_pass and led_fail signals respectively. After programming make sure that led_calib (calibration done) signal goes HIGH indicating that initial calibration finished successfully. Also, led_pass should go HIGH indicating that data was written to memory and read back successfully. The led_fail should stay LOW since led_fail being HIGH indicates failed attempt to write and read data from memory.

If you observe led_calib and led_pass LEDs glowing and led_fail LED stays low, then your Waxwing LPDDR example design worked. Congratulations!

Was this helpful?

2 Comments
  • Derek Mulcahy says:

    Hi,

    Nice example, it really helped me make sense of the MIG and how to use it. I implemented this on my Saturn XC6SLX45 and encountered a few issues.

    1. The setting for

    c3_p0_cmd_bl <= 1

    is wrong. The manual (UG388, P.26) describes this setting as meaning a burst length of 2. We only write one 128-bit word and only read one 128-bit word. So the write fifo underruns because the burst setting indicates two words are to be written, this sets the c3_p0_wr_underrun flag. Similarly, after the read completes the flag c3_p0_rd_empty is not set, indicating that we have a read still in the fifo.

    2. When I implement this it fails to meet timing. The maximum frequency is 40.206MHz. It runs on the hardware and seems to work fine.

    I am new to this and any comments would be appreciated.

    As a final comment, your products are great!

    Cheers,
    Derek

    November 14, 2018 at 2:47 am
    • Rohit Singh says:

      Hi Derek,

      Thanks for your encouraging appreciation and feedback. Your point on burst length seems valid. I’ve requested our team to check and confirm your findings. Once they confirm this, I’ll request them to update this article with correct burst length.

      Thanks

      November 22, 2018 at 7:56 am
Leave A Comment
*
*