Files
retroSoC/tests/test_apu_primitives.py
T
maksyuki 9c706ba0ff feat: refactor test scripts to update paths and improve organization
- Updated paths in test files to reflect new directory structure for RTL and script files.
- Changed references from rtl/mini to rtl/ip and scripts/rtl where applicable.
- Added a new test file for Tiny product isolation, ensuring independent ABI and bus acceptance.
- Enhanced tests for memory map generation and protocol contracts for Tiny architecture.
- Ensured that all tests are aligned with the latest directory structure and naming conventions.
2026-09-25 15:44:02 +08:00

1475 lines
56 KiB
Python

"""APU-P4 primitive BAM and target-profile coverage."""
from __future__ import annotations
import binascii
import os
import random
import shutil
import struct
import subprocess
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "scripts"))
from apu_isa import ( # noqa: E402
APUMC_P4_CAPABILITY_MASK,
BitstreamOpcode,
ControlOpcode,
EntropyOpcode,
Entry,
Instruction,
InstructionClass,
KernelOpcode,
LocalOpcode,
build_apumc,
parse_apumc,
validate_p3_instruction,
validate_p4_instruction,
)
from apu_primitives import LocalMemory, PrimitiveBam, PrimitiveFault # noqa: E402
from apu_mcasm import assemble # noqa: E402
from apu_interpreter import Machine, SequencerTrap # noqa: E402
ENTRY = Entry(
0,
0,
0,
0,
1,
1000,
scratch_base=0x1000,
scratch_bytes=0x5000,
primitive_mask=APUMC_P4_CAPABILITY_MASK,
table_offset=0,
table_bytes=0x1000,
)
def instruction(instruction_class: int, opcode: int, **values: int) -> Instruction:
return Instruction(instruction_class, opcode, **values)
def write_words(memory: LocalMemory, address: int, values: list[int]) -> None:
memory.inject(address, b"".join(struct.pack("<I", value & 0xFFFFFFFF) for value in values))
def read_words(memory: LocalMemory, address: int, count: int) -> list[int]:
return list(struct.unpack_from(f"<{count}I", memory.data, address))
def test_p4_target_is_separate_from_p3() -> None:
primitive = instruction(InstructionClass.BITSTREAM, BitstreamOpcode.GET, dst=0, immediate=8)
validate_p4_instruction(primitive)
with pytest.raises(ValueError, match="unavailable"):
validate_p3_instruction(primitive)
for source in (0, 4, 5):
with pytest.raises(ValueError, match="unavailable"):
validate_p4_instruction(
instruction(InstructionClass.CONTROL, ControlOpcode.WAIT, aux=source)
)
for source in (1, 2, 3):
validate_p4_instruction(instruction(InstructionClass.CONTROL, ControlOpcode.WAIT, aux=source))
with pytest.raises(ValueError, match="unavailable"):
validate_p4_instruction(instruction(InstructionClass.TRANSPORT, 0))
def test_p4_bundle_table_scratch_and_mask() -> None:
program = [instruction(InstructionClass.CONTROL, ControlOpcode.END)]
entries = [ENTRY.__class__(index, 0, 0, 0, 1, 1, 0x1000, 0x1000, 0, 0, 0) for index in range(3)]
table = struct.pack("<4I", 1, 2, 3, 4)
bundle = build_apumc(program, entries, target="p4", table_payload=table)
_, parsed_entries, header = parse_apumc(bundle, "p4")
assert header[5] == 200
assert header[6] == len(table)
assert header[9] == 0
assert header[10] == 0x2000
assert parsed_entries == entries
with pytest.raises(ValueError):
parse_apumc(bundle, "p3")
def test_p4_zero_byte_table_offset_stays_range_checked() -> None:
program = [instruction(InstructionClass.CONTROL, ControlOpcode.END)]
entries = [Entry(index, 0, 0, 0, 1, 1) for index in range(3)]
bundle = bytearray(build_apumc(program, entries, target="p4"))
struct.pack_into("<I", bundle, 20, len(bundle) + 4)
with pytest.raises(ValueError, match="range"):
parse_apumc(bytes(bundle), "p4")
def test_p4_assembler_emits_targeted_cfg_and_table() -> None:
assembly = assemble(
"""
.build_id 0x1234
.entry 0 start start finish 4 32 0x1000 0x1000 0x21 0 4
.entry 1 start start finish 4 32 0x1000 0x1000 0x21 0 4
.entry 2 start start finish 4 32 0x1000 0x1000 0x21 0 4
.table32 0x00010001
start:
wait 2
fifo_pop r2
finish:
end
""",
target="p4",
)
instructions, entries, header = parse_apumc(assembly.bundle, "p4")
assert assembly.target == "p4"
assert assembly.table_payload == struct.pack("<I", 0x00010001)
assert header[6] == 4
assert header[9] == 0x21
assert entries[0].primitive_mask == 0x21
assert instructions[0].opcode == ControlOpcode.WAIT
def test_p4_interpreter_stalls_without_eof_and_watchdogs() -> None:
program = [
instruction(InstructionClass.BITSTREAM, BitstreamOpcode.GET, dst=0, immediate=8),
instruction(InstructionClass.CONTROL, ControlOpcode.END),
]
entry = Entry(0, 0, 0, 1, 1, 16, 0, 0, 1, 0, 0)
machine = Machine(
program,
entry,
timeout=3,
fetch_no_retirement_cycles=0,
target="p4",
primitives=PrimitiveBam(),
)
with pytest.raises(SequencerTrap) as caught:
machine.run()
assert caught.value.reason == 7
def test_p4_local_sram_has_macro_and_inferred_profiles() -> None:
source = (ROOT / "rtl/ip/multimedia/apu_local_sram.sv").read_text(encoding="utf-8")
compact = "".join(source.split())
assert "localparamintunsignedLogicalBankCount=28" in compact
assert "localparamintunsignedPhysicalBankCount=LogicalBankCount-16" in compact
assert "tc_sram_1024x32 u_local_sram" in source
assert "parameterintunsignedMemoryWordCount=`APB4_APU__LOCAL_DATA_BYTES/4" in compact
assert "logic[31:0]mem[0:PhysicalWordCount-1]" in compact
assert "s_physical_word_selected" in source
assert "`ifdef HAVE_SRAM_MACRO" in source
assert "`else" in source
kws_source = (ROOT / "rtl/ip/multimedia/apu_kws_sram_client.sv").read_text(
encoding="utf-8"
)
engine_source = (ROOT / "rtl/ip/multimedia/apu_kws_engine.sv").read_text(
encoding="utf-8"
)
compact_engine = "".join(engine_source.split())
compact_kws = "".join(kws_source.split())
assert "localparamintunsignedBankCount=16" in compact_kws
assert "tc_sram_1024x32u_kws_sram" in compact_kws
assert "logic[31:0]mem[0:16383]" in compact_kws
assert "`ifdefHAVE_SRAM_MACRO" in compact_kws
coefficient_source = (
ROOT / "rtl/ip/multimedia/apu_kws_coeff_store.sv"
).read_text(encoding="utf-8")
compact_coefficient = "".join(coefficient_source.split())
assert "localparamintunsignedBankCount=15" in compact_coefficient
assert "tc_sram_1024x32u_coefficient_sram" in compact_coefficient
assert "logics_storage_q[0:1023]" not in compact_coefficient
assert "logic[31:0]s_storage_q[0:1023]" in compact_coefficient
memo_source = (ROOT / "rtl/ip/multimedia/apu_proof_memo.sv").read_text(
encoding="utf-8"
)
compact_memo = "".join(memo_source.split())
assert "tc_sram_1024x32u_key_low_sram" in compact_memo
assert "tc_sram_1024x32u_key_high_sram" in compact_memo
assert "tc_sram_1024x32u_valid_bitmap_sram" in compact_memo
assert "logicsigned[7:0]s_tensor_a_q[" not in compact_engine
assert "logicsigned[7:0]s_tensor_b_q[" not in compact_engine
assert "logic[31:0]s_mel_q[" not in compact_engine
def test_bitstream_crc_and_frame_sync() -> None:
bam = PrimitiveBam()
bam.input_fifo.push(0x44332211 | (4 << 32))
bam.input_fifo.push(0x55 | (1 << 32) | (1 << 40))
registers = [0] * 16
result = bam.execute(instruction(2, BitstreamOpcode.REFILL, dst=0, immediate=16), registers, ENTRY)
assert result.writes[0] == 32
assert result.cycles == 3
assert bam.execute(instruction(2, BitstreamOpcode.PEEK, dst=1, immediate=8), registers, ENTRY).writes[1] == 0x11
assert bam.execute(instruction(2, BitstreamOpcode.GET, dst=1, immediate=8), registers, ENTRY).writes[1] == 0x11
bam.execute(instruction(2, BitstreamOpcode.SKIP, immediate=4), registers, ENTRY)
bam.execute(instruction(2, BitstreamOpcode.ALIGN), registers, ENTRY)
registers[2], registers[3] = 0x4455, 0xFFFF
result = bam.execute(
instruction(2, BitstreamOpcode.FRAME_SYNC, dst=4, src0=2, src1=3, aux=16, immediate=4),
registers,
ENTRY,
)
assert result.writes[4] == 1
registers[5], registers[6] = 0, 0x31
assert bam.execute(instruction(2, BitstreamOpcode.CRC8, dst=7, src0=5, src1=6), registers, ENTRY).writes[7] == 0x97
assert bam.execute(instruction(2, BitstreamOpcode.CRC16, dst=7, src0=5, src1=6), registers, ENTRY).writes[7] == 0x80A5
def test_entropy_modes_and_faults() -> None:
memory = LocalMemory()
table = struct.pack("<3I", 1 | (1 << 16), 0x23 | (2 << 16), 0xA | (2 << 16))
memory.publish_table(table + bytes(0x1000 - len(table)))
bam = PrimitiveBam(memory)
registers = [0] * 16
registers[0], registers[1] = 0, 3
bam.input_fifo.push(0b01011000 | (1 << 32) | (1 << 40))
assert bam.execute(instruction(3, EntropyOpcode.HUFF_SYMBOL, dst=2, src0=0, src1=1, aux=2), registers, ENTRY).writes[2] == 1
assert bam.execute(instruction(3, EntropyOpcode.HUFF_PAIR, dst=2, src0=0, src1=1, aux=2), registers, ENTRY).writes == {2: 2, 3: 3}
bam = PrimitiveBam(memory)
bam.input_fifo.push(0b00101011 | (1 << 32) | (1 << 40))
assert bam.execute(instruction(3, EntropyOpcode.UNARY, dst=0, aux=0, immediate=8), registers, ENTRY).writes[0] == 2
registers[0] = 1
result = bam.execute(instruction(3, EntropyOpcode.RICE4, dst=2, src0=0, src1=1), registers, ENTRY)
assert result.writes[2] == 1
registers[0], registers[1] = 4, 1
assert bam.execute(instruction(3, EntropyOpcode.SIGN_RESTORE, dst=2, src0=0, src1=1), registers, ENTRY).writes[2] == 0xFFFFFFFB
bam = PrimitiveBam(memory)
bam.input_fifo.push(1 << 32 | 1 << 40)
with pytest.raises(PrimitiveFault) as caught:
bam.execute(instruction(3, EntropyOpcode.UNARY, dst=0, aux=0, immediate=1), registers, ENTRY)
assert (caught.value.code, caught.value.stage) == (7, 5)
def test_local_memory_and_fifo_epoch() -> None:
memory = LocalMemory()
memory.publish_table(struct.pack("<I", 0x12345678) + bytes(0xFFC))
memory.new_epoch()
bam = PrimitiveBam(memory)
registers = [0] * 16
registers[0], registers[1] = 0, 0xA5A55A5A
bam.execute(instruction(4, LocalOpcode.ST32, src0=0, src1=1), registers, ENTRY)
assert bam.execute(instruction(4, LocalOpcode.LD32, dst=2, src0=0), registers, ENTRY).writes[2] == registers[1]
assert bam.execute(instruction(4, LocalOpcode.TABLE16, dst=3, src0=0, src1=0), registers, ENTRY).writes[3] == 0x5678
bam.input_fifo.push(0xDEADBEEF | (4 << 32) | (1 << 40))
assert bam.execute(instruction(4, LocalOpcode.FIFO_POP, dst=4), registers, ENTRY).writes == {4: 0xDEADBEEF, 5: 0x104}
registers[4], registers[5] = 0x01020304, 0x104
bam.execute(instruction(4, LocalOpcode.FIFO_PUSH, src0=4, src1=5), registers, ENTRY)
assert bam.output_fifo.pop() == 0x01020304 | (4 << 32) | (1 << 40)
bam.reset_mutable()
with pytest.raises(PrimitiveFault) as caught:
bam.execute(instruction(4, LocalOpcode.LD32, dst=2, src0=0), registers, ENTRY)
assert caught.value.reason == 5
def test_requant_stereo_fixed_lpc_and_pcm_pack() -> None:
memory = LocalMemory()
memory.publish_table(bytes(0x1000))
memory.new_epoch()
bam = PrimitiveBam(memory)
registers = [0] * 16
write_words(memory, 0x1000, [1000, -1000])
write_words(memory, 0x1040, [1 << 30, 1 << 30])
write_words(memory, 0x1080, [0x40])
registers[0], registers[1], registers[2] = 0, 0x80, 0x100
result = bam.execute(instruction(5, KernelOpcode.REQUANT, dst=2, src0=0, src1=1, aux=2 << 6, immediate=2), registers, ENTRY)
assert read_words(memory, 0x1100, 2) == [1000, 0xFFFFFC18]
assert result.cycles == 16
write_words(memory, 0x1200, [3, 4])
write_words(memory, 0x1240, [1, 2])
write_words(memory, 0x1280, [0x240])
registers[0], registers[1], registers[2] = 0x200, 0x280, 0x300
result = bam.execute(instruction(5, KernelOpcode.STEREO, dst=2, src0=0, src1=1, aux=1, immediate=2), registers, ENTRY)
assert read_words(memory, 0x1300, 4) == [3, 4, 2, 2]
assert result.writes[2] == 4
write_words(memory, 0x1400, [1, 1])
write_words(memory, 0x1440, [2, 1])
write_words(memory, 0x1480, [0x440])
registers[0], registers[1], registers[2] = 0x400, 0x480, 0x500
bam.execute(instruction(5, KernelOpcode.FIXED, dst=2, src0=0, src1=1, aux=2, immediate=2), registers, ENTRY)
assert read_words(memory, 0x1500, 2) == [4, 7]
write_words(memory, 0x1600, [1, 1])
write_words(memory, 0x1640, [1, 1])
write_words(memory, 0x1680, [1, 0])
write_words(memory, 0x16C0, [0x640, 0x680, 0])
registers[0], registers[1], registers[2] = 0x600, 0x6C0, 0x700
bam.execute(instruction(5, KernelOpcode.LPC, dst=2, src0=0, src1=1, aux=2, immediate=2), registers, ENTRY)
assert read_words(memory, 0x1700, 2) == [2, 4]
write_words(memory, 0x1800, [100, -100])
write_words(memory, 0x1840, [1 << 30, 2])
registers[0], registers[1], registers[2] = 0x800, 0x840, 0x900
result = bam.execute(instruction(5, KernelOpcode.PCM_PACK, dst=2, src0=0, src1=1, aux=0, immediate=1), registers, ENTRY)
assert memory.data[0x1900:0x1904] == struct.pack("<hh", 100, -100)
assert result.writes[2] == 4
@pytest.mark.parametrize("opcode", list(KernelOpcode))
@pytest.mark.parametrize("misaligned_register", (0, 1, 2))
def test_kernel_offsets_reject_misalignment_before_access(
opcode: KernelOpcode, misaligned_register: int
) -> None:
memory = LocalMemory()
memory.publish_table(b"")
memory.new_epoch()
bam = PrimitiveBam(memory)
registers = [0] * 16
registers[misaligned_register] = 1
with pytest.raises(PrimitiveFault) as caught:
bam.execute(
instruction(5, opcode, dst=2, src0=0, src1=1, immediate=1),
registers,
ENTRY,
)
assert (caught.value.code, caught.value.stage, caught.value.reason) == (11, 11, 5)
def test_kernel_reference_rejects_misalignment_before_output() -> None:
memory = LocalMemory()
memory.publish_table(b"")
memory.new_epoch()
bam = PrimitiveBam(memory)
registers = [0] * 16
write_words(memory, 0x1000, [100])
write_words(memory, 0x1100, [1])
registers[0], registers[1], registers[2] = 0, 0x100, 0x200
with pytest.raises(PrimitiveFault) as caught:
bam.execute(
instruction(5, KernelOpcode.REQUANT, dst=2, src0=0, src1=1, immediate=1),
registers,
ENTRY,
)
assert (caught.value.code, caught.value.stage, caught.value.reason) == (11, 11, 5)
def test_interpreter_models_pending_kernel_scoreboard_and_abort() -> None:
program = [
instruction(5, KernelOpcode.REQUANT, dst=2, src0=0, src1=1, aux=2 << 6, immediate=1),
instruction(1, 1, dst=3, immediate=0x55),
instruction(1, 0, dst=4, src0=2),
instruction(0, ControlOpcode.WAIT, aux=1),
instruction(0, ControlOpcode.END),
]
entry = Entry(0, 0, 0, 4, 1, 32, 0x1000, 0x1000, 0x41, 0, 0)
primitives = PrimitiveBam()
machine = Machine(
program,
entry,
timeout=64,
fetch_no_retirement_cycles=0,
target="p4",
primitives=primitives,
)
write_words(primitives.memory, 0x1000, [1000])
write_words(primitives.memory, 0x1040, [1 << 30])
write_words(primitives.memory, 0x1080, [0x40])
machine.registers[0], machine.registers[1], machine.registers[2] = 0, 0x80, 0x100
assert not machine.step()
assert machine.retired == 1
assert machine.kernel_pending_dst == 2
assert machine.registers[2] == 0x100
assert not machine.step()
assert machine.registers[3] == 0x55
retired_before = machine.retired
assert not machine.step()
assert machine.retired == retired_before
while machine.kernel_pending_dst is not None:
assert not machine.step()
assert machine.registers[2] == 1
assert machine.step() is False
assert machine.registers[4] == 1
machine.begin_entry(entry)
write_words(primitives.memory, 0x1000, [1000])
write_words(primitives.memory, 0x1040, [1 << 30])
write_words(primitives.memory, 0x1080, [0x40])
machine.registers[0], machine.registers[1], machine.registers[2] = 0, 0x80, 0x100
assert not machine.step()
machine.abort()
assert machine.kernel_pending_dst is None
assert not primitives.kernel_done
assert not primitives.memory.valid_words[0x1000 // 4]
def test_interpreter_pending_kernel_watchdog_and_error_boundary() -> None:
requant_program = [
instruction(5, KernelOpcode.REQUANT, dst=2, src0=0, src1=1, aux=2 << 6, immediate=1),
instruction(1, 0, dst=4, src0=2),
instruction(0, ControlOpcode.END),
]
entry = Entry(0, 0, 0, 2, 1, 16, 0x1000, 0x1000, 0x40, 0, 0)
primitives = PrimitiveBam()
machine = Machine(
requant_program,
entry,
timeout=1,
fetch_no_retirement_cycles=0,
target="p4",
primitives=primitives,
)
write_words(primitives.memory, 0x1000, [1000])
write_words(primitives.memory, 0x1040, [1 << 30])
write_words(primitives.memory, 0x1080, [0x40])
machine.registers[0], machine.registers[1], machine.registers[2] = 0, 0x80, 0x100
assert not machine.step()
with pytest.raises(SequencerTrap) as caught:
machine.step()
assert caught.value.reason == 7
error_program = [
instruction(5, KernelOpcode.DECORRELATE, dst=2, src0=0, src1=1, aux=1, immediate=1),
instruction(1, 1, dst=3, immediate=0x55),
instruction(0, ControlOpcode.END),
]
primitives = PrimitiveBam()
machine = Machine(
error_program,
entry,
timeout=16,
fetch_no_retirement_cycles=0,
target="p4",
primitives=primitives,
)
write_words(primitives.memory, 0x1000, [0x7FFFFFFF])
write_words(primitives.memory, 0x1040, [0xFFFFFFFF])
write_words(primitives.memory, 0x1080, [0x40])
machine.registers[0], machine.registers[1], machine.registers[2] = 0, 0x80, 0x100
assert not machine.step()
assert machine.retired == 1
with pytest.raises(SequencerTrap) as caught:
machine.step()
assert caught.value.reason == 9
def test_transform_and_resampler_profiles() -> None:
memory = LocalMemory()
table = bytearray(0x1000)
for index in range(72):
struct.pack_into("<I", table, index * 4, (1 << 30) if index % 6 == index // 6 else 0)
memory.publish_table(table)
memory.new_epoch()
bam = PrimitiveBam(memory)
registers = [0] * 16
write_words(memory, 0x1000, [1, 2, 3, 4, 5, 6])
write_words(memory, 0x1080, [0x80000000])
registers[0], registers[1], registers[2] = 0, 0x80, 0x200
result = bam.execute(instruction(5, KernelOpcode.IMDCT6, dst=2, src0=0, src1=1, immediate=1), registers, ENTRY)
assert read_words(memory, 0x1200, 6) == [1, 2, 3, 4, 5, 6]
assert result.cycles == 102
write_words(memory, 0x1400, [7, 8])
write_words(memory, 0x1480, [0, 0, 0, 0, 0, 0, 0, 2])
registers[0], registers[1], registers[2] = 0x400, 0x480, 0x500
result = bam.execute(instruction(5, KernelOpcode.RESAMPLE, dst=2, src0=0, src1=1, aux=0, immediate=1), registers, ENTRY)
assert read_words(memory, 0x1500, 2) == [7, 8]
assert result.cycles == 12
def test_imdct18_dct32_decorrelate_and_pcm_duplicate() -> None:
memory = LocalMemory()
table = bytearray(0x1800)
for row in range(32):
struct.pack_into("<I", table, (row * 32 + row) * 4, 1 << 30)
struct.pack_into("<I", table, 0x1000 + row * 4, 1 << 30)
memory.publish_table(table)
memory.new_epoch()
entry = Entry(0, 0, 0, 0, 1, 20000, 0x2000, 0x4000, 0xFFFF, 0, 0x1800)
bam = PrimitiveBam(memory)
registers = [0] * 16
write_words(memory, 0x2000, list(range(1, 19)))
write_words(memory, 0x2800, [0x80000000])
registers[0], registers[1], registers[2] = 0, 0x800, 0x1000
result = bam.execute(
instruction(5, KernelOpcode.IMDCT18, dst=2, src0=0, src1=1, immediate=1),
registers,
entry,
)
assert result.writes[2] == 36
assert result.cycles == 714
write_words(memory, 0x2000, list(range(1, 33)))
write_words(memory, 0x2800, [0x80000000, 0x80001000, 0x1000, 0])
write_words(memory, 0x3000, [0] * (16 * 32))
registers[0], registers[1], registers[2] = 0, 0x800, 0x2000
result = bam.execute(
instruction(5, KernelOpcode.DCT32_POLY, dst=2, src0=0, src1=1, immediate=1),
registers,
entry,
)
assert result.writes[2] == 32
assert read_words(memory, 0x4000, 32) == list(range(1, 33))
assert result.cycles == 2312
write_words(memory, 0x2200, [10])
write_words(memory, 0x2240, [3])
write_words(memory, 0x2280, [0x240])
registers[0], registers[1], registers[2] = 0x200, 0x280, 0x300
result = bam.execute(
instruction(5, KernelOpcode.DECORRELATE, dst=2, src0=0, src1=1, aux=1, immediate=1),
registers,
entry,
)
assert read_words(memory, 0x2300, 2) == [10, 7]
assert result.writes[2] == 2
write_words(memory, 0x2400, [100])
write_words(memory, 0x2440, [1 << 30, 1])
registers[0], registers[1], registers[2] = 0x400, 0x440, 0x500
result = bam.execute(
instruction(5, KernelOpcode.PCM_PACK, dst=2, src0=0, src1=1, aux=4, immediate=1),
registers,
entry,
)
assert memory.data[0x2500:0x2504] == struct.pack("<hh", 100, 100)
assert result.writes[2] == 4
@pytest.mark.parametrize(
("profile", "numerator", "denominator"),
[
(0, 1, 1),
(1, 1, 2),
(2, 80, 147),
(3, 160, 147),
(4, 3, 2),
(5, 2, 1),
(6, 3, 1),
(7, 4, 1),
(8, 6, 1),
(9, 8, 1),
(10, 12, 1),
(11, 320, 147),
(12, 640, 147),
(13, 1280, 147),
(14, 2, 3),
(15, 4, 3),
],
)
def test_all_resampler_profiles(profile: int, numerator: int, denominator: int) -> None:
memory = LocalMemory()
memory.publish_table(bytes(0x1800))
memory.new_epoch()
entry = Entry(0, 0, 0, 0, 1, 20000, 0x2000, 0x4000, 0xFFFF, 0, 0x1800)
bam = PrimitiveBam(memory)
registers = [0] * 16
write_words(memory, 0x20E4, [0] * 17)
write_words(
memory,
0x2400,
[0x80000000, 0x80000800, 0x80001000, 0, 0, 0, 0, 1],
)
registers[0], registers[1], registers[2] = 0x100, 0x400, 0x800
result = bam.execute(
instruction(5, KernelOpcode.RESAMPLE, dst=2, src0=0, src1=1, aux=profile, immediate=1),
registers,
entry,
)
expected_frames = (numerator + denominator - 1) // denominator
assert result.writes[2] == expected_frames
expected_cycles = 10 if profile == 0 else 10 + 36 * expected_frames
assert result.cycles == expected_cycles
assert read_words(memory, 0x2800, expected_frames) == [0] * expected_frames
def _p4_rtl_sources() -> list[Path]:
multimedia = ROOT / "rtl/ip/multimedia"
return [
ROOT / "rtl/managed/clusterip/common/rtl/utils/fifo.sv",
multimedia / "apu_local_sram.sv",
multimedia / "apu_bitstream_engine.sv",
multimedia / "apu_entropy_engine.sv",
multimedia / "apu_reconstruction_engine.sv",
multimedia / "apu_transform_engine.sv",
multimedia / "apu_resampler.sv",
multimedia / "apu_kernel_engine.sv",
multimedia / "apu_primitive_dispatcher.sv",
ROOT / "tests/rtl/apu_p4_primitives_tb.sv",
]
def _requant_corpus(tmp_path: Path) -> tuple[Path, int]:
random_source = random.Random(0xA4C0DE)
cases = [
(-0x80000000, -0x80000000, 0x80),
(-0x80000000, 0x7FFFFFFF, 0xA0),
(0x7FFFFFFF, -0x80000000, 0x41),
(0x7FFFFFFF, 0x7FFFFFFF, 0x61),
(1, 1 << 29, 0xA0),
(3, 1 << 29, 0xA0),
(-1, 1 << 29, 0xA0),
(-3, 1 << 29, 0xA0),
]
for _ in range(24):
sample = random_source.randint(-0x80000000, 0x7FFFFFFF)
scale = random_source.randint(-0x80000000, 0x7FFFFFFF)
shift = random_source.randrange(32)
rounding = random_source.randrange(2)
width = random_source.randrange(3)
cases.append((sample, scale, shift | (rounding << 5) | (width << 6)))
words: list[int] = []
entry = Entry(0, 0, 0, 0, 1, 1000, 0x2000, 0x4000, 0x40, 0, 0)
for sample, scale, aux in cases:
memory = LocalMemory()
memory.publish_table(b"")
memory.new_epoch()
bam = PrimitiveBam(memory)
registers = [0] * 16
write_words(memory, 0x2E00, [sample])
write_words(memory, 0x2E40, [scale])
write_words(memory, 0x2E80, [0xE40])
registers[0], registers[1], registers[2] = 0xE00, 0xE80, 0xEC0
bam.execute(
instruction(
5,
KernelOpcode.REQUANT,
dst=2,
src0=0,
src1=1,
aux=aux,
immediate=1,
),
registers,
entry,
)
expected = read_words(memory, 0x2EC0, 1)[0]
words.extend((sample & 0xFFFFFFFF, scale & 0xFFFFFFFF, aux, expected))
vector_path = tmp_path / "apu_p4_requant_vectors.hex"
vector_path.write_text("\n".join(f"{word:08x}" for word in words) + "\n", encoding="utf-8")
return vector_path, len(cases)
def _fifo_word(payload: bytes, *, last: bool = True) -> int:
if not 1 <= len(payload) <= 4:
raise ValueError("P4 corpus FIFO payload must contain one to four bytes")
return int.from_bytes(payload.ljust(4, b"\0"), "little") | (len(payload) << 32) | (
int(last) << 40
)
class _SharedCorpus:
def __init__(self) -> None:
self.lines: list[str] = []
self.covered: set[tuple[int, int]] = set()
self.case_index = 0
@staticmethod
def _word(value: int) -> str:
return f"32'h{value & 0xFFFFFFFF:08x}"
def add(
self,
name: str,
operations: list[Instruction],
*,
registers: dict[int, int] | None = None,
table_words: dict[int, int] | None = None,
scratch_words: dict[int, int] | None = None,
input_words: list[int] | None = None,
check_words: list[int] | None = None,
check_output_fifo: bool = False,
) -> None:
registers = {} if registers is None else registers
table_words = {} if table_words is None else table_words
scratch_words = {} if scratch_words is None else scratch_words
input_words = [] if input_words is None else input_words
check_words = [] if check_words is None else check_words
table = bytearray(0x1800)
for address, value in table_words.items():
struct.pack_into("<I", table, address, value & 0xFFFFFFFF)
memory = LocalMemory()
memory.publish_table(bytes(table))
memory.new_epoch()
bam = PrimitiveBam(memory)
for address, value in scratch_words.items():
write_words(memory, address, [value])
for value in input_words:
bam.input_fifo.push(value)
gpr = [0] * 16
for register, value in registers.items():
gpr[register] = value & 0xFFFFFFFF
label = "".join(character if character.isalnum() else "_" for character in name)
self.lines.extend((f"begin : shared_corpus_{self.case_index}_{label}", " hard_reset();"))
self.case_index += 1
for address, value in sorted(table_words.items()):
self.lines.append(
f" publish_table_word(17'h{address:05x}, {self._word(value)});"
)
for address, value in sorted(scratch_words.items()):
self.lines.append(f" inject_word(17'h{address:05x}, {self._word(value)});")
for value in input_words:
self.lines.append(f" push_input(41'h{value & ((1 << 41) - 1):011x});")
fault_seen = False
for operation_index, operation in enumerate(operations):
self.covered.add((int(operation.instruction_class), int(operation.opcode)))
source0 = gpr[operation.src0]
source1 = gpr[operation.src1]
destination = gpr[operation.dst]
try:
result = bam.execute(operation, gpr, RTL_CORPUS_ENTRY)
except PrimitiveFault as fault:
latency_bound = self._fault_latency_bound(operation, gpr)
self.lines.append(
" issue_expect_corpus_error("
f"64'h{operation.encode():016x}, {self._word(source0)}, {self._word(source1)}, "
f"{self._word(destination)}, 6'd{fault.code}, 4'd{fault.stage}, 8'd{fault.reason}, "
f"1'b{int(latency_bound is not None)}, 32'd{latency_bound or 0}, "
f"1'b{int(operation.instruction_class == InstructionClass.LOCAL)});"
)
fault_seen = True
if operation_index + 1 != len(operations):
raise AssertionError(f"faulting corpus operation must terminate case {name}")
self._append_stream_state_checks(name, bam)
break
self.lines.append(
f" issue(64'h{operation.encode():016x}, {self._word(source0)}, "
f"{self._word(source1)}, {self._word(destination)}, observed, cycles);"
)
ordered_writes = sorted(result.writes.items())
self.lines.append(
f" if (result_words != 3'd{len(ordered_writes)} || cycles != 32'd{result.cycles}) "
f"$fatal(1, \"shared corpus {name} result shape/latency mismatch\");"
)
for word_index, (register, value) in enumerate(ordered_writes):
self.lines.append(
f" if (result_dst + 4'd{word_index} != 4'd{register} || "
f"result_data[{word_index}] != {self._word(value)}) "
f"$fatal(1, \"shared corpus {name} destination mismatch\");"
)
gpr[register] = value & 0xFFFFFFFF
self.lines.append(
f" if (result_kernel != 1'b{int(result.kernel_done)}) "
f"$fatal(1, \"shared corpus {name} kernel tag mismatch\");"
)
self._append_stream_state_checks(name, bam)
for address in check_words:
expected = int.from_bytes(memory.data[address : address + 4], "little")
self.lines.extend(
(
f" read_word(17'h{address:05x}, observed);",
f" if (observed != {self._word(expected)}) "
f"$fatal(1, \"shared corpus {name} memory mismatch at {address:#x} "
f"got=%08x expected=%08x\", observed, {self._word(expected)});",
)
)
if check_output_fifo:
if fault_seen or len(bam.output_fifo.words) != 1:
raise AssertionError(f"shared corpus {name} did not produce one output FIFO word")
expected_fifo = bam.output_fifo.words[0]
self.lines.extend(
(
f" if (!output_valid || output_data != 41'h{expected_fifo:011x}) "
f"$fatal(1, \"shared corpus {name} output FIFO mismatch\");",
" output_accept = 1'b1;",
" @(posedge clk_i);",
" output_accept = 1'b0;",
)
)
self.lines.append("end")
@staticmethod
def _fault_latency_bound(operation: Instruction, registers: list[int]) -> int | None:
instruction_class = int(operation.instruction_class)
opcode = int(operation.opcode)
if instruction_class == InstructionClass.BITSTREAM:
if opcode in (BitstreamOpcode.PEEK, BitstreamOpcode.GET, BitstreamOpcode.SKIP):
return 1
if opcode == BitstreamOpcode.FRAME_SYNC:
return 2 + 2 * operation.immediate
if instruction_class == InstructionClass.ENTROPY:
if opcode == EntropyOpcode.UNARY:
return 2 + operation.immediate
if opcode == EntropyOpcode.SIGN_RESTORE:
return 3 + (registers[operation.src1] & 0x1F)
if instruction_class == InstructionClass.LOCAL:
return 2 if opcode in (
LocalOpcode.LD32,
LocalOpcode.TABLE8,
LocalOpcode.TABLE16,
LocalOpcode.TABLE32,
) else 1
return None
def _append_stream_state_checks(self, name: str, bam: PrimitiveBam) -> None:
self.lines.extend(
(
" @(negedge clk_i);",
f" if (input_count != 7'd{len(bam.input_fifo.words)} || "
f"output_count != 7'd{len(bam.output_fifo.words)}) "
f"$fatal(1, \"shared corpus {name} FIFO mutation mismatch\");",
f" if (u_dut.s_available_bits != 7'd{len(bam.bits)} || "
f"u_dut.u_bitstream_engine.s_eof_q != 1'b{int(bam.eof)} || "
f"u_dut.u_bitstream_engine.s_bit_position_q != 3'd{bam.bit_position}) "
f"$fatal(1, \"shared corpus {name} reservoir mutation mismatch "
f"got=%0d/%0d/%0d expected={len(bam.bits)}/{int(bam.eof)}/"
f"{bam.bit_position}\", u_dut.s_available_bits, "
"u_dut.u_bitstream_engine.s_eof_q, "
"u_dut.u_bitstream_engine.s_bit_position_q);",
)
)
def write(self, path: Path) -> None:
path.write_text("\n".join(self.lines) + "\n", encoding="utf-8")
RTL_CORPUS_ENTRY = Entry(0, 0, 0, 0, 1, 0xFFFFFF, 0x2000, 0x4000, 0xFFFF, 0, 0x1800)
def _word_map(base: int, values: list[int]) -> dict[int, int]:
return {base + index * 4: value for index, value in enumerate(values)}
def _shared_primitive_corpus(tmp_path: Path) -> tuple[Path, int]:
random_source = random.Random(0x5044434F)
corpus = _SharedCorpus()
random_word = random_source.getrandbits(32)
input_word = _fifo_word(random_word.to_bytes(4, "little"))
corpus.add("refill_boundary", [instruction(2, BitstreamOpcode.REFILL, dst=0, immediate=32)], input_words=[input_word])
corpus.add("peek_random", [instruction(2, BitstreamOpcode.PEEK, dst=1, immediate=32)], input_words=[input_word])
corpus.add("get_random", [instruction(2, BitstreamOpcode.GET, dst=1, immediate=32)], input_words=[input_word])
corpus.add("skip_boundary", [instruction(2, BitstreamOpcode.SKIP, immediate=32)], input_words=[input_word])
corpus.add(
"align_after_partial_get",
[
instruction(2, BitstreamOpcode.GET, dst=0, immediate=3),
instruction(2, BitstreamOpcode.ALIGN),
],
input_words=[input_word],
)
sync_prefix = random_source.randrange(0x100)
corpus.add(
"frame_sync_masked",
[instruction(2, BitstreamOpcode.FRAME_SYNC, dst=4, src0=2, src1=3, aux=16, immediate=3)],
registers={2: 0xABCD, 3: 0xFFFF},
input_words=[_fifo_word(bytes((sync_prefix, 0xAB, 0xCD)))],
)
corpus.add(
"crc8_random",
[instruction(2, BitstreamOpcode.CRC8, dst=7, src0=5, src1=6)],
registers={5: random_source.randrange(0x100), 6: 0xFF},
)
corpus.add(
"crc16_random",
[instruction(2, BitstreamOpcode.CRC16, dst=7, src0=5, src1=6)],
registers={5: random_source.randrange(0x10000), 6: 0xFF},
)
for opcode in (BitstreamOpcode.PEEK, BitstreamOpcode.GET, BitstreamOpcode.SKIP):
corpus.add(
f"{opcode.name.lower()}_truncated",
[instruction(2, opcode, dst=1 if opcode != BitstreamOpcode.SKIP else 0, immediate=16)],
input_words=[_fifo_word(bytes((random_source.randrange(0x100),)))],
)
corpus.add(
"frame_sync_not_found",
[instruction(2, BitstreamOpcode.FRAME_SYNC, dst=4, src0=2, src1=3, aux=8, immediate=1)],
registers={2: 0xFF, 3: 0xFF},
input_words=[_fifo_word(b"\x00")],
)
for opcode in (
EntropyOpcode.HUFF_SYMBOL,
EntropyOpcode.HUFF_PAIR,
EntropyOpcode.HUFF_QUAD,
):
symbol = random_source.getrandbits(16)
corpus.add(
f"{opcode.name.lower()}_canonical",
[instruction(3, opcode, dst=4, src0=0, src1=1, aux=1)],
registers={0: 0, 1: 1},
table_words={0: symbol | (1 << 16)},
input_words=[_fifo_word(bytes((random_source.randrange(0x80),)))],
)
corpus.add(
f"{opcode.name.lower()}_empty_table",
[instruction(3, opcode, dst=4, src0=0, src1=1, aux=1)],
registers={0: 0, 1: 0},
)
corpus.add(
"unary_boundary",
[instruction(3, EntropyOpcode.UNARY, dst=2, aux=0, immediate=5)],
input_words=[_fifo_word(bytes((0x04 | random_source.randrange(4),)))],
)
corpus.add(
"rice4_random",
[instruction(3, EntropyOpcode.RICE4, dst=2, src0=0, src1=1)],
registers={0: 2},
input_words=[_fifo_word(bytes((0x28 | random_source.randrange(8),)))],
)
corpus.add(
"rice5_random",
[instruction(3, EntropyOpcode.RICE5, dst=2, src0=0, src1=1)],
registers={0: 3},
input_words=[_fifo_word(bytes((0x68 | random_source.randrange(8),)))],
)
corpus.add(
"sign_restore_random",
[instruction(3, EntropyOpcode.SIGN_RESTORE, dst=2, src0=0, src1=1)],
registers={0: 0x7FFFFFF8, 1: 3},
input_words=[_fifo_word(bytes((0xB0 | random_source.randrange(16),)))],
)
for opcode, escape in ((EntropyOpcode.RICE4, 15), (EntropyOpcode.RICE5, 31)):
corpus.add(
f"{opcode.name.lower()}_escape_extreme",
[instruction(3, opcode, dst=2, src0=0, src1=1)],
registers={0: escape, 1: 32},
input_words=[_fifo_word(b"\x80\x00\x00\x00")],
)
corpus.add(
"unary_declared_limit",
[instruction(3, EntropyOpcode.UNARY, dst=2, aux=0, immediate=1)],
input_words=[_fifo_word(b"\x00")],
)
for opcode, escape in ((EntropyOpcode.RICE4, 15), (EntropyOpcode.RICE5, 31)):
corpus.add(
f"{opcode.name.lower()}_invalid_escape_width",
[instruction(3, opcode, dst=2, src0=0, src1=1)],
registers={0: escape, 1: 0},
)
corpus.add(
"sign_restore_truncated",
[instruction(3, EntropyOpcode.SIGN_RESTORE, dst=2, src0=0, src1=1)],
registers={0: 7, 1: 8},
input_words=[_fifo_word(b"\xa5")],
)
local_value = random_source.getrandbits(32)
corpus.add(
"local_load_boundary",
[instruction(4, LocalOpcode.LD32, dst=2, src0=0)],
registers={0: 0x3FFC},
scratch_words={0x5FFC: local_value},
)
corpus.add(
"local_store_boundary",
[instruction(4, LocalOpcode.ST32, src0=0, src1=1)],
registers={0: 0x3FFC, 1: local_value},
check_words=[0x5FFC],
)
table_value = random_source.getrandbits(32)
for opcode, index, expected_address in (
(LocalOpcode.TABLE8, 3, 0x103),
(LocalOpcode.TABLE16, 1, 0x102),
(LocalOpcode.TABLE32, 0, 0x100),
):
corpus.add(
f"{opcode.name.lower()}_random",
[instruction(4, opcode, dst=2, src0=0, src1=1)],
registers={0: 0x100, 1: index},
table_words={0x100: table_value},
)
corpus.add(
f"{opcode.name.lower()}_range_fault",
[instruction(4, opcode, dst=2, src0=0, src1=1)],
registers={0: 0x1800, 1: index},
)
assert expected_address < 0x1800
corpus.add(
"fifo_pop_boundary",
[instruction(4, LocalOpcode.FIFO_POP, dst=4)],
input_words=[_fifo_word(random_source.getrandbits(32).to_bytes(4, "little"))],
)
corpus.add(
"fifo_push_random",
[instruction(4, LocalOpcode.FIFO_PUSH, src0=4, src1=5)],
registers={4: random_source.getrandbits(32), 5: 0x104},
check_output_fifo=True,
)
corpus.add(
"local_load_range_fault",
[instruction(4, LocalOpcode.LD32, dst=2, src0=0)],
registers={0: 0x4000},
)
corpus.add(
"local_store_range_fault",
[instruction(4, LocalOpcode.ST32, src0=0, src1=1)],
registers={0: 0x4000, 1: local_value},
)
corpus.add(
"local_load_address_overflow",
[instruction(4, LocalOpcode.LD32, dst=2, src0=0, immediate=1)],
registers={0: 0xFFFFFFFF},
)
corpus.add(
"local_store_address_overflow",
[instruction(4, LocalOpcode.ST32, src0=0, src1=1, immediate=1)],
registers={0: 0xFFFFFFFF, 1: local_value},
)
corpus.add(
"table8_address_overflow",
[instruction(4, LocalOpcode.TABLE8, dst=2, src0=0, src1=1)],
registers={0: 0xFFFFFFFF, 1: 0x2001},
)
corpus.add(
"table16_scaled_address_overflow",
[instruction(4, LocalOpcode.TABLE16, dst=2, src0=0, src1=1)],
registers={0: 2, 1: 0xFFFFFFFF},
)
corpus.add(
"table32_scaled_address_overflow",
[instruction(4, LocalOpcode.TABLE32, dst=2, src0=0, src1=1)],
registers={0: 0, 1: 0x80000000},
)
corpus.add(
"fifo_push_metadata_fault",
[instruction(4, LocalOpcode.FIFO_PUSH, src0=4, src1=5)],
registers={4: local_value, 5: 0},
)
sample0 = random_source.randint(-0x80000000, 0x7FFFFFFF)
sample1 = random_source.randint(-0x80000000, 0x7FFFFFFF)
corpus.add(
"requant_shared_extreme",
[instruction(5, KernelOpcode.REQUANT, dst=2, src0=0, src1=1, aux=0xA0, immediate=2)],
registers={0: 0x100, 1: 0x180, 2: 0x200},
scratch_words={
**_word_map(0x2100, [sample0, -0x80000000]),
**_word_map(0x2180, [0x300]),
**_word_map(0x2300, [1 << 29, 0x7FFFFFFF]),
},
check_words=[0x2200, 0x2204],
)
corpus.add(
"stereo_shared_extreme",
[instruction(5, KernelOpcode.STEREO, dst=2, src0=0, src1=1, aux=2, immediate=2)],
registers={0: 0x100, 1: 0x180, 2: 0x200},
scratch_words={
**_word_map(0x2100, [sample0, 0x7FFFFFFF]),
**_word_map(0x2180, [0x300]),
**_word_map(0x2300, [sample1, 1]),
},
check_words=[0x2200, 0x2204, 0x2208, 0x220C],
)
imdct6_matrix = [0] * 72
for index in range(6):
imdct6_matrix[index * 6 + index] = 1 << 30
corpus.add(
"imdct6_shared_extreme",
[instruction(5, KernelOpcode.IMDCT6, dst=2, src0=0, src1=1, immediate=1)],
registers={0: 0x100, 1: 0x180, 2: 0x200},
table_words=_word_map(0, imdct6_matrix),
scratch_words={
**_word_map(0x2100, [sample0, sample1, -0x80000000, 0x7FFFFFFF, -1, 1]),
**_word_map(0x2180, [0x80000000]),
},
check_words=[0x2200 + index * 4 for index in range(12)],
)
imdct18_matrix = [0] * 648
for index in range(18):
imdct18_matrix[index * 18 + index] = 1 << 30
imdct18_inputs = [
-0x80000000,
0x7FFFFFFF,
*(random_source.randint(-0x80000000, 0x7FFFFFFF) for _ in range(16)),
]
corpus.add(
"imdct18_shared_random",
[instruction(5, KernelOpcode.IMDCT18, dst=2, src0=0, src1=1, immediate=1)],
registers={0: 0x100, 1: 0x180, 2: 0x200},
table_words=_word_map(0x200, imdct18_matrix),
scratch_words={
**_word_map(0x2100, imdct18_inputs),
**_word_map(0x2180, [0x80000200]),
},
check_words=[0x2200 + index * 4 for index in range(36)],
)
dct_matrix = [0] * 1024
dct_window = [0] * 512
for index in range(32):
dct_matrix[index * 32 + index] = 1 << 30
dct_window[index] = 1 << 30
dct_inputs = [
-0x80000000,
0x7FFFFFFF,
*(random_source.randint(-0x100000, 0x100000) for _ in range(30)),
]
corpus.add(
"dct32_shared_random",
[instruction(5, KernelOpcode.DCT32_POLY, dst=2, src0=0, src1=1, immediate=1)],
registers={0: 0x100, 1: 0x180, 2: 0x200},
table_words={**_word_map(0, dct_matrix), **_word_map(0x1000, dct_window)},
scratch_words={
**_word_map(0x2100, dct_inputs),
**_word_map(0x2180, [0x80000000, 0x80001000, 0x300, 0]),
**_word_map(0x2300, [0] * 512),
},
check_words=[0x2200 + index * 4 for index in range(32)] + [0x218C],
)
corpus.add(
"fixed_shared_random",
[instruction(5, KernelOpcode.FIXED, dst=2, src0=0, src1=1, aux=0, immediate=3)],
registers={0: 0x100, 1: 0x180, 2: 0x200},
scratch_words={
**_word_map(0x2100, [-0x80000000, 0x7FFFFFFF, sample0]),
**_word_map(0x2180, [0x300]),
**_word_map(0x2300, [random_source.getrandbits(32)]),
},
check_words=[0x2200, 0x2204, 0x2208],
)
corpus.add(
"fixed_shared_history_retention",
[
instruction(5, KernelOpcode.FIXED, dst=2, src0=0, src1=1, aux=2, immediate=2),
instruction(5, KernelOpcode.FIXED, dst=4, src0=3, src1=1, aux=2, immediate=2),
],
registers={0: 0x100, 1: 0x180, 2: 0x200, 3: 0x400, 4: 0x500},
scratch_words={
**_word_map(0x2100, [13, -7]),
**_word_map(0x2180, [0x300]),
**_word_map(0x2300, [5, -3]),
**_word_map(0x2400, [1, -2]),
},
check_words=[0x2200, 0x2204, 0x2300, 0x2304, 0x2500, 0x2504],
)
corpus.add(
"lpc_shared_random",
[instruction(5, KernelOpcode.LPC, dst=2, src0=0, src1=1, aux=2, immediate=2)],
registers={0: 0x100, 1: 0x180, 2: 0x200},
table_words=_word_map(0x1400, [1, -1]),
scratch_words={
**_word_map(0x2100, [sample0 >> 4, sample1 >> 4]),
**_word_map(0x2180, [0x80001400, 0x300, 0]),
**_word_map(0x2300, [5, -3]),
},
check_words=[0x2200, 0x2204, 0x2300, 0x2304],
)
corpus.add(
"lpc_shared_extreme",
[instruction(5, KernelOpcode.LPC, dst=2, src0=0, src1=1, aux=2, immediate=2)],
registers={0: 0x100, 1: 0x180, 2: 0x200},
table_words=_word_map(0x1400, [0, 0]),
scratch_words={
**_word_map(0x2100, [-0x80000000, 0x7FFFFFFF]),
**_word_map(0x2180, [0x80001400, 0x300, 0]),
**_word_map(0x2300, [sample0, sample1]),
},
check_words=[0x2200, 0x2204, 0x2300, 0x2304],
)
corpus.add(
"decorrelate_shared_extreme",
[instruction(5, KernelOpcode.DECORRELATE, dst=2, src0=0, src1=1, aux=1, immediate=3)],
registers={0: 0x100, 1: 0x180, 2: 0x200},
scratch_words={
**_word_map(0x2100, [-0x80000000, 0x7FFFFFFF, sample0]),
**_word_map(0x2180, [0x300]),
**_word_map(0x2300, [0, 0, 0]),
},
check_words=[0x2200 + index * 4 for index in range(6)],
)
resample_coefficients = [0] * 512
for phase in range(32):
resample_coefficients[phase * 16 + 7] = 1 << 30
corpus.add(
"resample_shared_profile3",
[instruction(5, KernelOpcode.RESAMPLE, dst=2, src0=0, src1=1, aux=3, immediate=1)],
registers={0: 0x100, 1: 0x180, 2: 0x200},
table_words=_word_map(0, resample_coefficients),
scratch_words={
**_word_map(
0x20E4,
[0] * 7
+ [-0x80000000]
+ [random_source.randint(-1000, 1000) for _ in range(9)],
),
**_word_map(0x2180, [0x80000000, 0x80000800, 0x80001000, 0, 0, 0, 0, 1]),
},
check_words=[0x2200, 0x2204, 0x218C, 0x2190, 0x2194, 0x2198],
)
corpus.add(
"pcm_pack_shared_extreme",
[instruction(5, KernelOpcode.PCM_PACK, dst=2, src0=0, src1=1, aux=0, immediate=2)],
registers={0: 0x100, 1: 0x180, 2: 0x200},
scratch_words={
**_word_map(0x2100, [-0x80000000, 0x7FFFFFFF, sample0, sample1]),
**_word_map(0x2180, [1 << 30, 2]),
},
check_words=[0x2200, 0x2204],
)
parameter_words = {
KernelOpcode.REQUANT: [0],
KernelOpcode.STEREO: [0],
KernelOpcode.IMDCT6: [0],
KernelOpcode.IMDCT18: [0],
KernelOpcode.DCT32_POLY: [0, 0, 0, 0],
KernelOpcode.FIXED: [0],
KernelOpcode.LPC: [0, 0, 0],
KernelOpcode.DECORRELATE: [0],
KernelOpcode.RESAMPLE: [0, 0, 0, 0, 0, 0, 0, 1],
KernelOpcode.PCM_PACK: [1 << 30, 1],
}
fault_aux = {
KernelOpcode.REQUANT: 0x80,
KernelOpcode.STEREO: 0,
KernelOpcode.IMDCT6: 0,
KernelOpcode.IMDCT18: 0,
KernelOpcode.DCT32_POLY: 0,
KernelOpcode.FIXED: 2,
KernelOpcode.LPC: 2,
KernelOpcode.DECORRELATE: 0,
KernelOpcode.RESAMPLE: 0,
KernelOpcode.PCM_PACK: 0,
}
for opcode in KernelOpcode:
corpus.add(
f"{opcode.name.lower()}_alignment_fault",
[instruction(5, opcode, dst=2, src0=0, src1=1, aux=fault_aux[opcode], immediate=1)],
registers={0: 0x101, 1: 0x180, 2: 0x200},
scratch_words=_word_map(0x2180, parameter_words[opcode]),
)
expected_opcodes = {
*((2, int(opcode)) for opcode in BitstreamOpcode),
*((3, int(opcode)) for opcode in EntropyOpcode),
*((4, int(opcode)) for opcode in LocalOpcode),
*((5, int(opcode)) for opcode in KernelOpcode),
}
assert corpus.covered == expected_opcodes
path = tmp_path / "apu_p4_corpus_generated.svh"
corpus.write(path)
return path, corpus.case_index
def test_p4_production_primitives_icarus_and_verilator(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
sv2v = shutil.which("sv2v")
verilator = shutil.which("verilator")
sources = _p4_rtl_sources()
include = ROOT / "rtl/ip/multimedia"
vector_path, vector_count = _requant_corpus(tmp_path)
shared_corpus_path, shared_case_count = _shared_primitive_corpus(tmp_path)
assert shared_case_count >= 32
assert shared_corpus_path.is_file()
if iverilog is not None and vvp is not None and sv2v is not None:
source_list = tmp_path / "apu_p4_primitives.fl"
source_list.write_text(
"\n".join(
[
"+define+APU_P4_SHARED_CORPUS",
f"+incdir+{include}",
f"+incdir+{tmp_path}",
*(str(path) for path in sources),
"",
]
),
encoding="utf-8",
)
converted = tmp_path / "apu_p4_primitives.v"
subprocess.run(
[
sys.executable,
str(ROOT / "scripts/rtl/convt_sv2v.py"),
"-f",
str(source_list),
"--output",
str(converted),
],
check=True,
)
simulation = tmp_path / "apu_p4_iverilog"
subprocess.run(
[iverilog, "-g2012", "-s", "apu_p4_primitives_tb", "-o", str(simulation), str(converted)],
check=True,
)
result = subprocess.run(
[vvp, str(simulation), f"+VECTORS={vector_path}", f"+VECTOR_COUNT={vector_count}"],
check=True,
text=True,
capture_output=True,
)
assert "APU_P4_PRIMITIVES_PASS" in result.stdout
if verilator is not None:
object_dir = tmp_path / "verilator_obj"
ccache_dir = tmp_path / "ccache"
ccache_dir.mkdir()
subprocess.run(
[
verilator,
"--binary",
"--timing",
"-Wno-fatal",
"--top-module",
"apu_p4_primitives_tb",
"-DAPU_P4_SHARED_CORPUS",
f"-I{include}",
f"-I{tmp_path}",
"-Mdir",
str(object_dir),
"-o",
"simv",
*(str(path) for path in sources),
],
check=True,
text=True,
capture_output=True,
env={
**os.environ,
"CCACHE_DIR": str(ccache_dir),
"CCACHE_TEMPDIR": str(ccache_dir),
},
)
result = subprocess.run(
[
str(object_dir / "simv"),
f"+VECTORS={vector_path}",
f"+VECTOR_COUNT={vector_count}",
],
check=True,
text=True,
capture_output=True,
)
assert "APU_P4_PRIMITIVES_PASS" in result.stdout
def test_p4_loader_publishes_control_and_table_atomically(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
sv2v = shutil.which("sv2v")
if iverilog is None or vvp is None or sv2v is None:
return
program = [
instruction(InstructionClass.CONTROL, ControlOpcode.WAIT, aux=1),
instruction(InstructionClass.CONTROL, ControlOpcode.END),
]
entries = [
Entry(index, 0, 0, 1, 1, 2, 0x1000, 0x1000, 0x40, 0, 4)
for index in range(3)
]
bundle = build_apumc(
program,
entries,
build_id=0x1234,
target="p4",
table_payload=struct.pack("<I", 0x00010001),
)
image = tmp_path / "apu_p4_loader.hex"
image.write_text(
"\n".join(f"{word:08x}" for (word,) in struct.iter_unpack("<I", bundle)) + "\n",
encoding="utf-8",
)
invalid_bundle = bytearray(bundle)
struct.pack_into("<I", invalid_bundle, 64 + 28, 8)
struct.pack_into("<I", invalid_bundle, 44, 0)
struct.pack_into("<I", invalid_bundle, 44, binascii.crc32(invalid_bundle[64:]))
invalid_image = tmp_path / "apu_p4_loader_invalid.hex"
invalid_image.write_text(
"\n".join(
f"{word:08x}" for (word,) in struct.iter_unpack("<I", invalid_bundle)
)
+ "\n",
encoding="utf-8",
)
multimedia = ROOT / "rtl/ip/multimedia"
source_list = tmp_path / "apu_p4_loader.fl"
source_list.write_text(
"\n".join(
[
f"+incdir+{multimedia}",
str(ROOT / "rtl/tech/tc_sram.sv"),
str(multimedia / "apu_microcode_pkg.sv"),
str(multimedia / "apu_control_store.sv"),
str(multimedia / "apu_local_sram.sv"),
str(multimedia / "apu_proof_memo.sv"),
str(multimedia / "apu_microcode_loader.sv"),
str(ROOT / "tests/rtl/apu_p4_loader_tb.sv"),
"",
]
),
encoding="utf-8",
)
converted = tmp_path / "apu_p4_loader.v"
subprocess.run(
[
sys.executable,
str(ROOT / "scripts/rtl/convt_sv2v.py"),
"-f",
str(source_list),
"--output",
str(converted),
],
check=True,
)
simulation = tmp_path / "apu_p4_loader"
subprocess.run(
[iverilog, "-g2012", "-s", "apu_p4_loader_tb", "-o", str(simulation), str(converted)],
check=True,
)
result = subprocess.run(
[vvp, str(simulation), f"+IMAGE={image}", f"+INVALID_IMAGE={invalid_image}"],
check=True,
text=True,
capture_output=True,
)
assert "APU_P4_LOADER_PASS" in result.stdout
def test_p4_sequencer_kernel_pending_and_wait(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
sv2v = shutil.which("sv2v")
if iverilog is None or vvp is None or sv2v is None:
return
multimedia = ROOT / "rtl/ip/multimedia"
sources = [
ROOT / "rtl/managed/clusterip/common/rtl/utils/fifo.sv",
multimedia / "apu_microcode_pkg.sv",
multimedia / "apu_control_store.sv",
multimedia / "apu_local_sram.sv",
multimedia / "apu_bitstream_engine.sv",
multimedia / "apu_entropy_engine.sv",
multimedia / "apu_reconstruction_engine.sv",
multimedia / "apu_transform_engine.sv",
multimedia / "apu_resampler.sv",
multimedia / "apu_kernel_engine.sv",
multimedia / "apu_primitive_dispatcher.sv",
multimedia / "apu_codec_sequencer.sv",
ROOT / "tests/rtl/apu_p4_sequencer_tb.sv",
]
source_list = tmp_path / "apu_p4_sequencer.fl"
source_list.write_text(
"\n".join([f"+incdir+{multimedia}", *(str(path) for path in sources), ""]),
encoding="utf-8",
)
converted = tmp_path / "apu_p4_sequencer.v"
subprocess.run(
[
sys.executable,
str(ROOT / "scripts/rtl/convt_sv2v.py"),
"-f",
str(source_list),
"--output",
str(converted),
],
check=True,
)
simulation = tmp_path / "apu_p4_sequencer"
subprocess.run(
[iverilog, "-g2012", "-s", "apu_p4_sequencer_tb", "-o", str(simulation), str(converted)],
check=True,
)
result = subprocess.run([vvp, str(simulation)], check=True, text=True, capture_output=True)
assert "APU_P4_SEQUENCER_PASS" in result.stdout