Files
retroSoC/scripts/apu_primitives.py
T
maksyuki 57ffd03216 feat: update APU tests to synchronize with P4 ISA and add APU primitives filelist checks
- Modified test_apu_register_parity.py to reflect changes in P4 ISA definitions.
- Updated test_apu_register_parity.py to include new manifest entries for P4.
- Enhanced test_script_tools.py to include checks for APU primitives filelists.
- Added assertions for APU primitives in the formal filelists test to ensure proper scoping and inclusion.
2026-09-05 07:29:50 +08:00

681 lines
31 KiB
Python

"""Bit-accurate APU-P4 primitive reference model."""
from __future__ import annotations
from dataclasses import dataclass, field
from apu_isa import (
BitstreamOpcode,
EntropyOpcode,
Entry,
Instruction,
InstructionClass,
KernelOpcode,
LocalOpcode,
)
LOCAL_DATA_BYTES = 0x1C000
CODEC_BYTES = 0xA000
TABLE_SCRATCH_BYTES = 0x6000
INPUT_BASE = 0x6000
OUTPUT_BASE = 0x8000
KWS_BASE = 0xA000
INTERNAL_BASE = 0x1A000
class PrimitiveFault(RuntimeError):
def __init__(self, code: int, stage: int, reason: int = 9):
self.code = code
self.stage = stage
self.reason = reason
super().__init__(f"APU primitive fault code={code} stage={stage} reason={reason}")
@dataclass
class LocalMemory:
data: bytearray = field(default_factory=lambda: bytearray(LOCAL_DATA_BYTES))
valid_words: list[bool] = field(default_factory=lambda: [False] * (CODEC_BYTES // 4))
table_bytes: int = 0
image_valid: bool = False
def publish_table(self, payload: bytes) -> None:
if len(payload) > TABLE_SCRATCH_BYTES or len(payload) & 3:
raise ValueError("P4 table payload is outside the frozen workspace")
self.data[: len(payload)] = payload
self.table_bytes = len(payload)
self.image_valid = True
def invalidate_image(self) -> None:
self.image_valid = False
self.table_bytes = 0
def new_epoch(self) -> None:
self.valid_words = [False] * len(self.valid_words)
def _check_range(self, address: int, size: int, entry: Entry, table: bool = False) -> None:
if address < 0 or size < 1 or address + size > LOCAL_DATA_BYTES:
raise PrimitiveFault(11, 11, 5)
if table:
first = entry.table_offset
last = first + entry.table_bytes
if not self.image_valid or address < first or address + size > last:
raise PrimitiveFault(11, 11, 5)
else:
first = entry.scratch_base
last = first + entry.scratch_bytes
if address < first or address + size > last:
raise PrimitiveFault(11, 11, 5)
def read(self, address: int, size: int, entry: Entry, table: bool = False) -> bytes:
self._check_range(address, size, entry, table)
if not table:
first_word = address // 4
last_word = (address + size - 1) // 4
if any(not self.valid_words[index] for index in range(first_word, last_word + 1)):
raise PrimitiveFault(11, 11, 5)
return bytes(self.data[address : address + size])
def write(self, address: int, payload: bytes, entry: Entry) -> None:
self._check_range(address, len(payload), entry)
if address < self.table_bytes:
raise PrimitiveFault(11, 11, 5)
self.data[address : address + len(payload)] = payload
first_word = address // 4
last_word = (address + len(payload) - 1) // 4
for index in range(first_word, last_word + 1):
self.valid_words[index] = True
def inject(self, address: int, payload: bytes) -> None:
if address < 0 or address + len(payload) > CODEC_BYTES:
raise ValueError("verification injection exceeds the P4 codec region")
self.data[address : address + len(payload)] = payload
first_word = address // 4
last_word = (address + len(payload) - 1) // 4
for index in range(first_word, last_word + 1):
self.valid_words[index] = True
@dataclass
class PrimitiveFifo:
words: list[int] = field(default_factory=list)
@property
def full(self) -> bool:
return len(self.words) == 64
@property
def empty(self) -> bool:
return not self.words
def push(self, word: int) -> None:
if self.full or word & (0x1F << 35) or not 1 <= ((word >> 32) & 7) <= 4:
raise PrimitiveFault(22, 7)
self.words.append(word & ((1 << 41) - 1))
def pop(self) -> int:
if self.empty:
raise PrimitiveFault(22, 4)
return self.words.pop(0)
def flush(self) -> None:
self.words.clear()
@dataclass
class PrimitiveResult:
writes: dict[int, int] = field(default_factory=dict)
cycles: int = 1
kernel_done: bool = False
class PrimitiveBam:
def __init__(self, memory: LocalMemory | None = None):
self.memory = memory if memory is not None else LocalMemory()
self.input_fifo = PrimitiveFifo()
self.output_fifo = PrimitiveFifo()
self.bits: list[int] = []
self.eof = False
self.bit_position = 0
self.kernel_done = False
self.resample_profile: int | None = None
@staticmethod
def signed(value: int, width: int = 32) -> int:
value &= (1 << width) - 1
return value - (1 << width) if value & (1 << (width - 1)) else value
@staticmethod
def sat(value: int, width: int) -> int:
return min(max(value, -(1 << (width - 1))), (1 << (width - 1)) - 1)
@staticmethod
def rne(value: int, shift: int) -> int:
if shift == 0:
return value
magnitude = abs(value)
quotient, remainder = divmod(magnitude, 1 << shift)
halfway = 1 << (shift - 1)
if remainder > halfway or (remainder == halfway and quotient & 1):
quotient += 1
return -quotient if value < 0 else quotient
@staticmethod
def _u32(value: int) -> int:
return value & 0xFFFFFFFF
def reset_mutable(self) -> None:
self.memory.new_epoch()
self.input_fifo.flush()
self.output_fifo.flush()
self.bits.clear()
self.eof = False
self.bit_position = 0
self.kernel_done = False
self.resample_profile = None
def _refill(self, width: int) -> None:
while len(self.bits) < width and not self.input_fifo.empty:
word = self.input_fifo.pop()
count = (word >> 32) & 7
for byte_index in range(count):
byte = (word >> (8 * byte_index)) & 0xFF
self.bits.extend((byte >> bit_index) & 1 for bit_index in range(7, -1, -1))
if word & (1 << 40):
self.eof = True
def _peek_bits(self, width: int, stage: int) -> int:
self._refill(width)
if len(self.bits) < width:
raise PrimitiveFault(5, stage)
value = 0
for bit in self.bits[:width]:
value = (value << 1) | bit
return value
def _get_bits(self, width: int, stage: int) -> int:
value = self._peek_bits(width, stage)
del self.bits[:width]
self.bit_position = (self.bit_position + width) & 7
return value
def _read_u32(self, address: int, entry: Entry, table: bool = False) -> int:
return int.from_bytes(self.memory.read(address, 4, entry, table), "little")
def _read_s32(self, address: int, entry: Entry, table: bool = False) -> int:
return self.signed(self._read_u32(address, entry, table))
def _write_s32(self, address: int, value: int, entry: Entry) -> None:
self.memory.write(address, self._u32(value).to_bytes(4, "little"), entry)
def _resolve_ref(self, word: int, entry: Entry, size: int, *, state: bool = False) -> tuple[int, bool]:
table = bool(word & 0x80000000)
if word & (0x7FFF0000 if table else 0x7FFE0000) or word & 3:
raise PrimitiveFault(11, 11, 5)
if state and table:
raise PrimitiveFault(11, 11, 5)
offset = word & (0xFFFF if table else 0x1FFFF)
address = (entry.table_offset if table else entry.scratch_base) + offset
self.memory._check_range(address, size, entry, table)
return address, table
def _dot(self, left: list[int], right: list[int]) -> int:
total = sum(a * b for a, b in zip(left, right, strict=True))
if total < -(1 << 71) or total > (1 << 71) - 1:
raise PrimitiveFault(22, 6)
return total
def execute(self, instruction: Instruction, registers: list[int], entry: Entry) -> PrimitiveResult:
instruction_class = instruction.instruction_class
if instruction_class == InstructionClass.BITSTREAM:
return self._execute_bitstream(instruction, registers)
if instruction_class == InstructionClass.ENTROPY:
return self._execute_entropy(instruction, registers, entry)
if instruction_class == InstructionClass.LOCAL:
return self._execute_local(instruction, registers, entry)
if instruction_class == InstructionClass.KERNEL:
return self._execute_kernel(instruction, registers, entry)
raise ValueError("instruction is not an APU-P4 primitive")
def _execute_bitstream(self, instruction: Instruction, registers: list[int]) -> PrimitiveResult:
opcode = BitstreamOpcode(instruction.opcode)
width = instruction.immediate & 0x3F
writes: dict[int, int] = {}
cycles = 1
if opcode == BitstreamOpcode.REFILL:
self._refill(width)
writes[instruction.dst] = min(len(self.bits), 64)
cycles = 3
elif opcode == BitstreamOpcode.PEEK:
writes[instruction.dst] = self._peek_bits(width, 4)
elif opcode == BitstreamOpcode.GET:
writes[instruction.dst] = self._get_bits(width, 4)
elif opcode == BitstreamOpcode.SKIP:
self._get_bits(width, 4)
elif opcode == BitstreamOpcode.ALIGN:
skip = (-self.bit_position) & 7
if skip:
self._get_bits(skip, 4)
elif opcode == BitstreamOpcode.FRAME_SYNC:
if self.bit_position:
self._get_bits((-self.bit_position) & 7, 4)
pattern = registers[instruction.src0] & 0xFFFF
mask = registers[instruction.src1] & 0xFFFF
width = instruction.aux & 0x1F
skipped = 0
while skipped < instruction.immediate:
if self._peek_bits(width, 4) & mask == pattern & mask:
writes[instruction.dst] = skipped
cycles = 2 + 2 * skipped
break
if skipped + 1 == instruction.immediate:
raise PrimitiveFault(4, 4)
self._get_bits(8, 4)
skipped += 1
else:
raise PrimitiveFault(4, 4)
elif opcode == BitstreamOpcode.CRC8:
crc = registers[instruction.src0] & 0xFF
byte = registers[instruction.src1] & 0xFF
for bit_index in range(8):
feedback = ((crc >> 7) ^ (byte >> (7 - bit_index))) & 1
crc = (crc << 1) & 0xFF
if feedback:
crc ^= 0x07
writes[instruction.dst] = crc
else:
crc = registers[instruction.src0] & 0xFFFF
byte = registers[instruction.src1] & 0xFF
for bit_index in range(8):
feedback = ((crc >> 15) ^ (byte >> (7 - bit_index))) & 1
crc = (crc << 1) & 0xFFFF
if feedback:
crc ^= 0x8005
writes[instruction.dst] = crc
return PrimitiveResult(writes, cycles)
def _execute_entropy(
self, instruction: Instruction, registers: list[int], entry: Entry
) -> PrimitiveResult:
opcode = EntropyOpcode(instruction.opcode)
writes: dict[int, int] = {}
if opcode <= EntropyOpcode.HUFF_QUAD:
count = registers[instruction.src1]
if not 1 <= count <= 4096:
raise PrimitiveFault(7, 5)
start = entry.table_offset + registers[instruction.src0]
entries = [self._read_u32(start + index * 4, entry, True) for index in range(count)]
code = 0
previous_length = 0
match = None
for word in entries:
length = (word >> 16) & 0x1F
if not previous_length <= length <= instruction.aux or word >> 21:
raise PrimitiveFault(7, 5)
code <<= length - previous_length
if self._peek_bits(length, 5) == code:
match = (word & 0xFFFF, length)
break
code += 1
previous_length = length
if match is None:
raise PrimitiveFault(7, 5)
symbol, length = match
self._get_bits(length, 5)
if opcode == EntropyOpcode.HUFF_SYMBOL:
writes[instruction.dst] = symbol
elif opcode == EntropyOpcode.HUFF_PAIR:
writes[instruction.dst] = (symbol >> 4) & 0xF
writes[instruction.dst + 1] = symbol & 0xF
else:
for index in range(4):
writes[instruction.dst + index] = (symbol >> (3 - index)) & 1
return PrimitiveResult(writes, 2 + 24 + count)
if opcode == EntropyOpcode.UNARY:
polarity = instruction.aux & 1
run = 0
while run <= instruction.immediate:
bit = self._get_bits(1, 5)
if bit != polarity:
writes[instruction.dst] = run
return PrimitiveResult(writes, 2 + run)
run += 1
raise PrimitiveFault(7, 5)
if opcode in (EntropyOpcode.RICE4, EntropyOpcode.RICE5):
width = 4 if opcode == EntropyOpcode.RICE4 else 5
escape = 15 if width == 4 else 31
parameter = registers[instruction.src0] & ((1 << width) - 1)
if parameter == escape:
raw_width = registers[instruction.src1] & 0x3F
if not 1 <= raw_width <= 32:
raise PrimitiveFault(7, 5)
value = self._get_bits(raw_width, 5)
writes[instruction.dst] = self._u32(self.signed(value, raw_width))
return PrimitiveResult(writes, 4 + raw_width)
quotient = 0
while self._get_bits(1, 5) == 0:
quotient += 1
if quotient > 65535:
raise PrimitiveFault(7, 5)
remainder = self._get_bits(parameter, 5) if parameter else 0
unsigned = (quotient << parameter) | remainder
writes[instruction.dst] = self._u32((unsigned >> 1) ^ -(unsigned & 1))
return PrimitiveResult(writes, 4 + quotient + parameter)
magnitude = registers[instruction.src0]
if magnitude == 0:
writes[instruction.dst] = 0
return PrimitiveResult(writes, 3)
linbits = registers[instruction.src1] & 0x1F
extra = self._get_bits(linbits, 5) if linbits else 0
value = magnitude + extra
if self._get_bits(1, 5):
value = -value
writes[instruction.dst] = self._u32(value)
return PrimitiveResult(writes, 3 + linbits)
def _execute_local(
self, instruction: Instruction, registers: list[int], entry: Entry
) -> PrimitiveResult:
opcode = LocalOpcode(instruction.opcode)
writes: dict[int, int] = {}
if opcode in (LocalOpcode.LD32, LocalOpcode.ST32):
offset = self.signed(instruction.immediate, 16)
address = entry.scratch_base + registers[instruction.src0] + offset
if address & 3:
raise PrimitiveFault(11, 11, 5)
if opcode == LocalOpcode.LD32:
writes[instruction.dst] = self._read_u32(address, entry)
return PrimitiveResult(writes, 2)
self._write_s32(address, registers[instruction.src1], entry)
return PrimitiveResult(cycles=1)
if opcode in (LocalOpcode.TABLE8, LocalOpcode.TABLE16, LocalOpcode.TABLE32):
size = 1 << (instruction.opcode - LocalOpcode.TABLE8)
address = entry.table_offset + registers[instruction.src0] + registers[instruction.src1] * size
if address & (size - 1):
raise PrimitiveFault(11, 11, 5)
writes[instruction.dst] = int.from_bytes(self.memory.read(address, size, entry, True), "little")
return PrimitiveResult(writes, 2)
if opcode == LocalOpcode.FIFO_POP:
word = self.input_fifo.pop()
writes[instruction.dst] = word & 0xFFFFFFFF
writes[instruction.dst + 1] = ((word >> 32) & 7) | (((word >> 40) & 1) << 8)
return PrimitiveResult(writes)
metadata = registers[instruction.src1]
if metadata & ~0x107 or not 1 <= (metadata & 7) <= 4:
raise PrimitiveFault(22, 7)
self.output_fifo.push(
(registers[instruction.src0] & 0xFFFFFFFF)
| ((metadata & 7) << 32)
| (((metadata >> 8) & 1) << 40)
)
return PrimitiveResult()
def _kernel_arrays(
self, instruction: Instruction, registers: list[int], entry: Entry
) -> tuple[int, int, int, int]:
count = instruction.immediate & 0xFFFF
input_address = entry.scratch_base + registers[instruction.src0]
parameter_address = entry.scratch_base + registers[instruction.src1]
output_address = entry.scratch_base + registers[instruction.dst]
output_alignment = (
2
if instruction.opcode == KernelOpcode.PCM_PACK and (instruction.aux & 3) == 0
else 4
)
if (
entry.scratch_base & 3
or input_address & 3
or parameter_address & 3
or output_address & (output_alignment - 1)
):
raise PrimitiveFault(11, 11, 5)
self.memory._check_range(parameter_address, 4, entry)
return count, input_address, parameter_address, output_address
def _execute_kernel(
self, instruction: Instruction, registers: list[int], entry: Entry
) -> PrimitiveResult:
opcode = KernelOpcode(instruction.opcode)
count, input_address, parameter_address, output_address = self._kernel_arrays(
instruction, registers, entry
)
produced = 0
cycles = 8
if opcode == KernelOpcode.REQUANT:
reference = self._read_u32(parameter_address, entry)
scale_address, scale_table = self._resolve_ref(reference, entry, count * 4)
self.memory._check_range(input_address, count * 4, entry)
width = (16, 24, 32)[instruction.aux >> 6]
shift = 30 + (instruction.aux & 0x1F)
for index in range(count):
sample = self._read_s32(input_address + index * 4, entry)
scale = self._read_s32(scale_address + index * 4, entry, scale_table)
product = sample * scale
value = self.rne(product, shift) if instruction.aux & 0x20 else product >> shift
self._write_s32(output_address + index * 4, self.sat(value, width), entry)
produced, cycles = count, 8 + 4 * count
elif opcode in (KernelOpcode.STEREO, KernelOpcode.DECORRELATE):
reference = self._read_u32(parameter_address, entry)
second_address, _ = self._resolve_ref(reference, entry, count * 4, state=True)
self.memory._check_range(input_address, count * 4, entry)
for index in range(count):
left = self._read_s32(input_address + index * 4, entry)
right = self._read_s32(second_address + index * 4, entry)
mode = instruction.aux & 3
if mode == 0:
pair = left, right
elif mode == 1:
pair = left, left - right
elif mode == 2:
pair = left + right, right
else:
middle = left * 2 + (right & 1)
pair = (middle + right) >> 1, (middle - right) >> 1
if opcode == KernelOpcode.DECORRELATE and any(
value < -(1 << 31) or value > (1 << 31) - 1 for value in pair
):
raise PrimitiveFault(8, 6)
for channel, value in enumerate(pair):
self._write_s32(output_address + (channel * count + index) * 4, self.sat(value, 32), entry)
produced, cycles = 2 * count, 8 + 4 * count
elif opcode in (KernelOpcode.IMDCT6, KernelOpcode.IMDCT18):
order = 6 if opcode == KernelOpcode.IMDCT6 else 18
reference = self._read_u32(parameter_address, entry)
matrix_address, matrix_table = self._resolve_ref(reference, entry, 2 * order * order * 4)
self.memory._check_range(input_address, count * order * 4, entry)
for block in range(count):
values = [self._read_s32(input_address + (block * order + item) * 4, entry) for item in range(order)]
for row in range(2 * order):
coefficients = [
self._read_s32(matrix_address + (row * order + item) * 4, entry, matrix_table)
for item in range(order)
]
value = self.sat(self.rne(self._dot(values, coefficients), 30), 32)
self._write_s32(output_address + (block * 2 * order + row) * 4, value, entry)
produced = count * 2 * order
cycles = 8 + (94 if order == 6 else 706) * count
elif opcode == KernelOpcode.FIXED:
order = instruction.aux & 7
reference = self._read_u32(parameter_address, entry)
history_address, _ = self._resolve_ref(reference, entry, max(order, 1) * 4, state=True)
history = [self._read_s32(history_address + index * 4, entry) for index in range(order)]
self.memory._check_range(input_address, count * 4, entry)
for index in range(count):
if order == 0:
predictor = 0
elif order == 1:
predictor = history[0]
elif order == 2:
predictor = 2 * history[0] - history[1]
elif order == 3:
predictor = 3 * history[0] - 3 * history[1] + history[2]
else:
predictor = 4 * history[0] - 6 * history[1] + 4 * history[2] - history[3]
value = predictor + self._read_s32(input_address + index * 4, entry)
if value < -(1 << 31) or value > (1 << 31) - 1:
raise PrimitiveFault(8, 6)
self._write_s32(output_address + index * 4, value, entry)
if order:
history = [value, *history[:-1]]
for index, value in enumerate(history):
self._write_s32(history_address + index * 4, value, entry)
produced, cycles = count, 8 + (order + 4) * count
elif opcode == KernelOpcode.LPC:
order = instruction.aux & 0x3F
coefficient_ref = self._read_u32(parameter_address, entry)
history_ref = self._read_u32(parameter_address + 4, entry)
shift = self.signed(self._read_u32(parameter_address + 8, entry), 32)
if not -31 <= shift <= 31:
raise PrimitiveFault(11, 11, 5)
coefficient_address, coefficient_table = self._resolve_ref(coefficient_ref, entry, order * 4)
history_address, _ = self._resolve_ref(history_ref, entry, order * 4, state=True)
coefficients = [
self._read_s32(coefficient_address + index * 4, entry, coefficient_table)
for index in range(order)
]
history = [self._read_s32(history_address + index * 4, entry) for index in range(order)]
for index in range(count):
predictor = self._dot(coefficients, history)
predictor = predictor >> shift if shift >= 0 else predictor << -shift
value = predictor + self._read_s32(input_address + index * 4, entry)
if value < -(1 << 31) or value > (1 << 31) - 1:
raise PrimitiveFault(8, 6)
self._write_s32(output_address + index * 4, value, entry)
history = [value, *history[:-1]]
for index, value in enumerate(history):
self._write_s32(history_address + index * 4, value, entry)
produced, cycles = count, 8 + (2 * order + 5) * count
elif opcode == KernelOpcode.PCM_PACK:
gain = self._read_s32(parameter_address, entry)
channels = self._read_u32(parameter_address + 4, entry)
if channels not in (1, 2) or (instruction.aux & 4 and channels != 1):
raise PrimitiveFault(8, 7)
samples = count * channels
output = bytearray()
for index in range(samples):
sample = self._read_s32(input_address + index * 4, entry)
value = self.rne(sample * gain, 30)
packed = self.sat(value, 16 if (instruction.aux & 3) == 0 else 24)
encoded = self._u32(packed).to_bytes(4, "little")
encoded = encoded[:2] if (instruction.aux & 3) == 0 else encoded
output.extend(encoded)
if instruction.aux & 4:
output.extend(encoded)
self.memory.write(output_address, output, entry)
produced, cycles = len(output), 8 + 5 * (count * (2 if instruction.aux & 4 else channels))
elif opcode == KernelOpcode.RESAMPLE:
produced, cycles = self._resample(instruction, count, input_address, parameter_address, output_address, entry)
else:
produced, cycles = self._dct32(count, input_address, parameter_address, output_address, entry)
self.kernel_done = True
return PrimitiveResult({instruction.dst: produced}, cycles, True)
def _dct32(self, count: int, input_address: int, parameter_address: int,
output_address: int, entry: Entry) -> tuple[int, int]:
refs = [self._read_u32(parameter_address + index * 4, entry) for index in range(3)]
phase = self._read_u32(parameter_address + 12, entry)
if phase >> 4:
raise PrimitiveFault(11, 11, 5)
matrix_address, matrix_table = self._resolve_ref(refs[0], entry, 32 * 32 * 4)
window_address, window_table = self._resolve_ref(refs[1], entry, 16 * 32 * 4)
history_address, _ = self._resolve_ref(refs[2], entry, 16 * 32 * 4, state=True)
for block in range(count):
values = [self._read_s32(input_address + (block * 32 + item) * 4, entry) for item in range(32)]
transformed = []
for row in range(32):
coefficients = [
self._read_s32(matrix_address + (row * 32 + item) * 4, entry, matrix_table)
for item in range(32)
]
transformed.append(self.sat(self.rne(self._dot(values, coefficients), 30), 32))
for item, value in enumerate(transformed):
self._write_s32(history_address + (phase * 32 + item) * 4, value, entry)
for item in range(32):
history = [
self._read_s32(history_address + (((phase - tap) & 15) * 32 + item) * 4, entry)
for tap in range(16)
]
window = [
self._read_s32(window_address + (tap * 32 + item) * 4, entry, window_table)
for tap in range(16)
]
self._write_s32(
output_address + (block * 32 + item) * 4,
self.sat(self.rne(self._dot(history, window), 30), 32),
entry,
)
phase = (phase + 1) & 15
self._write_s32(parameter_address + 12, phase, entry)
return 32 * count, 8 + 2304 * count
def _resample(self, instruction: Instruction, count: int, input_address: int,
parameter_address: int, output_address: int, entry: Entry) -> tuple[int, int]:
ratios = (
(1, 1), (1, 2), (80, 147), (160, 147), (3, 2), (2, 1), (3, 1), (4, 1),
(6, 1), (8, 1), (12, 1), (320, 147), (640, 147), (1280, 147), (2, 3), (4, 3),
)
profile = instruction.aux & 15
if self.resample_profile is None:
self.resample_profile = profile
elif profile != self.resample_profile:
raise PrimitiveFault(8, 7)
refs = [self._read_u32(parameter_address + index * 4, entry) for index in range(3)]
next_output = self._read_u32(parameter_address + 12, entry) | (
self._read_u32(parameter_address + 16, entry) << 32
)
input_base = self._read_u32(parameter_address + 20, entry) | (
self._read_u32(parameter_address + 24, entry) << 32
)
channels = self._read_u32(parameter_address + 28, entry)
if channels not in (1, 2):
raise PrimitiveFault(8, 7)
if profile == 0:
payload = self.memory.read(input_address, count * channels * 4, entry)
self.memory.write(output_address, payload, entry)
next_output += count
input_base += count
produced = count
cycles = 8 + 2 * count * channels
else:
bank_index = 2 if profile in (1, 2) else (1 if profile == 14 else 0)
coefficient_address, coefficient_table = self._resolve_ref(refs[bank_index], entry, 32 * 16 * 4)
numerator, denominator = ratios[profile]
produced = 0
while next_output // numerator < input_base + count:
source_index, remainder = divmod(next_output, numerator)
quotient, phase_remainder = divmod(32 * remainder, numerator)
phase = quotient
doubled_remainder = 2 * phase_remainder
if doubled_remainder > numerator or (
doubled_remainder == numerator and phase & 1
):
phase += 1
if phase == 32:
phase = 0
source_index += 1
for channel in range(channels):
samples = [
self._read_s32(
input_address + ((source_index + tap - 7 - input_base) * channels + channel) * 4,
entry,
)
for tap in range(16)
]
coefficients = [
self._read_s32(coefficient_address + (phase * 16 + tap) * 4, entry, coefficient_table)
for tap in range(16)
]
self._write_s32(
output_address + (produced * channels + channel) * 4,
self.sat(self.rne(self._dot(samples, coefficients), 30), 32),
entry,
)
produced += 1
next_output += denominator
input_base += count
cycles = 8 + 2 * count * channels + 36 * produced * channels
for index, value in enumerate((next_output, input_base)):
self._write_s32(parameter_address + 12 + index * 8, value & 0xFFFFFFFF, entry)
self._write_s32(parameter_address + 16 + index * 8, value >> 32, entry)
return produced, cycles