> For the complete documentation index, see [llms.txt](https://weenslab.gitbook.io/pages/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://weenslab.gitbook.io/pages/fpga-tutorials/pynq-fpga-tutorial-102/part-7-access-to-ddr-from-pl.md).

# Part 7: Access to DDR from PL

## Objective

This tutorial contains information on how to create a module in PL that can access to DDR memory without AXI DMA. The module does a black and white image invert operation.

## Source Code

This repository contains all of the code required in order to follow this tutorial.

{% embed url="<https://github.com/weenslab/pynq102/tree/main>" %}

***

## 1. Hardware Design

### 1.1. RTL Design of Image Invert Core

This RTL module does a simple image invert operation for black and white image. It has one 8-bit input and one 8-bit output. Every input will be subtracted from 255 to produce the output.

{% code title="axis\_img\_inv.v" lineNumbers="true" %}

```verilog
module axis_img_inv
    (
        // ### Clock and reset signals #########################################
        input  wire       aclk,
        input  wire       aresetn,
        // ### AXI4-stream slave signals #######################################
        output wire       s_axis_tready,
        input wire [7:0]  s_axis_tdata,
        input wire [0:0]  s_axis_tkeep,
        input wire        s_axis_tvalid,
        input wire        s_axis_tlast,
        // ### AXI4-stream master signals ######################################
        input wire        m_axis_tready,
        output wire [7:0] m_axis_tdata,
        output wire [0:0] m_axis_tkeep,
        output wire       m_axis_tvalid,
        output wire       m_axis_tlast
    );
    
    assign s_axis_tready = m_axis_tready;
    assign m_axis_tdata = 255 - s_axis_tdata;
    assign m_axis_tkeep = s_axis_tkeep;
    assign m_axis_tvalid = s_axis_tvalid;
    assign m_axis_tlast = s_axis_tlast;
    
endmodule
```

{% endcode %}

### 1.2. Controller to Access DDR Memory

To access DDR memory from PL, we are going to use AXI Data Mover IP. This IP handles the AXI-Full protocol to provide access to DDR memory.

<figure><img src="/files/EZwl30Qg25jWFu2BCZ31" alt="" width="324"><figcaption></figcaption></figure>

To use the AXI Data Mover IP, we need a simple state machine that generated instruction to control the AXI Data Mover IP.

The state machine consists of core (**img\_inv\_ctrl.v**) and AXI-Lite wrapper (**axi\_img\_inv\_ctrl.v**).

This is the code for `img_inv_ctrl.v`.

{% code title="img\_inv\_ctrl.v" lineNumbers="true" %}

```verilog
`timescale 1ns / 1ps

module img_inv_ctrl
    (
        input wire         clk,
        input wire         rst_n,
        output wire        ready,
        input wire [31:0]  saddr,
        input wire [31:0]  daddr,
        input wire [31:0]  btt,
        input wire         start,
        output wire [71:0] m_axis_mm2s_cmd_tdata,
        output wire        m_axis_mm2s_cmd_tvalid,
        output wire [71:0] m_axis_s2mm_cmd_tdata,
        output wire        m_axis_s2mm_cmd_tvalid,
        input wire         s2mm_wr_xfer_cmplt
    );
    
    reg [2:0] state_reg, state_next;
    
    always @(posedge clk)
    begin
        if (!rst_n)
        begin
            state_reg <= 0;
        end
        else
        begin
            state_reg <= state_next;
        end
    end
    
    always @(*)
    begin
        state_next = state_reg;
        case (state_reg)
            0: // Wait for start from PS
            begin
                if (start)
                begin
                    state_next = 1;
                end
            end
            1: // Send S2MM instruction
            begin
                state_next = 2;
            end
            2: // Send MM2S instruction
            begin
                state_next = 3;
            end
            3: // Wait until S2MM transfer is completed
            begin
                if (s2mm_wr_xfer_cmplt)
                begin
                    state_next = 0; // Back to idle
                end
            end
        endcase
    end
    
    assign ready = (state_reg == 0) ? 1 : 0;
    
    assign m_axis_s2mm_cmd_tdata = {8'b00000000, daddr, 1'b0, 1'b1, 6'b000000, 1'b1, btt[22:0]}; // S2MM instruction
    assign m_axis_s2mm_cmd_tvalid = (state_reg == 1) ? 1 : 0; // Send S2MM instruction
    assign m_axis_mm2s_cmd_tdata = {8'b00000000, saddr, 1'b0, 1'b1, 6'b000000, 1'b1, btt[22:0]}; // MM2S instruction
    assign m_axis_mm2s_cmd_tvalid = (state_reg == 2) ? 1 : 0; // Send MM2S instruction
    
endmodule
```

{% endcode %}

This is the code for `axi_img_inv_ctrl.v`.

{% code title="axi\_img\_inv\_ctrl.v" lineNumbers="true" %}

```verilog
module axi_img_inv_ctrl
    (
        // ### Clock and reset signals #########################################
        input  wire        aclk,
        input  wire        aresetn,
        // ### AXI4-lite slave signals #########################################
        // *** Write address signals ***
        output wire        s_axi_awready,
        input  wire [31:0] s_axi_awaddr,
        input  wire        s_axi_awvalid,
        // *** Write data signals ***
        output wire        s_axi_wready,
        input  wire [31:0] s_axi_wdata,
        input  wire [3:0]  s_axi_wstrb,
        input  wire        s_axi_wvalid,
        // *** Write response signals ***
        input  wire        s_axi_bready,
        output wire [1:0]  s_axi_bresp,
        output wire        s_axi_bvalid,
        // *** Read address signals ***
        output wire        s_axi_arready,
        input  wire [31:0] s_axi_araddr,
        input  wire        s_axi_arvalid,
        // *** Read data signals ***	
        input  wire        s_axi_rready,
        output wire [31:0] s_axi_rdata,
        output wire [1:0]  s_axi_rresp,
        output wire        s_axi_rvalid,
        // ### User signals ####################################################
        output wire [71:0] m_axis_mm2s_cmd_tdata,
        output wire        m_axis_mm2s_cmd_tvalid,
        output wire [71:0] m_axis_s2mm_cmd_tdata,
        output wire        m_axis_s2mm_cmd_tvalid,
        input wire         s2mm_wr_xfer_cmplt
    );

    // ### Register map ########################################################
    // 0x00: source address
    //       bit 31~0 = saddr[31:0] (R/W)
    // 0x04: destination address
    //       bit 31~0 = daddr[31:0] (R/W)
    // 0x08: byte to transfer
    //       bit 22~0 = btt[22:0] (R/W)
    // 0x0c: control
    //       bit 0 = START (R/W)
    //       bit 1 = READY (R)
    localparam C_ADDR_BITS = 8;
    //    // *** Address (32-bit) ***
//    localparam C_ADDR_SADDR = 8'h00,
//               C_ADDR_DADDR = 8'h04,
//               C_ADDR_BTT   = 8'h08,
//               C_ADDR_CTRL  = 8'h0c;
    // *** Address (40-bit) ***
    localparam C_ADDR_SADDR = 8'h00,
               C_ADDR_DADDR = 8'h08,
               C_ADDR_BTT   = 8'h10,
               C_ADDR_CTRL  = 8'h18;
    // *** AXI write FSM ***
    localparam S_WRIDLE = 2'd0,
               S_WRDATA = 2'd1,
               S_WRRESP = 2'd2;
    // *** AXI read FSM ***
    localparam S_RDIDLE = 2'd0,
               S_RDDATA = 2'd1;
    
    // *** AXI write ***
    reg [1:0] wstate_cs, wstate_ns;
    reg [C_ADDR_BITS-1:0] waddr;
    wire [31:0] wmask;
    wire aw_hs, w_hs;
    // *** AXI read ***
    reg [1:0] rstate_cs, rstate_ns;
    wire [C_ADDR_BITS-1:0] raddr;
    reg [31:0] rdata;
    wire ar_hs;
    // *** Control registers ***
    reg start_reg;
    wire ready_w;
    reg [31:0] saddr_reg;
    reg [31:0] daddr_reg;
    reg [31:0] btt_reg;
    
    // ### AXI write ###########################################################
    assign s_axi_awready = (wstate_cs == S_WRIDLE);
    assign s_axi_wready = (wstate_cs == S_WRDATA);
    assign s_axi_bresp = 2'b00;    // OKAY
    assign s_axi_bvalid = (wstate_cs == S_WRRESP);
    assign wmask = {{8{s_axi_wstrb[3]}}, {8{s_axi_wstrb[2]}}, {8{s_axi_wstrb[1]}}, {8{s_axi_wstrb[0]}}};
    assign aw_hs = s_axi_awvalid & s_axi_awready;
    assign w_hs = s_axi_wvalid & s_axi_wready;

    // *** Write state register ***
    always @(posedge aclk)
    begin
        if (!aresetn)
            wstate_cs <= S_WRIDLE;
        else
            wstate_cs <= wstate_ns;
    end
    
    // *** Write state next ***
    always @(*)
    begin
        case (wstate_cs)
            S_WRIDLE:
                if (s_axi_awvalid)
                    wstate_ns = S_WRDATA;
                else
                    wstate_ns = S_WRIDLE;
            S_WRDATA:
                if (s_axi_wvalid)
                    wstate_ns = S_WRRESP;
                else
                    wstate_ns = S_WRDATA;
            S_WRRESP:
                if (s_axi_bready)
                    wstate_ns = S_WRIDLE;
                else
                    wstate_ns = S_WRRESP;
            default:
                wstate_ns = S_WRIDLE;
        endcase
    end
    
    // *** Write address register ***
    always @(posedge aclk)
    begin
        if (aw_hs)
            waddr <= s_axi_awaddr[C_ADDR_BITS-1:0];
    end
    
    // ### AXI read ############################################################
    assign s_axi_arready = (rstate_cs == S_RDIDLE);
    assign s_axi_rdata = rdata;
    assign s_axi_rresp = 2'b00;    // OKAY
    assign s_axi_rvalid = (rstate_cs == S_RDDATA);
    assign ar_hs = s_axi_arvalid & s_axi_arready;
    assign raddr = s_axi_araddr[C_ADDR_BITS-1:0];
    
    // *** Read state register ***
    always @(posedge aclk)
    begin
        if (!aresetn)
            rstate_cs <= S_RDIDLE;
        else
            rstate_cs <= rstate_ns;
    end

    // *** Read state next ***
    always @(*) 
    begin
        case (rstate_cs)
            S_RDIDLE:
                if (s_axi_arvalid)
                    rstate_ns = S_RDDATA;
                else
                    rstate_ns = S_RDIDLE;
            S_RDDATA:
                if (s_axi_rready)
                    rstate_ns = S_RDIDLE;
                else
                    rstate_ns = S_RDDATA;
            default:
                rstate_ns = S_RDIDLE;
        endcase
    end
    
    // *** Read data register ***
    always @(posedge aclk)
    begin
        if (!aresetn)
            rdata <= 0;
        else if (ar_hs)
            case (raddr)
                C_ADDR_SADDR: 
                    rdata <= saddr_reg;
                C_ADDR_DADDR: 
                    rdata <= daddr_reg;
                C_ADDR_BTT:
                    rdata <= btt_reg;
                C_ADDR_CTRL:
                    rdata <= {{30{1'b0}}, ready_w, start_reg};
            endcase
    end
    
    // ### User design #########################################################
    // *** Start register ***
    always @(posedge aclk)
    begin
        if (!aresetn)
        begin
            start_reg <= 0;
        end
        else if (w_hs && waddr == C_ADDR_CTRL && s_axi_wdata[0])
        begin
            start_reg <= 1;
        end
        else
        begin
            start_reg <= 0;
        end
    end

    // *** Register saddr and btt ***
    always @(posedge aclk)
    begin
        if (!aresetn)
        begin
            saddr_reg[31:0] <= 0;
            daddr_reg[31:0] <= 0;
            btt_reg[31:0] <= 0;
        end
        else if (w_hs && waddr == C_ADDR_SADDR)
        begin
            saddr_reg[31:0] <= (s_axi_wdata[31:0] & wmask) | (saddr_reg[31:0] & ~wmask);
        end
        else if (w_hs && waddr == C_ADDR_DADDR)
        begin
            daddr_reg[31:0] <= (s_axi_wdata[31:0] & wmask) | (daddr_reg[31:0] & ~wmask);
        end
        else if (w_hs && waddr == C_ADDR_BTT)
        begin
            btt_reg[31:0] <= (s_axi_wdata[31:0] & wmask) | (btt_reg[31:0] & ~wmask);
        end
    end

    img_inv_ctrl img_inv_ctrl_0
    (
        .clk(aclk),
        .rst_n(aresetn),
        .ready(ready_w),
        .saddr(saddr_reg),
        .daddr(daddr_reg),
        .btt(btt_reg),
        .start(start_reg),
        .m_axis_mm2s_cmd_tdata(m_axis_mm2s_cmd_tdata),
        .m_axis_mm2s_cmd_tvalid(m_axis_mm2s_cmd_tvalid),
        .m_axis_s2mm_cmd_tdata(m_axis_s2mm_cmd_tdata),
        .m_axis_s2mm_cmd_tvalid(m_axis_s2mm_cmd_tvalid),
        .s2mm_wr_xfer_cmplt(s2mm_wr_xfer_cmplt)
    );

endmodule
```

{% endcode %}

### 1.3. System Design

This diagram shows our system. It consists of an ARM CPU, DRAM, AXI Image Invert Control (), AXI Data Mover, and our AXI-Stream image invert module. Our AXI-Stream image invert module is connected to the AXI Data Mover. Between them, we also add AXI-Stream FIFO IP.

<figure><img src="/files/PrY2uvNZuCH2qW5Pc9Hx" alt=""><figcaption></figcaption></figure>

The following figure shows the Zynq IP high-performance port configuration. There are two pots enabled, which are AXI HP0 and AXI HP2.

<figure><img src="/files/77dcCPbpSckdb5ZjvXrR" alt=""><figcaption></figcaption></figure>

The following figure shows the AXI Data Mover configuration.

<figure><img src="/files/XZdVYCqOVTJNRFDxYvHy" alt=""><figcaption></figcaption></figure>

This is the final block design diagram as shown in Vivado.

<figure><img src="/files/Ga86N1aOLb7WWBLJlKKP" alt=""><figcaption></figcaption></figure>

## 2. Software Design

First, we need to create access to the state machine module for the AXI Data Mover. Because it is just a memory map IP, so we use `MMIO`.

```python
# Access to memory map of the axi_inv_img_ctrl 
ADDR_BASE = 0xA0000000
ADDR_RANGE = 0x80
ctrl_obj = MMIO(ADDR_BASE, ADDR_RANGE)
```

Prepare the input image. Declare several black and white image as arrays. The image is MNIST digit image 28x28 pixels.

```python
# MNIST image as text
img_txt_1 = b'0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,124,253,255,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,244,251,253,62,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,251,251,253,62,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,68,236,251,211,31,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,60,228,251,251,94,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,155,253,253,189,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,253,251,235,66,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,205,253,251,126,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,104,251,253,184,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,80,240,251,193,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,253,253,253,159,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,151,251,251,251,39,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,48,221,251,251,172,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,234,251,251,196,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,253,251,251,89,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,159,255,253,253,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,48,228,253,247,140,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,251,253,220,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,251,253,220,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,193,253,220,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0'
img_txt_6 = b'0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,38,222,225,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,147,234,252,176,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,197,253,252,208,19,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,38,178,252,253,117,65,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,57,252,252,253,89,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,38,222,253,253,79,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,131,252,179,27,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,198,246,220,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,79,253,252,135,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,140,253,252,118,0,0,0,0,111,140,140,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,191,255,253,56,0,0,114,113,222,253,253,255,27,0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,252,253,223,37,0,48,174,252,252,242,214,253,199,31,0,0,0,0,0,0,0,0,0,0,0,0,13,109,252,228,130,0,38,165,253,233,164,49,63,253,214,31,0,0,0,0,0,0,0,0,0,0,0,0,73,252,252,126,0,23,178,252,240,148,7,44,215,240,148,0,0,0,0,0,0,0,0,0,0,0,0,0,119,252,252,0,0,197,252,252,63,0,57,252,252,140,0,0,0,0,0,0,0,0,0,0,0,0,0,0,135,253,174,0,48,229,253,112,0,38,222,253,112,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,135,252,173,0,48,227,252,158,226,234,201,27,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,57,252,252,57,104,240,252,252,253,233,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,51,242,252,253,252,252,252,252,240,148,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,75,189,253,252,252,157,112,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0'
img_txt_8 = b'0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,203,229,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,47,47,30,95,254,215,13,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,154,185,185,223,253,253,133,175,255,188,19,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,110,253,253,253,246,161,228,253,253,254,92,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,245,253,158,137,21,0,48,233,253,233,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,139,254,223,25,0,0,36,170,254,244,106,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,55,212,253,161,11,26,178,253,236,113,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,155,253,228,80,223,253,253,109,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,141,253,253,253,254,253,154,29,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,110,253,253,253,254,179,38,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,171,254,254,254,179,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,171,253,253,253,253,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,123,254,253,203,156,253,200,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,93,253,254,121,13,93,253,158,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,239,253,76,8,32,219,253,126,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,133,254,191,0,5,108,234,254,106,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132,253,190,5,85,253,236,154,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,153,253,169,192,253,253,77,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,112,253,253,254,236,129,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,118,243,191,113,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0'

# Convert MNIST image from text to numpy array 
img_bytes = np.array([int(i) for i in img_txt_8.decode('utf-8').split(',')], dtype=np.uint8)
img_bytes.shape

# Convert image array to 2D for plotting
img_bytes_2d = np.reshape(img_bytes, (28, 28))
img_bytes_2d.shape

# Plot original image before inversion
pyplot.imshow(img_bytes_2d, cmap=pyplot.get_cmap('gray'))
```

This is the input image plotted by `pyplot`.

<figure><img src="/files/s2fT1BQte427neG0Yolh" alt=""><figcaption></figcaption></figure>

Process the image bytes with the image invert module.

```python
# Allocate physical memory for AXI data mover MM2S and S2MM
input_buffer = allocate(shape=img_bytes.shape, dtype=np.uint8)
output_buffer = allocate(shape=img_bytes.shape, dtype=np.uint8)

# Copy original image from numpy array to physical memory
np.copyto(input_buffer, img_bytes)
print("Input buffer address   :", hex(input_buffer.physical_address))

# Setup AXI data mover instruction
ctrl_obj.write(0x0, input_buffer.physical_address)
ctrl_obj.write(0x8, output_buffer.physical_address)
ctrl_obj.write(0x10, len(img_bytes))

# Start main controller
ctrl_obj.write(0x18, 1)
# Wait until ready flag is 1
while ((ctrl_obj.read(0x18) & (1 << 1)) == 0):
    pass
```

Read and check the result.

```python
# Convert inverted image array to 2D for plotting
img_bytes_2d_inv = np.reshape(output_buffer, (28, 28))
img_bytes_2d_inv.shape

# Plot image after inversion
pyplot.imshow(img_bytes_2d_inv, cmap=pyplot.get_cmap('gray'))

# Delete buffer to prevent memory leak
del input_buffer, output_buffer
```

This is the output image plotted by `pyplot`.

<figure><img src="/files/E7UqOJOFZMglB3lsiy8O" alt=""><figcaption></figcaption></figure>

## 3. Full Step-by-Step Tutorial

This video contains detailed steps for making this project.

{% embed url="<https://www.youtube.com/watch?v=3iS3HAawebw>" %}

## 4. Conclusion

In this tutorial, we covered a tutorial on how to create a module in PL that can access to DDR memory without AXI DMA, but using AXI Data Mover. This method is suitable if the main control in in PL.
