Introduction
The purpose of this article is to help readers understand how to use DDR3 memory available on Skoll using Xilinx MIG 7 IP core easily. The MIG 7 IP core provides users with two interface options: User Interface (a wrapper over Native interface) and the AXI4 Interface. This article will demonstrate how to write to the DDR3 memory on Skoll using simple Verilog code and then read back the data. The design will compare the data written and the data read back from it. The verification results and DDR3 calibration results are indicated using GPIO pins on Skoll GPIO header P4.
Prerequisites
To follow this article, you would need the following:
- Hardware:
- Skoll Kintex 7 FPGA Module
- Xilinx Platform Cable USB II JTAG debugger (optional).
- Software:
- Xilinx Vivado Suite 2023.2.1
Let’s get started
The following steps will walk you through the process of creating simple DDR3 project using Xilinx Vivado.
Step 1:
Download and install Vivado Board Support Package files for Skoll from here. Follow the README.md file on how to install Vivado Board Support Package files for Numato Lab’s boards.
Step 2:
Open Xilinx Vivado Design Suite and select “New Project” from “File” menu. “New Project” dialog will open. Click “Next” to proceed.
Step 3:
Type in a project name, select a convenient project location and select “Create project subdirectory” to keep all project files in single folder. In this article, author has used “DDR3” as a project name. Click “Next”, select “RTL Project” as project type and select “Do not specify sources at this time”. Click “Next” to proceed.
Step 4:
Select “Boards” from “Default Part” tab, choose “numato.com” as a Vendor. Select “Skoll” and click “Next”. If Skoll is not listed, make sure board support files are installed correctly. Click “Finish”. A new Vivado project will open with the selected settings.
Step 5:
Under the “Flow Navigator”, double-click on “IP Catalogue” to add necessary IP cores. Search for MIG 7 and double click on “Memory Interface Generator (MIG 7 Series)” to customize.
Step 6:
The “Xilinx Memory Interface Generator” configuration window will open. Click “Next”, select component name and de-select “AXI4 Interface”. For this article, author used “mem” as component name. After clicking on “Next” twice, select “DDR3 SDRAM” as Memory. Click “Next”. Select controller options as shown below and Click Next. Select “Input Clock Period” as “5000 ps (200MHz) “, keep remaining settings as default. Click “Next” to proceed.
Step 7:
Select the ‘FPGA options’ as shown below.
Click Next, choose “Internal Termination Impedance” as “OFF”. Select “Fixed Pin Out” in the next tab and click “Next”. Assign DDR pins as mentioned in Skoll constraint file and validate it by clicking on “Validate”. If the pinout is valid, click “OK” and “Next” to proceed. Accept the license and click “Next” to proceed. Click “Generate” to generate IP core with our customized settings.
Step 8:
Search for “Clocking Wizard” IP and double-click on it to customize. Choose component name as “clk_wiz” and select “CLK_IN1” board interface to ‘sys_clock’. In “Clocking Options” choose primary clock port name as “clk_in”, clk_out1 port name as “clk_200”, requested frequency as “200.000”, de-select locked signal and select “Reset Type” as “Active Low”. Click “OK”.
Step 9 :
Create a Verilog file with .v extension and copy paste the following code in “skoll_ddr3.v” to run simple DDR3 with the user interface.
The RTL code uses Xilinx Clock Wizard IP core and MIG IP core along with its user interface logic for interfacing with the DDR3 memory. The clock wizard IP core is used to provide 200MHz input clock for MIG 7 IP core, derived from the 100MHz system clock.
A simple FSM is implemented with the six states in the following RTL code to interface with the memory controller. The states are: IDLE, WRITE, WRITE_DONE, READ, READ_DONE and PARK. Initially, the state machine is in the IDLE state.
IDLE: In this state, the state machine waits for the memory calibration to complete. As soon as the memory calibration completes (indicated by led_calib
going HIGH), the state machine changes to WRITE state.
WRITE: To accept command and data from the core, the machine waits for the ready signals from the MIG. Whenever the MIG asserts command ready (app_rdy
) and data ready (app_wdf_rdy
) signals, the core is ready to accept the command and the data to write to DDR3. The state changes to WRITE_DONE, and the command and data are written to the MIG IP core.
WRITE_DONE: In this state, the state machine de-asserts the command enable (app_en
) and data enable (app_wdf_en
) signals and the state changes to READ to read back the data from memory.
READ: The state machine waits for the ready signal (app_rdy
) from MIG IP core to send “read” command. Whenever MIG asserts the ready signal, the core is ready to accept the command to read back the data from DDR3. The machine changes to READ_DONE state.
READ_DONE: The state machine de-asserts the command enable and waits for the app_rd_data_valid
in this state. Whenever MIG asserts app_rd_data_valid
, data is read back from memory to the data_read_from_memory
register and the machine goes to PARK state.
PARK: In this state, the state machine compares the data written to memory and the data read back from memory. If the data matches, led_pass
goes HIGH otherwise led_fail
goes HIGH.
module skoll_ddr3 ( input clk_in, // DDR3 Physical Interface Signals //Inouts inout [15:0] ddr3_dq, inout [1:0] ddr3_dqs_n, inout [1:0] ddr3_dqs_p, // Outputs output [13:0] ddr3_addr, output [2:0] ddr3_ba, output ddr3_ras_n, output ddr3_cas_n, output ddr3_we_n, output ddr3_reset_n, output [0:0] ddr3_ck_p, output [0:0] ddr3_ck_n, output [0:0] ddr3_cke, output [0:0] ddr3_cs_n, output [1:0] ddr3_dm, output [0:0] ddr3_odt, // LEDs to signal pass/fail output reg led_pass, output reg led_fail, output wire led_calib ); wire calib_done; reg [27:0] app_addr = 0; reg [2:0] app_cmd = 0; reg app_en; wire app_rdy; reg [127:0] app_wdf_data; wire app_wdf_end = 1; reg app_wdf_wren; wire app_wdf_rdy; wire [127:0] app_rd_data; wire [15:0] app_wdf_mask = 0; wire app_rd_data_end; wire app_rd_data_valid; wire app_sr_req = 0; wire app_ref_req = 0; wire app_zq_req = 0; wire app_sr_active; wire app_ref_ack; wire app_zq_ack; wire ui_clk; wire ui_clk_sync_rst; wire sys_clk_i; reg [127:0] data_to_write = {32'hcafebabe, 32'h12345678, 32'hAA55AA55, 32'h55AA55AA}; reg [127:0] data_read_from_memory = 128'd0; // Power-on-reset generator circuit. // Asserts resetn for 1023 cycles, then deasserts // `resetn` is Active low reset reg [9:0] por_counter = 1023; always @ (posedge clk_in) begin if (por_counter) begin por_counter <= por_counter - 1 ; end end wire resetn = (por_counter == 0); // Clock Wizard // DDR3 core requires 200MHz input clock // We generate this clock using Xilinx Clocking Wizard IP Core clk_wiz clk_wiz ( .clk_in (clk_in), .clk_200 (sys_clk_i), .resetn (resetn) ); // Instatiation of MIG core named `mem` mem mem ( // DDR3 Physical interface ports .ddr3_addr (ddr3_addr), .ddr3_ba (ddr3_ba), .ddr3_cas_n (ddr3_cas_n), .ddr3_ck_n (ddr3_ck_n), .ddr3_ck_p (ddr3_ck_p), .ddr3_cke (ddr3_cke), .ddr3_ras_n (ddr3_ras_n), .ddr3_reset_n(ddr3_reset_n), .ddr3_we_n (ddr3_we_n), .ddr3_dq (ddr3_dq), .ddr3_dqs_n (ddr3_dqs_n), .ddr3_dqs_p (ddr3_dqs_p), .ddr3_cs_n (ddr3_cs_n), .ddr3_dm (ddr3_dm), .ddr3_odt (ddr3_odt), .init_calib_complete (calib_done), // User interface ports .app_addr (app_addr), .app_cmd (app_cmd), .app_en (app_en), .app_wdf_data(app_wdf_data), .app_wdf_end (app_wdf_end), .app_wdf_wren(app_wdf_wren), .app_rd_data (app_rd_data), .app_rd_data_end (app_rd_data_end), .app_rd_data_valid (app_rd_data_valid), .app_rdy (app_rdy), .app_wdf_rdy (app_wdf_rdy), .app_sr_req (app_sr_req), .app_ref_req (app_ref_req), .app_zq_req (app_zq_req), .app_sr_active(app_sr_active), .app_ref_ack (app_ref_ack), .app_zq_ack (app_zq_ack), .ui_clk (ui_clk), .ui_clk_sync_rst (ui_clk_sync_rst), .app_wdf_mask(app_wdf_mask), // Clock and Reset input ports .sys_clk_i (sys_clk_i), .sys_rst (resetn) ); localparam IDLE = 3'd0; localparam WRITE = 3'd1; localparam WRITE_DONE = 3'd2; localparam READ = 3'd3; localparam READ_DONE = 3'd4; localparam PARK = 3'd5; reg [2:0] state = IDLE; localparam CMD_WRITE = 3'b000; localparam CMD_READ = 3'b001; assign led_calib = calib_done; always @ (posedge ui_clk) begin if (ui_clk_sync_rst) begin state <= IDLE; app_en <= 0; app_wdf_wren <= 0; end else begin case (state) IDLE: begin if (calib_done) begin state <= WRITE; end end WRITE: begin if (app_rdy & app_wdf_rdy) begin state <= WRITE_DONE; app_en <= 1; app_wdf_wren <= 1; app_addr <= 0; app_cmd <= CMD_WRITE; app_wdf_data <= data_to_write; end end WRITE_DONE: begin if (app_rdy & app_en) begin app_en <= 0; end if (app_wdf_rdy & app_wdf_wren) begin app_wdf_wren <= 0; end if (~app_en & ~app_wdf_wren) begin state <= READ; end end READ: begin if (app_rdy) begin app_en <= 1; app_addr <= 0; app_cmd <= CMD_READ; state <= READ_DONE; end end READ_DONE: begin if (app_rdy & app_en) begin app_en <= 0; end if (app_rd_data_valid) begin data_read_from_memory <= app_rd_data; state <= PARK; end end PARK: begin 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 // top
Create a constraints file and copy-paste the following constraints.
set_property -dict {PACKAGE_PIN K22 IOSTANDARD LVCMOS33} [get_ports led_calib] set_property -dict {PACKAGE_PIN K21 IOSTANDARD LVCMOS33} [get_ports led_pass] set_property -dict {PACKAGE_PIN M18 IOSTANDARD LVCMOS33} [get_ports led_fail]
Step 11:
Generate Bitstream by clicking on “Generate Bitstream”. Once the Bitstream is generated successfully, power up Skoll and program the device.
FPGA pins K22
, K21
and M18
which are in 2nd row of GPIO Header P4
are connected to led_calib
, led_pass
and led_fail
signals respectively. After programming make sure that led_calib
(calibration done) signal goes HIGH indicating that initial calibration completed successfully. Also, led_pass
should go HIGH indicating that data written to memory and read back successfully. led_fail
should stay LOW since led_fail
being HIGH indicates failed attempt to write and read data from memory. The below picture illustrates the FPGA pins on the Skoll Kintex 7 FPGA Module GPIO header used for status indication.
You can use either LED with series resistor or multimeter to measure output of led_calib
, led_pass
and led_fail
signals. If you observe led_calib
and led_pass
LEDs high and led_fail
LED stays low, then your Skoll Kintex 7 DDR3 example design worked. Congratulations!