Part 7: Access to DDR from PL
Last updated
Last updated
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.
This repository contains all of the code required in order to follow this tutorial.
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.
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
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.
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
.
`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
This is the code for axi_img_inv_ctrl.v
.
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
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.
The following figure shows the Zynq IP high-performance port configuration. There are two pots enabled, which are AXI HP0 and AXI HP2.
The following figure shows the AXI Data Mover configuration.
This is the final block design diagram as shown in Vivado.
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
.
# 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.
# 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
.
Process the image bytes with the image invert module.
# 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.
# 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
.
This video contains detailed steps for making this project.
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.