293 lines
11 KiB
Python
293 lines
11 KiB
Python
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 |