copy
This commit is contained in:
775
gateware/adat_usb2_audio_interface.py
Normal file
775
gateware/adat_usb2_audio_interface.py
Normal file
@@ -0,0 +1,775 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2021 Hans Baier <hansfbaier@gmail.com>
|
||||
# SPDX-License-Identifier: CERN-OHL-W-2.0
|
||||
import os
|
||||
|
||||
from amaranth import *
|
||||
from amaranth.lib.fifo import AsyncFIFOBuffered, AsyncFIFO, SyncFIFOBuffered, SyncFIFO
|
||||
from amaranth.lib.cdc import FFSynchronizer
|
||||
|
||||
from amlib.stream import connect_fifo_to_stream, connect_stream_to_fifo
|
||||
from amlib.io.i2s import I2STransmitter
|
||||
from amlib.io.debouncer import Debouncer
|
||||
from amlib.dsp.convolution.mac import StereoConvolutionMAC, ConvolutionMode
|
||||
|
||||
from luna import top_level_cli
|
||||
from luna.usb2 import USBDevice, \
|
||||
USBStreamInEndpoint, \
|
||||
USBStreamOutEndpoint, \
|
||||
USBIsochronousInMemoryEndpoint, \
|
||||
USBIsochronousOutStreamEndpoint, \
|
||||
USBIsochronousInStreamEndpoint
|
||||
|
||||
from usb_protocol.types import USBRequestType, USBStandardRequests
|
||||
|
||||
from luna.gateware.usb.usb2.device import USBDevice
|
||||
|
||||
from adat import ADATTransmitter, ADATReceiver
|
||||
from adat import EdgeToPulse
|
||||
|
||||
from usb_stream_to_channels import USBStreamToChannels
|
||||
from channels_to_usb_stream import ChannelsToUSBStream
|
||||
from channel_stream_combiner import ChannelStreamCombiner
|
||||
from channel_stream_splitter import ChannelStreamSplitter
|
||||
from bundle_multiplexer import BundleMultiplexer
|
||||
from bundle_demultiplexer import BundleDemultiplexer
|
||||
from stereopair_extractor import StereoPairExtractor
|
||||
from requesthandlers import UAC2RequestHandlers
|
||||
from debug import setup_ila, add_debug_led_array
|
||||
|
||||
from usb_descriptors import USBDescriptors
|
||||
|
||||
import wave
|
||||
import numpy as np
|
||||
|
||||
class USB2AudioInterface(Elaboratable):
|
||||
""" USB Audio Class v2 interface """
|
||||
# one isochronous packet typically has 6 or 7 samples of 8 channels of 32 bit samples
|
||||
# 6 samples * 8 channels * 4 bytes/sample = 192 bytes
|
||||
# 7 samples * 8 channels * 4 bytes/sample = 224 bytes
|
||||
USB2_NO_CHANNELS = 4
|
||||
USB1_NO_CHANNELS = 32 + USB2_NO_CHANNELS
|
||||
USB2_MAX_PACKET_SIZE = int(224 // 8 * USB2_NO_CHANNELS)
|
||||
USB1_MAX_PACKET_SIZE = int(224 * 4 + USB2_MAX_PACKET_SIZE)
|
||||
INPUT_CDC_FIFO_DEPTH = 256 * 4
|
||||
|
||||
USE_ILA = False
|
||||
ILA_MAX_PACKET_SIZE = 512
|
||||
|
||||
USE_DEBUG_LED_ARRAY = False
|
||||
|
||||
USE_CONVOLUTION = False
|
||||
|
||||
USE_SOC = False
|
||||
|
||||
def __init__(self) -> None:
|
||||
if self.USE_SOC:
|
||||
from amlib.soc import SimpleSoC
|
||||
from lambdasoc.periph.serial import AsyncSerialPeripheral, AsyncSerial
|
||||
from lambdasoc.periph.timer import TimerPeripheral
|
||||
|
||||
self.soc = soc = SimpleSoC()
|
||||
|
||||
soc.add_rom("firmware/firmware.bin", 0x4000)
|
||||
soc.add_ram(0x4000)
|
||||
|
||||
self.uart_pins = Record([
|
||||
('rx', [('i', 1)]),
|
||||
('tx', [('o', 1)])
|
||||
])
|
||||
|
||||
uart = AsyncSerialPeripheral(core=AsyncSerial(divisor=int(60e6 // 115200), pins=self.uart_pins))
|
||||
soc.add_peripheral(uart)
|
||||
|
||||
timer = TimerPeripheral(24)
|
||||
soc.add_peripheral(timer)
|
||||
|
||||
with open("firmware/soc.ld", 'w') as ld:
|
||||
soc.generate_ld_script(file=ld)
|
||||
with open("firmware/resources.h", 'w') as res_header:
|
||||
soc.generate_c_header(file=res_header)
|
||||
|
||||
result = os.system("(cd firmware; make)")
|
||||
assert result == 0, "compilation failed, aborting...."
|
||||
print("firmware compilation succeeded.")
|
||||
|
||||
super().__init__()
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
usb1_number_of_channels = self.USB1_NO_CHANNELS
|
||||
usb1_number_of_channels_bits = Shape.cast(range(usb1_number_of_channels)).width
|
||||
usb2_number_of_channels = self.USB2_NO_CHANNELS
|
||||
usb2_number_of_channels_bits = Shape.cast(range(usb2_number_of_channels)).width
|
||||
audio_bits = 24
|
||||
samplerate = 48000
|
||||
adat_number_of_channels = usb1_number_of_channels - usb2_number_of_channels
|
||||
|
||||
m.submodules.car = platform.clock_domain_generator()
|
||||
|
||||
#
|
||||
# SoC
|
||||
#
|
||||
if self.USE_SOC:
|
||||
m.submodules.soc = self.soc
|
||||
uart_pads = platform.request("uart", 0)
|
||||
m.d.comb += [
|
||||
uart_pads.tx .eq(self.uart_pins.tx),
|
||||
self.uart_pins.rx .eq(uart_pads.rx)
|
||||
]
|
||||
|
||||
#
|
||||
# USB
|
||||
#
|
||||
ulpi1 = platform.request("ulpi", 1)
|
||||
ulpi2 = platform.request("ulpi", 2)
|
||||
m.submodules.usb1 = usb1 = USBDevice(bus=ulpi1)
|
||||
m.submodules.usb2 = usb2 = USBDevice(bus=ulpi2)
|
||||
|
||||
descriptors = USBDescriptors(ila_max_packet_size=self.ILA_MAX_PACKET_SIZE, \
|
||||
use_ila=self.USE_ILA)
|
||||
|
||||
usb1_control_ep = usb1.add_control_endpoint()
|
||||
usb1_descriptors = descriptors.create_usb1_descriptors(usb1_number_of_channels, self.USB1_MAX_PACKET_SIZE)
|
||||
usb1_control_ep.add_standard_request_handlers(usb1_descriptors, blacklist=[
|
||||
lambda setup: (setup.type == USBRequestType.STANDARD)
|
||||
& (setup.request == USBStandardRequests.SET_INTERFACE)
|
||||
])
|
||||
usb1_class_request_handler = UAC2RequestHandlers()
|
||||
usb1_control_ep.add_request_handler(usb1_class_request_handler)
|
||||
|
||||
usb2_control_ep = usb2.add_control_endpoint()
|
||||
usb2_descriptors = descriptors.create_usb2_descriptors(usb2_number_of_channels, self.USB2_MAX_PACKET_SIZE)
|
||||
usb2_control_ep.add_standard_request_handlers(usb2_descriptors, blacklist=[
|
||||
lambda setup: (setup.type == USBRequestType.STANDARD)
|
||||
& (setup.request == USBStandardRequests.SET_INTERFACE)
|
||||
])
|
||||
usb2_class_request_handler = UAC2RequestHandlers()
|
||||
usb2_control_ep.add_request_handler(usb2_class_request_handler)
|
||||
|
||||
# audio out ports of the host
|
||||
usb1_ep1_out = USBIsochronousOutStreamEndpoint(
|
||||
endpoint_number=1, # EP 1 OUT
|
||||
max_packet_size=self.USB1_MAX_PACKET_SIZE)
|
||||
usb1.add_endpoint(usb1_ep1_out)
|
||||
usb2_ep1_out = USBIsochronousOutStreamEndpoint(
|
||||
endpoint_number=1, # EP 1 OUT
|
||||
max_packet_size=self.USB2_MAX_PACKET_SIZE)
|
||||
usb2.add_endpoint(usb2_ep1_out)
|
||||
|
||||
# audio rate feedback input ports of the host
|
||||
usb1_ep1_in = USBIsochronousInMemoryEndpoint(
|
||||
endpoint_number=1, # EP 1 IN
|
||||
max_packet_size=4)
|
||||
usb1.add_endpoint(usb1_ep1_in)
|
||||
usb2_ep1_in = USBIsochronousInMemoryEndpoint(
|
||||
endpoint_number=1, # EP 1 IN
|
||||
max_packet_size=4)
|
||||
usb2.add_endpoint(usb2_ep1_in)
|
||||
|
||||
# audio input ports of the host
|
||||
usb1_ep2_in = USBIsochronousInStreamEndpoint(
|
||||
endpoint_number=2, # EP 2 IN
|
||||
max_packet_size=self.USB1_MAX_PACKET_SIZE)
|
||||
usb1.add_endpoint(usb1_ep2_in)
|
||||
usb2_ep2_in = USBIsochronousInStreamEndpoint(
|
||||
endpoint_number=2, # EP 2 IN
|
||||
max_packet_size=self.USB2_MAX_PACKET_SIZE)
|
||||
usb2.add_endpoint(usb2_ep2_in)
|
||||
|
||||
# MIDI endpoints
|
||||
usb1_ep3_out = USBStreamOutEndpoint(
|
||||
endpoint_number=3, # EP 3 OUT
|
||||
max_packet_size=USBDescriptors.MAX_PACKET_SIZE_MIDI)
|
||||
usb1.add_endpoint(usb1_ep3_out)
|
||||
usb2_ep3_out = USBStreamOutEndpoint(
|
||||
endpoint_number=3, # EP 3 OUT
|
||||
max_packet_size=USBDescriptors.MAX_PACKET_SIZE_MIDI)
|
||||
usb2.add_endpoint(usb2_ep3_out)
|
||||
|
||||
usb1_ep3_in = USBStreamInEndpoint(
|
||||
endpoint_number=3, # EP 3 IN
|
||||
max_packet_size=USBDescriptors.MAX_PACKET_SIZE_MIDI)
|
||||
usb1.add_endpoint(usb1_ep3_in)
|
||||
usb2_ep3_in = USBStreamInEndpoint(
|
||||
endpoint_number=3, # EP 3 IN
|
||||
max_packet_size=USBDescriptors.MAX_PACKET_SIZE_MIDI)
|
||||
usb2.add_endpoint(usb2_ep3_in)
|
||||
|
||||
|
||||
m.d.comb += [
|
||||
usb1.connect .eq(1),
|
||||
usb2.connect .eq(1),
|
||||
# Connect our device as a high speed device
|
||||
usb1.full_speed_only .eq(0),
|
||||
usb2.full_speed_only .eq(0),
|
||||
]
|
||||
|
||||
usb1_audio_in_frame_bytes = \
|
||||
self.calculate_usb_input_frame_size(m, "usb1", usb1_ep1_out, usb1_ep2_in, usb1_number_of_channels, self.USB1_MAX_PACKET_SIZE)
|
||||
usb2_audio_in_frame_bytes = \
|
||||
self.calculate_usb_input_frame_size(m, "usb2", usb2_ep1_out, usb2_ep2_in, usb2_number_of_channels, self.USB2_MAX_PACKET_SIZE)
|
||||
|
||||
usb1_sof_counter, usb1_to_output_fifo_level, usb1_to_output_fifo_depth, \
|
||||
usb2_sof_counter, usb2_to_usb1_fifo_level, usb2_to_usb1_fifo_depth = \
|
||||
self.create_sample_rate_feedback_circuit(m, usb1, usb1_ep1_in, usb2, usb2_ep1_in)
|
||||
|
||||
usb1_audio_in_active = self.detect_active_audio_in (m, "usb1", usb1, usb1_ep2_in)
|
||||
usb2_audio_in_active = self.detect_active_audio_in (m, "usb2", usb2, usb2_ep2_in)
|
||||
usb2_audio_out_active = self.detect_active_audio_out(m, "usb2", usb2, usb2_ep1_out)
|
||||
|
||||
#
|
||||
# USB <-> Channel Stream conversion
|
||||
#
|
||||
m.submodules.usb1_to_channel_stream = usb1_to_channel_stream = \
|
||||
DomainRenamer("usb")(USBStreamToChannels(usb1_number_of_channels))
|
||||
m.submodules.usb2_to_channel_stream = usb2_to_channel_stream = \
|
||||
DomainRenamer("usb")(USBStreamToChannels(usb2_number_of_channels))
|
||||
|
||||
m.submodules.usb1_channel_stream_combiner = usb1_channel_stream_combiner = \
|
||||
DomainRenamer("usb")(ChannelStreamCombiner(adat_number_of_channels, usb2_number_of_channels))
|
||||
|
||||
m.submodules.usb1_channel_stream_splitter = usb1_channel_stream_splitter = \
|
||||
DomainRenamer("usb")(ChannelStreamSplitter(adat_number_of_channels, usb2_number_of_channels))
|
||||
|
||||
m.submodules.channels_to_usb1_stream = channels_to_usb1_stream = \
|
||||
DomainRenamer("usb")(ChannelsToUSBStream(usb1_number_of_channels, max_packet_size=self.USB1_MAX_PACKET_SIZE))
|
||||
m.submodules.channels_to_usb2_stream = channels_to_usb2_stream = \
|
||||
DomainRenamer("usb")(ChannelsToUSBStream(usb2_number_of_channels, max_packet_size=self.USB2_MAX_PACKET_SIZE))
|
||||
|
||||
usb1_no_channels = Signal(range(usb1_number_of_channels * 2), reset=2)
|
||||
usb1_no_channels_sync = Signal.like(usb1_no_channels)
|
||||
|
||||
usb2_no_channels = Signal(range(usb2_number_of_channels * 2), reset=2)
|
||||
|
||||
m.submodules.no_channels_sync_synchronizer = FFSynchronizer(usb1_no_channels, usb1_no_channels_sync, o_domain="sync")
|
||||
|
||||
m.d.comb += [
|
||||
usb1_to_channel_stream.no_channels_in.eq(usb1_no_channels),
|
||||
channels_to_usb1_stream.no_channels_in.eq(usb1_no_channels),
|
||||
channels_to_usb1_stream.audio_in_active.eq(usb1_audio_in_active),
|
||||
|
||||
usb2_to_channel_stream.no_channels_in.eq(usb2_no_channels),
|
||||
channels_to_usb2_stream.no_channels_in.eq(usb2_no_channels),
|
||||
channels_to_usb2_stream.audio_in_active.eq(usb2_audio_in_active),
|
||||
]
|
||||
|
||||
with m.Switch(usb1_class_request_handler.output_interface_altsetting_nr):
|
||||
with m.Case(2):
|
||||
m.d.usb += usb1_no_channels.eq(usb1_number_of_channels)
|
||||
with m.Default():
|
||||
m.d.usb += usb1_no_channels.eq(2)
|
||||
|
||||
with m.Switch(usb2_class_request_handler.output_interface_altsetting_nr):
|
||||
with m.Case(2):
|
||||
m.d.usb += usb2_no_channels.eq(usb2_number_of_channels)
|
||||
with m.Default():
|
||||
m.d.usb += usb2_no_channels.eq(2)
|
||||
|
||||
m.submodules.usb_to_output_fifo = usb1_to_output_fifo = \
|
||||
AsyncFIFO(width=audio_bits + usb1_number_of_channels_bits + 2, depth=usb1_to_output_fifo_depth, w_domain="usb", r_domain="sync")
|
||||
|
||||
m.submodules.usb2_to_usb1_fifo = usb2_to_usb1_fifo = \
|
||||
DomainRenamer("usb")(SyncFIFOBuffered(width=audio_bits + usb2_number_of_channels_bits + 2, depth=usb2_to_usb1_fifo_depth))
|
||||
|
||||
m.submodules.bundle_demultiplexer = bundle_demultiplexer = BundleDemultiplexer()
|
||||
m.submodules.bundle_multiplexer = bundle_multiplexer = DomainRenamer("fast")(BundleMultiplexer())
|
||||
|
||||
adat_transmitters = []
|
||||
adat_receivers = []
|
||||
adat_pads = []
|
||||
for i in range(1, 5):
|
||||
transmitter = ADATTransmitter(fifo_depth=9*4)
|
||||
setattr(m.submodules, f"adat{i}_transmitter", transmitter)
|
||||
adat_transmitters.append(transmitter)
|
||||
|
||||
receiver = DomainRenamer("fast")(ADATReceiver(platform.fast_domain_clock_freq))
|
||||
setattr(m.submodules, f"adat{i}_receiver", receiver)
|
||||
adat_receivers.append(receiver)
|
||||
|
||||
adat_pads.append(platform.request("toslink", i))
|
||||
|
||||
#
|
||||
# signal path: USB ===> ADAT transmitters
|
||||
#
|
||||
audio_bits_end = audio_bits
|
||||
channel_bits_start = audio_bits
|
||||
|
||||
usb1_channel_bits_end = channel_bits_start + usb1_number_of_channels_bits
|
||||
usb1_first_bit_pos = usb1_channel_bits_end
|
||||
usb1_last_bit_pos = usb1_first_bit_pos + 1
|
||||
|
||||
m.d.comb += [
|
||||
# convert USB stream to channel splitter to (output audio, USB2 audio IN)
|
||||
usb1_to_channel_stream.usb_stream_in.stream_eq(usb1_ep1_out.stream),
|
||||
usb1_channel_stream_splitter.combined_channel_stream_in.stream_eq(usb1_to_channel_stream.channel_stream_out),
|
||||
|
||||
*connect_stream_to_fifo(usb1_channel_stream_splitter.lower_channel_stream_out, usb1_to_output_fifo),
|
||||
|
||||
usb1_to_output_fifo.w_data[channel_bits_start:usb1_channel_bits_end]
|
||||
.eq(usb1_channel_stream_splitter.lower_channel_stream_out.channel_nr),
|
||||
|
||||
usb1_to_output_fifo.w_data[usb1_first_bit_pos]
|
||||
.eq(usb1_channel_stream_splitter.lower_channel_stream_out.first),
|
||||
|
||||
usb1_to_output_fifo.w_data[usb1_last_bit_pos]
|
||||
.eq(usb1_channel_stream_splitter.lower_channel_stream_out.last),
|
||||
|
||||
usb1_to_output_fifo.r_en .eq(bundle_demultiplexer.channel_stream_in.ready),
|
||||
usb1_to_output_fifo_level .eq(usb1_to_output_fifo.w_level),
|
||||
|
||||
# demultiplex channel stream to the different transmitters
|
||||
bundle_demultiplexer.channel_stream_in.payload.eq(usb1_to_output_fifo.r_data[0:audio_bits_end]),
|
||||
bundle_demultiplexer.channel_stream_in.channel_nr.eq(usb1_to_output_fifo.r_data[channel_bits_start:usb1_channel_bits_end]),
|
||||
bundle_demultiplexer.channel_stream_in.last.eq(usb1_to_output_fifo.r_data[-1]),
|
||||
bundle_demultiplexer.channel_stream_in.valid.eq(usb1_to_output_fifo.r_rdy & usb1_to_output_fifo.r_en),
|
||||
bundle_demultiplexer.no_channels_in.eq(usb1_no_channels_sync),
|
||||
]
|
||||
|
||||
# wire up transmitters / receivers
|
||||
for i in range(4):
|
||||
m.d.comb += [
|
||||
# transmitters
|
||||
adat_transmitters[i].sample_in .eq(bundle_demultiplexer.bundles_out[i].payload),
|
||||
adat_transmitters[i].addr_in .eq(bundle_demultiplexer.bundles_out[i].channel_nr),
|
||||
adat_transmitters[i].last_in .eq(bundle_demultiplexer.bundles_out[i].last),
|
||||
adat_transmitters[i].valid_in .eq(bundle_demultiplexer.bundles_out[i].valid),
|
||||
bundle_demultiplexer.bundles_out[i].ready.eq(adat_transmitters[i].ready_out),
|
||||
adat_transmitters[i].user_data_in .eq(0),
|
||||
|
||||
adat_pads[i].tx.eq(adat_transmitters[i].adat_out),
|
||||
|
||||
# receivers
|
||||
adat_receivers[i].adat_in.eq(adat_pads[i].rx),
|
||||
|
||||
# wire up receive FIFO to ADAT receiver
|
||||
bundle_multiplexer.no_channels_in[i] .eq(8),
|
||||
bundle_multiplexer.bundles_in[i].payload .eq(adat_receivers[i].sample_out),
|
||||
bundle_multiplexer.bundles_in[i].channel_nr .eq(adat_receivers[i].addr_out),
|
||||
bundle_multiplexer.bundles_in[i].valid .eq(adat_receivers[i].output_enable),
|
||||
bundle_multiplexer.bundles_in[i].last .eq(adat_receivers[i].addr_out == 7),
|
||||
bundle_multiplexer.bundle_active_in[i] .eq(adat_receivers[i].synced_out),
|
||||
]
|
||||
|
||||
#
|
||||
# signal path: ADAT receivers ===> USB
|
||||
#
|
||||
m.submodules.input_to_usb_fifo = input_to_usb_fifo = \
|
||||
AsyncFIFOBuffered(width=audio_bits + usb1_number_of_channels_bits + 2, depth=self.INPUT_CDC_FIFO_DEPTH, w_domain="fast", r_domain="usb")
|
||||
|
||||
chnr_start = audio_bits
|
||||
input_chnr_end = chnr_start + adat_number_of_channels
|
||||
input_channel_nr = input_to_usb_fifo.r_data[chnr_start:input_chnr_end]
|
||||
|
||||
first_channel = 0
|
||||
input_last_channel = (adat_number_of_channels - 1)
|
||||
|
||||
m.d.comb += [
|
||||
# wire up receive FIFO to bundle multiplexer
|
||||
input_to_usb_fifo.w_data[0:chnr_start] .eq(bundle_multiplexer.channel_stream_out.payload),
|
||||
input_to_usb_fifo.w_data[chnr_start:input_chnr_end] .eq(bundle_multiplexer.channel_stream_out.channel_nr),
|
||||
input_to_usb_fifo.w_en .eq(bundle_multiplexer.channel_stream_out.valid & input_to_usb_fifo.w_rdy),
|
||||
bundle_multiplexer.channel_stream_out.ready.eq(input_to_usb_fifo.w_rdy),
|
||||
|
||||
# convert audio stream to USB stream
|
||||
# connect ADAT channels to combiner
|
||||
usb1_channel_stream_combiner.lower_channel_stream_in.payload .eq(input_to_usb_fifo.r_data[0:chnr_start]),
|
||||
usb1_channel_stream_combiner.lower_channel_stream_in.channel_nr .eq(input_channel_nr),
|
||||
usb1_channel_stream_combiner.lower_channel_stream_in.first .eq(input_channel_nr == first_channel),
|
||||
usb1_channel_stream_combiner.lower_channel_stream_in.last .eq(input_channel_nr == input_last_channel),
|
||||
usb1_channel_stream_combiner.lower_channel_stream_in.valid .eq(input_to_usb_fifo.r_rdy),
|
||||
input_to_usb_fifo.r_en.eq(usb1_channel_stream_combiner.lower_channel_stream_in.ready),
|
||||
|
||||
# connect combiner output to USB1
|
||||
channels_to_usb1_stream.channel_stream_in.stream_eq(usb1_channel_stream_combiner.combined_channel_stream_out),
|
||||
channels_to_usb1_stream.data_requested_in .eq(usb1_ep2_in.data_requested),
|
||||
channels_to_usb1_stream.frame_finished_in .eq(usb1_ep2_in.frame_finished),
|
||||
|
||||
# wire up USB1 audio IN
|
||||
usb1_ep2_in.stream.stream_eq(channels_to_usb1_stream.usb_stream_out),
|
||||
]
|
||||
|
||||
#
|
||||
# signal path: USB2 <-> USB1
|
||||
#
|
||||
usb2_channel_bits_end = channel_bits_start + usb2_number_of_channels_bits
|
||||
usb2_first_bit_pos = usb2_channel_bits_end
|
||||
usb2_last_bit_pos = usb2_first_bit_pos + 1
|
||||
|
||||
usb2_channel_nr = usb2_to_usb1_fifo.r_data[chnr_start:usb2_channel_bits_end]
|
||||
m.d.comb +=[
|
||||
usb2_to_channel_stream.usb_stream_in.stream_eq(usb2_ep1_out.stream),
|
||||
*connect_stream_to_fifo(usb2_to_channel_stream.channel_stream_out, usb2_to_usb1_fifo),
|
||||
|
||||
usb2_to_usb1_fifo.w_data[channel_bits_start:usb2_channel_bits_end]
|
||||
.eq(usb2_to_channel_stream.channel_stream_out.channel_nr),
|
||||
|
||||
usb2_to_usb1_fifo.w_data[usb2_first_bit_pos]
|
||||
.eq(usb2_to_channel_stream.channel_stream_out.first),
|
||||
|
||||
usb2_to_usb1_fifo.w_data[usb2_last_bit_pos]
|
||||
.eq(usb2_to_channel_stream.channel_stream_out.last),
|
||||
|
||||
usb2_to_usb1_fifo_level
|
||||
.eq(usb2_to_usb1_fifo.w_level),
|
||||
|
||||
# connect USB2 OUT channels to USB1 IN
|
||||
usb1_channel_stream_combiner.upper_channels_active_in .eq(~usb2.suspended & usb2_audio_out_active),
|
||||
usb1_channel_stream_combiner.upper_channel_stream_in.payload .eq(usb2_to_usb1_fifo.r_data[0:chnr_start]),
|
||||
usb1_channel_stream_combiner.upper_channel_stream_in.channel_nr .eq(usb2_channel_nr),
|
||||
usb1_channel_stream_combiner.upper_channel_stream_in.first .eq(usb2_to_usb1_fifo.r_data[usb2_first_bit_pos]),
|
||||
usb1_channel_stream_combiner.upper_channel_stream_in.last .eq(usb2_to_usb1_fifo.r_data[usb2_last_bit_pos]),
|
||||
usb1_channel_stream_combiner.upper_channel_stream_in.valid .eq(usb2_to_usb1_fifo.r_rdy),
|
||||
usb2_to_usb1_fifo.r_en.eq(usb1_channel_stream_combiner.upper_channel_stream_in.ready),
|
||||
|
||||
# connect USB2 IN channels to USB1 OUT
|
||||
channels_to_usb2_stream.channel_stream_in.stream_eq(usb1_channel_stream_splitter.upper_channel_stream_out),
|
||||
channels_to_usb2_stream.data_requested_in .eq(usb2_ep2_in.data_requested),
|
||||
channels_to_usb2_stream.frame_finished_in .eq(usb2_ep2_in.frame_finished),
|
||||
|
||||
usb2_ep2_in.stream.stream_eq(channels_to_usb2_stream.usb_stream_out),
|
||||
]
|
||||
|
||||
#
|
||||
# I2S DACs
|
||||
#
|
||||
dac1_fifo_depth = 32 if self.USE_CONVOLUTION else 16 #when introducing delay with the convolver we need a larger fifo
|
||||
m.submodules.dac1_transmitter = dac1 = DomainRenamer("usb")(I2STransmitter(sample_width=audio_bits, fifo_depth=dac1_fifo_depth))
|
||||
m.submodules.dac2_transmitter = dac2 = DomainRenamer("usb")(I2STransmitter(sample_width=audio_bits))
|
||||
m.submodules.dac1_extractor = dac1_extractor = DomainRenamer("usb")(StereoPairExtractor(usb1_number_of_channels, usb1_to_output_fifo_depth))
|
||||
m.submodules.dac2_extractor = dac2_extractor = DomainRenamer("usb")(StereoPairExtractor(usb1_number_of_channels, usb1_to_output_fifo_depth))
|
||||
dac1_pads = platform.request("i2s", 1)
|
||||
dac2_pads = platform.request("i2s", 2)
|
||||
|
||||
# divide bitclock to get word clock
|
||||
# each half cycle has 32 bits in it
|
||||
lrclk = Signal(reset=1)
|
||||
bit_counter = Signal(6)
|
||||
|
||||
m.d.dac += bit_counter.eq(bit_counter + 1)
|
||||
m.d.comb += lrclk.eq(bit_counter[-1])
|
||||
|
||||
# hardwire DAC1 to channels 0/1 and DAC2 to 2/3
|
||||
# until making it switchable via USB request
|
||||
m.d.comb += [
|
||||
dac1_extractor.selected_channel_in.eq(0),
|
||||
# if stereo mode is enabled we want the second DAC to be wired
|
||||
# to main lef/right channels, just as the first one
|
||||
dac2_extractor.selected_channel_in.eq(Mux(usb1_no_channels == 2, 0, 2)),
|
||||
]
|
||||
|
||||
if self.USE_CONVOLUTION:
|
||||
enable_convolver = Signal()
|
||||
|
||||
# load the IR data
|
||||
with wave.open('IRs/DT990_crossfeed_4800taps.wav', 'rb') as wav:
|
||||
ir_data = wav.readframes(wav.getnframes())
|
||||
ir_sample_rate = wav.getframerate()
|
||||
|
||||
ir_sig = np.zeros((len(ir_data) // 6, 2), dtype='int32')
|
||||
for i in range(0, len(ir_sig), 6):
|
||||
ir_sig[i // 6, 0] = int.from_bytes(ir_data[i:i + 3], byteorder='little', signed=True)
|
||||
ir_sig[i // 6, 1] = int.from_bytes(ir_data[i + 3:i + 6], byteorder='little', signed=True)
|
||||
|
||||
# tapcount 4096 - more is failing to synthesize right now. 4800 would be the goal for 100ms.
|
||||
taps = ir_sig[:4096,:]
|
||||
|
||||
m.submodules.convolver = convolver = DomainRenamer("usb")(StereoConvolutionMAC(taps=taps, samplerate=samplerate, clockfrequency=60e6,
|
||||
bitwidth=audio_bits, convolutionMode=ConvolutionMode.CROSSFEED))
|
||||
|
||||
# validate the IR file
|
||||
assert ir_sample_rate == samplerate, f"Unsupported samplerate {ir_sample_rate} for IR file. Required samplerate is {samplerate}"
|
||||
for tap in range(len(taps)):
|
||||
assert -1 * 2 ** (audio_bits - 1) <= taps[tap, 0] <= 1 * 2 ** (audio_bits - 1) - 1,\
|
||||
f"Tap #{tap} is out of range for bitwidth {audio_bits}: {taps[tap, 0]}"
|
||||
else:
|
||||
convolver = None
|
||||
enable_convolver = None
|
||||
|
||||
self.wire_up_dac(m, usb1_to_channel_stream, dac1_extractor, dac1, lrclk, dac1_pads, convolver, enable_convolver)
|
||||
self.wire_up_dac(m, usb1_to_channel_stream, dac2_extractor, dac2, lrclk, dac2_pads)
|
||||
|
||||
if self.USE_CONVOLUTION:
|
||||
# the convolver can be toggled in-/active either via the first button on the devboard or via the
|
||||
# TOGGLE_CONVOLUTION(1) vendor request
|
||||
m.submodules.button_debouncer = button_debouncer = Debouncer()
|
||||
m.submodules.request_debouncer = request_debouncer = Debouncer()
|
||||
m.d.comb += [
|
||||
button_debouncer.btn_in.eq(platform.request("core_button")[0]),
|
||||
request_debouncer.btn_in.eq(usb1_class_request_handler.enable_convolution)
|
||||
]
|
||||
with m.If(button_debouncer.btn_up_out | request_debouncer.btn_up_out): # toggle convolution on/off
|
||||
m.d.sync += enable_convolver.eq(~enable_convolver)
|
||||
m.d.comb += dac1.enable_in.eq(0) # reset the DAC once we toggle its signal source
|
||||
|
||||
#
|
||||
# USB => output FIFO level debug signals
|
||||
#
|
||||
min_fifo_level = Signal.like(usb1_to_output_fifo_level, reset=usb1_to_output_fifo_depth)
|
||||
max_fifo_level = Signal.like(usb1_to_output_fifo_level)
|
||||
|
||||
with m.If(usb1_to_output_fifo_level > max_fifo_level):
|
||||
m.d.sync += max_fifo_level.eq(usb1_to_output_fifo_level)
|
||||
|
||||
with m.If(usb1_to_output_fifo_level < min_fifo_level):
|
||||
m.d.sync += min_fifo_level.eq(usb1_to_output_fifo_level)
|
||||
|
||||
#
|
||||
# USB MIDI
|
||||
#
|
||||
usb_midi_fifo_depth = USBDescriptors.MAX_PACKET_SIZE_MIDI
|
||||
m.submodules.usb1_to_usb2_midi_fifo = usb1_to_usb2_midi_fifo = \
|
||||
DomainRenamer("usb")(SyncFIFOBuffered(width=8+2, depth=usb_midi_fifo_depth))
|
||||
m.submodules.usb2_to_usb1_midi_fifo = usb2_to_usb1_midi_fifo = \
|
||||
DomainRenamer("usb")(SyncFIFOBuffered(width=8+2, depth=usb_midi_fifo_depth))
|
||||
|
||||
m.d.comb += [
|
||||
*connect_stream_to_fifo(usb1_ep3_out.stream, usb1_to_usb2_midi_fifo, firstBit=-2, lastBit=-1),
|
||||
*connect_fifo_to_stream(usb1_to_usb2_midi_fifo, usb2_ep3_in.stream, firstBit=-2, lastBit=-1),
|
||||
*connect_stream_to_fifo(usb2_ep3_out.stream, usb2_to_usb1_midi_fifo, firstBit=-2, lastBit=-1),
|
||||
*connect_fifo_to_stream(usb2_to_usb1_midi_fifo, usb1_ep3_in.stream, firstBit=-2, lastBit=-1),
|
||||
]
|
||||
|
||||
# Internal Logic Analyzer
|
||||
if self.USE_ILA:
|
||||
setup_ila(locals(), self.ILA_MAX_PACKET_SIZE, self.USE_CONVOLUTION)
|
||||
|
||||
if self.USE_DEBUG_LED_ARRAY:
|
||||
add_debug_led_array(locals())
|
||||
|
||||
usb_aux1 = platform.request("usb_aux", 1)
|
||||
usb_aux2 = platform.request("usb_aux", 2)
|
||||
|
||||
#
|
||||
# board status LEDs
|
||||
#
|
||||
leds = platform.request("leds")
|
||||
|
||||
m.d.comb += [
|
||||
leds.active1.eq(usb1.tx_activity_led | usb1.rx_activity_led),
|
||||
leds.suspended1.eq(usb1.suspended),
|
||||
leds.active2.eq(usb2.tx_activity_led | usb2.rx_activity_led),
|
||||
leds.suspended2.eq(usb2.suspended),
|
||||
leds.usb1.eq(usb_aux1.vbus),
|
||||
leds.usb2.eq(usb_aux2.vbus),
|
||||
]
|
||||
m.d.comb += [getattr(leds, f"sync{i + 1}").eq(adat_receivers[i].synced_out) for i in range(4)]
|
||||
|
||||
if self.USE_CONVOLUTION:
|
||||
convolver_led = platform.request("core_led", 0)
|
||||
m.d.comb += convolver_led.o.eq(enable_convolver)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
def detect_active_audio_in(self, m, name: str, usb, ep2_in):
|
||||
audio_in_seen = Signal(name=f"{name}_audio_in_seen")
|
||||
audio_in_active = Signal(name=f"{name}_audio_in_active")
|
||||
|
||||
# detect if we don't have a USB audio IN packet
|
||||
with m.If(usb.sof_detected):
|
||||
m.d.usb += [
|
||||
audio_in_active.eq(audio_in_seen),
|
||||
audio_in_seen.eq(0),
|
||||
]
|
||||
|
||||
with m.If(ep2_in.data_requested):
|
||||
m.d.usb += audio_in_seen.eq(1)
|
||||
|
||||
return audio_in_active
|
||||
|
||||
|
||||
def detect_active_audio_out(self, m, name: str, usb, ep1_out):
|
||||
audio_out_seen = Signal(name=f"{name}_audio_out_seen")
|
||||
audio_out_active = Signal(name=f"{name}_audio_out_active")
|
||||
|
||||
# detect if we don't have a USB audio OUT packet
|
||||
with m.If(usb.sof_detected):
|
||||
m.d.usb += [
|
||||
audio_out_active.eq(audio_out_seen),
|
||||
audio_out_seen.eq(0),
|
||||
]
|
||||
|
||||
with m.If(ep1_out.stream.last):
|
||||
m.d.usb += audio_out_seen.eq(1)
|
||||
|
||||
return audio_out_active
|
||||
|
||||
|
||||
def calculate_usb_input_frame_size(self, m: Module, usb_name: str, ep1_out, ep2_in, number_of_channels: int, max_packet_size: int):
|
||||
"""calculate the number of bytes one packet of audio input contains"""
|
||||
|
||||
audio_in_frame_byte_counter = Signal(range(max_packet_size), name=f"{usb_name}_audio_in_frame_byte_counter", reset=24 * number_of_channels)
|
||||
audio_in_frame_bytes_counting = Signal(name=f"{usb_name}_audio_in_frame_bytes_counting")
|
||||
audio_in_frame_bytes = Signal.like(audio_in_frame_byte_counter, name=f"{usb_name}_audio_in_frame_bytes")
|
||||
|
||||
with m.If(ep1_out.stream.valid & ep1_out.stream.ready):
|
||||
with m.If(audio_in_frame_bytes_counting):
|
||||
m.d.usb += audio_in_frame_byte_counter.eq(audio_in_frame_byte_counter + 1)
|
||||
|
||||
with m.If(ep1_out.stream.first):
|
||||
m.d.usb += [
|
||||
audio_in_frame_byte_counter.eq(1),
|
||||
audio_in_frame_bytes_counting.eq(1),
|
||||
]
|
||||
|
||||
with m.If(ep1_out.stream.last):
|
||||
m.d.usb += [
|
||||
audio_in_frame_bytes_counting.eq(0),
|
||||
audio_in_frame_bytes.eq(audio_in_frame_byte_counter + 1),
|
||||
]
|
||||
|
||||
m.d.comb += ep2_in.bytes_in_frame.eq(audio_in_frame_bytes),
|
||||
|
||||
return audio_in_frame_bytes
|
||||
|
||||
|
||||
def create_sample_rate_feedback_circuit(self, m: Module, usb1, usb1_ep1_in, usb2, usb2_ep1_in):
|
||||
#
|
||||
# USB rate feedback
|
||||
#
|
||||
adat_clock_usb = Signal()
|
||||
m.submodules.adat_clock_usb_sync = FFSynchronizer(ClockSignal("adat"), adat_clock_usb, o_domain="usb")
|
||||
m.submodules.adat_clock_usb_pulse = adat_clock_usb_pulse = DomainRenamer("usb")(EdgeToPulse())
|
||||
adat_clock_tick = Signal()
|
||||
m.d.usb += [
|
||||
adat_clock_usb_pulse.edge_in.eq(adat_clock_usb),
|
||||
adat_clock_tick.eq(adat_clock_usb_pulse.pulse_out),
|
||||
]
|
||||
|
||||
usb1_feedback_value = Signal(32, reset=0x60000)
|
||||
usb1_bit_pos = Signal(5)
|
||||
usb2_feedback_value = Signal(32, reset=0x60000)
|
||||
usb2_bit_pos = Signal(5)
|
||||
|
||||
# this tracks the number of ADAT frames in N microframes
|
||||
# with 12.288MHz / 8kHz = 1536 samples per microframe
|
||||
# we have N = 256, so we need
|
||||
# math.ceil(math.log2(1536 * 256)) = 19 bits
|
||||
usb1_adat_clock_counter = Signal(19)
|
||||
usb2_adat_clock_counter = Signal(19)
|
||||
|
||||
# according to USB2 standard chapter 5.12.4.2
|
||||
# we need at least 2**13 / 2**8 = 2**5 = 32 SOF-frames of
|
||||
# sample master frequency counter to get the minimal
|
||||
# precision for the sample frequency estimate
|
||||
# / 2**8 because the ADAT-clock = 256 times = 2**8
|
||||
# the sample frequency
|
||||
# we average over 256 microframes, because that gives
|
||||
# us the maximum precision needed by the feedback endpoint
|
||||
usb1_sof_counter = Signal(8)
|
||||
usb2_sof_counter = Signal(8)
|
||||
|
||||
# since samples are constantly consumed from the FIFO
|
||||
# half the maximum USB packet size should be more than enough
|
||||
usb1_to_output_fifo_depth = self.USB1_MAX_PACKET_SIZE // 2
|
||||
usb1_to_output_fifo_level = Signal(range(usb1_to_output_fifo_depth + 1))
|
||||
print("usb1_to_output_fifo_depth in bits: " + str(usb1_to_output_fifo_level.width))
|
||||
usb1_fifo_level_feedback = Signal.like(usb1_to_output_fifo_level)
|
||||
m.d.comb += usb1_fifo_level_feedback.eq(usb1_to_output_fifo_level >> (usb1_to_output_fifo_level.width - 7))
|
||||
|
||||
usb2_to_usb1_fifo_depth = self.USB2_MAX_PACKET_SIZE // 2
|
||||
usb2_to_usb1_fifo_level = Signal(range(usb2_to_usb1_fifo_depth + 1))
|
||||
print("usb2_to_usb1_fifo_depth in bits: " + str(usb2_to_usb1_fifo_level.width))
|
||||
usb2_fifo_level_feedback = Signal.like(usb2_to_usb1_fifo_level)
|
||||
m.d.comb += usb2_fifo_level_feedback.eq(usb2_to_usb1_fifo_level >> (usb2_to_usb1_fifo_level.width - 4))
|
||||
|
||||
with m.If(adat_clock_tick):
|
||||
m.d.usb += [
|
||||
usb1_adat_clock_counter.eq(usb1_adat_clock_counter + 1),
|
||||
usb2_adat_clock_counter.eq(usb2_adat_clock_counter + 1),
|
||||
]
|
||||
|
||||
with m.If(usb1.sof_detected):
|
||||
m.d.usb += usb1_sof_counter.eq(usb1_sof_counter + 1)
|
||||
|
||||
with m.If(usb1_sof_counter == 0):
|
||||
# when feedbackValue == adat_clock_counter the
|
||||
# FIFO underflows slowly, but also when
|
||||
# feedbackValue == adat_clock_counter + 1
|
||||
# the FIFO slowly but surely fills to overflow.
|
||||
# since both of those feedback values are only one apart,
|
||||
# we need to start with the slowly overflowing value and
|
||||
# provide negative feedback proportional to the fill level
|
||||
# of the FIFO
|
||||
m.d.usb += [
|
||||
usb1_feedback_value.eq(usb1_adat_clock_counter + 1 - usb1_fifo_level_feedback),
|
||||
usb1_adat_clock_counter.eq(0),
|
||||
]
|
||||
|
||||
with m.If(usb2.sof_detected):
|
||||
m.d.usb += usb2_sof_counter.eq(usb2_sof_counter + 1)
|
||||
|
||||
with m.If(usb2_sof_counter == 0):
|
||||
m.d.usb += [
|
||||
usb2_feedback_value.eq(usb2_adat_clock_counter + 1 - usb2_fifo_level_feedback),
|
||||
usb2_adat_clock_counter.eq(0),
|
||||
]
|
||||
|
||||
|
||||
m.d.comb += [
|
||||
usb1_ep1_in.bytes_in_frame.eq(4),
|
||||
usb1_bit_pos.eq(usb1_ep1_in.address << 3),
|
||||
usb1_ep1_in.value.eq(0xff & (usb1_feedback_value >> usb1_bit_pos)),
|
||||
|
||||
usb2_ep1_in.bytes_in_frame.eq(4),
|
||||
usb2_bit_pos.eq(usb2_ep1_in.address << 3),
|
||||
usb2_ep1_in.value.eq(0xff & (usb2_feedback_value >> usb2_bit_pos)),
|
||||
]
|
||||
|
||||
return (usb1_sof_counter, usb1_to_output_fifo_level, usb1_to_output_fifo_depth, \
|
||||
usb2_sof_counter, usb2_to_usb1_fifo_level, usb2_to_usb1_fifo_depth)
|
||||
|
||||
|
||||
def wire_up_dac(self, m, usb_to_channel_stream, dac_extractor, dac, lrclk, dac_pads, convolver=None, enable_convolver=None):
|
||||
# wire up DAC extractor
|
||||
m.d.comb += [
|
||||
dac_extractor.channel_stream_in.valid.eq( usb_to_channel_stream.channel_stream_out.valid
|
||||
& usb_to_channel_stream.channel_stream_out.ready),
|
||||
dac_extractor.channel_stream_in.payload.eq(usb_to_channel_stream.channel_stream_out.payload),
|
||||
dac_extractor.channel_stream_in.channel_nr.eq(usb_to_channel_stream.channel_stream_out.channel_nr),
|
||||
]
|
||||
|
||||
if convolver:
|
||||
with m.If(enable_convolver):
|
||||
m.d.comb += [
|
||||
convolver.signal_in.stream_eq(dac_extractor.channel_stream_out),
|
||||
dac.stream_in.stream_eq(convolver.signal_out)
|
||||
]
|
||||
with m.Else():
|
||||
m.d.comb += dac.stream_in.stream_eq(dac_extractor.channel_stream_out)
|
||||
else:
|
||||
m.d.comb += dac.stream_in.stream_eq(dac_extractor.channel_stream_out)
|
||||
|
||||
# wire up DAC/ADC
|
||||
m.d.comb += [
|
||||
# wire up DAC/ADC
|
||||
# in I2S, everything happens on the negedge
|
||||
# the easiest way to achieve this, is to invert
|
||||
# the clock signal
|
||||
dac_pads.sclk.eq(~ClockSignal("adat")),
|
||||
dac_pads.bclk.eq(~ClockSignal("dac")),
|
||||
dac_pads.lrclk.eq(~lrclk),
|
||||
dac_pads.data.eq(dac.serial_data_out),
|
||||
dac.enable_in.eq(1),
|
||||
|
||||
# wire up I2S transmitter
|
||||
dac.word_select_in.eq(~lrclk),
|
||||
dac.serial_clock_in.eq(~ClockSignal("dac")),
|
||||
]
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ["AMARANTH_verbose"] = "True"
|
||||
#os.environ["LUNA_PLATFORM"] = "platforms:ADATFaceCycloneIV"
|
||||
#os.environ["LUNA_PLATFORM"] = "platforms:ADATFaceCycloneV"
|
||||
#os.environ["LUNA_PLATFORM"] = "platforms:ADATFaceCyclone10"
|
||||
#os.environ["LUNA_PLATFORM"] = "platforms:ADATFaceArtix7"
|
||||
|
||||
# ECP5 platform
|
||||
os.environ["AMARANTH_synth_opts"] = "-abc9"
|
||||
os.environ["AMARANTH_nextpnr_opts"] = "--timing-allow-fail"
|
||||
os.environ["LUNA_PLATFORM"] = "platforms:ADATFaceColorlight"
|
||||
|
||||
top_level_cli(USB2AudioInterface)
|
||||
Reference in New Issue
Block a user