Skip to main content

Reusable FPGA simulation and hardware-in-the-loop verification tools

Project description

fpga-verification

Reusable FPGA verification helpers. The package provides shared data formats, wire-protocol codecs, cocotb simulation utilities, and an Intel System Console HIL transport.

Install

Install the package:

python -m pip install fpga-verification

This installs the numeric format helpers, protocol codecs, cocotb simulation utilities, pyuvm agents, and Intel System Console HIL helpers together.

Public API

from fpga_verification.formats import QFormat, UIntFormat
from fpga_verification.protocols.avalon_st.intel_video import (
    IntelVIPFrameCodec,
    VIPControlPacket,
    VIPFrame,
    VIPInterlacing,
    VIPPacketType,
    VIPProtocolChecker,
    VIPProtocolError,
    VIPUserPacket,
    VIPVideoPacket,
    vip_packet_from_symbols,
)
from fpga_verification.sim.buses import (
    AvalonSTBeat,
    AvalonSTBus,
    AvalonSTFrame,
    AvalonSTMonitor,
    AvalonSTSink,
    AvalonSTSource,
)
from fpga_verification.sim.bfms.intel_dma import (
    DMAAddressRegion,
    IntelDMABFM,
    IntelDMACommandMonitor,
    SparseByteMemory,
)
from fpga_verification.sim.agents import VIPAgent, VIPItem, VIPSequence
from fpga_verification.sim.platform_designer import platform_test_cocotb
from fpga_verification.sim.runners import intel_component_test_cocotb, rtl_test_cocotb
from fpga_verification.hil.intel import IntelSystemConsoleSession

Numeric Formats

UIntFormat and QFormat convert between Python/numpy values and raw integer words used by hardware buses, memories, and scoreboards.

UIntFormat

from fpga_verification.formats import UIntFormat

pixel = UIntFormat(width=10)
raw_pixels = pixel.array([0, 1023, 1024, -1])

assert raw_pixels.tolist() == [0, 1023, 0, 1023]
assert raw_pixels.dtype == pixel.dtype

Inputs:

  • width: unsigned word width in bits, from 1 to 64.
  • zeros(shape): creates a zero-filled numpy array.
  • wrap(values): masks values to the configured width.
  • array(values, shape=None): masks, casts to the smallest unsigned storage dtype, and optionally reshapes.

Outputs:

  • dtype: numpy unsigned dtype selected from uint8, uint16, uint32, or uint64.
  • mask: integer bit mask for the configured width.

QFormat

from fpga_verification.formats import QFormat

sample = QFormat(qi=3, qf=2, signed=True)
raw = sample.float_to_qraw([1.25, -1.0])
back = sample.qraw_to_float(raw)

assert raw.tolist() == [5, 28]
assert back.tolist() == [1.25, -1.0]

Inputs:

  • qi: integer width. For signed formats, this includes the sign bit.
  • qf: fractional width.
  • signed: True for two's-complement signed values, False for unsigned.
  • float_to_qraw(x, saturate=True): converts floats to raw fixed-point words.
  • int_to_qraw(raw, saturate=True): converts signed integer values to raw stored words.
  • qraw_to_int(raw): converts raw words to signed or unsigned integers.
  • qraw_to_float(raw): converts raw words to floating-point values.
  • multiply(left_raw, right_format, right_raw, out_qf=None): multiplies two raw fixed-point arrays. If out_qf is provided, the result is shifted to the requested fractional width.
  • zeros(size=None), ones(size=None), full(size, value, raw=False), and randomize(...): create test data.

Outputs:

  • width: total raw word width, qi + qf.
  • scale: 2 ** qf.
  • mask: integer bit mask for the raw word.
  • min_float, max_float: representable numeric range.
  • dtype: numpy unsigned storage dtype for the raw word.

Video frames

The neutral video layer is independent of cocotb and protocol-specific packet formats:

from fpga_verification.video import (
    FrameSize,
    ImageGenerator,
    VideoFormat,
    VideoPayloadCodec,
    compare_frames,
)

fmt = VideoFormat(
    bits_per_symbol=10,
    number_of_color_planes=3,
    color_planes_are_in_parallel=True,
    pixels_in_parallel=2,
)
size = FrameSize(width=640, height=480)
generator = ImageGenerator(fmt, rng=1)
frame = generator.random(size)

codec = VideoPayloadCodec(fmt)
payload_beats = codec.pack_frame(frame, size)
decoded = codec.unpack_frame(payload_beats, size)
compare_frames(decoded, frame)

VideoFormat contains only static AV-ST sample layout. FrameSize contains the width and height of one frame and is an explicit argument to every generation and conversion operation. One codec can therefore process frames with different resolutions without retaining hidden state.

Canonical frame shapes are (height, width) for one color plane and (height, width, planes) for multiple planes. Sample zero occupies the least significant payload bits. In parallel-plane mode each pixel's planes are adjacent; in serial-plane mode each beat carries one plane for pixels_in_parallel adjacent pixels.

Row-oriented adapters (row_to_symbols, pack_row, pack_frame) pad each incomplete row to the configured interface beat width and validate that padding on decode. Frame-symbol adapters (frame_to_symbols, symbols_to_frame) use a continuous raster stream with no per-row padding; protocols such as Intel VIP carry any final partial beat with Avalon-ST empty.

ImageGenerator provides constant, linspace, random, and horizontal_ramp. VideoPayloadCodec provides frame/row/symbol/beat round-trips and strict shape, range, payload-length, and padding validation.

Avalon-ST Protocols

Avalon-ST helpers follow the Avalon interface terminology used by Intel/Altera. The protocol reference is: https://docs.altera.com/r/docs/683091/22.3/avalon-interface-specifications/introduction-to-the-avalon-interface-specifications

The protocol codec layer is independent of cocotb and simulator state. It accepts and returns Python lists of symbols.

Intel Avalon-ST Video Packets

from fpga_verification.protocols.avalon_st.intel_video import (
    VIPControlPacket,
    VIPFrame,
    VIPInterlacing,
    VIPUserPacket,
    vip_packet_from_symbols,
)

control = VIPControlPacket(
    width=1920,
    height=1080,
    interlacing=VIPInterlacing.PROGRESSIVE_FRAME,
)
symbols = control.to_symbols()
decoded = vip_packet_from_symbols(symbols)

assert decoded.width == 1920
assert decoded.height == 1080

frame = VIPFrame(
    width=2,
    height=2,
    pixels=[0x10, 0x20, 0x30, 0x40],
    user_packets=[VIPUserPacket(1, [0xA, 0xB])],
)
packets = frame.packets()

Inputs:

  • VIPControlPacket(width, height, interlacing=...): frame dimensions and interlacing metadata. Width and height must fit in 16 bits.
  • VIPVideoPacket(payload): video payload symbols.
  • VIPUserPacket(user_type, payload): user packet type 1..8 and payload symbols.
  • VIPFrame(width, height, pixels, interlacing=..., user_packets=...): a black-box container that produces user, control, and video packets.
  • vip_packet_from_symbols(symbols, symbols_per_beat=1): decodes one packet from raw symbols. symbols_per_beat controls how many symbols belong to the first Avalon-ST beat; payload starts after that first beat.

Outputs:

  • to_symbols(): returns a list of 4-bit packet symbols.
  • VIPFrame.control_packet(): returns a VIPControlPacket.
  • VIPFrame.video_packet(): returns a VIPVideoPacket.
  • VIPFrame.packets(): returns user packets followed by control and video packets.
  • VIPInterlacing.description: human-readable interlacing mode.

Ancillary packets are currently reported as unsupported by the decoder.

Avalon-ST Cocotb Bus Helpers

The cocotb bus helpers drive and observe Avalon-ST interfaces through cocotb handles. They support scalar valid/ready, optional packet signals, optional empty, error, and channel, and ready modes ready_latency=0 or ready_latency=1.

Instantiating A Source And Sink

import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge

from fpga_verification.sim.buses import (
    AvalonSTBus,
    AvalonSTFrame,
    AvalonSTSink,
    AvalonSTSource,
)


@cocotb.test()
async def stream_loopback_test(dut):
    cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())

    dut.reset.value = 1
    await RisingEdge(dut.clk)
    dut.reset.value = 0

    source = AvalonSTSource(
        AvalonSTBus.from_prefix(dut, "sink"),
        dut.clk,
        reset=dut.reset,
        data_bits_per_symbol=8,
        packets=True,
    )
    sink = AvalonSTSink(
        AvalonSTBus.from_prefix(dut, "source"),
        dut.clk,
        reset=dut.reset,
        data_bits_per_symbol=8,
        packets=True,
    )

    await source.send(AvalonSTFrame([0x11, 0x22, 0x33]))
    received = await sink.recv()

    assert received.data == [0x11, 0x22, 0x33]

Inputs:

  • AvalonSTBus.from_prefix(dut, prefix): binds signals named like <prefix>_data, <prefix>_valid, <prefix>_ready, <prefix>_startofpacket, and <prefix>_endofpacket.
  • AvalonSTFrame(data, channel=None, error=None, empty=None, tx_complete=None): frame payload and optional sideband metadata.
  • AvalonSTSource(bus, clock, reset=None, data_bits_per_symbol=8, symbols_per_beat=None, first_symbol_in_high_order_bits=False, ready_latency=0, ready_allowance=None, packets=None, idle_value="x").
  • AvalonSTSink(...) and AvalonSTMonitor(...): use the same bus format options as AvalonSTSource.
  • send(frame) / send_nowait(frame): queue transmit data.
  • recv() / recv_nowait(): receive complete frames.
  • recv_beat() / recv_beat_nowait(): receive one transferred beat.
  • set_pause_generator(generator): apply backpressure or idle insertion from an iterable of booleans.

Outputs:

  • AvalonSTFrame.data: list of symbols.
  • AvalonSTFrame.channel, error, empty: captured sideband metadata.
  • AvalonSTFrame.sim_time_start, sim_time_end: simulation timestamps.
  • AvalonSTBeat: one handshake beat with data, decoded symbols, sop, eop, empty, error, channel, and sim_time.
  • wait(): waits for a source to become idle or a monitor/sink to see activity, depending on the helper type.

Intel DMA BFM

IntelDMABFM is a cocotb black-box model for Intel read and write DMA streaming interfaces. It consumes DMA command descriptors, emits DMA responses, sources read data from memory, and stores write data into memory.

import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge

from fpga_verification.sim.buses import AvalonSTBus
from fpga_verification.sim.bfms.intel_dma import (
    DMAAddressRegion,
    IntelDMABFM,
    IntelDMACommandMonitor,
    SparseByteMemory,
)


@cocotb.test()
async def dma_component_test(dut):
    cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())

    memory = SparseByteMemory()
    memory.write(0x1000, b"\x01\x02\x03\x04")

    dma = IntelDMABFM(
        dut,
        clock=dut.clk,
        reset=dut.reset,
        memory=memory,
        mode="full",
    ).start()

    command_monitor = IntelDMACommandMonitor(
        clock=dut.clk,
        reset=dut.reset,
        rdma_cmd_bus=AvalonSTBus.from_prefix(dut, "rdma_cmd"),
        wdma_cmd_bus=AvalonSTBus.from_prefix(dut, "wdma_cmd"),
        read_address_regions=[DMAAddressRegion("input", 0x1000, 0x4000)],
        write_address_regions=[DMAAddressRegion("output", 0x8000, 0x4000)],
    ).start()

    dut.reset.value = 1
    await RisingEdge(dut.clk)
    dut.reset.value = 0

    # Drive the DUT here. The BFM responds on the DMA Avalon-ST interfaces.
    # Later, inspect memory or descriptor logs as black-box outputs.
    written_bytes = memory.read(0x8000, 16)
    read_descriptors = command_monitor.read_descriptors

    dma.stop()
    command_monitor.stop()

Inputs:

  • IntelDMABFM(dut, clock, reset, memory=None, read_response_delay_cycles=2, write_response_delay_cycles=2, ..., mode="full").
  • mode: "full", "read"/"read_only", or "write"/"write_only".
  • memory: optional SparseByteMemory shared by read and write paths.
  • Optional bus overrides: rdma_cmd_bus, rdma_resp_bus, wdma_cmd_bus, wdma_resp_bus, din_bus, and dout_bus. If omitted, buses are discovered from DUT prefixes with the same names.
  • SparseByteMemory.write(address, data): initializes byte-addressed memory.
  • DMAAddressRegion(name, start, size): allowed address interval for passive checking. End address is exclusive.
  • IntelDMACommandMonitor(...): pass command buses or existing AvalonSTMonitor instances and optional allowed address regions.

Outputs:

  • SparseByteMemory.read(address, length): returns bytes stored by the BFM.
  • IntelDMABFM.read_commands, write_commands: descriptor queues observed by the model.
  • IntelDMABFM.read_responses, write_responses: queues of descriptors whose responses were issued.
  • IntelDMACommandMonitor.read_descriptors, write_descriptors: decoded descriptor history.
  • ReadDMADescriptor.decode(value) and WriteDMADescriptor.decode(value): convert raw descriptor words into address, length, and control fields.

HIL Session

IntelSystemConsoleSession opens one persistent system-console process and uses it sequentially for Avalon-MM memory access and JTAG UART commands. Intel Quartus system-console must be available on PATH.

import numpy as np

from fpga_verification.hil.intel import IntelSystemConsoleSession

frame = np.arange(1024 * 1280, dtype=np.uint16).reshape(1024, 1280)

with IntelSystemConsoleSession(
    system_console="system-console",
    master_index=0,
    uart_index=0,
    startup_timeout=30.0,
    work_dir=".",
) as hw:
    hw.write_memory(frame, address=0x01E84800)
    response = hw.command("g\n", timeout=3.0)
    frame_out = hw.read_memory((1024, 1280), address=0x02DC6C00)

print(response)
print(frame_out.shape)

Inputs:

  • system_console: executable name or path.
  • master_index: System Console Avalon-MM master index.
  • uart_index: JTAG UART service index.
  • startup_timeout: seconds to wait for the Tcl worker to become ready.
  • work_dir: directory used for temporary binary transfer files.
  • write_memory(data, address, chunk_size=4096): writes numpy-compatible data as little-endian 16-bit words.
  • read_memory(shape, address, chunk_size=4096): reads little-endian 16-bit words and reshapes them.
  • command(command, timeout=3.0, debug=False): sends a UTF-8 command over JTAG UART and waits for the first non-empty response line.

Outputs:

  • read_memory(...): numpy array with the requested shape.
  • command(...): response string.
  • Methods raise TimeoutError or RuntimeError if System Console stops or reports a protocol error.

Simulation Runners

The simulation helpers cover three levels of generated and non-generated designs:

rtl_runner
  RTL sources -> cocotb build/test

intel_component_runner
  *_hw.tcl -> ip-generate -> generated composition HDL + original RTL -> rtl_runner

platform_runner
  already generated Platform Designer sim dir/msim_setup.tcl -> simulator flow

rtl_test_cocotb is the direct RTL path. Pass it explicit HDL sources or source directories, and it delegates build/test to the selected cocotb simulator runner.

intel_component_test_cocotb is for Platform Designer component .tcl files. It generates only the HDL needed for simulation, keeps composition HDL that has no source equivalent, replaces generated copies of project RTL with exact matches from source_dirs, and then calls rtl_test_cocotb.

Its generated-catalog flow is:

source_dirs
  -> ip-make-ipx --thorough-descent --source-directory=<source_dirs>
  -> components.ipx in generated temp dir
  -> ip-generate --search-path=<components.ipx>,$
  -> parse .spd
  -> replace generated RTL copies with original source files
  -> rtl_test_cocotb

Pass generate_only=True to retain and return the generated composition directory without running simulation.

platform_test_cocotb is for already generated Platform Designer simulation trees. The expected layout is:

project_root/
  <hdl_toplevel>/
    <hdl_toplevel>/
      testbench/
        mentor/
          msim_setup.tcl

For Questa, the platform runner compiles through msim_setup.tcl and runs cocotb against the generated simulator libraries. For Verilator, it reads Verilog/SystemVerilog sources from msim_setup.tcl and builds them directly.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

fpga_verification-0.2.2-cp313-cp313-win_amd64.whl (908.8 kB view details)

Uploaded CPython 3.13Windows x86-64

fpga_verification-0.2.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (6.8 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64manylinux: glibc 2.28+ x86-64

fpga_verification-0.2.2-cp312-cp312-win_amd64.whl (913.0 kB view details)

Uploaded CPython 3.12Windows x86-64

fpga_verification-0.2.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (6.9 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64manylinux: glibc 2.28+ x86-64

fpga_verification-0.2.2-cp311-cp311-win_amd64.whl (924.2 kB view details)

Uploaded CPython 3.11Windows x86-64

fpga_verification-0.2.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (6.8 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ x86-64manylinux: glibc 2.28+ x86-64

fpga_verification-0.2.2-cp310-cp310-win_amd64.whl (926.2 kB view details)

Uploaded CPython 3.10Windows x86-64

fpga_verification-0.2.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (6.4 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.17+ x86-64manylinux: glibc 2.28+ x86-64

File details

Details for the file fpga_verification-0.2.2-cp313-cp313-win_amd64.whl.

File metadata

File hashes

Hashes for fpga_verification-0.2.2-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 d05e0e3122b1dcf7a7a1527eb5022f828f80838d6c15699c763c57b9f8711726
MD5 c28db811d3d0b36335d34bd1b95f881e
BLAKE2b-256 3b24f53bbf25befa37847449f6aad89946d70cb3c43532fca8d5f17c055a8d2e

See more details on using hashes here.

File details

Details for the file fpga_verification-0.2.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for fpga_verification-0.2.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 a54fa81cd266d27abdee699b488343ec82ab37693b434f1de83aa03cc1733a12
MD5 4564ffef90433842c2d04ef48c49a52e
BLAKE2b-256 137add5ad0e0a8b833cca4d4da6f877e4ba29fa664edbfc887ea8183c910e6a2

See more details on using hashes here.

File details

Details for the file fpga_verification-0.2.2-cp312-cp312-win_amd64.whl.

File metadata

File hashes

Hashes for fpga_verification-0.2.2-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 324e9cf5b0f0d9bb4f1114536781a5a826fe9902b04d9044eb3548d36545a019
MD5 d48cf92a59bdef1b91581b9864f39b00
BLAKE2b-256 276b6777c10856ac0df383d6f5ffa8f75f981330a1bae0d187bbee55b39bfe33

See more details on using hashes here.

File details

Details for the file fpga_verification-0.2.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for fpga_verification-0.2.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 61a0ef66ad6219c53296d672bbb1e444f176ec2cf5e359ecc0ef60162f282b3d
MD5 0dd60d626c6023565188579840eda25a
BLAKE2b-256 f2155b2e92d09095dec299464f75f1731c44030f3453caa48b3a7357e496c1dc

See more details on using hashes here.

File details

Details for the file fpga_verification-0.2.2-cp311-cp311-win_amd64.whl.

File metadata

File hashes

Hashes for fpga_verification-0.2.2-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 b0b8e1306f79b626b967e24946c4eee5a1bc23369fa6cc9a1d8e32f9c0b2d514
MD5 0146bba71096dd5d682a0299ddc8fa8b
BLAKE2b-256 10927db840eb73fe1da06d3f7c0f641fdaa3caeb9aea7717071c8542cb0155ff

See more details on using hashes here.

File details

Details for the file fpga_verification-0.2.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for fpga_verification-0.2.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 9edcdce9421ede46ff15b6f9fbef214a37b14d25f28ad8148331c29849c2e020
MD5 9aadb6f3a453c532f7995f192a588cf8
BLAKE2b-256 aa4d0357c34a0efb3b40e4a157852269da97aa195f876802c29eec82279b4157

See more details on using hashes here.

File details

Details for the file fpga_verification-0.2.2-cp310-cp310-win_amd64.whl.

File metadata

File hashes

Hashes for fpga_verification-0.2.2-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 153b844984c68909c959b84f16424c32e17df96c7b05a95155f9fe7240c98b7d
MD5 f06259be0336224cb04dd51f63a1f0a1
BLAKE2b-256 24aa39a28c060d5c1a34b75d3f8ef21e74a25176596d60c79adbab2c67a91ca2

See more details on using hashes here.

File details

Details for the file fpga_verification-0.2.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for fpga_verification-0.2.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 02673806bd48274b00a1a2fd17db45b673dcb29767a42fb50695007c676bf7f3
MD5 2a9e58f6a047915c759f6cc54db87e8b
BLAKE2b-256 ab9e74a0f9921aa805bd588134e13b7d605752b63fb764c714b032e700223fd2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page