Exploring 7 Series MIG Part – 2
Hello, in the previous blog we explored how to use DDR present on the Arty S7 board and made sure it is working fine by running Memory Tests bare-metal application using SDK. The previous example made use of the AXI Interface to access the DDR, AXI interface is simple to use along with a soft processor like MicroBlaze but when we want to use DDR with custom Verilog logic AXI interface might be difficult to use. MIG also provides a simple User Interface (UI) to access the DDR, in this blog we will explore how to make use of it.
Please refer to [1] for more details about the signals and timing diagram of the UI interface of MIG IP. The signals can be divided into 3 different paths, the signals are synchronized to ui_clk and are briefly described as follows.
Command Path: The command path is used to provide commands from the user.
- MIG indicates it is ready to accept user commands by making app_rdy signal high
- User needs to provide app_cmd (WRITE = 000, READ = 001) along with app_addr and app_en
- If app_rdy is low user command needs to be held stable until app_rdy goes high as shown by the timing diagram
Write Path: Write path is used to provide write data from the user.
- Write data and write enable can be provided along with the command, 1 cycle before command or maximum of within 2 cycles after providing command
- App_wdf_data, app_wdf_wren must be provided when the app_wdf_rdy signal is high indicating MIG is ready to accept incoming data from the user
- By default, BL8 burst mode will be enabled for MIG, the size of each burst is calculated as follows for BL8 mode
Burst Length = 8 x DDR Data Width = 8 x 16 = 128 bits
Next Increment = (ui_data width/8) = (128/8) = 16
Read Path: The read path is used to read the data from DDR using MIG.
- Data can be read from MIG when app_rdy is high by providing the READ command and app_addr.
- The read data is presented to the user using app_rd_data bus.
- app_rd_data_valid signal is made high when valid data is present on app_rd_data bus
Creating Vivado Project:
Create a new vivado project targeting Arty S7 board, and add MIG IP using IP catalog.
Make sure the AXI Interface option is not selected so that MIG IP will expose UI interface.
Steps to configure MIG IP from here onwards are same as mentioned in previous blog complete the configuration and generation steps.
Now its time to instantiate clocking wizard. Add clocking wizard IP from the IP catalog in a similar way as MIG IP was added. Configure input clock as shown below.
Configure output clock and reset as shown below. After completing the configuration generate the IP.
Simple Verilog code to write and read from 1000 locations of DDR is described as below.
- After completing DDR calibration move to write state where 1000 locations are written
- After completing move to wait state and wait there for 1 cycle
- Move to read state and read 1000 locations
Data read from DDR is compared with data written, if test is passed RGB LED LD1 turns out green if test is failed led turns red.
RGB LD0 turns out to green after completing ddr calibration.
//------------------------------------------------------------------------------- // Description : Perform 1000 write transactions // Perform 1000 read transactions and check for data integrity //------------------------------------------------------------------------------- module ddr3_read_write( input ddr_clk_i , // 100 MHz clock input sys_clk_i , // 12 MHz system clock input sys_rst_n_i , // Active low system reset output calib_done_o , // DDR calibration is complete output [ 1:0] test_status_o , // test status, Red - Fail, Green - Pass // Signals going towards DDR3 chip output [13:0] ddr3_addr , output [ 2:0] ddr3_ba , output ddr3_cas_n , output ddr3_ras_n , output [ 0:0] ddr3_ck_p , output [ 0:0] ddr3_ck_n , output [ 0:0] ddr3_cke , output ddr3_reset_n , output ddr3_we_n , inout [15:0] ddr3_dq , inout [ 1:0] ddr3_dqs_n , inout [ 1:0] ddr3_dqs_p , output [ 0:0] ddr3_cs_n , output [ 1:0] ddr3_dm , output [ 0:0] ddr3_odt ); // FSM States parameter IDLE_S = 4'd0; parameter WRITE_S = 4'd1; parameter WAIT_S = 4'd2; parameter READ_S = 4'd3; parameter STOP_S = 4'd4; parameter ADDR_INCR = 16 ; parameter TRANSCATIONS = 100 ; (* mark_debug = "true" *) reg [ 3:0] curr_state ; // FSM current state register (* mark_debug = "true" *) reg [ 3:0] next_state ; // FSM next state register (* mark_debug = "true" *) wire app_en ; // enable user command (* mark_debug = "true" *) wire app_wdf_wren ; // write enable (* mark_debug = "true" *) wire app_wdf_end ; // last data of current write transaction (* mark_debug = "true" *) wire [ 2:0] app_cmd ; // user command, Write - 000, Read - 001 (* mark_debug = "true" *) reg [127:0] app_wdf_data ; // write data (* mark_debug = "true" *) reg [ 27:0] app_addr ; // ui interface address (* mark_debug = "true" *) wire [127:0] app_rd_data ; // read data (* mark_debug = "true" *) wire app_rd_data_end ; // last data current read transaction (* mark_debug = "true" *) wire app_rd_data_valid ; // read data valid (* mark_debug = "true" *) wire app_rdy ; // mig is ready to accept new command (* mark_debug = "true" *) wire app_wdf_rdy ; // mig is ready to accept new write command (* mark_debug = "true" *) reg [ 27:0] rd_addr_cnt ; // write address counter (* mark_debug = "true" *) reg [ 27:0] wr_addr_cnt ; // read address counter (* mark_debug = "true" *) reg [ 27:0] rd_cnt ; // read data counter (* mark_debug = "true" *) wire error ; // read data validation error (* mark_debug = "true" *) wire wr_proc ; // condition for writing (* mark_debug = "true" *) wire wr_last ; // last write transaction (* mark_debug = "true" *) wire rd_addr_last ; // last read transaction (* mark_debug = "true" *) reg [ 15:0] error_count ; // total number of read error encountered wire ui_clk ; // ui interface clock wire ui_rst ; // ui interface reset wire clk_ref ; // 200 MHz reference clock for MIG assign app_en = app_rdy && ((curr_state == WRITE_S && app_wdf_rdy) || (curr_state == READ_S)); assign app_wdf_wren = (curr_state == WRITE_S) && wr_proc; assign app_wdf_end = app_wdf_wren; assign app_cmd = (curr_state == READ_S) ? 3'h1 : 3'h0; assign wr_proc = ~app_cmd && app_rdy && app_wdf_rdy; assign wr_last = app_wdf_wren && (wr_addr_cnt == TRANSCATIONS - 1); assign rd_addr_last = app_cmd && app_rdy && (rd_addr_cnt == TRANSCATIONS - 1); assign error = (app_rd_data_valid && (rd_cnt != app_rd_data)); assign test_status_o = (error_count == 16'h0) ? 2'b10 : 2'b01; // read data counter always@(posedge ui_clk) begin if(ui_rst) begin rd_cnt <= 'h0 ; end else if(app_rd_data_valid && rd_cnt == TRANSCATIONS - 1) begin rd_cnt <= 'h0 ; end else if (app_rd_data_valid) begin rd_cnt <= rd_cnt + 1'b1; end end // error counter always@(posedge ui_clk) begin if(ui_rst) begin error_count <= 'h0; end else begin error_count <= error ? error_count + 1'b1 : error_count; end end // Sequential state change logic always@(posedge ui_clk) begin if(ui_rst) begin curr_state <= IDLE_S ; end else begin curr_state <= next_state; end end // Combinational next state decoder logic always@(*) begin if(ui_rst) begin next_state = IDLE_S ; end else begin case(curr_state) IDLE_S : begin if(calib_done_o) begin // move to write state when ddr calibration is completed next_state = WRITE_S; end else begin next_state = IDLE_S; end end WRITE_S :begin if(wr_last) begin // move to wait state when all write transactiona are completed next_state = WAIT_S; end else begin next_state = WRITE_S; end end WAIT_S : begin // 1 cycle delay before entering read state next_state = READ_S; end READ_S : begin if(rd_addr_last) begin // move to wait state when all write transactiona are completed next_state = STOP_S; end else begin next_state = READ_S; end end STOP_S : begin // halt in stop state after completing all transactions next_state = STOP_S; end default : begin next_state = IDLE_S; end endcase end end // Registered output logic always@(posedge ui_clk) begin if(ui_rst) begin app_wdf_data <= 'h0; wr_addr_cnt <= 'h0; rd_addr_cnt <= 'h0; app_addr <= 'h0; end else begin case(curr_state) IDLE_S : begin app_wdf_data <= 'h0; wr_addr_cnt <= 'h0; rd_addr_cnt <= 'h0; app_addr <= 'h0; end WRITE_S : begin if(wr_proc) begin app_wdf_data <= app_wdf_data + 1; wr_addr_cnt <= wr_addr_cnt + 1; app_addr <= app_addr + ADDR_INCR; end end WAIT_S : begin app_addr <= 'h0; end READ_S : begin if(app_rdy) begin rd_addr_cnt <= rd_addr_cnt + 1'b1; app_addr <= app_addr + ADDR_INCR; end end STOP_S : begin app_wdf_data <= 'h0; wr_addr_cnt <= 'h0; rd_addr_cnt <= 'h0; app_addr <= 'h0; end default : begin app_wdf_data <= 'h0; wr_addr_cnt <= 'h0; rd_addr_cnt <= 'h0; app_addr <= 'h0; end endcase end end mig_7series_0 u_mig_7series_0 ( // Global Ports .sys_clk_i ( ddr_clk_i ), .clk_ref_i ( clk_ref ), .sys_rst ( sys_rst_n_i ), .init_calib_complete ( calib_done_o ), // Memory interface ports .ddr3_dq ( ddr3_dq ), .ddr3_dqs_n ( ddr3_dqs_n ), .ddr3_dqs_p ( ddr3_dqs_p ), .ddr3_addr ( ddr3_addr ), .ddr3_ba ( ddr3_ba ), .ddr3_ras_n ( ddr3_ras_n ), .ddr3_cas_n ( ddr3_cas_n ), .ddr3_we_n ( ddr3_we_n ), .ddr3_reset_n ( ddr3_reset_n ), .ddr3_ck_p ( ddr3_ck_p ), .ddr3_ck_n ( ddr3_ck_n ), .ddr3_cke ( ddr3_cke ), .ddr3_cs_n ( ddr3_cs_n ), .ddr3_dm ( ddr3_dm ), .ddr3_odt ( ddr3_odt ), // Application 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_mask ( 32'd0 ), .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 ( 'b0 ), .app_ref_req ( 'b0 ), .app_zq_req ( 'b0 ), .app_sr_active ( ), .app_ref_ack ( ), .app_zq_ack ( ), .ui_clk ( ui_clk ), .ui_clk_sync_rst ( ui_rst ) ); clk_wiz_0 clk_wiz_inst( .clk_out1 ( clk_ref ), // 200 MHz .resetn ( sys_rst_n_i ), .locked ( ), .clk_in1 ( sys_clk_i ) // 12 MHz ); endmodule
XDC constraints are given below.
# System Clock set_property -dict {PACKAGE_PIN F14 IOSTANDARD LVCMOS33} [get_ports sys_clk_i] create_clock -period 83.333 -name sys_clk_pin -waveform {0.000 41.667} -add [get_ports sys_clk_i] # DDR Clock set_property -dict {PACKAGE_PIN R2 IOSTANDARD SSTL135} [get_ports ddr_clk_i] create_clock -period 10.000 -name sys_clk_pin -waveform {0.000 5.000} -add [get_ports ddr_clk_i] # RGB LEDs set_property -dict {PACKAGE_PIN G17 IOSTANDARD LVCMOS33} [get_ports calib_done_o] set_property -dict {PACKAGE_PIN E15 IOSTANDARD LVCMOS33} [get_ports {test_status_o[0]}] set_property -dict {PACKAGE_PIN F18 IOSTANDARD LVCMOS33} [get_ports {test_status_o[1]}] # System Reset set_property -dict {PACKAGE_PIN C18 IOSTANDARD LVCMOS33} [get_ports sys_rst_n_i]
Setting Up debug:
The code includes debug signals which will help in visualizing the transactions happening on ui_interface.
To add a signal for debug, add (* mark_debug = “true” *) attribute in front of signal.
Open synthesized design and select Set Up Debug.
Click on Next.
Click on Next after making sure right clock is selected for debug signals.
Click Next and Finish. Save the synthesized design and generate bitstream.
After Loading the bitstream open ILA window to observe the DDR transactions, for capturing ILA only 100 writes and reads are performed.
Write Transactions.
Read Transactions.
References: