Avalon-MM and DMA¶
A streaming data path often has a control port and sometimes accesses external memory. Keep these roles separate from the data-stream agent: control transactions update the behavior model; memory BFMs model a visible memory contract; DMA BFMs model command, response and stream traffic.
Avalon-MM master¶
AvalonMMMasterBFM is a lightweight, single-transaction host. It supports
reads, writes, read-modify-write, polling, timeouts, optional byte enables and
waitrequest. It does not issue bursts or multiple outstanding reads.
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge
from cocotbext.avalon import AvalonMMMasterBFM
@cocotb.test()
async def control_register_test(dut):
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
avmm = AvalonMMMasterBFM.from_prefix(
dut, "control", dut.clk, reset=dut.reset,
default_byteenable=0xF,
).start()
dut.reset.value = 1
await RisingEdge(dut.clk)
dut.reset.value = 0
await avmm.wait_reset_release(active_value=1)
await avmm.write(0x00, 0x00000001, timeout_cycles=32)
status = await avmm.read(0x04, timeout_cycles=32)
await avmm.wait_set(0x04, 0x1, timeout_cycles=256)
assert status & 0x1 in (0, 1)
Manual construction is useful when signal names do not share a prefix.
from cocotbext.avalon import AvalonMMBus, AvalonMMMasterBFM
bus = AvalonMMBus(
address=dut.ctrl_address,
writedata=dut.ctrl_writedata,
write=dut.ctrl_write,
read=dut.ctrl_read,
readdata=dut.ctrl_readdata,
waitrequest=getattr(dut, "ctrl_waitrequest", None),
readdatavalid=getattr(dut, "ctrl_readdatavalid", None),
byteenable=getattr(dut, "ctrl_byteenable", None),
)
avmm = AvalonMMMasterBFM(bus, dut.clk, reset=dut.reset)
Convenience operations keep a test's intent visible:
async def configure_component(avmm):
old, new = await avmm.read_modify_write(
0x00, lambda value: value | 0x1, timeout_cycles=32,
)
status = await avmm.poll(
0x04, lambda value: value & 0x1,
interval_cycles=2, timeout_cycles=256,
)
return old, new, status
Slave and memory BFMs¶
When the DUT is an Avalon-MM master, AvalonMMMemoryBFM exposes a
byte-addressed memory. SparseByteMemory has no configured size: it stores
only written addresses and reads zero from untouched addresses. See the
memory API for its allocation and
capacity semantics.
from cocotbext.avalon import AvalonMMMemoryBFM
from fpga_verification.sim.bfms import SparseByteMemory
def make_memory_slave(dut):
memory = SparseByteMemory()
memory.write(0x1000, bytes.fromhex("44332211"))
slave = AvalonMMMemoryBFM.from_prefix(
dut, "memory", dut.clk, reset=dut.reset,
memory=memory, byteorder="little", read_latency=2,
record_transactions=True, randomize=True,
).start()
return memory, slave
Byte enables apply per byte lane. In little-endian order lane zero is the
lowest addressed byte. With record_transactions=True, read and write lists
contain AvalonMMTransaction records with kind, address, data, byteenable,
burst count and beat index.
AvalonMMMonitor publishes accepted transactions to a pyuvm analysis port.
AvalonMMAgent packages the passive monitor with an optional active master.
Connect that port to the custom scoreboard so model state changes in exactly
the order the DUT observed them.
from fpga_verification.sim.agents import AvalonMMAgent
control_agent = AvalonMMAgent(
"control_agent", parent,
bus=AvalonMMBus.from_prefix(dut, "control"),
clock=dut.clk, reset=dut.reset, packet_logging=True,
)
DMA model¶
IntelDMABFM is a black-box behavioral replacement for Intel streaming DMA
interfaces. It models externally observable transactions, not internal FIFOs
or Avalon-MM implementation details.
read command -> IntelDMABFM -> input data + read response -> DUT
write command + output data -> IntelDMABFM -> write response
|
SparseByteMemory
Descriptors are immutable decoded objects. Address regions give the command
monitor an explicit allowed half-open range [start, end).
from fpga_verification.sim.bfms import (
DMAAddressRegion,
ReadDMADescriptor,
SparseByteMemory,
WriteDMADescriptor,
)
memory = SparseByteMemory()
memory.write(0x1000, bytes([1, 2, 3, 4]))
print(memory.read(0x0FFE, 8))
region = DMAAddressRegion("input", start=0x1000, size=0x100)
assert region.contains(0x1080, 16)
assert not region.contains(0x10F8, 16)
read_word = 0x1000 | (16 << 32) | (3 << 64) | (1 << 72) | (1 << 73)
write_word = 0x8000 | (16 << 32) | (1 << 64)
print(ReadDMADescriptor.decode(read_word))
print(WriteDMADescriptor.decode(write_word))
In full mode the BFM owns both paths; read and write modes select one.
The default prefixes are rdma_cmd, rdma_resp, wdma_cmd, wdma_resp,
din and dout, but explicit AvalonSTBus objects can be supplied.
from fpga_verification.sim.bfms.intel_dma import IntelDMABFM, IntelDMACommandMonitor
from cocotbext.avalon import AvalonSTBus
memory = SparseByteMemory()
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()
try:
# Drive DUT-specific control and wait for its observable result.
written = memory.read(0x8000, 16)
finally:
dma.stop()
command_monitor.stop()
For a normal read descriptor the model reads memory, sends a packet with the
requested channel and schedules a completion response. Read length must align
to the exported data beat because that interface has no empty signal in this
model. For a write descriptor it collects enough beats, truncates the last
beat to the requested byte length, commits memory and responds.
read_response_delay_cycles and write_response_delay_cycles control response
timing. dma.din_source.pause delays produced data; dma.dout_sink.pause
creates write-side backpressure. Public command and response queues allow a
test to wait for accepted or completed operations.
Always call stop() in teardown or a finally block. The model validates
negative response delays, malformed packet flags, unaligned reads and addresses
outside configured regions with contextual errors.