Skip to content

semaphoric775/cocotb_video_source

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cocotb-video-source

Overview

Reusable cocotb drivers and test utilities for simulating video sources in FPGA testbenches. Supports parallel (HS/VS/DE/RGB) and AXI4-Stream video interfaces with built-in test pattern generation and image loading.

Included Drivers:

Driver Interface Backpressure Blanking
ParallelVideoDriver HS / VS / DE / R / G / B Full timing grid
AXIStreamCameraDriver AXI4-Stream Ignored (free-running) Full timing grid
AXIStreamVideoDriverBuffered AXI4-Stream Waits for tready None

Installation

Requires Python 3.10+, cocotb 2.0+, and a supported simulator (Verilator, XSIM, ModelSim, etc.).

What's Included

Timing

VideoTiming is a dataclass that describes a complete video timing standard.

from cocotb_video_source import VideoTiming, TIMING_PRESETS

# Timing Preset
timing = TIMING_PRESETS["1080p60"]

# Custom Timing
timing = VideoTiming(
    h_active=1280, h_fp=110, h_sync=40, h_bp=220,
    v_active=720,  v_fp=5,   v_sync=5,  v_bp=20,
    pclk_hz=74_250_000.0,
    name="720p60",
)

Built-in presets: 480p60, 720p60, 1080p30, 1080p60, svga.


Drivers

All drivers share the same async send/queue API via VideoDriverBase:

await drv.send_frame(frame)          # send one frame, block until done
drv.queue_frame(frame)               # fire-and-forget
drv.queue_frames([frame_a, frame_b]) # enqueue multiple
print(drv.frames_driven)             # count of completed frames

Frames are (H, W, 3) uint8 NumPy arrays in RGB order.

ParallelVideoDriver

Drives hs, vs, de, r, g, b signals through the complete timing grid — active pixels, front porch, sync pulse, and back porch — one clock cycle at a time.

from cocotb_video_source.drivers.parallel import ParallelVideoDriver

drv = ParallelVideoDriver(dut.clk, dut, timing)
await drv.send_frame(frame)

Signal names are resolved as dut.<prefix><name>, configurable with prefix=.

AXIStreamCameraDriver

Emulates a free-running camera sensor. Drives the full h_total × v_total timing grid each frame. tvalid is asserted only during active pixels; blanking regions produce tvalid=0. tready is never sampled — the driver advances unconditionally every clock cycle.

from cocotb_video_source.drivers.axi_stream_camera import AXIStreamCameraDriver

drv = AXIStreamCameraDriver(dut.clk, dut, timing, data_width=32)
await drv.send_frame(frame)

AXIStreamVideoDriverBuffered

Streams packed pixel data over AXI4-Stream using pure backpressure flow control. The driver stalls on each beat until tready is asserted. No blanking cycles are inserted between lines; the interface is saturated whenever data is available.

Supports 1 or 2 pixels per clock via pixels_per_cycle (useful for 2ppc interfaces with 64-bit or 48-bit tdata).

from cocotb_video_source.drivers.axi_stream import AXIStreamVideoDriverBuffered

# 32-bit, 1 pixel per clock
drv = AXIStreamVideoDriverBuffered(dut.clk, dut, timing, data_width=32)

# 64-bit, 2 pixels per clock
drv = AXIStreamVideoDriverBuffered(dut.clk, dut, timing, data_width=64, pixels_per_cycle=2)

await drv.send_frame(frame)

Both drivers assert tuser=1 on the first beat of each frame (start-of-frame) and tlast=1 on the last beat of each line.


PixelPacker

Packs RGB pixel rows into tdata integer words. Used internally by both AXI-Stream drivers; also useful for writing custom monitors.

from cocotb_video_source import PixelPacker

packer = PixelPacker(data_width=32, byte_order="little")
beats = packer.pack_line(frame[row])  # list[int]

Supported data widths: 24, 32, 48, 64. Byte order: "little" (R at LSB, Xilinx default) or "big".


PatternGenerator

Generates standard test frames as (H, W, 3) uint8 NumPy arrays.

from cocotb_video_source import PatternGenerator, VideoTiming

timing = VideoTiming(h_active=1920, v_active=1080, ...)
gen = PatternGenerator(timing)

frame = gen.color_bars()      # SMPTE 75% colour bars
frame = gen.gradient_h()      # horizontal luma ramp
frame = gen.gradient_v()      # vertical luma ramp
frame = gen.checkerboard(32)  # 32×32 pixel checkerboard
frame = gen.solid(255, 0, 0)  # solid red
frame = gen.ramp()            # linear pixel index ramp (integrity testing)
frame = gen.grid(h_period=64, v_period=64)  # grid lines

Image Loader

Load real images from disk and resize them to match a VideoTiming.

from cocotb_video_source import load_and_resize

frame = load_and_resize("test_card.png", timing)  # (H, W, 3) uint8

Individual helpers: load_image(path) → array, resize_to_timing(image, timing) → resized array.


Factory

Use make_video_driver to select a driver by name — useful when parametrising testbenches.

from cocotb_video_source import make_video_driver

drv = make_video_driver("axi_stream_camera", dut.clk, dut, timing, data_width=32)
drv = make_video_driver("axi_stream_buffered", dut.clk, dut, timing, data_width=64, pixels_per_cycle=2)
drv = make_video_driver("parallel", dut.clk, dut, timing)

Examples

AXI-Stream camera source with colour bars

import cocotb
from cocotb.clock import Clock
from cocotb.triggers import ClockCycles

from cocotb_video_source import VideoTiming, PatternGenerator
from cocotb_video_source.drivers.axi_stream_camera import AXIStreamCameraDriver

TIMING = VideoTiming(
    h_active=1280, h_fp=110, h_sync=40, h_bp=220,
    v_active=720,  v_fp=5,   v_sync=5,  v_bp=20,
)

@cocotb.test()
async def test_video_pipeline(dut):
    cocotb.start_soon(Clock(dut.clk, 13, unit="ns").start())  # ~74.25 MHz

    frame = PatternGenerator(TIMING).color_bars()

    drv = AXIStreamCameraDriver(dut.clk, dut, TIMING, data_width=32)
    await drv.send_frame(frame)

    # The frame occupies exactly h_total * v_total clock cycles
    assert int(dut.frame_done.value) == 1

AXI-Stream buffered source under random backpressure

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

from cocotb_video_source import VideoTiming, PatternGenerator
from cocotb_video_source.drivers.axi_stream import AXIStreamVideoDriverBuffered

TIMING = VideoTiming(h_active=640, h_fp=16, h_sync=96, h_bp=48,
                     v_active=480, v_fp=10, v_sync=2,  v_bp=33)

@cocotb.test()
async def test_backpressure(dut):
    cocotb.start_soon(Clock(dut.clk, 40, unit="ns").start())

    async def random_tready():
        rng = random.Random(0)
        while True:
            dut.tready_override.value = int(rng.random() < 0.5)
            await RisingEdge(dut.clk)

    cocotb.start_soon(random_tready())

    frame = PatternGenerator(TIMING).checkerboard(cell=32)
    drv = AXIStreamVideoDriverBuffered(dut.clk, dut, TIMING, data_width=32)
    await drv.send_frame(frame)

    assert int(dut.crc_ok.value) == 1

Parallel video source from a real image

import cocotb
from cocotb.clock import Clock

from cocotb_video_source import VideoTiming, load_and_resize
from cocotb_video_source.drivers.parallel import ParallelVideoDriver

TIMING = VideoTiming(h_active=800, h_fp=40, h_sync=128, h_bp=88,
                     v_active=600, v_fp=1,  v_sync=4,   v_bp=23)

@cocotb.test()
async def test_scaler_input(dut):
    cocotb.start_soon(Clock(dut.clk, 25, unit="ns").start())

    frame = load_and_resize("reference.png", TIMING)
    drv = ParallelVideoDriver(dut.clk, dut, TIMING)
    await drv.send_frame(frame)

Running the Tests

The repository includes Verilator-based cocotb simulation tests. From tests/dut_stubs/:

# Parallel driver
make TOPLEVEL=parallel_loopback COCOTB_TEST_MODULES=test_parallel_driver

# AXI-Stream camera driver
make TOPLEVEL=axis_camera_monitor COCOTB_TEST_MODULES=test_axis_camera_driver

# AXI-Stream buffered driver (32-bit)
make TOPLEVEL=axis_loopback COCOTB_TEST_MODULES=test_axis_driver

# AXI-Stream buffered driver (64-bit)
make TOPLEVEL=axis_loopback COCOTB_TEST_MODULES=test_axis_driver_64 DATA_WIDTH=64

# AXI-Stream buffered driver (2 pixels per clock, 64-bit)
make TOPLEVEL=axis_loopback COCOTB_TEST_MODULES=test_axis_driver_2ppc DATA_WIDTH=64

# AXI-Stream buffered driver (2 pixels per clock, 48-bit)
make TOPLEVEL=axis_loopback COCOTB_TEST_MODULES=test_axis_driver_2ppc_48 DATA_WIDTH=48

# Backpressure tests
make TOPLEVEL=axis_loopback COCOTB_TEST_MODULES=test_backpressure

# Pure Python unit tests (no simulator required)
pytest tests/test_timing.py tests/test_packer.py tests/test_patterns.py tests/test_image_loader.py

License

MIT

About

Simulation models for camera video source in Cocotb

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors