#!/usr/bin/env python3 # # Copyright (c) 2021 Hans Baier # 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)