To master the control of FPGA hardware directly from Python without writing a single line of Verilog/VHDL.
In this phase, we will peel back the layers of the PYNQ framework. You will move from using high-level “Drivers” (like led.on()) to low-level Direct Memory Access (MMIO), which is the fundamental mechanism used by all embedded Linux drivers to talk to hardware.
Ensure Phase 1 was successful and your board is ready.
The PYNQ image comes with a pre-compiled hardware design called base.bit. It connects the HDMI, Audio, and GPIO pins to the processor.
from pynq import Overlay
# This command programs the FPGA fabric instantly
base = Overlay("base.bit")
# Check what IP blocks are available in this design
# This prints a dictionary of all hardware accelerators currently active
print("Overlay Loaded. Hardware Blocks available:")
for ip_name in base.ip_dict:
print(f" - {ip_name}")
PYNQ provides Python classes that wrap the complex AXI protocol into simple object methods.
leds = base.leds_gpio
led0 = leds[0] led1 = leds[1]
led0.write(1) led1.write(0)
* *Action:* Look at your board. LD0 should be ON.
2. **Read Buttons:**
```python
# Read Button 0 (BTN0)
# Note: PYNQ maps this to 'btns_gpio'
btn0 = base.btns_gpio[0]
print(f"Button 0 State: {btn0.read()}")
Now we strip away the magic. How does led.on() actually work? It writes a 1 to a specific physical memory address.
We will bypass the Python driver and talk to the silicon directly using Memory Mapped I/O (MMIO).
# Get the dictionary entry for the LED controller
led_ip = base.ip_dict['leds_gpio']
physical_addr = led_ip['phys_addr']
addr_range = led_ip['addr_range']
print(f"LED Controller is at: 0x{physical_addr:x}")
from pynq import MMIO
# Create a window into the hardware
led_mmio = MMIO(physical_addr, addr_range)
# Write 0xF (Binary 1111) to offset 0x0
# This should turn ALL 4 LEDs ON
led_mmio.write(0x0, 0xF)
# Write 0x5 (Binary 0101) to offset 0x0
# This should turn LD0 and LD2 ON, LD1 and LD3 OFF
led_mmio.write(0x0, 0x5)
We will create a loop that uses the High-Level driver to read buttons, but uses Low-Level MMIO to write the LEDs. This proves you understand both layers.
Run this loop in a new cell:
import time
print("Press BTN0 to light up LEDs (MMIO Mode). Press BTN3 to exit.")
while True:
# High-Level Read
if base.btns_gpio[3].read() == 1:
print("Exit command received.")
break
# Logic: Copy Button 0 state to all 4 LEDs
if base.btns_gpio[0].read() == 1:
# Low-Level Write: Turn all ON (0xF)
led_mmio.write(0x0, 0xF)
else:
# Low-Level Write: Turn all OFF (0x0)
led_mmio.write(0x0, 0x0)
time.sleep(0.1)
Action: Press BTN0 repeatedly. The LEDs should flash in sync with your press.
Why this matters: When you build your custom “Loopback Project” later, there won’t be a pre-made pynq.lib.LED driver for your custom IP. You will have to use MMIO to talk to it. You just learned how.
Now that we know how to control the hardware, let’s learn how to create it.