Thursday, 11 January 2018

Full Block design for accessing SDRAM

Foreword

In the previous post we started exploring how to interface our FPGA to the SDRAM on the ZYBO board.

We ended off the post with writing the User Logic for interfacing with IPIC.

In this post we will continue creating a workable block design and see if we can write to SDRAM from FPGA.

In this post we will spend a lot of time with Vivado IDE specifics, so please bear with me :-)

Creating an AXI Interface Block

Time for us to create an AXI Interface Block.

Start off by creating a new Vivado Project, also choosing the Zybo as the target board.

The AXI Interface block we will also create as an IP, so you will also start off by selecting Tools/Create and Package New IP. In the screen where you need to select the applicable task, you will need to select a different task than what we did in previous posts:

This option will create an AXI Pheripheral for us.

On the next page of the wizard you will be given the option to customise your AXI peripheral. By default a AXI slave will be created. We need to change this so that a AXI Master peripheral gets created. With that change performed the wizard page will look as follows:


On the next wizard page click Edit IP and then Finish.

Let us now have a look at the generated source code:

`timescale 1 ns / 1 ps

 module myip_burst_test_v1_0 #
 (
  // Users to add parameters here

  // User parameters ends
  // Do not modify the parameters beyond this line

  // Parameters of Axi Master Bus Interface M00_AXI
  parameter  C_M00_AXI_TARGET_SLAVE_BASE_ADDR = 32'h40000000,
  parameter integer C_M00_AXI_BURST_LEN = 16,
  parameter integer C_M00_AXI_ID_WIDTH = 1,
  parameter integer C_M00_AXI_ADDR_WIDTH = 32,
  parameter integer C_M00_AXI_DATA_WIDTH = 32,
  parameter integer C_M00_AXI_AWUSER_WIDTH = 0,
  parameter integer C_M00_AXI_ARUSER_WIDTH = 0,
  parameter integer C_M00_AXI_WUSER_WIDTH = 0,
  parameter integer C_M00_AXI_RUSER_WIDTH = 0,
  parameter integer C_M00_AXI_BUSER_WIDTH = 0
 )
 (
  // Users to add ports here
  // User ports ends
  // Do not modify the ports beyond this line

  // Ports of Axi Master Bus Interface M00_AXI
  input wire  m00_axi_init_axi_txn,
  output wire  m00_axi_txn_done,
  output wire  m00_axi_error,
  input wire  m00_axi_aclk,
  input wire  m00_axi_aresetn,
  output wire [C_M00_AXI_ID_WIDTH-1 : 0] m00_axi_awid,
  output wire [C_M00_AXI_ADDR_WIDTH-1 : 0] m00_axi_awaddr,
  output wire [7 : 0] m00_axi_awlen,
  output wire [2 : 0] m00_axi_awsize,
  output wire [1 : 0] m00_axi_awburst,
  output wire  m00_axi_awlock,
  output wire [3 : 0] m00_axi_awcache,
  output wire [2 : 0] m00_axi_awprot,
  output wire [3 : 0] m00_axi_awqos,
  output wire [C_M00_AXI_AWUSER_WIDTH-1 : 0] m00_axi_awuser,
  output wire  m00_axi_awvalid,
  input wire  m00_axi_awready,
  output wire [C_M00_AXI_DATA_WIDTH-1 : 0] m00_axi_wdata,
  output wire [C_M00_AXI_DATA_WIDTH/8-1 : 0] m00_axi_wstrb,
  output wire  m00_axi_wlast,
  output wire [C_M00_AXI_WUSER_WIDTH-1 : 0] m00_axi_wuser,
  output wire  m00_axi_wvalid,
  input wire  m00_axi_wready,
  input wire [C_M00_AXI_ID_WIDTH-1 : 0] m00_axi_bid,
  input wire [1 : 0] m00_axi_bresp,
  input wire [C_M00_AXI_BUSER_WIDTH-1 : 0] m00_axi_buser,
  input wire  m00_axi_bvalid,
  output wire  m00_axi_bready,
  output wire [C_M00_AXI_ID_WIDTH-1 : 0] m00_axi_arid,
  output wire [C_M00_AXI_ADDR_WIDTH-1 : 0] m00_axi_araddr,
  output wire [7 : 0] m00_axi_arlen,
  output wire [2 : 0] m00_axi_arsize,
  output wire [1 : 0] m00_axi_arburst,
  output wire  m00_axi_arlock,
  output wire [3 : 0] m00_axi_arcache,
  output wire [2 : 0] m00_axi_arprot,
  output wire [3 : 0] m00_axi_arqos,
  output wire [C_M00_AXI_ARUSER_WIDTH-1 : 0] m00_axi_aruser,
  output wire  m00_axi_arvalid,
  input wire  m00_axi_arready,
  input wire [C_M00_AXI_ID_WIDTH-1 : 0] m00_axi_rid,
  input wire [C_M00_AXI_DATA_WIDTH-1 : 0] m00_axi_rdata,
  input wire [1 : 0] m00_axi_rresp,
  input wire  m00_axi_rlast,
  input wire [C_M00_AXI_RUSER_WIDTH-1 : 0] m00_axi_ruser,
  input wire  m00_axi_rvalid,
  output wire  m00_axi_rready
 );

    
// Instantiation of Axi Bus Interface M00_AXI
 myip_burst_test_v1_0_M00_AXI # ( 
  .C_M_TARGET_SLAVE_BASE_ADDR(C_M00_AXI_TARGET_SLAVE_BASE_ADDR),
  .C_M_AXI_BURST_LEN(C_M00_AXI_BURST_LEN),
  .C_M_AXI_ID_WIDTH(C_M00_AXI_ID_WIDTH),
  .C_M_AXI_ADDR_WIDTH(C_M00_AXI_ADDR_WIDTH),
  .C_M_AXI_DATA_WIDTH(C_M00_AXI_DATA_WIDTH),
  .C_M_AXI_AWUSER_WIDTH(C_M00_AXI_AWUSER_WIDTH),
  .C_M_AXI_ARUSER_WIDTH(C_M00_AXI_ARUSER_WIDTH),
  .C_M_AXI_WUSER_WIDTH(C_M00_AXI_WUSER_WIDTH),
  .C_M_AXI_RUSER_WIDTH(C_M00_AXI_RUSER_WIDTH),
  .C_M_AXI_BUSER_WIDTH(C_M00_AXI_BUSER_WIDTH)
 ) myip_burst_test_v1_0_M00_AXI_inst (
  .INIT_AXI_TXN(m00_axi_init_axi_txn),
  .TXN_DONE(m00_axi_txn_done),
  .ERROR(m00_axi_error),
  .M_AXI_ACLK(m00_axi_aclk),
  .M_AXI_ARESETN(m00_axi_aresetn),
  .M_AXI_AWID(m00_axi_awid),
  .M_AXI_AWADDR(m00_axi_awaddr),
  .M_AXI_AWLEN(m00_axi_awlen),
  .M_AXI_AWSIZE(m00_axi_awsize),
  .M_AXI_AWBURST(m00_axi_awburst),
  .M_AXI_AWLOCK(m00_axi_awlock),
  .M_AXI_AWCACHE(m00_axi_awcache),
  .M_AXI_AWPROT(m00_axi_awprot),
  .M_AXI_AWQOS(m00_axi_awqos),
  .M_AXI_AWUSER(m00_axi_awuser),
  .M_AXI_AWVALID(m00_axi_awvalid),
  .M_AXI_AWREADY(m00_axi_awready),
  .M_AXI_WDATA(m00_axi_wdata),
  .M_AXI_WSTRB(m00_axi_wstrb),
  .M_AXI_WLAST(m00_axi_wlast),
  .M_AXI_WUSER(m00_axi_wuser),
  .M_AXI_WVALID(m00_axi_wvalid),
  .M_AXI_WREADY(m00_axi_wready),
  .M_AXI_BID(m00_axi_bid),
  .M_AXI_BRESP(m00_axi_bresp),
  .M_AXI_BUSER(m00_axi_buser),
  .M_AXI_BVALID(m00_axi_bvalid),
  .M_AXI_BREADY(m00_axi_bready),
  .M_AXI_ARID(m00_axi_arid),
  .M_AXI_ARADDR(m00_axi_araddr),
  .M_AXI_ARLEN(m00_axi_arlen),
  .M_AXI_ARSIZE(m00_axi_arsize),
  .M_AXI_ARBURST(m00_axi_arburst),
  .M_AXI_ARLOCK(m00_axi_arlock),
  .M_AXI_ARCACHE(m00_axi_arcache),
  .M_AXI_ARPROT(m00_axi_arprot),
  .M_AXI_ARQOS(m00_axi_arqos),
  .M_AXI_ARUSER(m00_axi_aruser),
  .M_AXI_ARVALID(m00_axi_arvalid),
  .M_AXI_ARREADY(m00_axi_arready),
  .M_AXI_RID(m00_axi_rid),
  .M_AXI_RDATA(m00_axi_rdata),
  .M_AXI_RRESP(m00_axi_rresp),
  .M_AXI_RLAST(m00_axi_rlast),
  .M_AXI_RUSER(m00_axi_ruser),
  .M_AXI_RVALID(m00_axi_rvalid),
  .M_AXI_RREADY(m00_axi_rready)
 );




 // Add user logic here

 // User logic ends

 endmodule


In the ports section you will see all the AXI Master signals defined. In the code it also provides the user a couple of places to add some custom code or ports.

Towards the end you will see an instantiation of the AXI bus interface with the instance name myip_burst_test_v1_0_M00_AXI_inst. We will need change this instance to an instance of the AXI Master Burst Core, which we covered in the previous post.

It should be noted that by default the AXI Master Burst Core is not available in Vivado to use in your design. There is a couple of hoops you need to jump through to add it to your design, so let us quickly jump through them :-)

Firstly you need to locate the source file for AXI Master Burst Core within your Vivado installation. In my installation it is located in the folder Vivado/2017.1/data/ip/xilinx. The folder 2017.1 will be different in your case. It should be the version number of Vivado you are using.

Now, within the xilinx folder you will see folders for different IP's. The folder we are interested in is axi_master_burst_v2_0. Moving into this folder you will see three folders: doc, hdl and xgui.

When you move into the hdl folder you will see a file called axi_master_burst_v2_0_vh_rfs.vhd and this is the file we are after. A vhd file is also a Hardware Description language file as Verilog is one. Vivado can work with both formats.

You now need to add the file as a design source to your current IP Project.

The vhd file you have added have some additional dependencies that you need to add, which can also be located in the xilinx folder. You need to add the vhd files for these dependencies in a similar way:  lib_pkg_v1_0 and lib_srl_fifo_v1_0.

With all dependencies added let us see if axi_master_burst_v2_0_vh_rfs.vhd open up without any errors. This time around we are not lucky:


It complains about a module not found in library lib_srl_fifo, with a hint that the library might not exist. This is despite the fact that we added lib_srl_fifo_v1_0 to our project.

What is going on? The key to this question is the library names for three files. To see the library name for these files click on the Libraries tab within the Sources Panel:


As seen from the diagram all three vhd files with a library name of xil_defaultlib. We need to change the library name for lib_srl_fifo. So click on this file and click on the Ellipse of the library attribute within the Source File Panel. Change the library name ass follows:


The other vhd files should changed in a similar way. The resulting library tab will look as follows:

If you now close down all the source edit panels for these vhd files and reopen them, no errors should be reported.

We are now ready to change our IP to use an instance of AXI Master Burst. The final Verilog file will look as follows:

`timescale 1 ns / 1 ps

 module myip_burst_test_v1_0 #
 (
  // Users to add parameters here

  // User parameters ends
  // Do not modify the parameters beyond this line

  // Parameters of Axi Master Bus Interface M00_AXI
  parameter  C_M00_AXI_TARGET_SLAVE_BASE_ADDR = 32'h40000000,
  parameter integer C_M00_AXI_BURST_LEN = 16,
  parameter integer C_M00_AXI_ID_WIDTH = 1,
  parameter integer C_M00_AXI_ADDR_WIDTH = 32,
  parameter integer C_M00_AXI_DATA_WIDTH = 32,
  parameter integer C_M00_AXI_AWUSER_WIDTH = 0,
  parameter integer C_M00_AXI_ARUSER_WIDTH = 0,
  parameter integer C_M00_AXI_WUSER_WIDTH = 0,
  parameter integer C_M00_AXI_RUSER_WIDTH = 0,
  parameter integer C_M00_AXI_BUSER_WIDTH = 0
 )
 (
  // Users to add ports here
        input wire [31:0] ip2bus_mst_addr,
        input wire [11:0] ip2bus_mst_length,
        input wire [31:0] ip2bus_mstwr_d,
        input wire [4:0] ip2bus_inputs,
        output wire [5:0] ip2bus_otputs,
  // User ports ends
  // Do not modify the ports beyond this line

  // Ports of Axi Master Bus Interface M00_AXI
  input wire  m00_axi_init_axi_txn,
  output wire  m00_axi_txn_done,
  output wire  m00_axi_error,
  input wire  m00_axi_aclk,
  input wire  m00_axi_aresetn,
  output wire [C_M00_AXI_ID_WIDTH-1 : 0] m00_axi_awid,
  output wire [C_M00_AXI_ADDR_WIDTH-1 : 0] m00_axi_awaddr,
  output wire [7 : 0] m00_axi_awlen,
  output wire [2 : 0] m00_axi_awsize,
  output wire [1 : 0] m00_axi_awburst,
  output wire  m00_axi_awlock,
  output wire [3 : 0] m00_axi_awcache,
  output wire [2 : 0] m00_axi_awprot,
  output wire [3 : 0] m00_axi_awqos,
  output wire [C_M00_AXI_AWUSER_WIDTH-1 : 0] m00_axi_awuser,
  output wire  m00_axi_awvalid,
  input wire  m00_axi_awready,
  output wire [C_M00_AXI_DATA_WIDTH-1 : 0] m00_axi_wdata,
  output wire [C_M00_AXI_DATA_WIDTH/8-1 : 0] m00_axi_wstrb,
  output wire  m00_axi_wlast,
  output wire [C_M00_AXI_WUSER_WIDTH-1 : 0] m00_axi_wuser,
  output wire  m00_axi_wvalid,
  input wire  m00_axi_wready,
  input wire [C_M00_AXI_ID_WIDTH-1 : 0] m00_axi_bid,
  input wire [1 : 0] m00_axi_bresp,
  input wire [C_M00_AXI_BUSER_WIDTH-1 : 0] m00_axi_buser,
  input wire  m00_axi_bvalid,
  output wire  m00_axi_bready,
  output wire [C_M00_AXI_ID_WIDTH-1 : 0] m00_axi_arid,
  output wire [C_M00_AXI_ADDR_WIDTH-1 : 0] m00_axi_araddr,
  output wire [7 : 0] m00_axi_arlen,
  output wire [2 : 0] m00_axi_arsize,
  output wire [1 : 0] m00_axi_arburst,
  output wire  m00_axi_arlock,
  output wire [3 : 0] m00_axi_arcache,
  output wire [2 : 0] m00_axi_arprot,
  output wire [3 : 0] m00_axi_arqos,
  output wire [C_M00_AXI_ARUSER_WIDTH-1 : 0] m00_axi_aruser,
  output wire  m00_axi_arvalid,
  input wire  m00_axi_arready,
  input wire [C_M00_AXI_ID_WIDTH-1 : 0] m00_axi_rid,
  input wire [C_M00_AXI_DATA_WIDTH-1 : 0] m00_axi_rdata,
  input wire [1 : 0] m00_axi_rresp,
  input wire  m00_axi_rlast,
  input wire [C_M00_AXI_RUSER_WIDTH-1 : 0] m00_axi_ruser,
  input wire  m00_axi_rvalid,
  output wire  m00_axi_rready
 );

    //inputs
    wire ip2bus_mstwr_req;
    wire ip2bus_mst_type;
    
    wire ip2bus_mstwr_sof_n;
    wire ip2bus_mstwr_src_rdy_n;
    //outputs
    wire bus2ip_mst_cmdack;
    wire bus2ip_mst_cmplt;
    wire bus2ip_mst_error;
    wire ip2bus_mstwr_eof_n;
    wire bus2ip_mstwr_dst_dsc_n;
    wire bus2ip_mstwr_dst_rdy_n;
    wire md_error;
    
    

axi_master_burst myip_burst_test_v1_0_M00_AXI_inst
  (

    .m_axi_aclk(m00_axi_aclk),

    .m_axi_aresetn(m00_axi_aresetn),

    .md_error(md_error),

    .m_axi_arready(m00_axi_arready),
    .m_axi_arvalid(m00_axi_arvalid),
    .m_axi_araddr(m00_axi_araddr),
    .m_axi_arlen(m00_axi_arlen),
    .m_axi_arsize(m00_axi_arsize),
    .m_axi_arburst(m00_axi_arburst),
    .m_axi_arprot(m00_axi_arprot),
    .m_axi_arcache(m00_axi_arcache),
    .m_axi_rready(m00_axi_rready),
    .m_axi_rvalid(m00_axi_rvalid),
    .m_axi_rdata(m00_axi_rdata),
    .m_axi_rresp(m00_axi_rresp),
    .m_axi_rlast(m00_axi_rlast),

    .m_axi_awready(m00_axi_awready),
    .m_axi_awvalid(m00_axi_awvalid),
    .m_axi_awaddr(m00_axi_awaddr),
    .m_axi_awlen(m00_axi_awlen),
    .m_axi_awsize(m00_axi_awsize),
    .m_axi_awburst(m00_axi_awburst),
    .m_axi_awprot(m00_axi_awprot),
    .m_axi_awcache(m00_axi_awcache),
    .m_axi_wready(m00_axi_wready),
    .m_axi_wvalid(m00_axi_wvalid),
    .m_axi_wdata(m00_axi_wdata),
               
    .m_axi_wstrb(m00_axi_wstrb),
    .m_axi_wlast(m00_axi_wlast),
    .m_axi_bready(m00_axi_bready),
    .m_axi_bvalid(m00_axi_bvalid),
    .m_axi_bresp(m00_axi_bresp),

    .ip2bus_mstrd_req(1'b0), 
    .ip2bus_mstwr_req(ip2bus_mstwr_req), 
    .ip2bus_mst_addr(ip2bus_mst_addr),
    .ip2bus_mst_length(ip2bus_mst_length),
    .ip2bus_mst_type(ip2bus_mst_type),
    .ip2bus_mst_lock(1'b0),
    .ip2bus_mst_reset(1'b0),

    .bus2ip_mst_cmdack(bus2ip_mst_cmdack),
    .bus2ip_mst_cmplt(bus2ip_mst_cmplt),
    .bus2ip_mst_error(bus2ip_mst_error),

    .ip2bus_mstwr_d(ip2bus_mstwr_d),
    .ip2bus_mstwr_rem(4'h0),
    .ip2bus_mstwr_sof_n(ip2bus_mstwr_sof_n),
    .ip2bus_mstwr_eof_n(ip2bus_mstwr_eof_n),
    .ip2bus_mstwr_src_rdy_n(ip2bus_mstwr_src_rdy_n),
    .ip2bus_mstwr_src_dsc_n(1'b1),

    .bus2ip_mstwr_dst_rdy_n(bus2ip_mstwr_dst_rdy_n),
    .bus2ip_mstwr_dst_dsc_n(bus2ip_mstwr_dst_dsc_n)

    );



 // Add user logic here
 
 

    //inputs
    assign ip2bus_mstwr_req = ip2bus_inputs[0];
    assign ip2bus_mst_type = ip2bus_inputs[1];
    
    assign ip2bus_mstwr_sof_n = ip2bus_inputs[2];
    assign ip2bus_mstwr_eof_n = ip2bus_otputs[3];
    assign ip2bus_mstwr_src_rdy_n = ip2bus_inputs[4];
    //outputs
    assign ip2bus_otputs[0] = bus2ip_mst_cmdack;
    assign ip2bus_otputs[1] = bus2ip_mst_cmplt;
    assign ip2bus_otputs[2] = bus2ip_mst_error;
    
    assign ip2bus_otputs[3] = bus2ip_mstwr_dst_dsc_n;
    assign ip2bus_otputs[4] = bus2ip_mstwr_dst_rdy_n;
    assign ip2bus_otputs[5] = md_error;
    assign m00_axi_aruser[0] = 1'b0;
    assign m00_axi_awuser[0] = 1'b1;
    assign m00_axi_arlock = 1'b0;
    assign m00_axi_awlock = 1'b0;
    assign m00_axi_arqos = 15;
    assign m00_axi_awqos = 15;
    assign m00_axi_arid[0] = 0;
 // User logic ends

 endmodule


You will see that we hooked up most of the Master AXI ports to our Burst instance and some are unconnected.

Some AXI Master ports are required, but not present on the AXI Master Burst instance. For these ports we have hooked up to constant values.

You will also notice the ip2bus_inputs and ip2bus_outputs ports, similar that we defined for a module we defined in the previous post.

With all these changes performed you can now package your AXI enabled IP.

Hooking everything up

With the IP we have developed in the previous post and the AXI enabled IP we developed in the previous section, we are now ready to create the full block design.

Since we need a ZYNQ Processing System block to access the SDRAM, we will start off adding this Block together with auto connection.

Next, we need to add the two IP's we have created in the previous section and in the previous post.

With these changes done, your block diagram should more or less like the following:


One thing that is missing is an AXI slave port on the ZYNQ Block to connect he AXI Master port of our Burst Block. Slave ports on the ZYNQ need to be enabled via ZYNQ IP Customization.

So, double click on the ZYNQ block and then on PS-PL Configuration in the left panel.  In this screenshot I have highlighted the type of slave ports available on the ZYNQ:


These Salve ports are covered in detail in the ZYNQ Technical Reference Manual, but let us try to summarise the purpose of each:


  • General Purpose (GP) Slave interface: Only for general purpose use and not intended to achieve high performance
  • High Performance (HP) Slave Interface: Provides high bandwidth datapaths to the DDR
  • Accelerator Coherency port (ACP) Slave Interface: Low latency access to Masters, with optional coherency with L1 and L2 cache. 
The question now is which AXI Slave port should we enable? We should keep in mind that in the beginning we will inspect memory most of the time with the XSCT console within the Xilinx SDK. The XSCT always see data from the same perspective of the active CPU core via the L1 cache.

With the XSCT console in mind, the ACP slave port will fit our needs perfectly. With the ACP port we don't need to worry about seeing stale L1 cache values, since if we write data to DDR via ACP, our L1 cache will contain also contain the written data. A Snoop controller (https://en.wikipedia.org/wiki/Bus_snooping) takes care of this in the background.

Let us enable the ACP Slave port as follows:


You will see that our ZYNQ Block now have a Slave port as well as Slave Clock pin, all on the left:

Time again to do some connection automation for the AXI Slave port on the ZYNQ. The result will look something like the following:


You will see that the Connection Automation introduced a new Block, an AXI SmartConnect.

Next we need to wire up all the ip2bus wires:


Everything looks connected now, except for the write and write_data ports of burst_block_0. We need to create an IP block that can generate test data feeding these wires.

The verilog code for this new IP will look like the following:

module test_data_gen(
  input wire reset,
  input wire clk,
  output wire write,
  output wire [31:0] data_out
    );
    
  parameter
      INITIALISED = 4'h0,
      STARTED = 4'h1,
      STOPPED = 4'h2;

  reg [39:0] init_counter;
  reg [31:0] data_counter;
  reg [2:0] state;
  
  always @(posedge clk)
    if (!reset)
      state <= INITIALISED;
    else
      case(state)
        INITIALISED: if (init_counter > 40'd20000000000)
          state <= STARTED;
        STARTED: if (data_counter > 20)
          state <= STOPPED;
       endcase
       
  always @(posedge clk)
    if (!reset)
      init_counter <= 0;
    else
      init_counter <= init_counter + 1;
      
  always @(posedge clk)
    if (!reset)
      data_counter <= 0;
    else if(state == STARTED)
      data_counter <= data_counter + 1;
      
   assign write = state == STARTED ? 1 : 0;
   assign data_out = data_counter;
             
endmodule


Nothing more than a state machine waiting some time before sending data. Assuming a clock speed of 100MHZ, this block will wait about 3 minutes before sending data. This give you enough time to get everything in the Xilinx SDK up and running.

Add the New IP to you rblock design and link up with the write and write_data ports.

You are now ready to Run Synthesis and then Generating the Bitstream.

After generating the BitStream remember to Export the Hardware. You can then launch the Xilinx SDK.

Testing

We are now ready to test our design within the Xilinx SDK.

For our testing we just need a plain HelloWorld App. The app is just required to get one of the CPU cores in a runnable state so that we can use the XCST console.

Ensure that the FPGA is programmed and fire off the HelloWorld App in Debug Mode.

Wait till the first breakpoint fires and switch to the XCT Console. At this point your SDK window will look something like the following:


CPU Core#0 is at a breakpoint within the Main function.

Within above screenshot I have also marked two important areas. The first one is where you can locate the XCST console tab.

The second area is the actual XCST console where you will be entering the command.

At this point in time there would be still a number of minutes left before our FPGA core will write some information to SDRAM.

Our FPGA will be writing information to memory location 0. So this is the location to monitor with the XSCT console. To see the current contents of this memory location enter the following command in the XSCT console:

mrd 0

As you might have guest, mrd stands for memory read.

The output of this command will look something like the following:


It is expected to see some initial random number. After about three and a half minutes our FPGA core should have written the values 0 to 4 in the first four memory locations.

Let us run the mrd command again, but this time let it return more values by specifying it as follows:

mrd 0 8

This variant of the command states that the contents of eight consecutive memory locations should be returned starting at location 0.

The output of the command should look as follows:


This shows that our FPGA core actually managed to write successfully to SDRAM!

A final note on ACP and Caching

Before ending off this section, I just would like to mention something interesting regarding ACP and Caching.

If you look at section 3.5.1 of the ZYNQ technical reference manual, you see the following:

ACP coherent write requests: An ACP write request is coherent when AWUSER[0] = 1 and AWCACHE[1] =1 alongside AWVALID. In this case, the SCU enforces coherency. When the data is present in one of the Cortex-A9 processors, the data is first cleaned and invalidated from the relevant CPU. When the data is not present in any of the Cortex-A9 processors, or when it has been cleaned and invalidated, the write request is issued on one of the SCU AXI master ports, along with all corresponding AXI parameters with the exception of the locked attribute.
Our design has enabled ACP coherent writes: The AXI Master Burst core always set AWCACHE[1] = 1 and within our AXI enabled IP we set AWUSER[0] high with this assignment:

assign m00_axi_awuser[0] = 1'b1;

Assigning a zero this signal will effectively disable ACP coherent writes.

If we ran the steps in the previous section with the user signal set to zero, the L1 cache would cling to the value 9FA90DDC upon the first read and wouldn't pick up the changes our FPGA core made to that memory location.

So, be careful of caching!

In Summary

In this post we completed the Block Design for testing writing to SDRAM from an FPGA core.

We also confirmed via the XSCT console that the write was indeed successful.

In the next post we will start to develop a VIC-II core.

Till next time!

3 comments:

  1. Hi!

    I couldn't get it working.. My Vivado (2018.2)is warning about a circular reference in '/axi_master_burst_v2_0_7/axi_master_burst_fifo/imp', and I'm guessing that maybe be that is the problem. Did you had the same warning? Thank you!

    ReplyDelete
  2. With the AXI blocks you can sometimes end up with quite a fiddly business, Especially across Vivado versions. Couldn't find a way to yet to automate the process to reduce the pain :-)

    I can however give you a pointer to where start looking from the error message that you have posted. axi_master_burst_v2_0_7 is usually a component supplied with Vivado. You can perhaps try to add it to your source as described in this post.

    ReplyDelete
  3. Hi Johan,

    Its working now. Actually it was already working, the problem the circular reference was causing was unabling me to add the IP to my block design, I must say.. in the "graphical way". But I managed to add him in my block design using tcl commands and it worked. Thank you for aswering and for all these great content on your blog.

    ReplyDelete