pynq_to_zynq

Loopback Project Step 1: The PL Logic (Hardware)

1. The Goal

To create a custom hardware Intellectual Property (IP) block using Verilog that acts as a coprocessor.

This “Loopback” mechanism proves that we have successfully bridged the gap between the software (PS) and the hardware (PL).

2. Sanity Check (Pre-Flight)

Before starting, ensure your environment is ready.

3. Step-by-Step Instructions

Part A: The “Create and Package New IP” Wizard

We will not write the AXI interface from scratch (which is error-prone). We will use Xilinx’s wizard to generate a robust template.

  1. Create Temporary Project:
    • Open Vivado.
    • Click Create Project.
    • Name: ip_generation_temp.
    • Part: Select PYNQ-Z2 (or xc7z020clg400-1).
    • Note: This project is just a wrapper to launch the wizard; we will delete it later.
  2. Launch the Wizard:
    • Go to Tools > Create and Package New IP.
    • Click Next.
    • Select Create a new AXI4 peripheral. Click Next.
  3. Configure the IP:
    • Name: math_accelerator
    • Description: A simple loopback adder
    • IP Location: Choose a folder you can easily access (e.g., C:/pynq_work/ip_repo).
    • Click Next.
  4. Configure Interface:
    • Name: S00_AXI (Default)
    • Interface Type: Lite
    • Interface Mode: Slave
    • Data Width: 32
    • Number of Registers: 4 (We only need 2, but 4 is the minimum for the wizard).
    • Click Next.
  5. Edit the IP:
    • Select Edit IP (Last option).
    • Click Finish.
    • Result: A new Vivado window will open. This is your IP editing environment.

Part B: Implementing the “Add 1” Logic

Now we modify the Verilog code to make it do something.

  1. Open the Source:
    • In the Sources panel (left side), expand math_accelerator_v1_0.
    • Double-click math_accelerator_v1_0_S00_AXI.v.
    • Concept: This file handles the AXI handshake. It contains 4 registers: slv_reg0, slv_reg1, slv_reg2, slv_reg3.
  2. Locate the Read Logic:
    • Scroll down (around line 390) to the comment: // Implement memory mapped register select and read logic generation.
    • Depending on your Vivado version, this will either be a case statement or an assign statement.
  3. The Modification:
    • We will keep the standard write logic (CPU writes to slv_reg0).
    • We will hijack the read logic for slv_reg1. Instead of reading back a stored value, we will return slv_reg0 + 1.

Scenario A: If you see a case statement (Older Templates):

  // ... existing code ...  
case ( axi\_araddr\[ADDR\_LSB+OPT\_MEM\_ADDR\_BITS:ADDR\_LSB\] )  
    2'h0   : reg\_data\_out \<= slv\_reg0;  
    2'h1   : reg\_data\_out \<= slv\_reg1;  
    2'h2   : reg\_data\_out \<= slv\_reg2;  
    2'h3   : reg\_data\_out \<= slv\_reg3;  
    default : reg\_data\_out \<= 0;  
endcase  

Modified Code (Do This):

// ... existing code ...  
case ( axi\_araddr\[ADDR\_LSB+OPT\_MEM\_ADDR\_BITS:ADDR\_LSB\] )  
   2'h0   : reg\_data\_out \<= slv\_reg0;      // Read back input  
   2'h1   : reg\_data\_out \<= slv\_reg0 \+ 32'd1; // HARDWARE ADDER LOGIC HERE  
   2'h2   : reg\_data\_out \<= slv\_reg2;  
   2'h3   : reg\_data\_out \<= slv\_reg3;  
   default : reg\_data\_out \<= 0;  
endcase  

Scenario B: If you see an assign statement (Newer Templates): Find the line starting with assign S\_AXI\_RDATA \= ... and modify the section for address 2’h1.

Original Code (Ternary Style):

assign S\_AXI\_RDATA \= (axi\_araddr\[ADDR\_LSB+OPT\_MEM\_ADDR\_BITS:ADDR\_LSB\] \== 2'h0) ? slv\_reg0 :   
                     (axi\_araddr\[ADDR\_LSB+OPT\_MEM\_ADDR\_BITS:ADDR\_LSB\] \== 2'h1) ? slv\_reg1 :   
                     (axi\_araddr\[ADDR\_LSB+OPT\_MEM\_ADDR\_BITS:ADDR\_LSB\] \== 2'h2) ? slv\_reg2 :   
                     (axi\_araddr\[ADDR\_LSB+OPT\_MEM\_ADDR\_BITS:ADDR\_LSB\] \== 2'h3) ? slv\_reg3 : 0;  

Modified Code (Do This):

assign S\_AXI\_RDATA \= (axi\_araddr\[ADDR\_LSB+OPT\_MEM\_ADDR\_BITS:ADDR\_LSB\] \== 2'h0) ? slv\_reg0 :   
                     (axi\_araddr\[ADDR\_LSB+OPT\_MEM\_ADDR\_BITS:ADDR\_LSB\] \== 2'h1) ? slv\_reg0 \+ 32'd1 : // MODIFIED HERE  
                     (axi\_araddr\[ADDR\_LSB+OPT\_MEM\_ADDR\_BITS:ADDR\_LSB\] \== 2'h2) ? slv\_reg2 :   
                     (axi\_araddr\[ADDR\_LSB+OPT\_MEM\_ADDR\_BITS:ADDR\_LSB\] \== 2'h3) ? slv\_reg3 : 0;
  1. Save and Close the file.

Part C: Packaging the IP

Now that the logic is changed, we must “seal” the package.

  1. Check for Errors:
    • Look at the Design Runs tab (bottom).
    • Right-click Synthesis and choose Run Synthesis.
    • Checkpoint: If this fails, you have a syntax error in your Verilog. Fix it before proceeding.
  2. Package IP:
    • Open the Package IP - math_accelerator tab (usually visible, or open via Flow Navigator > Package IP).
    • Click on Review and Package.
    • Click the Re-Package IP button.
    • Vivado will close the editing window and return to your original dummy project.

Part D: The Critical Makefile Fix (Mandatory)

Vivado 2024.x has a known bug where it generates invalid Makefiles for custom drivers. This will cause the Linux build to fail in Step 3 with a syntax error (ildcard). You must fix it now in the source so the fix is propagated to PetaLinux.

  1. Locate the File:
    Navigate to your IP repository on your hard drive (Windows Explorer):
    C:/pynq_work/ip_repo/math_accelerator_1.0/drivers/math_accelerator_v1_0/src/Makefile
  2. Edit the Makefile:
    Open it in a text editor (Notepad, VS Code).
    Find these lines (around line 11-12):
    LIBSOURCES=($wildcard *.c)
    OUTS = ($wildcard *.o)

  3. Apply the Fix:
    Move the $ symbol outside the parentheses and simplify the OUTS line:
    LIBSOURCES=$(wildcard *.c)
    OUTS = *.o

  4. Save and Close.
    Note: You do not need to do anything else in Vivado. The file is now fixed on disk and will be correctly included when you add this IP to a block design.

4. Final Validation

Did it work? Let’s check the artifact.

  1. Open the folder you specified in Part A, Step 3 (e.g., C:/pynq_work/ip_repo).
  2. Navigate to math_accelerator_1.0.
  3. Ensure you see a component.xml file. This is the descriptor Vivado needs.

5. Recap: What did we just do?

Next Step: In Step 2, we will pull this new IP into a Vivado Block Design and wire it up to the Zynq Processor.

6. Next Step

We have the brick. Now let’s build the house.

Go to Step 2: The System Integration