4. Python-UVM Integration - Architecture at Scale

In the previous posts, we built a DPI-C bridge and explored Python test patterns for block-level verification. But the real power of Python in verification shines when we scale up to full System-on-Chip (SoC) designs and complex algorithmic blocks.

This post transitions from "how to build the bridge" to "how to architect the system." We'll explore three powerful architectural patterns:

  • Algorithmic Reference Modeling - The "Sidecar" pattern
  • Firmware Co-Simulation - The "Abstract Firmware" pattern
  • System-Level Orchestration - The "Hub" pattern

1. Algorithmic Reference Modeling (The "Sidecar")

For blocks like DSP filters, AI/ML accelerators, or Cryptography engines, the verification challenge isn't just "does usage match the spec?" but "is the math correct?"

Writing bit-exact floating-point reference models in SystemVerilog is painful. In Python, it's trivial thanks to numpy and scipy.

The Architecture

In the Sidecar model, Python runs alongside the UVM environment. It monitors inputs, computes the Golden Reference output using standard libraries, and compares it against the RTL output.

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#e0f2fe', 'primaryTextColor': '#0f172a', 'primaryBorderColor': '#0066cc', 'lineColor': '#475569', 'secondaryColor': '#f8fafc', 'tertiaryColor': '#ffffff'}}}%%
flowchart LR
    subgraph SV["SystemVerilog / UVM"]
        direction TB
        DRV[Driver]
        MON_IN[Input\nMonitor]
        MON_OUT[Output\nMonitor]
        DUT{{FFT Engine}}
        
        DRV ==>|stimulus| DUT
        DUT ==> MON_OUT
        DRV -.-> MON_IN
    end

    subgraph PY["Python Sidecar"]
        direction TB
        REF["numpy.fft\nReference"]
        CHECK["Scoreboard"]
        
        REF -->|expected| CHECK
    end

    MON_IN -.->|DPI: input data| REF
    MON_OUT -.->|DPI: RTL result| CHECK

    style DUT fill:#d1fae5,stroke:#10b981,stroke-width:2px
    style REF fill:#fef3c7,stroke:#f59e0b,stroke-width:2px
    style CHECK fill:#fef3c7,stroke:#f59e0b,stroke-width:2px

The key insight: Python becomes a verification co-processor, handling complex math while UVM handles protocol and timing.

Implementation Example

import numpy as np

def check_fft_packet(data_in, rtl_real, rtl_imag):
    """Verify FFT hardware output against numpy"""
    # Create complex input vector
    complex_in = [complex(r, i) for r, i in data_in]
    
    # Golden Reference Calculation
    expected = np.fft.fft(complex_in)
    
    # Compare with tolerance (hardware precision is limited)
    actual = [complex(r, i) for r, i in zip(rtl_real, rtl_imag)]
    
    if not np.allclose(expected, actual, atol=1e-5):
        print(f"[MISMATCH] Expected {expected[0]}... Got {actual[0]}...")
        return False
    return True

2. Firmware Co-Simulation (The "Abstract Firmware")

Before boot code or drivers are ready, we often need to verify that register sequences work as expected. Writing these in raw UVM sequences is verbose and disconnected from the software view.

By mimicking the firmware API in Python, we can write tests that look like software drivers. These scripts can later serve as the specification for actual C firmware.

Python HAL Driver

# python_firmware/drivers/dma.py
class DMADriver:
    """Hardware Abstraction Layer - mirrors C firmware API"""
    
    def __init__(self, bridge):
        self.base_addr = 0x4000_0000
        self.bridge = bridge

    def transfer(self, src, dst, size):
        # Configure source address
        self.bridge.write(self.base_addr + 0x0, src)
        # Configure destination address  
        self.bridge.write(self.base_addr + 0x4, dst)
        # Set size and trigger start bit
        self.bridge.write(self.base_addr + 0x8, size | 0x1)
        
        # Poll for completion
        while (self.bridge.read(self.base_addr + 0xC) & 0x1):
            pass
        
        return True

The benefit: Same test logic works in simulation, emulation, and post-silicon with only the bridge implementation changing.


3. System-Level Orchestration (The "Hub")

In full SoC verification, we often need to coordinate traffic across multiple interfaces (e.g., WiFi packets in, PCIe data out). Python creates an excellent Hub for this orchestration.

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#e0f2fe', 'primaryTextColor': '#0f172a', 'primaryBorderColor': '#0066cc', 'lineColor': '#475569', 'secondaryColor': '#f8fafc', 'tertiaryColor': '#ffffff'}}}%%
flowchart TB
    subgraph PY["Python Test Orchestrator"]
        direction LR
        MGR["Traffic\nManager"]
        WIFI_GEN["WiFi\nGenerator"]
        PCIE_CHK["PCIe\nChecker"]
        
        MGR --> WIFI_GEN
        MGR --> PCIE_CHK
    end

    subgraph SV["SystemVerilog SoC Environment"]
        direction TB
        AG_WIFI[WiFi Agent]
        AG_PCIE[PCIe Agent]
        DUT{{"SoC DUT"}}
        
        AG_WIFI ==> DUT
        DUT ==> AG_PCIE
    end

    WIFI_GEN -.->|DPI: inject packets| AG_WIFI
    AG_PCIE -.->|DPI: check output| PCIE_CHK

    style DUT fill:#d1fae5,stroke:#10b981,stroke-width:2px
    style MGR fill:#fef3c7,stroke:#f59e0b,stroke-width:2px
    style PY fill:#fffbeb,stroke:#d97706

Python orchestrates end-to-end scenarios while UVM agents handle protocol-level details.


4. Scalability: The GIL Consideration

The Global Interpreter Lock (GIL) prevents Python threads from running fully in parallel. In simulation, this is rarely the bottleneck—the simulator (Verilator/VCS) is usually slower than Python logic.

However, for intense computation (image processing, ML inference), consider:

  • Multiprocessing: Spawn separate processes for heavy computation using multiprocessing module
  • Batching: Send data in large arrays rather than word-by-word to amortize DPI call overhead

5. From Simulation to Emulation

A key architectural benefit is portability. The same Python test patterns can be reused across:

EnvironmentBridge Implementation
RTL SimulationDPI-C direct calls
FPGA EmulationTransactor over PCIe/UART
Post-Silicon LabJTAG/USB host interface

This "Shift-Left" and "Shift-Right" capability maximizes ROI on test development.


This concludes our series on Python-driven Verification. We hope this inspires you to explore how modern software practices can modernize hardware verification!

Author
Mayur Kubavat
VLSI Design and Verification Engineer sharing knowledge about SystemVerilog, UVM, and hardware verification methodologies.

Comments (0)

Leave a Comment