0% found this document useful (0 votes)
1 views24 pages

VLSI Implementation Guide

Uploaded by

sriramrayana7
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views24 pages

VLSI Implementation Guide

Uploaded by

sriramrayana7
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

COMPLETE IMPLEMENTATION GUIDE

Mixed-Precision MAC Accelerator for Edge CNN Inference


on PYNQ-Z2 FPGA

Xilinx Vivado 2025.1 | IEEE Conference Paper Implementation


Zynq-7020 | AXI-Lite Control | AXI DMA | RTL Verilog

Board Tool Language Target Network

PYNQ-Z2 Vivado 2025.1 Verilog RTL LeNet-5 / CNN


PHASE
PROJECT SETUP & VIVADO CONFIGURATION
1 Install board files, create project, configure part

1.1 Prerequisites & Software Requirements


Requirement Details

Vivado Version Xilinx Vivado 2025.1 (ML Edition recommended)

FPGA Part xc7z020clg400-1 (Zynq-7020)

Board TUL PYNQ-Z2 (Zynq-7000 SoC)

OS Windows 10/11 or Ubuntu 20.04/22.04

RAM Minimum 16 GB (32 GB recommended for synthesis)

Disk Space ~70 GB for Vivado + project files

Python 3.8+ (for PYNQ runtime on board)

PYNQ Image PYNQ v3.0.1 or later on SD card

1.2 Install PYNQ-Z2 Board Files


Before creating the project, you must install the PYNQ-Z2 board definition files into Vivado. Without these,
the board will not appear in the project wizard.

Step 1: Download Board Files


Open a browser and go to:
[Link]
Download the PYNQ-Z2 board files ZIP archive from the TUL/Digilent repository.

Step 2: Locate Vivado Board Files Directory

# Windows path:
C:\Xilinx\Vivado\2025.1\data\boards\board_files\

# Linux path:
/opt/Xilinx/Vivado/2025.1/data/boards/board_files/

Step 3: Copy Board Files


Extract the downloaded ZIP and copy the pynq-z2 folder into the board_files directory. The final path
should look like:

board_files/
pynq-z2/
C.0/
[Link]
part0_pins.xml
[Link]

Step 4: Restart Vivado


Close and reopen Vivado 2025.1. The board will now appear in the board selection dialog.

NOT If board files still don't appear, go to Tools -> Settings -> Board Repository in Vivado and add the
E path to your board_files directory manually.

1.3 Create New Vivado Project


Step 1: Launch Vivado 2025.1
Open Vivado 2025.1. At the Vivado Start page, click Quick Start -> Create Project.

Step 2: Project Wizard - Project Name

Project name: mixed_prec_cnn_accel


Project location: C:/Projects/ (or ~/Projects/ on Linux)
[ ] Create project subdirectory <- CHECK THIS
Click: Next >

Step 3: Project Wizard - Project Type

Select: (o) RTL Project


[ ] Do not specify sources at this time <- CHECK THIS
Click: Next >

Step 4: Project Wizard - Add Sources


Skip for now. We will add Verilog sources manually after project creation.

Step 5: Project Wizard - Default Part / Board

Click: [Boards] tab (not Parts)


Search: pynq
Select: PYNQ-Z2 (TUL Consortium)
Board Revision: 1.0
Click: Next > -> Finish

IMPO Always select the BOARD, not the PART. Selecting the board automatically configures PS
RTA settings, DDR memory, and pin assignments for PYNQ-Z2.
NT
PHASE
VERILOG HDL SOURCE FILES
2 Create all RTL modules - MAC unit, controller, AXI interface

2.1 Add Source Files to Project


In the Vivado Sources panel, right-click Design Sources -> Add Sources -> Add or Create Design
Sources -> Create File. Create each file below with the exact filename shown.

Files to Create
1. mac_unit.v - Reconfigurable 4/8/16-bit MAC processing element
2. conv_layer_ctrl.v - Convolution layer FSM controller (drives MAC array)
3. axi_ctrl_regs.v - AXI-Lite slave for precision mode & start control
4. top_accel.v - Top-level wrapper connecting all modules

2.2 File 1: mac_unit.v - Mixed-Precision MAC Unit


This is the fundamental compute primitive. It performs signed multiply-accumulate in 4-bit, 8-bit, or 16-bit
mode selected by a 2-bit control signal. The mode can be changed per-layer without hardware
reconfiguration.

// ============================================================
// mac_unit.v - Reconfigurable Mixed-Precision MAC Unit
// Supports: 4-bit (INT4), 8-bit (INT8), 16-bit (INT16)
// Target: PYNQ-Z2 (Zynq-7020) @ 100 MHz
// ============================================================
module mac_unit #(
parameter ACCU_WIDTH = 32 // Accumulator always 32-bit
)( input wire clk,
input wire rst_n, // Active-low sync reset
input wire [1:0] mode, // 00=INT4 01=INT8 10=INT16
input wire [15:0] weight_in, // Weight (upper bits ignored in lower modes)
input wire [15:0] act_in, // Activation input
input wire valid_in, // Data valid strobe
input wire clear_acc, // Reset accumulator for new output pixel
output reg [ACCU_WIDTH-1:0] acc_out,
output reg valid_out
);
reg signed [3:0] w4, a4;
reg signed [7:0] w8, a8;
reg signed [15:0] w16, a16;
reg signed [31:0] product;

always @(posedge clk) begin


if (!rst_n) begin
acc_out <= {ACCU_WIDTH{1'b0}};
valid_out <= 1'b0;
end else begin
if (clear_acc) acc_out <= {ACCU_WIDTH{1'b0}};
valid_out <= valid_in;
if (valid_in) begin
case (mode)
2'b00: begin // INT4
w4 = $signed(weight_in[3:0]);
a4 = $signed(act_in[3:0]);
product = {{28{w4[3]}},w4} * {{28{a4[3]}},a4};
end
2'b01: begin // INT8
w8 = $signed(weight_in[7:0]);
a8 = $signed(act_in[7:0]);
product = {{24{w8[7]}},w8} * {{24{a8[7]}},a8};
end
2'b10: begin // INT16
w16 = $signed(weight_in);
a16 = $signed(act_in);
product = w16 * a16;
end
default: product = 32'sd0;
endcase
acc_out <= acc_out + product[ACCU_WIDTH-1:0];
end
end
end
endmodule

DSP On Zynq-7020, each DSP48E1 block is 25x18 (18-bit signed multiplier). The 16-bit mode maps 1:1.
MAP The 8-bit mode packs two MACs per DSP. The 4-bit mode packs four MACs per DSP - this is the
PING key novelty vs. prior work.

2.3 File 2: conv_layer_ctrl.v - Convolution Controller FSM


This module implements a 5-state FSM that drives the MAC unit through a complete convolution layer. It
generates weight and activation BRAM addresses, controls the MAC pipeline, applies ReLU activation,
and writes output feature maps.

// ============================================================
// conv_layer_ctrl.v - Convolution Layer FSM Controller
// Computes: Output[r][c] = ReLU( sum_{kr,kc} W[kr][kc]*A[r+kr][c+kc] )
// ============================================================
module conv_layer_ctrl #(
parameter DATA_W = 16,
parameter ACCU_W = 32,
parameter IMG_SIZE = 28,
parameter K_SIZE = 5
)( input wire clk,
input wire rst_n,
input wire [1:0] precision_mode,
input wire start,
output reg [9:0] weight_addr,
input wire [DATA_W-1:0] weight_data,
output reg [9:0] act_addr,
input wire [DATA_W-1:0] act_data,
output reg [ACCU_W-1:0] out_data,
output reg [9:0] out_addr,
output reg out_valid,
output reg done
);
localparam IDLE=3'd0, LOAD=3'd1, WAIT=3'd2, COMP=3'd3, STORE=3'd4, FINISH=3'd5;
reg [2:0] state;
reg [4:0] row_cnt, col_cnt;
reg [2:0] k_row, k_col;
reg [9:0] out_pixel_cnt;
reg mac_valid, mac_clear;
localparam OUT_SIZE = IMG_SIZE - K_SIZE;

wire [ACCU_W-1:0] mac_acc;


wire mac_vout;

mac_unit #(.ACCU_WIDTH(ACCU_W)) u_mac (


.clk(clk), .rst_n(rst_n), .mode(precision_mode),
.weight_in(weight_data), .act_in(act_data),
.valid_in(mac_valid), .clear_acc(mac_clear),
.acc_out(mac_acc), .valid_out(mac_vout)
);

always @(posedge clk) begin


if (!rst_n) begin
state<=IDLE; done<=0; out_valid<=0;
row_cnt<=0; col_cnt<=0; k_row<=0; k_col<=0;
mac_valid<=0; mac_clear<=0; out_pixel_cnt<=0;
end else begin
mac_valid<=0; mac_clear<=0; out_valid<=0; done<=0;
case (state)
IDLE: if (start) begin
row_cnt<=0; col_cnt<=0; k_row<=0; k_col<=0;
out_pixel_cnt<=0; mac_clear<=1; state<=LOAD;
end
LOAD: begin
act_addr <= (row_cnt+k_row)*IMG_SIZE+(col_cnt+k_col);
weight_addr <= k_row*K_SIZE + k_col;
state <= WAIT;
end
WAIT: state <= COMP;
COMP: begin
mac_valid <= 1;
if (k_col==K_SIZE-1) begin
k_col<=0;
if (k_row==K_SIZE-1) begin k_row<=0; state<=STORE; end
else begin k_row<=k_row+1; state<=LOAD; end
end else begin k_col<=k_col+1; state<=LOAD; end
end
STORE: begin
out_data <= mac_acc[ACCU_W-1] ? {ACCU_W{1'b0}} : mac_acc;
out_addr <= out_pixel_cnt;
out_valid <= 1; out_pixel_cnt <= out_pixel_cnt+1; mac_clear<=1;
if (col_cnt==OUT_SIZE-1) begin
col_cnt<=0;
if (row_cnt==OUT_SIZE-1) state<=FINISH;
else begin row_cnt<=row_cnt+1; state<=LOAD; end
end else begin col_cnt<=col_cnt+1; state<=LOAD; end
end
FINISH: begin done<=1; state<=IDLE; end
endcase
end
end
endmodule

2.4 File 3: axi_ctrl_regs.v - AXI-Lite Control Interface


This module implements an AXI-Lite slave exposing two 32-bit memory-mapped registers to the ARM
Cortex-A9 processor.

Register Map
Address 0x00 (Write): [1:0] = precision_mode [2] = start pulse
Address 0x04 (Read): [0] = done flag

Precision Mode Encoding:


2'b00 = INT4 (4-bit weights & activations)
2'b01 = INT8 (8-bit - default)
2'b10 = INT16 (16-bit - highest accuracy)

// ============================================================
// axi_ctrl_regs.v - AXI-Lite Slave Control Registers
// Base Address (assigned in block design): 0x43C00000
// ============================================================
module axi_ctrl_regs (
input wire s_axi_aclk,
input wire s_axi_aresetn,
input wire [31:0] s_axi_awaddr,
input wire s_axi_awvalid,
output reg s_axi_awready,
input wire [31:0] s_axi_wdata,
input wire [3:0] s_axi_wstrb,
input wire s_axi_wvalid,
output reg s_axi_wready,
output reg [1:0] s_axi_bresp,
output reg s_axi_bvalid,
input wire s_axi_bready,
input wire [31:0] s_axi_araddr,
input wire s_axi_arvalid,
output reg s_axi_arready,
output reg [31:0] s_axi_rdata,
output reg [1:0] s_axi_rresp,
output reg s_axi_rvalid,
input wire s_axi_rready,
output reg [1:0] precision_mode,
output reg start,
input wire done
);
reg [1:0] aw_addr_latch;
reg aw_valid_latch;
always @(posedge s_axi_aclk) begin
if (!s_axi_aresetn) begin
precision_mode<=2'b01; start<=1'b0;
s_axi_awready<=0; s_axi_wready<=0; s_axi_bvalid<=0;
s_axi_arready<=0; s_axi_rvalid<=0; aw_valid_latch<=0;
end else begin
start <= 1'b0;
if (s_axi_awvalid && !s_axi_awready) begin
s_axi_awready<=1; aw_addr_latch<=s_axi_awaddr[3:2]; aw_valid_latch<=1;
end else s_axi_awready<=0;
if (s_axi_wvalid && !s_axi_wready) begin
s_axi_wready<=1;
if (aw_valid_latch) begin
if (aw_addr_latch==2'b00) begin
precision_mode<=s_axi_wdata[1:0];
start<=s_axi_wdata[2];
end
aw_valid_latch<=0; s_axi_bvalid<=1; s_axi_bresp<=2'b00;
end
end else s_axi_wready<=0;
if (s_axi_bvalid && s_axi_bready) s_axi_bvalid<=0;
if (s_axi_arvalid && !s_axi_arready) begin
s_axi_arready<=1; s_axi_rvalid<=1; s_axi_rresp<=2'b00;
case (s_axi_araddr[3:2])
2'b00: s_axi_rdata<={29'b0,start,precision_mode};
2'b01: s_axi_rdata<={31'b0,done};
default: s_axi_rdata<=32'hDEADBEEF;
endcase
end else begin
s_axi_arready<=0;
if (s_axi_rvalid && s_axi_rready) s_axi_rvalid<=0;
end
end
end
endmodule

2.5 File 4: top_accel.v - Top-Level Wrapper


This module ties all three sub-modules together into a single hierarchy exposed as a custom IP in the
block design.

// ============================================================
// top_accel.v - Top-Level Accelerator Wrapper
// ============================================================
module top_accel #(
parameter DATA_W=16, ACCU_W=32, IMG_SIZE=28, K_SIZE=5
)( input wire s_axi_aclk, s_axi_aresetn,
// AXI-Lite ports (abbreviated - see full version in project)
input wire [31:0] s_axi_awaddr, s_axi_wdata, s_axi_araddr,
input wire [3:0] s_axi_wstrb,
input wire s_axi_awvalid, s_axi_wvalid, s_axi_bready,
input wire s_axi_arvalid, s_axi_rready,
output wire s_axi_awready, s_axi_wready, s_axi_bvalid,
output wire s_axi_arready, s_axi_rvalid,
output wire [31:0] s_axi_rdata,
output wire [1:0] s_axi_bresp, s_axi_rresp,
// BRAM ports
output wire [9:0] weight_addr, act_addr, out_addr,
input wire [DATA_W-1:0] weight_data, act_data,
output wire [ACCU_W-1:0] out_data,
output wire out_valid,
output wire [3:0] status_led
);
wire [1:0] precision_mode;
wire start, done;

axi_ctrl_regs u_ctrl (
.s_axi_aclk(s_axi_aclk), .s_axi_aresetn(s_axi_aresetn),
// ... (connect all AXI ports)
.precision_mode(precision_mode), .start(start), .done(done)
);

conv_layer_ctrl #(.DATA_W(DATA_W),.ACCU_W(ACCU_W),
.IMG_SIZE(IMG_SIZE),.K_SIZE(K_SIZE)) u_conv (
.clk(s_axi_aclk), .rst_n(s_axi_aresetn),
.precision_mode(precision_mode), .start(start),
.weight_addr(weight_addr), .weight_data(weight_data),
.act_addr(act_addr), .act_data(act_data),
.out_data(out_data), .out_addr(out_addr),
.out_valid(out_valid), .done(done)
);

assign status_led[0]=done; assign status_led[1]=precision_mode[0];


assign status_led[2]=precision_mode[1]; assign status_led[3]=start;
endmodule
PHASE
TESTBENCH & SIMULATION
3 Verify all modules before synthesis - never synthesize unverified RTL

3.1 Add Simulation Source


In the Vivado Sources panel: right-click Simulation Sources -> Add Sources -> Create File. Name it
tb_mac_unit.v.

3.2 File: tb_mac_unit.v - Full Verification Testbench


This testbench tests all three precision modes, sign extension correctness, accumulator clearing, overflow
handling, and mode-switching. All 7 test cases must pass before proceeding to synthesis.

`timescale 1ns/1ps
module tb_mac_unit;
reg clk, rst_n, valid_in, clear_acc;
reg [1:0] mode;
reg [15:0] weight_in, act_in;
wire [31:0] acc_out;
wire valid_out;
integer pass_count, fail_count;

mac_unit #(.ACCU_WIDTH(32)) dut (


.clk(clk), .rst_n(rst_n), .mode(mode),
.weight_in(weight_in), .act_in(act_in),
.valid_in(valid_in), .clear_acc(clear_acc),
.acc_out(acc_out), .valid_out(valid_out)
);

initial clk = 0;
always #5 clk = ~clk; // 100 MHz

task do_reset;
begin
rst_n=0; valid_in=0; clear_acc=0;
mode=2'b01; weight_in=0; act_in=0;
repeat(4) @(posedge clk); #1;
rst_n=1; @(posedge clk); #1;
end
endtask

task feed(input [1:0] m, input [15:0] w, a);


begin
@(posedge clk); #1;
mode=m; weight_in=w; act_in=a; valid_in=1;
@(posedge clk); #1; valid_in=0;
end
endtask

task check(input [31:0] exp, input [7:0] tid);


begin
repeat(3) @(posedge clk);
if (acc_out===exp) begin
$display("[PASS] TC%0d: acc=%0d",tid,acc_out);
pass_count=pass_count+1;
end else begin
$display("[FAIL] TC%0d: acc=%0d expected %0d",tid,acc_out,exp);
fail_count=fail_count+1;
end
end
endtask

initial begin
pass_count=0; fail_count=0;
do_reset;
// TC1: INT4 3*2 + 1*(-1) = 5
feed(2'b00,16'h0003,16'h0002); feed(2'b00,16'h0001,16'hFFFF);
check(32'd5, 8'd1);
// TC2: INT8 50*3 + 10*5 = 200
feed(2'b01,16'h0032,16'h0003); feed(2'b01,16'h000A,16'h0005);
check(32'd200, 8'd2);
// TC3: INT16 1000*2 + 500*4 = 4000
feed(2'b10,16'h03E8,16'h0002); feed(2'b10,16'h01F4,16'h0004);
check(32'd4000, 8'd3);
// TC4: INT8 negative weight -128*2 = -256
feed(2'b01,16'hFF80,16'h0002); check(-32'd256, 8'd4);
// TC5-TC7: (accumulator clear, mode switch, all-negative INT4)
// ... (see full version in source)
$display("Results: %0d PASSED %0d FAILED",pass_count,fail_count);
#100 $finish;
end
endmodule

3.3 Run Simulation in Vivado


Step 1: Open Simulation Settings
Flow Navigator -> Project Settings -> Simulation. Confirm: Simulator = Vivado Simulator, top module =
tb_mac_unit.

Step 2: Launch Simulation


Flow Navigator -> SIMULATION -> Run Simulation -> Run Behavioral Simulation.

Step 3: Run to Completion

In the Tcl Console, type:


run all

Expected output:
[PASS] TC1: acc=5
[PASS] TC2: acc=200
[PASS] TC3: acc=4000
[PASS] TC4: acc=-256
[PASS] TC5: acc=9
[PASS] TC6: acc=19
[PASS] TC7: acc=6
Results: 7 PASSED, 0 FAILED
ALL TESTS PASSED - Safe to proceed to synthesis

CRIT Never proceed to synthesis or block design IP packaging if any testbench test cases fail. Fix the
ICAL RTL first. Synthesis errors after block design integration are extremely hard to debug.
PHASE
PACKAGE CUSTOM IP
4 Convert top_accel.v into a reusable Vivado IP block

4.1 Why Package as IP?


Vivado Block Design requires IP blocks to have AXI interfaces declared. Packaging your accelerator as
an IP tells Vivado the AXI-Lite port names and lets it auto-connect to the Zynq PS in the block design.

4.2 IP Packaging Steps


Step 1: Open IP Packager
In Vivado main menu: Tools -> Create and Package New IP.

Step 2: Create Package from Current Project

Select: (o) Package your current project


Click: Next >

IP location: C:/Projects/mixed_prec_cnn_accel/ip_repo/
[ ] Copy sources into IP directory <- CHECK THIS
Click: Next > -> Finish

Step 3: Ports and Interfaces Tab


Vivado will open the Package IP dialog. Navigate to Ports and Interfaces. Verify s_axi (AXI-Lite interface)
is auto-detected. If not:

Right-click s_axi_* ports -> Add Bus Interface


Bus Definition: AXI4-Lite ([Link]:interface:aximm:1.0)
Interface Mode: slave
Click the auto-infer button to map ports

Step 4: Addressing and Memory Tab

Ensure s_axi has address range: 4K (0x1000)


Offset: 0x00000000 (assigned in block design)

Step 5: Customization Parameters Tab

Add: DATA_W = 16 (integer)


Add: ACCU_W = 32 (integer)
Add: IMG_SIZE = 28 (integer)
Add: K_SIZE = 5 (integer)

Step 6: Review and Package


Click: Review and Package tab -> Package IP -> Yes

Step 7: Add IP Repository to Vivado

Tools -> Settings -> IP -> Repository


Click + and navigate to: C:/Projects/mixed_prec_cnn_accel/ip_repo/
Click: Select -> Apply -> OK
PHASE
BLOCK DESIGN IN VIVADO
5 Create PS-PL integration with AXI interconnect, DMA, BRAMs

5.1 Create Block Design


Step 1: Open Block Design
Flow Navigator -> IP INTEGRATOR -> Create Block Design. Name: design_1. Click OK.

5.2 Add and Configure All IP Blocks


Press Ctrl+I in the blank canvas to add each IP. Add them all before connecting anything.

IP Block Count Configuration Notes


ZYNQ7 Processing System 1 Run Block Automation -> Yes to apply board preset
AXI Interconnect 1 Num Master Interfaces: 2 (one for ctrl, one for DMA)
AXI DMA 1 Disable Scatter Gather, Buffer Length Register: 23 bits
Block Memory Generator (Weight 1 True Dual Port, Width 16, Depth 1024
BRAM)
Block Memory Generator (Activation 1 True Dual Port, Width 16, Depth 1024
BRAM)
Block Memory Generator (Output 1 Simple Dual Port, Width 32, Depth 1024
BRAM)
Custom IP (top_accel) 1 Add from your IP repository, default parameters

5.3 Configure Zynq PS


Step 1: Double-click ZYNQ7 Processing System and configure:

PS-PL Configuration -> AXI Non Secure Enablement:


[x] GP AXI Master Interface -> GP0 (for control registers)
[x] HP Slave AXI Interface -> HP0 (for DMA data transfer)

Clock Configuration -> PL Fabric Clocks:


FCLK_CLK0: Enable, Frequency = 100 MHz

Step 2: Run Block Automation


After closing Zynq config, click Run Block Automation -> Select All Automation -> OK. This auto-connects
DDR and FIXED_IO ports.

5.4 Connect All Blocks


Step 1: Clock and Reset
ZYNQ7/FCLK_CLK0 -> AXI Interconnect/ACLK
ZYNQ7/FCLK_CLK0 -> AXI DMA/s_axi_lite_aclk and m_axi_mm2s_aclk
ZYNQ7/FCLK_CLK0 -> top_accel/s_axi_aclk
ZYNQ7/FCLK_RESET0_N -> AXI Interconnect/ARESETN
ZYNQ7/FCLK_RESET0_N -> top_accel/s_axi_aresetn

Step 2: PS to AXI Interconnect to Slaves

ZYNQ7/M_AXI_GP0 -> AXI Interconnect/S00_AXI


AXI Interconnect/M00_AXI -> top_accel/s_axi
AXI Interconnect/M01_AXI -> AXI DMA/S_AXI_LITE

Step 3: DMA to HP0 for Data Transfer

AXI DMA/M_AXI_MM2S -> ZYNQ7/S_AXI_HP0


AXI DMA/M_AXI_S2MM -> ZYNQ7/S_AXI_HP0

Step 4: Connect BRAM Controllers to Custom IP

top_accel/weight_addr -> BRAM_CTRL_0/addra


top_accel/weight_data <- BRAM_CTRL_0/douta
top_accel/act_addr -> BRAM_CTRL_1/addra
top_accel/act_data <- BRAM_CTRL_1/douta
top_accel/out_addr -> BRAM_CTRL_2/addra
top_accel/out_data -> BRAM_CTRL_2/dina
top_accel/out_valid -> BRAM_CTRL_2/ena

Step 5: Assign Addresses

Block Design -> Address Editor tab:


top_accel/s_axi: Offset = 0x43C00000, Range = 4K
AXI DMA: Offset = 0x40400000, Range = 64K

Step 6: Validate Design

Tools -> Validate Design (F6)


Expected: 0 Errors, 0 Critical Warnings

TIP If you get clock domain crossing warnings, ensure all blocks share the same FCLK_CLK0 signal.
All AXI DMA aclk inputs must connect to the same clock source.
PHASE
CONSTRAINTS FILE (XDC)
6 Pin assignments, timing constraints, and bitstream settings for PYNQ-Z2

6.1 Add Constraint File


In Vivado Sources panel: right-click Constraints -> Add Sources -> Create File. Name it:
pynq_z2_constraints.xdc

6.2 Complete Constraints File

# ============================================================
# pynq_z2_constraints.xdc - PYNQ-Z2 (Zynq-7020) Constraints
# Mixed-Precision CNN Accelerator - Vivado 2025.1
# ============================================================

# LED Pin Assignments (PYNQ-Z2 onboard LEDs)


set_property PACKAGE_PIN R14 [get_ports {status_led_0[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {status_led_0[0]}]

set_property PACKAGE_PIN P14 [get_ports {status_led_0[1]}]


set_property IOSTANDARD LVCMOS33 [get_ports {status_led_0[1]}]

set_property PACKAGE_PIN N16 [get_ports {status_led_0[2]}]


set_property IOSTANDARD LVCMOS33 [get_ports {status_led_0[2]}]

set_property PACKAGE_PIN M14 [get_ports {status_led_0[3]}]


set_property IOSTANDARD LVCMOS33 [get_ports {status_led_0[3]}]

# Timing - LEDs are not timing critical


set_false_path -to [get_ports {status_led_0[*]}]

# Optional: Push Buttons (manual start/reset)


# set_property PACKAGE_PIN D19 [get_ports {btn[0]}]
# set_property IOSTANDARD LVCMOS33 [get_ports {btn[0]}]

# Optional: Slide Switches (precision mode select)


# set_property PACKAGE_PIN M20 [get_ports {sw[0]}]
# set_property IOSTANDARD LVCMOS33 [get_ports {sw[0]}]

# Bitstream Configuration
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property [Link] TRUE [current_design]
set_property [Link].SPI_BUSWIDTH 4 [current_design]

NOT The LED port names (status_led_0) must exactly match what Vivado generates when you make
E the port external in Block Design. After generating the wrapper, check design_1_wrapper.v and
update these port names if different.
PHASE
SYNTHESIS, IMPLEMENTATION & BITSTREAM
7 Generate the hardware configuration file for PYNQ-Z2

7.1 Generate Block Design Wrapper


Step 1: Create HDL Wrapper
In Sources panel: right-click design_1.bd -> Generate HDL Wrapper -> Let Vivado Manage and Auto-
Update -> OK. Vivado creates design_1_wrapper.v which instantiates your block design.

Step 2: Set as Top Module


In Sources panel, design_1_wrapper should appear bold (indicating it is the top). If not, right-click it -> Set
as Top.

7.2 Run Synthesis


Step 1: Launch Synthesis
Flow Navigator -> SYNTHESIS -> Run Synthesis -> synth_1 -> OK.

Step 2: Open Synthesized Design


When synthesis completes: Open Synthesized Design -> Report Utilization. Record these values for your
paper:

Resource Used Available Utilization %


LUT (Logic) (fill) (fill) (fill)
LUT (RAM) (fill) (fill) (fill)
Flip-Flops (fill) (fill) (fill)
DSP48E1 (fill) (fill) (fill)
BRAM Tile (fill) (fill) (fill)
BUFG (fill) (fill) (fill)

7.3 Run Implementation & Generate Bitstream


Step 1: Launch Implementation
Flow Navigator -> IMPLEMENTATION -> Run Implementation -> impl_1 -> OK. This step takes 10-25
minutes.

Step 2: Check Timing

Open Implemented Design -> Report Timing Summary

Worst Negative Slack (WNS) must be >= 0


WNS >= 0 -> Timing PASSED -> proceed to bitstream
WNS < 0 -> Timing FAILED -> reduce clock or add pipeline

If timing fails at 100 MHz:


Option 1: Reduce FCLK_CLK0 to 50 MHz in Zynq PS config
Option 2: Add pipeline register between multiply and accumulate

Step 3: Generate Bitstream and Export

Flow Navigator -> Generate Bitstream -> OK

File -> Export -> Export Hardware


Include Bitstream: YES
Output: design_1_wrapper.xsa

Copy to PYNQ-Z2 (default IP: [Link]):


scp design_1_wrapper.bit xilinx@[Link]:~/
scp design_1.hwh xilinx@[Link]:~/
Password: xilinx

Bitstream: <project>/runs/impl_1/design_1_wrapper.bit
HWH file: <project>/gen/sources_1/bd/design_1/hw_handoff/design_1.hwh
PHASE
PYNQ-Z2 PYTHON DRIVER & INFERENCE
8 Load overlay and run precision-selectable CNN inference from Python

8.1 Connect to PYNQ-Z2


Connect PYNQ-Z2 via USB-UART or Ethernet. Open Jupyter Lab at [Link] or SSH in.

8.2 Complete Python Inference Driver

# mixed_prec_inference.py - Run on PYNQ-Z2 board


from pynq import Overlay, allocate
import numpy as np, time

ol = Overlay('/home/xilinx/design_1_wrapper.bit')
ctrl = ol.top_accel_0

REG_CTRL = 0x00 # [1:0]=mode, [2]=start


REG_STATUS = 0x04 # [0]=done
INT4, INT8, INT16 = 0b00, 0b01, 0b10

def set_precision(mode):
[Link](REG_CTRL, int(mode) & 0x3)

def start_accel(mode):
[Link](REG_CTRL, (int(mode) & 0x3) | (1 << 2))

def wait_done(timeout_ms=5000):
t0 = [Link]()
while ([Link](REG_STATUS) & 0x1) == 0:
if ([Link]()-t0)*1000 > timeout_ms:
raise TimeoutError('Accelerator timed out!')

def quantize(arr, bits):


qmax = (2**(bits-1)) - 1
qmin = -(2**(bits-1))
scale = qmax / ([Link]([Link](arr)) + 1e-8)
return [Link]([Link](arr * scale), qmin, qmax).astype(np.int16)

def run_conv_layer(image, weights_fp, mode=INT8):


bits = {INT4:4, INT8:8, INT16:16}[mode]
w_q = quantize(weights_fp.flatten(), bits)
a_q = quantize([Link](), bits)

# Transfer weights to BRAM via DMA


buf = allocate(shape=(len(w_q),), dtype=np.int16)
[Link](buf, w_q)
ol.axi_dma_0.[Link](buf)
ol.axi_dma_0.[Link]()
[Link]()

# Start and wait


t0 = time.perf_counter()
start_accel(mode)
wait_done()
t1 = time.perf_counter()

# Read output via DMA


out_size = (28-5)**2 # = 529
out_buf = allocate(shape=(out_size,), dtype=np.int32)
ol.axi_dma_0.[Link](out_buf)
ol.axi_dma_0.[Link]()
result = [Link](out_buf).reshape(23, 23)
out_buf.freebuffer()

latency_us = (t1-t0)*1e6
return [Link](np.float32), latency_us

def benchmark_all_modes(image, weights):


results = {}
for mode, name in [(INT4,'INT4'),(INT8,'INT8'),(INT16,'INT16')]:
out, lat = run_conv_layer(image, weights, mode)
results[name] = {'output': out, 'latency': lat}
print(f'{name}: latency={lat:.1f}us')
# SNR vs INT16 reference
ref = results['INT16']['output']
for k, v in [Link]():
if k != 'INT16':
noise = v['output'] - ref
snr = 10*np.log10([Link](ref)/([Link](noise)+1e-10))
print(f'{k} SNR vs INT16: {snr:.2f} dB')
return results

if __name__ == '__main__':
[Link](42)
test_image = [Link](28, 28).astype(np.float32)
test_weights = [Link](5, 5).astype(np.float32)
results = benchmark_all_modes(test_image, test_weights)
PHASE
EXPERIMENTS & RESULTS COLLECTION
9 Data to collect for your IEEE paper tables and figures

9.1 Resource Utilization Comparison Table


Run synthesis with three configurations and record Vivado Report Utilization results:

Metric INT4 Only INT8 Only INT16 Only Mixed (Proposed) Prior Work [3]

LUT (%) - - - - -
DSP48E1 (%) - - - - -
BRAM (%) - - - - -
FF (%) - - - - -
Power (W) - - - - -
Throughput (GOP/s) - - - - -
Energy (GOP/s/W) - - - - -

9.2 Accuracy vs. Bit-Width Table


Train LeNet-5 on MNIST with PyTorch. Extract weights. Run inference at each precision on 1000 test
samples.

Precision Accuracy (%) Latency (us) Power (mW) SNR vs FP32 (dB)

INT4 (proposed) - - - -

INT8 (proposed) - - - -

INT16 (proposed) - - - -

ARM CPU (baseline) - - - -

Prior Work [3] - - - -

9.3 Vivado Power Report


After implementation: Report -> Report Power. Record these values for Table I and Table II of your paper:

Total On-Chip Power: ___ W


Dynamic: ___ W
Clocks: ___ W
Logic: ___ W
BRAM: ___ W
DSP: ___ W
Static: ___ W
Junction Temperature: ___ C
PHASE
TROUBLESHOOTING GUIDE
10 Common errors and their exact fixes

Error / Problem Fix

Board not found in Vivado Install board files to Xilinx/Vivado/2025.1/data/boards/board_files/ and restart
Vivado

AXI-Lite not auto-detected Port names must start with s_axi_ exactly. Check prefix in axi_ctrl_regs.v

Critical Warning: Clock Domain Ensure all AXI slave aclk ports connect to the same FCLK_CLK0 in block
Crossing design

Timing failure WNS < 0 Reduce FCLK_CLK0 from 100MHz to 50MHz in Zynq PS config and re-run
implementation

Overlay() fails on PYNQ Ensure both .bit AND .hwh files are in the same directory with matching names

[Link]() - No attribute error Check IP instance name in block design. Try ol.<Tab> in Jupyter to see
available names

DMA transfer hangs Check HP0 is enabled in Zynq PS. Ensure DMA MM2S and S2MM connect to
same HP port

Synthesis: reset_rtl_0 undriven Run Connection Automation in block design to connect all processor resets
automatically

LED port not found in XDC Check exact port name in design_1_wrapper.v after generation and update
XDC accordingly

All testbench outputs are X (unknown) rst_n is not deasserted long enough. Extend reset to 5+ cycles in testbench
do_reset task
Final Implementation Checklist

Don Phase Task


e

[] Phase 1 Install PYNQ-Z2 board files, create Vivado project with correct part

[] Phase 2 Write mac_unit.v, conv_layer_ctrl.v, axi_ctrl_regs.v, top_accel.v

[] Phase 3 Write testbench, run simulation, confirm 7/7 tests pass

[] Phase 4 Package top_accel as custom IP with AXI-Lite interface declared

[] Phase 5 Create block design: Zynq PS + AXI IC + DMA + BRAM + custom IP + connections

[] Phase 5 Assign addresses in Address Editor, validate design (0 errors)

[] Phase 6 Add XDC constraints file with LED pins and timing settings

[] Phase 7 Generate HDL wrapper, run synthesis, record resource utilization

[] Phase 7 Run implementation, verify WNS >= 0, generate bitstream, export XSA

[] Phase 8 Copy .bit and .hwh to PYNQ-Z2, run Python driver, verify output

[] Phase 9 Run benchmark: compare INT4/INT8/INT16 accuracy, latency, power

[] Paper Write 5-page IEEE paper with results tables from Phases 7 & 9

Mixed-Precision CNN Accelerator - IEEE Implementation Guide | PYNQ-Z2 / Vivado 2025.1

You might also like