This commit is contained in:
2026-03-13 19:58:28 +03:00
commit c938609393
87 changed files with 230200 additions and 0 deletions

View File

@@ -0,0 +1,293 @@
from amaranth import *
from amaranth.build import Platform
from amaranth.lib.fifo import SyncFIFO
from amlib.stream import StreamInterface, connect_fifo_to_stream
from amlib.test import GatewareTestCase, sync_test_case
class ChannelsToUSBStream(Elaboratable):
def __init__(self, max_nr_channels=2, sample_width=24, max_packet_size=256):
assert sample_width in [16, 24, 32]
# parameters
self._max_nr_channels = max_nr_channels
self._channel_bits = Shape.cast(range(max_nr_channels)).width
self._sample_width = sample_width
self._fifo_depth = 2 * max_packet_size
# ports
self.no_channels_in = Signal(self._channel_bits + 1)
self.channel_stream_in = StreamInterface(name="channel_stream", payload_width=self._sample_width, extra_fields=[("channel_nr", self._channel_bits)])
self.usb_stream_out = StreamInterface(name="usb_stream")
self.audio_in_active = Signal()
self.data_requested_in = Signal()
self.frame_finished_in = Signal()
# debug signals
self.feeder_state = Signal()
self.current_channel = Signal(self._channel_bits)
self.level = Signal(range(self._fifo_depth + 1))
self.fifo_read = Signal()
self.fifo_full = Signal()
self.fifo_level_insufficient = Signal()
self.done = Signal.like(self.level)
self.out_channel = Signal(self._channel_bits)
self.usb_channel = Signal.like(self.out_channel)
self.usb_byte_pos = Signal.like(2)
self.skipping = Signal()
self.filling = Signal()
def elaborate(self, platform: Platform) -> Module:
m = Module()
m.submodules.out_fifo = out_fifo = SyncFIFO(width=8 + self._channel_bits, depth=self._fifo_depth, fwft=True)
channel_stream = self.channel_stream_in
channel_valid = Signal()
channel_ready = Signal()
out_valid = Signal()
out_stream_ready = Signal()
# latch packet start and end
first_packet_seen = Signal()
frame_finished_seen = Signal()
with m.If(self.data_requested_in):
m.d.sync += [
first_packet_seen.eq(1),
frame_finished_seen.eq(0),
]
with m.If(self.frame_finished_in):
m.d.sync += [
frame_finished_seen.eq(1),
first_packet_seen.eq(0),
]
m.d.comb += [
self.usb_stream_out.payload.eq(out_fifo.r_data[:8]),
self.out_channel.eq(out_fifo.r_data[8:]),
self.usb_stream_out.valid.eq(out_valid),
out_stream_ready.eq(self.usb_stream_out.ready),
channel_valid.eq(channel_stream.valid),
channel_stream.ready.eq(channel_ready),
self.level.eq(out_fifo.r_level),
self.fifo_full.eq(self.level >= (self._fifo_depth - 4)),
self.fifo_read.eq(out_fifo.r_en),
]
with m.If(self.usb_stream_out.valid & self.usb_stream_out.ready):
m.d.sync += self.done.eq(self.done + 1)
with m.If(self.data_requested_in):
m.d.sync += self.done.eq(0)
current_sample = Signal(32 if self._sample_width > 16 else 16)
current_channel = Signal(self._channel_bits)
current_byte = Signal(2 if self._sample_width > 16 else 1)
m.d.comb += self.current_channel.eq(current_channel),
bytes_per_sample = 4
last_byte_of_sample = bytes_per_sample - 1
# USB audio still sends 32 bit samples,
# even if the descriptor says 24
shift = 8 if self._sample_width == 24 else 0
out_fifo_can_write_sample = Signal()
m.d.comb += out_fifo_can_write_sample.eq(
out_fifo.w_rdy
& (out_fifo.w_level < (out_fifo.depth - bytes_per_sample)))
# this FSM handles writing into the FIFO
with m.FSM(name="fifo_feeder") as fsm:
m.d.comb += self.feeder_state.eq(fsm.state)
with m.State("WAIT"):
m.d.comb += channel_ready.eq(out_fifo_can_write_sample)
# discard all channels above no_channels_in
# important for stereo operation
with m.If( out_fifo_can_write_sample
& channel_valid
& (channel_stream.channel_nr < self.no_channels_in)):
m.d.sync += [
current_sample.eq(channel_stream.payload << shift),
current_channel.eq(channel_stream.channel_nr),
]
m.next = "SEND"
with m.State("SEND"):
m.d.comb += [
out_fifo.w_data[:8].eq(current_sample[0:8]),
out_fifo.w_data[8:].eq(current_channel),
out_fifo.w_en.eq(1),
]
m.d.sync += [
current_byte.eq(current_byte + 1),
current_sample.eq(current_sample >> 8),
]
with m.If(current_byte == last_byte_of_sample):
m.d.sync += current_byte.eq(0)
m.next = "WAIT"
channel_counter = Signal.like(self.no_channels_in)
byte_pos = Signal(2)
first_byte = byte_pos == 0
last_byte = byte_pos == 3
with m.If(out_valid & out_stream_ready):
m.d.sync += byte_pos.eq(byte_pos + 1)
with m.If(last_byte):
m.d.sync += channel_counter.eq(channel_counter + 1)
with m.If(channel_counter == (self.no_channels_in - 1)):
m.d.sync += channel_counter.eq(0)
with m.If(self.data_requested_in):
m.d.sync += channel_counter.eq(0)
fifo_level_sufficient = Signal()
m.d.comb += [
self.usb_channel.eq(channel_counter),
self.usb_byte_pos.eq(byte_pos),
fifo_level_sufficient.eq(out_fifo.level >= (self.no_channels_in << 2)),
self.fifo_level_insufficient.eq(~fifo_level_sufficient),
]
with m.If(self.frame_finished_in):
m.d.sync += byte_pos.eq(0)
# this FSM handles reading fron the FIFO
# this FSM provides robustness against
# short reads. On next frame all bytes
# for nonzero channels will be discarded until
# we reach channel 0 again.
with m.FSM(name="fifo_postprocess") as fsm:
with m.State("NORMAL"):
m.d.comb += [
out_fifo.r_en.eq(self.usb_stream_out.ready),
out_valid.eq(out_fifo.r_rdy)
]
with m.If(~self.audio_in_active & out_fifo.r_rdy):
m.next = "DISCARD"
# frame ongoing
with m.Elif(~frame_finished_seen):
# start filling if there are not enough enough samples buffered
# for one channel set of audio samples
last_channel = self.out_channel == (self._max_nr_channels - 1)
with m.If(last_byte & last_channel & ~fifo_level_sufficient):
m.next = "FILL"
with m.If((self.out_channel != channel_counter)):
m.d.comb += [
out_fifo.r_en.eq(0),
self.usb_stream_out.payload.eq(0),
out_valid.eq(1),
self.filling.eq(1),
]
# frame finished: discard extraneous samples
with m.Else():
with m.If(out_fifo.r_rdy & (self.out_channel != 0)):
m.d.comb += [
out_fifo.r_en.eq(1),
out_valid.eq(0),
]
m.d.sync += [
frame_finished_seen.eq(0),
byte_pos.eq(0),
]
m.next = "DISCARD"
with m.State("DISCARD"):
with m.If(out_fifo.r_rdy):
m.d.comb += [
out_fifo.r_en.eq(1),
out_valid.eq(0),
self.skipping.eq(1),
]
with m.If(self.audio_in_active & (self.out_channel == 0)):
m.d.comb += out_fifo.r_en.eq(0)
m.next = "NORMAL"
with m.State("FILL"):
channel_is_ok = fifo_level_sufficient & (self.out_channel == channel_counter)
with m.If(self.frame_finished_in | channel_is_ok):
m.next = "NORMAL"
with m.Else():
m.d.comb += [
out_fifo.r_en.eq(0),
self.usb_stream_out.payload.eq(0),
out_valid.eq(1),
self.filling.eq(1),
]
return m
class ChannelsToUSBStreamTest(GatewareTestCase):
FRAGMENT_UNDER_TEST = ChannelsToUSBStream
FRAGMENT_ARGUMENTS = dict(max_nr_channels=8)
def send_one_frame(self, sample: int, channel: int, wait=True):
yield self.dut.channel_stream_in.channel_nr.eq(channel)
yield self.dut.channel_stream_in.payload.eq(sample)
yield self.dut.channel_stream_in.valid.eq(1)
yield
if wait:
yield
yield
yield
@sync_test_case
def test_smoke(self):
dut = self.dut
yield dut.usb_stream_out.ready.eq(0)
yield dut.frame_finished_in.eq(1)
yield
yield dut.frame_finished_in.eq(0)
yield
yield
yield
yield
yield
yield
yield dut.usb_stream_out.ready.eq(1)
yield from self.send_one_frame(0x030201, 0, wait=False)
yield from self.send_one_frame(0x131211, 1)
yield from self.send_one_frame(0x232221, 2)
yield from self.send_one_frame(0x333231, 3)
# source stream stalls, see if we wait
yield dut.channel_stream_in.valid.eq(0)
for _ in range(7): yield
yield from self.send_one_frame(0x434241, 4)
yield from self.send_one_frame(0x535251, 5)
yield from self.send_one_frame(0x636261, 6)
yield from self.send_one_frame(0x737271, 7, wait=False)
# out stream quits early, see if it
# consumes extraneous bytes
yield dut.usb_stream_out.ready.eq(0)
yield
for _ in range(15): yield
yield dut.frame_finished_in.eq(1)
yield
yield dut.frame_finished_in.eq(0)
for _ in range(35): yield
yield from self.send_one_frame(0x030201, 0)
yield from self.send_one_frame(0x131211, 1)
yield dut.usb_stream_out.ready.eq(1)
yield from self.send_one_frame(0x232221, 2)
yield from self.send_one_frame(0x333231, 3)
yield from self.send_one_frame(0x434241, 4)
yield from self.send_one_frame(0x535251, 5)
yield from self.send_one_frame(0x636261, 6)
yield from self.send_one_frame(0x737271, 7)
yield dut.channel_stream_in.valid.eq(0)
yield
for _ in range(45): yield