Skip to content

Hardware-in-the-loop

IntelSystemConsoleSession keeps one Quartus system-console process alive for repeated memory transfers and JTAG UART commands. It is suitable after a simulation-level contract already exists: the same Python data and reference model can then be used against a programmed board.

This API requires Quartus System Console, a programmed FPGA and matching services in the design.

Persistent session

The session starts one process and communicates with the packaged Tcl helper. Reusing it avoids the startup overhead on every transfer. JtagSession remains a backward-compatible alias.

import numpy as np

from fpga_verification.hil.intel import IntelSystemConsoleSession

source = np.arange(64, dtype=np.uint16).reshape(8, 8)

with IntelSystemConsoleSession(
    system_console="system-console",
    master_index=0,
    uart_index=0,
    startup_timeout=30.0,
    work_dir=".",
) as hw:
    hw.write_memory(source, address=0x01000000)
    response = hw.command("status", timeout=3.0)
    observed = hw.read_memory(source.shape, address=0x01000000)

print(response)
print(observed.shape)

master_index and uart_index select services discovered by System Console. work_dir contains temporary transfer files, so choose a dedicated writable directory for concurrent sessions.

Memory transfers

write_memory(data, address, chunk_size=4096) converts data to little-endian unsigned 16-bit words and transfers it in chunks. read_memory(shape, address, chunk_size=4096) returns a two-dimensional little-endian uint16 array and checks the received word count.

def memory_round_trip(hw, source, address):
    hw.write_memory(source, address=address, chunk_size=16 * 1024)
    observed = hw.read_memory(
        source.shape, address=address, chunk_size=16 * 1024,
    )
    np.testing.assert_array_equal(observed, source.astype("<u2"))
    return observed

The current API is intentionally specialised to 16-bit two-dimensional data. When transferring a video frame, first verify the plane order, geometry and endianness against the hardware memory contract. The session cannot infer them.

JTAG UART

command(text, timeout=3.0, debug=False) sends UTF-8 text and returns the first non-empty response line. The board software defines the command protocol; the library transports text only.

def query_target(hw):
    version = hw.command("version", timeout=3.0)
    status = hw.command("status", timeout=3.0, debug=True)
    return version, status

Set debug=True only while diagnosing System Console/Tcl exchange; it prints raw protocol lines.

Lifecycle and safety

The context manager calls open() and guarantees close() after normal exit or an exception. Manual use is available when a process must live longer:

hw = IntelSystemConsoleSession(...)
try:
    hw.open()
    # Transfer data and query the target.
finally:
    hw.close()

Calls are protected by a reentrant lock, so threads cannot interleave commands in one session. close() requests a clean Tcl shutdown, then terminates an unresponsive process.

The session creates and replaces data_in.bin and data_out.bin in its work directory. Confirm service indices and target addresses before writing: unlike simulation, an incorrect address can modify a live board state. Startup, timeouts, malformed responses and short reads are reported as Python errors.