copy
This commit is contained in:
BIN
gateware/IRs/DT990_crossfeed_4800taps.wav
Normal file
BIN
gateware/IRs/DT990_crossfeed_4800taps.wav
Normal file
Binary file not shown.
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)
|
||||
96
gateware/adatface_rev0_baseboard.py
Normal file
96
gateware/adatface_rev0_baseboard.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from amaranth.build import *
|
||||
from amaranth_boards.resources import *
|
||||
|
||||
class ADATFaceRev0Baseboard:
|
||||
@staticmethod
|
||||
def resources(attrs, colorlight=False):
|
||||
return [
|
||||
# LEDS
|
||||
Resource("leds", 0,
|
||||
Subsignal("host", Pins ("J_3:60", dir="o")),
|
||||
Subsignal("usb1", Pins ("J_3:57", dir="o")),
|
||||
Subsignal("usb2", Pins ("J_3:58", dir="o")),
|
||||
Subsignal("sync1", Pins ("J_3:55", dir="o")),
|
||||
Subsignal("sync2", Pins ("J_3:56", dir="o")),
|
||||
Subsignal("sync3", Pins ("J_3:53", dir="o")),
|
||||
Subsignal("sync4", Pins ("J_3:54", dir="o")),
|
||||
Subsignal("active1", Pins ("J_3:51", dir="o")),
|
||||
Subsignal("active2", Pins ("J_3:49", dir="o")),
|
||||
Subsignal("suspended1", Pins ("J_3:52", dir="o")),
|
||||
Subsignal("suspended2", Pins ("J_3:50", dir="o")),
|
||||
attrs),
|
||||
|
||||
|
||||
# USB
|
||||
ULPIResource("ulpi", 1,
|
||||
data="J_3:37 J_3:38 J_3:39 J_3:40 J_3:41 J_3:42 J_3:43 J_3:44",
|
||||
clk="J_3:27", clk_dir="o", # this needs to be a clock pin of the FPGA or the core won't work
|
||||
dir="J_3:31", nxt="J_3:33", stp="J_3:29", rst="J_3:34", rst_invert=True, # USB3320 reset is active low
|
||||
attrs=attrs),
|
||||
|
||||
Resource("usb_aux", 1,
|
||||
Subsignal("vbus", Pins("J_3:30", dir="i")),
|
||||
Subsignal("id", Pins("J_3:32", dir="o")),
|
||||
Subsignal("sbu1", Pins("J_3:45", dir="i")),
|
||||
Subsignal("sbu2", Pins("J_3:46", dir="i")),
|
||||
attrs),
|
||||
|
||||
ULPIResource("ulpi", 2,
|
||||
data="J_3:17 J_3:18 J_3:19 J_3:20 J_3:21 J_3:22 J_3:23 J_3:24",
|
||||
clk="J_3:7", clk_dir="o", # this needs to be a clock pin of the FPGA or the core won't work
|
||||
dir="J_3:11", nxt="J_3:13", stp="J_3:9", rst="J_3:14", rst_invert=True, # USB3320 reset is active low
|
||||
attrs=attrs),
|
||||
|
||||
Resource("usb_aux", 2,
|
||||
Subsignal("vbus", Pins("J_3:10", dir="i")),
|
||||
Subsignal("id", Pins("J_3:12", dir="o")),
|
||||
Subsignal("sbu1", Pins("J_3:25", dir="i")),
|
||||
Subsignal("sbu2", Pins("J_3:26", dir="i")),
|
||||
attrs),
|
||||
|
||||
Resource("i2s", 1,
|
||||
Subsignal("sclk", Pins("J_2:53", dir="o")),
|
||||
Subsignal("bclk", Pins("J_2:54", dir="o")),
|
||||
Subsignal("data", Pins("J_2:55", dir="o")),
|
||||
Subsignal("lrclk", Pins("J_2:56", dir="o")),
|
||||
attrs),
|
||||
|
||||
Resource("i2s", 2,
|
||||
Subsignal("sclk", Pins("J_2:57", dir="o")),
|
||||
Subsignal("bclk", Pins("J_2:58", dir="o")),
|
||||
Subsignal("data", Pins("J_2:50", dir="o") if colorlight else Pins("J_2:59", dir="o")),
|
||||
Subsignal("lrclk", Pins("J_2:52", dir="o") if colorlight else Pins("J_2:60", dir="o")),
|
||||
attrs),
|
||||
|
||||
|
||||
# TOSLINK
|
||||
Resource("toslink", 1,
|
||||
Subsignal("tx", Pins("J_2:49", dir="o")),
|
||||
Subsignal("rx", Pins("J_2:51", dir="i")),
|
||||
attrs),
|
||||
|
||||
Resource("toslink", 2,
|
||||
Subsignal("tx", Pins("J_2:33", dir="o")),
|
||||
Subsignal("rx", Pins("J_2:35", dir="i")),
|
||||
attrs),
|
||||
|
||||
Resource("toslink", 3,
|
||||
Subsignal("tx", Pins("J_2:23", dir="o")),
|
||||
Subsignal("rx", Pins("J_2:25", dir="i")),
|
||||
attrs),
|
||||
|
||||
Resource("toslink", 4,
|
||||
Subsignal("tx", Pins("J_2:18", dir="o") if colorlight else Pins("J_2:7", dir="o")),
|
||||
Subsignal("rx", Pins("J_2:9", dir="i")),
|
||||
attrs),
|
||||
|
||||
|
||||
# Debug
|
||||
SPIResource(0, clk="J_2:12", copi="J_2:8", cipo=None, cs_n="J_2:10", attrs=attrs),
|
||||
#UARTResource(0, tx="J_2:14", rx="J_2:16", attrs=attrs),
|
||||
|
||||
Resource("debug", 0, Pins("J_2:44", dir="o")),
|
||||
Resource("debug", 1, Pins("J_2:46", dir="o")),
|
||||
Resource("debug", 2, Pins("J_2:48", dir="o")),
|
||||
Resource("debug", 3, Pins("J_2:42", dir="o")),
|
||||
]
|
||||
3
gateware/attic/README.txt
Normal file
3
gateware/attic/README.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
This directory contains platform files which are not maintained
|
||||
anymore, but still can serve as examples on how to run on
|
||||
other boards.
|
||||
216
gateware/attic/de0nanoplatform.py
Normal file
216
gateware/attic/de0nanoplatform.py
Normal file
@@ -0,0 +1,216 @@
|
||||
#
|
||||
# This file is part of LUNA.
|
||||
#
|
||||
# Copyright (c) 2020 Great Scott Gadgets <info@greatscottgadgets.com>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""
|
||||
The DE0 Nano does not have an explicit USB port. Instead, you'll need to connect an external ULPI PHY breakout,
|
||||
such as https://www.waveshare.com/wiki/USB3300_USB_HS_Board.
|
||||
|
||||
See the pin definitions below for connection information (ULPIResource).
|
||||
|
||||
The DE0 Nano is an -unsupported- platform! To use it, you'll need to set your LUNA_PLATFORM variable:
|
||||
> export LUNA_PLATFORM="luna.gateware.platform.de0_nano:DE0NanoPlatform"
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
from amaranth import *
|
||||
from amaranth.build import *
|
||||
from amaranth.vendor.intel import IntelPlatform
|
||||
|
||||
from amaranth_boards.resources import *
|
||||
|
||||
from luna.gateware.platform.core import LUNAPlatform
|
||||
|
||||
__all__ = ["DE0NanoPlatform"]
|
||||
|
||||
|
||||
class DE0NanoClockAndResetController(Elaboratable):
|
||||
""" Controller for de0_nano's clocking and global resets. """
|
||||
def __init__(self, *, clock_frequencies=None, clock_signal_name=None):
|
||||
pass
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
# Create our domains; but don't do anything else for them, for now.
|
||||
m.domains.sync = ClockDomain()
|
||||
m.domains.usb = ClockDomain()
|
||||
m.domains.jt51 = ClockDomain()
|
||||
m.domains.adat = ClockDomain()
|
||||
|
||||
m.submodules.mainpll = Instance("ALTPLL",
|
||||
p_BANDWIDTH_TYPE = "AUTO",
|
||||
p_CLK0_DIVIDE_BY = 1,
|
||||
p_CLK0_DUTY_CYCLE = 50,
|
||||
p_CLK0_MULTIPLY_BY = 1,
|
||||
p_CLK0_PHASE_SHIFT = 0,
|
||||
p_INCLK0_INPUT_FREQUENCY = 16666,
|
||||
p_OPERATION_MODE = "NORMAL",
|
||||
|
||||
# Drive our clock from the USB clock
|
||||
# coming from the USB clock pin of the USB3300
|
||||
i_inclk = ClockSignal("usb"),
|
||||
o_clk = ClockSignal("sync"),
|
||||
)
|
||||
|
||||
m.submodules.jt51pll = Instance("ALTPLL",
|
||||
p_BANDWIDTH_TYPE = "AUTO",
|
||||
p_CLK0_DIVIDE_BY = 218,
|
||||
p_CLK0_DUTY_CYCLE = 50,
|
||||
p_CLK0_MULTIPLY_BY = 13,
|
||||
p_CLK0_PHASE_SHIFT = 0,
|
||||
p_INCLK0_INPUT_FREQUENCY = 16666,
|
||||
p_OPERATION_MODE = "NORMAL",
|
||||
|
||||
# Drive our clock from the USB clock
|
||||
# coming from the USB clock pin of the USB3300
|
||||
i_inclk = ClockSignal("usb"),
|
||||
o_clk = ClockSignal("jt51"),
|
||||
)
|
||||
|
||||
m.submodules.adatpll = Instance("ALTPLL",
|
||||
p_BANDWIDTH_TYPE = "AUTO",
|
||||
p_CLK0_DIVIDE_BY = 83,
|
||||
p_CLK0_DUTY_CYCLE = 50,
|
||||
p_CLK0_MULTIPLY_BY = 17,
|
||||
p_CLK0_PHASE_SHIFT = 0,
|
||||
p_INCLK0_INPUT_FREQUENCY = 16666,
|
||||
p_OPERATION_MODE = "NORMAL",
|
||||
|
||||
# Drive our clock from the USB clock
|
||||
# coming from the USB clock pin of the USB3300
|
||||
i_inclk = ClockSignal("usb"),
|
||||
o_clk = ClockSignal("adat"),
|
||||
)
|
||||
|
||||
# Use a blinky to see if the clock signal works
|
||||
# from amaranth_boards.test.blinky import Blinky
|
||||
# m.submodules += Blinky()
|
||||
|
||||
return m
|
||||
|
||||
class DE0NanoPlatform(IntelPlatform, LUNAPlatform):
|
||||
""" This is a de0_nano board with an USB3300 PHY attached to JP_2 """
|
||||
|
||||
name = "de0_nano"
|
||||
device = "EP4CE22"
|
||||
package = "F17"
|
||||
speed = "C6"
|
||||
|
||||
default_clk = "clk_50MHz"
|
||||
clock_domain_generator = DE0NanoClockAndResetController
|
||||
default_usb_connection = "ulpi"
|
||||
ignore_phy_vbus = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
logging.warning("This platform is not officially supported, and thus not tested. Your results may vary.")
|
||||
logging.warning("Note also that this platform does not use the DE0 nano's main USB port!")
|
||||
logging.warning("You'll need to connect a ULPI PHY breakout. See the platform file for more info.")
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
#
|
||||
# I/O resources.
|
||||
#
|
||||
resources = [
|
||||
# Primary clock generator clocks.
|
||||
Resource("clk_50MHz", 0, Pins("R8", dir="i"), Clock(50e6), Attrs(io_standard="3.3-V LVTTL")),
|
||||
|
||||
# USB2 / ULPI section of the USB3300.
|
||||
ULPIResource("ulpi", 0,
|
||||
data="JP_2:27 JP_2:25 JP_2:23 JP_2:21 JP_2:19 JP_2:17 JP_2:15 JP_2:13",
|
||||
clk="JP_2:1", # this needs to be a clock pin of the FPGA or the core won't work
|
||||
dir="JP_2:18", nxt="JP_2:16", stp="JP_2:14", rst="JP_2:22",
|
||||
attrs=Attrs(io_standard="3.3-V LVCMOS")
|
||||
),
|
||||
|
||||
UARTResource(0,
|
||||
# GND on JP1 Pin 12.
|
||||
rx="JP_1:8", tx="JP_1:10",
|
||||
attrs=Attrs(io_standard="3.3-V LVTTL")),
|
||||
|
||||
*LEDResources(
|
||||
pins="A15 A13 B13 A11 D1 F3 B1 L3",
|
||||
attrs=Attrs(io_standard="3.3-V LVTTL")),
|
||||
|
||||
*ButtonResources(
|
||||
pins="J15 E1", invert=True,
|
||||
attrs=Attrs(io_standard="3.3-V LVTTL")),
|
||||
|
||||
*SwitchResources(
|
||||
pins="M1 T8 B9 M15",
|
||||
attrs=Attrs(io_standard="3.3-V LVTTL")),
|
||||
|
||||
SDRAMResource(0,
|
||||
clk="R4", cke="L7", cs_n="P6", we_n="C2", ras_n="L2", cas_n="L1",
|
||||
ba="M7 M6", a="P2 N5 N6 M8 P8 T7 N8 T6 R1 P1 N2 N1 L4",
|
||||
dq="G2 G1 L8 K5 K2 J2 J1 R7 T4 T2 T3 R3 R5 P3 N3 K1", dqm="R6 T5",
|
||||
attrs=Attrs(io_standard="3.3-V LVTTL")),
|
||||
|
||||
# Accelerometer
|
||||
Resource("acc", 0,
|
||||
Subsignal("cs_n", Pins("G5", dir="o")),
|
||||
Subsignal("int", Pins("M2", dir="i")),
|
||||
Attrs(io_standard="3.3-V LVTTL")),
|
||||
# I2C is part of the Accelerometer
|
||||
I2CResource(0,
|
||||
scl="F2", sda="F1",
|
||||
attrs=Attrs(io_standard="3.3-V LVTTL")),
|
||||
|
||||
# ADC
|
||||
Resource("adc", 0,
|
||||
Subsignal("cs_n", Pins("A10")),
|
||||
Subsignal("saddr", Pins("B10")),
|
||||
Subsignal("sclk", Pins("B14")),
|
||||
Subsignal("sdat", Pins("A9")),
|
||||
Attrs(io_standard="3.3-V LVTTL")),
|
||||
|
||||
# ECPS
|
||||
Resource("epcs", 0,
|
||||
Subsignal("data0", Pins("H2")),
|
||||
Subsignal("dclk", Pins("H1")),
|
||||
Subsignal("ncs0", Pins("D2")),
|
||||
Subsignal("asd0", Pins("C1")),
|
||||
Attrs(io_standard="3.3-V LVTTL")),
|
||||
|
||||
Resource("adat", 0,
|
||||
Subsignal("tx", Pins("JP_3:5", dir="o")),
|
||||
Subsignal("rx", Pins("JP_3:6", dir="i")),
|
||||
Attrs(io_standard="3.3-V LVTTL")),
|
||||
]
|
||||
|
||||
connectors = [
|
||||
# PIN 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
||||
Connector("JP", 1, "A8 D3 B8 C3 A2 A3 B3 B4 A4 B5 - - A5 D5 B6 A6 B7 D6 A7 C6 C8 E6 E7 D8 E8 F8 F9 E9 - - C9 D9 E11 E10 C11 B11 A12 D11 D12 B12"),
|
||||
Connector("JP", 2, "T9 F13 R9 T15 T14 T13 R13 T12 R12 T11 - - T10 R11 P11 R10 N12 P9 N9 N11 L16 K16 R16 L15 P15 P16 R14 N16 - - N15 P14 L14 N14 M10 L13 J16 K15 J13 J14"),
|
||||
Connector("JP", 3, "- E15 E16 M16 A14 B16 C14 C16 C15 D16 D15 D14 F15 F16 F14 G16 G15 - - - - - - - - -")
|
||||
]
|
||||
|
||||
@property
|
||||
def file_templates(self):
|
||||
templates = super().file_templates
|
||||
templates["{{name}}.qsf"] += r"""
|
||||
set_global_assignment -name OPTIMIZATION_MODE "Aggressive Performance"
|
||||
set_global_assignment -name FITTER_EFFORT "Standard Fit"
|
||||
set_global_assignment -name PHYSICAL_SYNTHESIS_EFFORT "Extra"
|
||||
set_instance_assignment -name DECREASE_INPUT_DELAY_TO_INPUT_REGISTER OFF -to *ulpi*
|
||||
set_instance_assignment -name INCREASE_DELAY_TO_OUTPUT_PIN OFF -to *ulpi*
|
||||
set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL
|
||||
"""
|
||||
templates["{{name}}.sdc"] += r"""
|
||||
create_clock -name "clk_60MHz" -period 16.667 [get_ports "ulpi_0__clk__io"]
|
||||
"""
|
||||
return templates
|
||||
|
||||
|
||||
def toolchain_program(self, products, name):
|
||||
""" Programs the attached de0_nano board via a Quartus programming cable. """
|
||||
|
||||
quartus_pgm = os.environ.get("QUARTUS_PGM", "quartus_pgm")
|
||||
with products.extract("{}.sof".format(name)) as bitstream_filename:
|
||||
subprocess.check_call([quartus_pgm, "--haltcc", "--mode", "JTAG",
|
||||
"--operation", "P;" + bitstream_filename])
|
||||
56
gateware/attic/tinybx_luna.py
Normal file
56
gateware/attic/tinybx_luna.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
use the LUNA usb config for the tinyFPGA-Bx
|
||||
add some pins (at random) for adat and debug
|
||||
create an audio clock domain
|
||||
|
||||
Issues:
|
||||
The audio clock is just a carbon copy of usb 12MHz because the Bx only has one PLL.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
from amaranth import *
|
||||
from amaranth.build import *
|
||||
from amaranth_boards.resources import *
|
||||
|
||||
from luna.gateware.platform.tinyfpga import TinyFPGABxPlatform, TinyFPGABxDomainGenerator
|
||||
|
||||
|
||||
class TinyBxAdatDomainGenerator(Elaboratable):
|
||||
""" Creates audio clock domains on top of the LUNA domains for the TinyFPGA Bx. """
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = TinyFPGABxDomainGenerator.elaborate(self, platform)
|
||||
|
||||
# Create our domains...
|
||||
m.domains.adat = ClockDomain()
|
||||
m.d.comb += [
|
||||
ClockSignal("adat").eq(ClockSignal("usb")),
|
||||
ResetSignal("adat").eq(ResetSignal("usb")),
|
||||
]
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class TinyBxAdatPlatform(TinyFPGABxPlatform):
|
||||
clock_domain_generator = TinyBxAdatDomainGenerator
|
||||
number_of_channels = 4
|
||||
bitwidth = 24
|
||||
|
||||
def __init__(self):
|
||||
self.resources += [
|
||||
Resource("adat", 0,
|
||||
Subsignal("tx", Pins("A2", dir="o")),
|
||||
Subsignal("rx", Pins("A1", dir="i")),
|
||||
Attrs(IO_STANDARD="SB_LVCMOS")),
|
||||
|
||||
Resource("debug_led", 0, Pins("C9 A9 B8 A8 B7 A7 B6 A6", dir="o"),
|
||||
Attrs(IO_STANDARD="SB_LVCMOS")),
|
||||
]
|
||||
super().__init__(toolchain="IceStorm")
|
||||
|
||||
|
||||
|
||||
56
gateware/bundle_demultiplexer-bench.gtkw
Normal file
56
gateware/bundle_demultiplexer-bench.gtkw
Normal file
@@ -0,0 +1,56 @@
|
||||
[*]
|
||||
[*] GTKWave Analyzer v3.4.0 (w)1999-2022 BSI
|
||||
[*] Mon Dec 13 23:39:41 2021
|
||||
[*]
|
||||
[dumpfile] "test_BundleDemultiplexerTest_test_smoke.vcd"
|
||||
[dumpfile_mtime] "Mon Dec 13 23:35:48 2021"
|
||||
[dumpfile_size] 8023
|
||||
[savefile] "gateware/bundle_demultiplexer-bench.gtkw"
|
||||
[timestart] 0
|
||||
[size] 3828 2090
|
||||
[pos] -1 -1
|
||||
*-15.000000 384000 384000 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
|
||||
[treeopen] top.
|
||||
[sst_width] 65
|
||||
[signals_width] 510
|
||||
[sst_expanded] 0
|
||||
[sst_vpaned_height] 658
|
||||
@28
|
||||
top.clk
|
||||
top.channel_stream__ready
|
||||
top.channel_stream__valid
|
||||
@22
|
||||
top.channel_stream__channel_nr[4:0]
|
||||
top.channel_stream__payload[23:0]
|
||||
@200
|
||||
-Multiplexer
|
||||
@22
|
||||
top.bundle_nr[1:0]
|
||||
top.channel_nr[2:0]
|
||||
@200
|
||||
-Output
|
||||
@28
|
||||
top.ready
|
||||
@22
|
||||
top.output_bundle_0__valid
|
||||
top.output_bundle_0__channel_nr[2:0]
|
||||
top.output_bundle_0__payload[23:0]
|
||||
top.output_bundle_0__first
|
||||
top.output_bundle_0__last
|
||||
top.output_bundle_1__valid
|
||||
top.output_bundle_1__channel_nr[2:0]
|
||||
top.output_bundle_1__payload[23:0]
|
||||
top.output_bundle_1__first
|
||||
top.output_bundle_1__last
|
||||
top.output_bundle_2__valid
|
||||
top.output_bundle_2__channel_nr[2:0]
|
||||
top.output_bundle_2__payload[23:0]
|
||||
top.output_bundle_2__first
|
||||
top.output_bundle_2__last
|
||||
top.output_bundle_3__valid
|
||||
top.output_bundle_3__channel_nr[2:0]
|
||||
top.output_bundle_3__payload[23:0]
|
||||
top.output_bundle_3__first
|
||||
top.output_bundle_3__last
|
||||
[pattern_trace] 1
|
||||
[pattern_trace] 0
|
||||
95
gateware/bundle_demultiplexer.py
Normal file
95
gateware/bundle_demultiplexer.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import operator
|
||||
from functools import reduce
|
||||
|
||||
from amaranth import *
|
||||
from amaranth.build import Platform
|
||||
from amlib.stream import StreamInterface
|
||||
from amlib.test import GatewareTestCase, sync_test_case
|
||||
|
||||
class BundleDemultiplexer(Elaboratable):
|
||||
NO_CHANNELS_ADAT = 8
|
||||
SAMPLE_WIDTH = 24
|
||||
|
||||
def __init__(self, no_bundles=4):
|
||||
# parameters
|
||||
self._no_bundles = no_bundles
|
||||
self._channel_bits = Shape.cast(range(no_bundles * self.NO_CHANNELS_ADAT)).width
|
||||
self._bundle_channel_bits = Shape.cast(range(self.NO_CHANNELS_ADAT)).width
|
||||
|
||||
# ports
|
||||
self.no_channels_in = Signal(range(32 + 1))
|
||||
|
||||
self.channel_stream_in = StreamInterface(name="channel_stream",
|
||||
payload_width=self.SAMPLE_WIDTH,
|
||||
extra_fields=[("channel_nr", self._channel_bits)])
|
||||
|
||||
self.bundles_out = Array(StreamInterface(name=f"output_bundle_{i}",
|
||||
payload_width=self.SAMPLE_WIDTH,
|
||||
extra_fields=[("channel_nr", self._bundle_channel_bits)])
|
||||
for i in range(no_bundles))
|
||||
|
||||
# debug signals
|
||||
self.bundle_nr = Signal(range(self._no_bundles))
|
||||
|
||||
def elaborate(self, platform: Platform) -> Module:
|
||||
m = Module()
|
||||
|
||||
# this is necessary to keep the simulator happy
|
||||
keep_sync = Signal()
|
||||
m.d.sync += keep_sync.eq(1)
|
||||
|
||||
channel_stream = self.channel_stream_in
|
||||
bundle_ready = Signal()
|
||||
bundle_nr = Signal(range(self._no_bundles))
|
||||
channel_nr = Signal(range(self.NO_CHANNELS_ADAT))
|
||||
|
||||
last_channel = Signal(3)
|
||||
channel_mask = last_channel
|
||||
|
||||
m.d.comb += [
|
||||
bundle_nr .eq(channel_stream.channel_nr >> channel_nr.width),
|
||||
self.bundle_nr .eq(bundle_nr),
|
||||
channel_nr .eq(channel_stream.channel_nr & channel_mask),
|
||||
last_channel .eq(Mux(self.no_channels_in == 2, 1, 7)),
|
||||
|
||||
bundle_ready .eq(self.bundles_out[bundle_nr].ready),
|
||||
channel_stream.ready .eq(bundle_ready),
|
||||
]
|
||||
|
||||
with m.If(bundle_ready & channel_stream.valid):
|
||||
m.d.comb += [
|
||||
self.bundles_out[bundle_nr].valid.eq(1),
|
||||
self.bundles_out[bundle_nr].payload.eq(channel_stream.payload),
|
||||
self.bundles_out[bundle_nr].channel_nr.eq(channel_nr),
|
||||
self.bundles_out[bundle_nr].first.eq(channel_nr == 0),
|
||||
self.bundles_out[bundle_nr].last.eq(channel_nr == last_channel),
|
||||
]
|
||||
|
||||
return m
|
||||
|
||||
class BundleDemultiplexerTest(GatewareTestCase):
|
||||
FRAGMENT_UNDER_TEST = BundleDemultiplexer
|
||||
FRAGMENT_ARGUMENTS = dict()
|
||||
|
||||
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
|
||||
yield self.dut.channel_stream_in.valid.eq(0)
|
||||
if wait:
|
||||
yield
|
||||
|
||||
@sync_test_case
|
||||
def test_smoke(self):
|
||||
dut = self.dut
|
||||
for bundle in range(4):
|
||||
yield dut.bundles_out[bundle].ready.eq(1)
|
||||
yield
|
||||
yield
|
||||
for sample in range(3):
|
||||
for ch in range(4*BundleDemultiplexer.NO_CHANNELS_ADAT):
|
||||
yield from self.send_one_frame((sample << 8) | ch, ch, wait=False)
|
||||
|
||||
yield
|
||||
yield
|
||||
88
gateware/bundle_multiplexer-bench-inactive.gtkw
Normal file
88
gateware/bundle_multiplexer-bench-inactive.gtkw
Normal file
@@ -0,0 +1,88 @@
|
||||
[*]
|
||||
[*] GTKWave Analyzer v3.4.0 (w)1999-2022 BSI
|
||||
[*] Tue Dec 14 22:59:41 2021
|
||||
[*]
|
||||
[dumpfile] "test_BundleMultiplexerTest_test_inactive_bundle.vcd"
|
||||
[dumpfile_mtime] "Tue Dec 14 22:35:59 2021"
|
||||
[dumpfile_size] 17202
|
||||
[savefile] "gateware/bundle_multiplexer-bench.gtkw"
|
||||
[timestart] 0
|
||||
[size] 3828 2090
|
||||
[pos] -1921 27
|
||||
*-15.418433 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
|
||||
[treeopen] top.
|
||||
[sst_width] 562
|
||||
[signals_width] 606
|
||||
[sst_expanded] 1
|
||||
[sst_vpaned_height] 658
|
||||
@28
|
||||
bench.top.clk
|
||||
@200
|
||||
-Inputs
|
||||
@22
|
||||
bench.top.no_channels0[3:0]
|
||||
bench.top.no_channels1[3:0]
|
||||
bench.top.no_channels2[3:0]
|
||||
bench.top.no_channels3[3:0]
|
||||
bench.top.input_bundle0__channel_nr[2:0]
|
||||
bench.top.input_bundle0__last
|
||||
bench.top.input_bundle0__payload[23:0]
|
||||
bench.top.input_bundle0__valid
|
||||
bench.top.input_bundle1__channel_nr[2:0]
|
||||
bench.top.input_bundle1__last
|
||||
bench.top.input_bundle1__payload[23:0]
|
||||
bench.top.input_bundle1__valid
|
||||
bench.top.input_bundle2__channel_nr[2:0]
|
||||
bench.top.input_bundle2__last
|
||||
bench.top.input_bundle2__payload[23:0]
|
||||
bench.top.input_bundle2__valid
|
||||
bench.top.input_bundle3__channel_nr[2:0]
|
||||
bench.top.input_bundle3__last
|
||||
bench.top.input_bundle3__payload[23:0]
|
||||
bench.top.input_bundle3__valid
|
||||
@200
|
||||
-Module
|
||||
@22
|
||||
bench.top.current_bundle[1:0]
|
||||
bench.top.current_channel[4:0]
|
||||
@29
|
||||
bench.top.bundle0_active
|
||||
bench.top.bundle1_active
|
||||
bench.top.bundle2_active
|
||||
bench.top.bundle3_active
|
||||
@22
|
||||
bench.top.first_bundle_channel[4:0]
|
||||
bench.top.bundle0_channel[2:0]
|
||||
bench.top.bundle0_last
|
||||
bench.top.bundle0_read_en
|
||||
bench.top.bundle0_ready
|
||||
bench.top.bundle0_sample[23:0]
|
||||
bench.top.bundle1_channel[2:0]
|
||||
bench.top.bundle1_last
|
||||
bench.top.bundle1_read_en
|
||||
bench.top.bundle1_ready
|
||||
bench.top.bundle1_sample[23:0]
|
||||
bench.top.bundle2_channel[2:0]
|
||||
bench.top.bundle2_last
|
||||
bench.top.bundle2_read_en
|
||||
bench.top.bundle2_ready
|
||||
bench.top.bundle2_sample[23:0]
|
||||
bench.top.bundle3_channel[2:0]
|
||||
bench.top.bundle3_last
|
||||
bench.top.bundle3_read_en
|
||||
bench.top.bundle3_ready
|
||||
bench.top.bundle3_sample[23:0]
|
||||
@200
|
||||
-Output
|
||||
@28
|
||||
bench.top.channel_stream__first
|
||||
bench.top.channel_stream__last
|
||||
@23
|
||||
bench.top.channel_stream__channel_nr[4:0]
|
||||
@22
|
||||
bench.top.channel_stream__payload[23:0]
|
||||
@28
|
||||
bench.top.channel_stream__ready
|
||||
bench.top.channel_stream__valid
|
||||
[pattern_trace] 1
|
||||
[pattern_trace] 0
|
||||
103
gateware/bundle_multiplexer-bench.gtkw
Normal file
103
gateware/bundle_multiplexer-bench.gtkw
Normal file
@@ -0,0 +1,103 @@
|
||||
[*]
|
||||
[*] GTKWave Analyzer v3.4.0 (w)1999-2022 BSI
|
||||
[*] Fri Dec 31 08:36:29 2021
|
||||
[*]
|
||||
[dumpfile] "test_BundleMultiplexerTest_test_all_sending.vcd"
|
||||
[dumpfile_mtime] "Fri Dec 31 08:35:29 2021"
|
||||
[dumpfile_size] 55739
|
||||
[savefile] "gateware/bundle_multiplexer-bench.gtkw"
|
||||
[timestart] 0
|
||||
[size] 3828 2090
|
||||
[pos] -1 -1
|
||||
*-15.418433 151500 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
|
||||
[treeopen] bench.
|
||||
[treeopen] bench.top.
|
||||
[sst_width] 562
|
||||
[signals_width] 606
|
||||
[sst_expanded] 1
|
||||
[sst_vpaned_height] 650
|
||||
@28
|
||||
bench.top.clk
|
||||
@200
|
||||
-Inputs
|
||||
@22
|
||||
bench.top.no_channels0[3:0]
|
||||
bench.top.no_channels1[3:0]
|
||||
bench.top.no_channels2[3:0]
|
||||
bench.top.no_channels3[3:0]
|
||||
bench.top.input_bundle0__channel_nr[2:0]
|
||||
@28
|
||||
bench.top.input_bundle0__valid
|
||||
@22
|
||||
bench.top.input_bundle1__channel_nr[2:0]
|
||||
bench.top.input_bundle1__valid
|
||||
bench.top.input_bundle2__channel_nr[2:0]
|
||||
bench.top.input_bundle2__valid
|
||||
bench.top.input_bundle3__channel_nr[2:0]
|
||||
bench.top.input_bundle3__valid
|
||||
@200
|
||||
-FIFOS
|
||||
@22
|
||||
bench.top.receive_fifo0.w_level[4:0]
|
||||
bench.top.receive_fifo1.w_level[4:0]
|
||||
bench.top.receive_fifo2.w_level[4:0]
|
||||
@23
|
||||
bench.top.receive_fifo3.w_level[4:0]
|
||||
@200
|
||||
-Module
|
||||
@22
|
||||
bench.top.current_bundle[1:0]
|
||||
bench.top.current_channel[4:0]
|
||||
@28
|
||||
bench.top.bundle0_active
|
||||
bench.top.bundle1_active
|
||||
bench.top.bundle2_active
|
||||
bench.top.bundle3_active
|
||||
@22
|
||||
bench.top.first_bundle_channel[4:0]
|
||||
@200
|
||||
-
|
||||
@22
|
||||
bench.top.bundle0_channel[2:0]
|
||||
bench.top.bundle0_last
|
||||
bench.top.bundle0_read_en
|
||||
bench.top.bundle0_ready
|
||||
bench.top.bundle0_sample[23:0]
|
||||
@200
|
||||
-
|
||||
@22
|
||||
bench.top.bundle1_channel[2:0]
|
||||
bench.top.bundle1_last
|
||||
bench.top.bundle1_read_en
|
||||
bench.top.bundle1_ready
|
||||
bench.top.bundle1_sample[23:0]
|
||||
@200
|
||||
-
|
||||
@22
|
||||
bench.top.bundle2_channel[2:0]
|
||||
bench.top.bundle2_last
|
||||
bench.top.bundle2_read_en
|
||||
bench.top.bundle2_ready
|
||||
bench.top.bundle2_sample[23:0]
|
||||
@200
|
||||
-
|
||||
@22
|
||||
bench.top.bundle3_channel[2:0]
|
||||
bench.top.bundle3_last
|
||||
bench.top.bundle3_read_en
|
||||
bench.top.bundle3_ready
|
||||
bench.top.bundle3_sample[23:0]
|
||||
@200
|
||||
-
|
||||
-Output
|
||||
@28
|
||||
bench.top.channel_stream__first
|
||||
bench.top.channel_stream__last
|
||||
@22
|
||||
bench.top.channel_stream__channel_nr[4:0]
|
||||
bench.top.channel_stream__payload[23:0]
|
||||
@28
|
||||
bench.top.channel_stream__ready
|
||||
bench.top.channel_stream__valid
|
||||
[pattern_trace] 1
|
||||
[pattern_trace] 0
|
||||
233
gateware/bundle_multiplexer.py
Normal file
233
gateware/bundle_multiplexer.py
Normal file
@@ -0,0 +1,233 @@
|
||||
from amaranth import *
|
||||
from amaranth.build import Platform
|
||||
from amaranth.lib.fifo import SyncFIFO
|
||||
|
||||
from amlib.stream import StreamInterface, connect_stream_to_fifo
|
||||
from amlib.test import GatewareTestCase, sync_test_case
|
||||
|
||||
class BundleMultiplexer(Elaboratable):
|
||||
NO_CHANNELS_ADAT = 8
|
||||
SAMPLE_WIDTH = 24
|
||||
FIFO_DEPTH = 32 * NO_CHANNELS_ADAT
|
||||
|
||||
def __init__(self, no_bundles=4):
|
||||
# parameters
|
||||
self._no_bundles = no_bundles
|
||||
self._channel_bits = Shape.cast(range(no_bundles * self.NO_CHANNELS_ADAT)).width
|
||||
self._bundle_channel_bits = Shape.cast(range(self.NO_CHANNELS_ADAT)).width
|
||||
|
||||
# ports
|
||||
self.channel_stream_out = StreamInterface(name="channel_stream",
|
||||
payload_width=self.SAMPLE_WIDTH,
|
||||
extra_fields=[("channel_nr", self._channel_bits)])
|
||||
|
||||
self.bundles_in = Array(StreamInterface(name=f"input_bundle{i}",
|
||||
payload_width=self.SAMPLE_WIDTH,
|
||||
extra_fields=[("channel_nr", self._bundle_channel_bits)])
|
||||
for i in range(no_bundles))
|
||||
|
||||
self.bundle_active_in = Array(Signal(name=f"bundle{i}_active") for i in range(no_bundles))
|
||||
|
||||
self.no_channels_in = Array(Signal(self._bundle_channel_bits + 1, name=f"no_channels{i}") for i in range(no_bundles))
|
||||
|
||||
# debug ports
|
||||
self.current_bundle = Signal(range(no_bundles))
|
||||
self.last_bundle = Signal()
|
||||
self.levels = Array(Signal(5, name=f"rx{i}_fifo_level") for i in range(1,5))
|
||||
|
||||
def elaborate(self, platform: Platform) -> Module:
|
||||
m = Module()
|
||||
sample_width = self.SAMPLE_WIDTH
|
||||
bundle_bits = self._bundle_channel_bits
|
||||
|
||||
bundle_ready = Array(Signal( name=f"bundle{i}_ready") for i in range(self._no_bundles))
|
||||
bundle_sample = Array(Signal(sample_width, name=f"bundle{i}_sample") for i in range(self._no_bundles))
|
||||
bundle_channel = Array(Signal(bundle_bits, name=f"bundle{i}_channel") for i in range(self._no_bundles))
|
||||
last = Array(Signal( name=f"bundle{i}_last") for i in range(self._no_bundles))
|
||||
read_enable = Array(Signal( name=f"bundle{i}_read_en") for i in range(self._no_bundles))
|
||||
|
||||
for i in range(self._no_bundles):
|
||||
fifo = SyncFIFO(width=sample_width + bundle_bits + 1, depth=self.FIFO_DEPTH)
|
||||
setattr(m.submodules, f"receive_fifo{i}", fifo)
|
||||
|
||||
m.d.comb += [
|
||||
*connect_stream_to_fifo(self.bundles_in[i], fifo),
|
||||
fifo.w_data[sample_width:].eq(self.bundles_in[i].channel_nr),
|
||||
fifo.w_data[-1].eq(self.bundles_in[i].last),
|
||||
|
||||
bundle_ready[i] .eq(fifo.r_rdy),
|
||||
bundle_sample[i] .eq(fifo.r_data[:sample_width]),
|
||||
bundle_channel[i] .eq(fifo.r_data[sample_width:sample_width + bundle_bits]),
|
||||
self.levels[i] .eq(fifo.r_level),
|
||||
|
||||
last[i].eq(fifo.r_data[-1]),
|
||||
fifo.r_en.eq(read_enable[i]),
|
||||
]
|
||||
|
||||
current_bundle = Signal(range(self._no_bundles))
|
||||
first_bundle_channel = Signal(self._channel_bits)
|
||||
current_channel = Signal(self._channel_bits)
|
||||
last_bundle = Signal(self._bundle_channel_bits)
|
||||
|
||||
m.d.comb += last_bundle.eq(current_bundle == (self._no_bundles - 1))
|
||||
m.d.comb += [
|
||||
self.current_bundle.eq(current_bundle),
|
||||
self.last_bundle.eq(last_bundle),
|
||||
]
|
||||
|
||||
def handle_last_channel():
|
||||
with m.If(last_bundle):
|
||||
m.d.comb += self.channel_stream_out.last.eq(1)
|
||||
m.d.sync += [
|
||||
current_bundle.eq(0),
|
||||
first_bundle_channel.eq(0),
|
||||
]
|
||||
with m.Else():
|
||||
m.d.sync += [
|
||||
current_bundle.eq(current_bundle + 1),
|
||||
first_bundle_channel.eq(first_bundle_channel + self.no_channels_in[current_bundle])
|
||||
]
|
||||
|
||||
with m.If(self.channel_stream_out.ready):
|
||||
# bundle is active (ie. ADAT cable plugged in and synced)
|
||||
with m.If(self.bundle_active_in[current_bundle]):
|
||||
with m.If(bundle_ready[current_bundle]):
|
||||
m.d.comb += [
|
||||
self.channel_stream_out.payload.eq(bundle_sample[current_bundle]),
|
||||
self.channel_stream_out.channel_nr.eq(first_bundle_channel + bundle_channel[current_bundle]),
|
||||
read_enable[current_bundle].eq(1),
|
||||
self.channel_stream_out.valid.eq(1),
|
||||
self.channel_stream_out.first.eq((current_bundle == 0) & (bundle_channel[current_bundle] == 0))
|
||||
]
|
||||
|
||||
with m.If(last[current_bundle]):
|
||||
handle_last_channel()
|
||||
|
||||
# bundle inactive (eg. no ADAT/SPDIF cable plugged in or not synced) => fill zeros
|
||||
with m.Else():
|
||||
last_channel = Signal()
|
||||
m.d.comb += [
|
||||
self.channel_stream_out.payload.eq(0),
|
||||
self.channel_stream_out.channel_nr.eq(first_bundle_channel + current_channel),
|
||||
self.channel_stream_out.valid.eq(1),
|
||||
self.channel_stream_out.first.eq(0),
|
||||
self.channel_stream_out.last.eq(0),
|
||||
last_channel.eq(current_channel == (self.no_channels_in[current_bundle] - 1))
|
||||
]
|
||||
m.d.sync += current_channel.eq(current_channel + 1)
|
||||
|
||||
with m.If(last_channel):
|
||||
m.d.sync += current_channel.eq(0)
|
||||
handle_last_channel()
|
||||
|
||||
return m
|
||||
|
||||
class BundleMultiplexerTest(GatewareTestCase):
|
||||
FRAGMENT_UNDER_TEST = BundleMultiplexer
|
||||
FRAGMENT_ARGUMENTS = dict()
|
||||
|
||||
def send_one_frame(self, bundle: int, sample: int, channel: int, wait=False):
|
||||
yield self.dut.bundles_in[bundle].channel_nr.eq(channel)
|
||||
yield self.dut.bundles_in[bundle].payload.eq(sample)
|
||||
yield self.dut.bundles_in[bundle].valid.eq(1)
|
||||
yield self.dut.bundles_in[bundle].first.eq(channel == 0)
|
||||
yield self.dut.bundles_in[bundle].last.eq(channel == 7)
|
||||
yield
|
||||
yield self.dut.bundles_in[bundle].valid.eq(0)
|
||||
if wait:
|
||||
yield
|
||||
|
||||
def send_bundle_frame(self, bundle: int, sample: int):
|
||||
ch = list(range(self.dut.NO_CHANNELS_ADAT))
|
||||
first = ch[0:4]
|
||||
second = ch[4:]
|
||||
for channel in first:
|
||||
yield from self.send_one_frame(bundle, (sample << 8) + channel, channel)
|
||||
|
||||
yield self.dut.bundles_in[bundle].valid.eq(0)
|
||||
yield
|
||||
yield
|
||||
|
||||
for channel in second:
|
||||
yield from self.send_one_frame(bundle, (sample << 8) + channel, channel)
|
||||
|
||||
|
||||
@sync_test_case
|
||||
def test_smoke(self):
|
||||
dut = self.dut
|
||||
yield
|
||||
for bundle in range(4):
|
||||
yield self.dut.no_channels_in[bundle].eq(8)
|
||||
yield self.dut.bundle_active_in[bundle].eq(1)
|
||||
yield dut.channel_stream_out.ready.eq(1)
|
||||
yield
|
||||
for bundle in range(4):
|
||||
yield from self.send_bundle_frame(bundle, bundle)
|
||||
yield
|
||||
yield
|
||||
|
||||
yield
|
||||
yield
|
||||
|
||||
@sync_test_case
|
||||
def test_inactive_bundle(self):
|
||||
dut = self.dut
|
||||
yield
|
||||
yield dut.channel_stream_out.ready.eq(1)
|
||||
for bundle in range(4):
|
||||
yield self.dut.no_channels_in[bundle].eq(8)
|
||||
if (bundle != 2):
|
||||
yield dut.bundle_active_in[bundle].eq(1)
|
||||
yield
|
||||
for bundle in range(4):
|
||||
if bundle != 2:
|
||||
yield from self.send_bundle_frame(bundle, bundle)
|
||||
if bundle == 0:
|
||||
yield from self.advance_cycles(8)
|
||||
|
||||
yield from self.advance_cycles(16)
|
||||
|
||||
|
||||
@sync_test_case
|
||||
def test_all_inactive(self):
|
||||
dut = self.dut
|
||||
yield
|
||||
yield dut.channel_stream_out.ready.eq(1)
|
||||
for bundle in range(4):
|
||||
yield self.dut.no_channels_in[bundle].eq(8)
|
||||
yield dut.bundle_active_in[bundle].eq(0)
|
||||
|
||||
yield
|
||||
yield from self.advance_cycles(96)
|
||||
|
||||
|
||||
def send_all_bundle_frame(self):
|
||||
for channel in range(8):
|
||||
for bundle in range(4):
|
||||
yield self.dut.bundles_in[bundle].valid.eq(1)
|
||||
yield self.dut.bundles_in[bundle].channel_nr.eq(channel)
|
||||
yield self.dut.bundles_in[bundle].payload.eq((bundle << 16) | channel)
|
||||
yield self.dut.bundles_in[bundle].first.eq(channel == 0)
|
||||
yield self.dut.bundles_in[bundle].last.eq(channel == 7)
|
||||
|
||||
yield
|
||||
|
||||
for bundle in range(4):
|
||||
yield self.dut.bundles_in[bundle].valid.eq(0)
|
||||
|
||||
yield
|
||||
|
||||
@sync_test_case
|
||||
def test_all_sending(self):
|
||||
dut = self.dut
|
||||
yield
|
||||
yield dut.channel_stream_out.ready.eq(1)
|
||||
for bundle in range(4):
|
||||
yield self.dut.no_channels_in[bundle].eq(8)
|
||||
yield dut.bundle_active_in[bundle].eq(1)
|
||||
|
||||
yield
|
||||
for _ in range(20):
|
||||
yield from self.send_all_bundle_frame()
|
||||
|
||||
yield from self.advance_cycles(64)
|
||||
566
gateware/car.py
Normal file
566
gateware/car.py
Normal file
@@ -0,0 +1,566 @@
|
||||
from amaranth import *
|
||||
from amaranth.build import *
|
||||
from amaranth.lib.cdc import ResetSynchronizer
|
||||
from amaranth.lib.wiring import flipped
|
||||
|
||||
from amlib.utils import SimpleClockDivider
|
||||
|
||||
|
||||
class ClockDomainGeneratorBase():
|
||||
NO_PHASE_SHIFT = 0
|
||||
|
||||
def wire_up_reset(self, m, reset):
|
||||
m.submodules.reset_sync_fast = ResetSynchronizer(reset, domain="fast")
|
||||
m.submodules.reset_sync_usb = ResetSynchronizer(reset, domain="usb")
|
||||
m.submodules.reset_sync_sync = ResetSynchronizer(reset, domain="sync")
|
||||
m.submodules.reset_sync_dac = ResetSynchronizer(reset, domain="dac")
|
||||
m.submodules.reset_sync_adat = ResetSynchronizer(reset, domain="adat")
|
||||
|
||||
class IntelCycloneIVClockDomainGenerator(Elaboratable, ClockDomainGeneratorBase):
|
||||
ADAT_DIV_48k = 83
|
||||
ADAT_MULT_48k = 17
|
||||
|
||||
ADAT_DIV_44_1k = 62
|
||||
ADAT_MULT_44_1k = 14
|
||||
|
||||
DUTY_CYCLE = 50
|
||||
|
||||
def __init__(self, *, clock_frequencies=None, clock_signal_name=None):
|
||||
pass
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
# Create our domains
|
||||
m.domains.usb = ClockDomain("usb")
|
||||
m.domains.sync = ClockDomain("sync")
|
||||
m.domains.fast = ClockDomain("fast")
|
||||
m.domains.adat = ClockDomain("adat")
|
||||
m.domains.dac = ClockDomain("dac")
|
||||
|
||||
clk = flipped(platform.request(platform.default_clk))
|
||||
|
||||
main_clocks = Signal(5)
|
||||
audio_clocks = Signal(4)
|
||||
fast_clock_48k = Signal()
|
||||
|
||||
sys_locked = Signal()
|
||||
audio_locked = Signal()
|
||||
fast_locked = Signal()
|
||||
reset = Signal()
|
||||
|
||||
m.submodules.mainpll = Instance("ALTPLL",
|
||||
p_BANDWIDTH_TYPE = "AUTO",
|
||||
|
||||
# USB clock: 60MHz
|
||||
p_CLK0_DIVIDE_BY = 5,
|
||||
p_CLK0_DUTY_CYCLE = self.DUTY_CYCLE,
|
||||
p_CLK0_MULTIPLY_BY = 6,
|
||||
p_CLK0_PHASE_SHIFT = self.NO_PHASE_SHIFT,
|
||||
|
||||
# 44.1k ADAT Clock 11.2896 MHz = 44.1kHz * 256
|
||||
p_CLK1_DIVIDE_BY = self.ADAT_DIV_44_1k,
|
||||
p_CLK1_DUTY_CYCLE = self.DUTY_CYCLE,
|
||||
p_CLK1_MULTIPLY_BY = self.ADAT_MULT_44_1k,
|
||||
p_CLK1_PHASE_SHIFT = self.NO_PHASE_SHIFT,
|
||||
|
||||
# I2S DAC clock 44.1k = 3.072 MHz = 44.1kHz * 32 bit * 2 channels
|
||||
p_CLK2_DIVIDE_BY = self.ADAT_DIV_44_1k * 4,
|
||||
p_CLK2_DUTY_CYCLE = self.DUTY_CYCLE,
|
||||
p_CLK2_MULTIPLY_BY = self.ADAT_MULT_44_1k,
|
||||
p_CLK2_PHASE_SHIFT = self.NO_PHASE_SHIFT,
|
||||
|
||||
# ADAT sampling clock = 44.1kHz * 256 * 8 times oversampling
|
||||
p_CLK3_DIVIDE_BY = self.ADAT_DIV_44_1k,
|
||||
p_CLK3_DUTY_CYCLE = self.DUTY_CYCLE,
|
||||
p_CLK3_MULTIPLY_BY = self.ADAT_MULT_44_1k * 8,
|
||||
p_CLK3_PHASE_SHIFT = self.NO_PHASE_SHIFT,
|
||||
|
||||
# ADAT transmit domain clock = 44.1kHz * 5 output terminals
|
||||
p_CLK4_DIVIDE_BY = self.ADAT_DIV_44_1k,
|
||||
p_CLK4_DUTY_CYCLE = self.DUTY_CYCLE,
|
||||
p_CLK4_MULTIPLY_BY = self.ADAT_MULT_44_1k * 5,
|
||||
p_CLK4_PHASE_SHIFT = self.NO_PHASE_SHIFT,
|
||||
|
||||
# 50MHz = 20000 picoseconds
|
||||
p_INCLK0_INPUT_FREQUENCY = 20000,
|
||||
p_OPERATION_MODE = "NORMAL",
|
||||
|
||||
# Drive our clock from the USB clock
|
||||
# coming from the USB clock pin of the USB3300
|
||||
i_inclk = clk,
|
||||
o_clk = main_clocks,
|
||||
o_locked = sys_locked,
|
||||
)
|
||||
|
||||
m.submodules.audiopll = Instance("ALTPLL",
|
||||
p_BANDWIDTH_TYPE = "AUTO",
|
||||
|
||||
# ADAT clock = 12.288 MHz = 48 kHz * 256
|
||||
p_CLK0_DIVIDE_BY = self.ADAT_DIV_48k,
|
||||
p_CLK0_DUTY_CYCLE = self.DUTY_CYCLE,
|
||||
p_CLK0_MULTIPLY_BY = self.ADAT_MULT_48k,
|
||||
p_CLK0_PHASE_SHIFT = self.NO_PHASE_SHIFT,
|
||||
|
||||
# I2S DAC clock 48k = 3.072 MHz = 48 kHz * 32 bit * 2 channels
|
||||
p_CLK1_DIVIDE_BY = self.ADAT_DIV_48k * 4,
|
||||
p_CLK1_DUTY_CYCLE = self.DUTY_CYCLE,
|
||||
p_CLK1_MULTIPLY_BY = self.ADAT_MULT_48k,
|
||||
p_CLK1_PHASE_SHIFT = self.NO_PHASE_SHIFT,
|
||||
|
||||
# ADAT sampling clock = 48 kHz * 256 * 8 times oversampling
|
||||
p_CLK2_DIVIDE_BY = self.ADAT_DIV_48k,
|
||||
p_CLK2_DUTY_CYCLE = self.DUTY_CYCLE,
|
||||
p_CLK2_MULTIPLY_BY = self.ADAT_MULT_48k * 8,
|
||||
p_CLK2_PHASE_SHIFT = self.NO_PHASE_SHIFT,
|
||||
|
||||
# ADAT transmit domain clock = 48 kHz * 256 * 5 output terminals
|
||||
p_CLK3_DIVIDE_BY = self.ADAT_DIV_48k,
|
||||
p_CLK3_DUTY_CYCLE = self.DUTY_CYCLE,
|
||||
p_CLK3_MULTIPLY_BY = self.ADAT_MULT_48k * 5,
|
||||
p_CLK3_PHASE_SHIFT = self.NO_PHASE_SHIFT,
|
||||
|
||||
p_INCLK0_INPUT_FREQUENCY = 16667,
|
||||
p_OPERATION_MODE = "NORMAL",
|
||||
|
||||
i_inclk = main_clocks[0],
|
||||
o_clk = audio_clocks,
|
||||
o_locked = audio_locked,
|
||||
)
|
||||
|
||||
m.submodules.fastpll = Instance("ALTPLL",
|
||||
p_BANDWIDTH_TYPE = "AUTO",
|
||||
|
||||
# ADAT sampling clock = 48 kHz * 256 * 8 times oversampling
|
||||
p_CLK0_DIVIDE_BY = 1,
|
||||
p_CLK0_DUTY_CYCLE = self.DUTY_CYCLE,
|
||||
p_CLK0_MULTIPLY_BY = platform.fast_multiplier,
|
||||
p_CLK0_PHASE_SHIFT = self.NO_PHASE_SHIFT,
|
||||
|
||||
p_INCLK0_INPUT_FREQUENCY = 81395,
|
||||
p_OPERATION_MODE = "NORMAL",
|
||||
|
||||
i_inclk = audio_clocks[0],
|
||||
o_clk = fast_clock_48k,
|
||||
o_locked = fast_locked,
|
||||
)
|
||||
|
||||
|
||||
m.d.comb += [
|
||||
reset.eq(~(sys_locked & audio_locked & fast_locked)),
|
||||
ClockSignal("fast").eq(fast_clock_48k),
|
||||
ClockSignal("usb") .eq(main_clocks[0]),
|
||||
ClockSignal("adat").eq(audio_clocks[0]),
|
||||
ClockSignal("dac").eq(audio_clocks[1]),
|
||||
ClockSignal("sync").eq(audio_clocks[3]),
|
||||
]
|
||||
|
||||
self.wire_up_reset(m, reset)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class IntelCycloneVClockDomainGenerator(Elaboratable, ClockDomainGeneratorBase):
|
||||
|
||||
def __init__(self, *, clock_frequencies=None, clock_signal_name=None):
|
||||
pass
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
# Create our domains
|
||||
# usb: USB clock: 60MHz
|
||||
# adat: ADAT clock = 12.288 MHz = 48 kHz * 256
|
||||
# dac: I2S DAC clock 48k = 3.072 MHz = 48 kHz * 32 bit * 2 channels
|
||||
# sync: ADAT transmit domain clock = 61.44 MHz = 48 kHz * 256 * 5 output terminals
|
||||
# fast: ADAT sampling clock = 98.304 MHz = 48 kHz * 256 * 8 times oversampling
|
||||
m.domains.usb = ClockDomain("usb")
|
||||
m.domains.sync = ClockDomain("sync")
|
||||
m.domains.fast = ClockDomain("fast")
|
||||
m.domains.adat = ClockDomain("adat")
|
||||
m.domains.dac = ClockDomain("dac")
|
||||
|
||||
clk = flipped(platform.request(platform.default_clk))
|
||||
|
||||
main_clock = Signal()
|
||||
audio_clocks = Signal(4)
|
||||
|
||||
sys_locked = Signal()
|
||||
audio_locked = Signal()
|
||||
|
||||
reset = Signal()
|
||||
|
||||
m.submodules.mainpll = Instance("altera_pll",
|
||||
p_pll_type="General",
|
||||
p_pll_subtype="General",
|
||||
p_fractional_vco_multiplier="false",
|
||||
p_operation_mode="normal",
|
||||
p_reference_clock_frequency="50.0 MHz",
|
||||
p_number_of_clocks="1",
|
||||
|
||||
p_output_clock_frequency0="60.000000 MHz",
|
||||
|
||||
|
||||
# Drive our clock from the internal 50MHz clock
|
||||
i_refclk = clk,
|
||||
o_outclk = main_clock,
|
||||
o_locked = sys_locked
|
||||
)
|
||||
|
||||
m.submodules.audiopll = Instance("altera_pll",
|
||||
p_pll_type="General",
|
||||
p_pll_subtype="General",
|
||||
p_fractional_vco_multiplier="true",
|
||||
p_operation_mode="normal",
|
||||
p_reference_clock_frequency="60.0 MHz",
|
||||
p_number_of_clocks="4",
|
||||
|
||||
p_output_clock_frequency0="12.288000 MHz",
|
||||
p_output_clock_frequency1="3.072000 MHz",
|
||||
p_output_clock_frequency2="61.440000 MHz",
|
||||
p_output_clock_frequency3="98.304000 MHz",
|
||||
|
||||
# Drive our clock from the mainpll
|
||||
i_refclk=main_clock,
|
||||
o_outclk=audio_clocks,
|
||||
o_locked=audio_locked
|
||||
)
|
||||
|
||||
m.d.comb += [
|
||||
reset.eq(~(sys_locked & audio_locked)),
|
||||
ClockSignal("usb") .eq(main_clock),
|
||||
ClockSignal("adat").eq(audio_clocks[0]),
|
||||
ClockSignal("dac").eq(audio_clocks[1]),
|
||||
ClockSignal("sync").eq(audio_clocks[2]),
|
||||
ClockSignal("fast").eq(audio_clocks[3])
|
||||
]
|
||||
|
||||
self.wire_up_reset(m, reset)
|
||||
|
||||
return m
|
||||
|
||||
class Xilinx7SeriesClockDomainGenerator(Elaboratable, ClockDomainGeneratorBase):
|
||||
ADAT_DIV_48k = 83
|
||||
ADAT_MULT_48k = 17
|
||||
DUTY_CYCLE = 0.5
|
||||
|
||||
def __init__(self, *, clock_frequencies=None, clock_signal_name=None):
|
||||
pass
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
# Create our domains
|
||||
m.domains.usb = ClockDomain("usb")
|
||||
m.domains.sync = ClockDomain("sync")
|
||||
m.domains.fast = ClockDomain("fast")
|
||||
m.domains.adat = ClockDomain("adat")
|
||||
m.domains.dac = ClockDomain("dac")
|
||||
|
||||
clk = flipped(platform.request(platform.default_clk))
|
||||
|
||||
main_clocks = Signal()
|
||||
audio_clocks = Signal(4)
|
||||
fast_clock_48k = Signal()
|
||||
|
||||
sys_locked = Signal()
|
||||
audio_locked = Signal()
|
||||
fast_locked = Signal()
|
||||
reset = Signal()
|
||||
|
||||
mainpll_feedback = Signal()
|
||||
audiopll_feedback = Signal()
|
||||
fastpll_feedback = Signal()
|
||||
|
||||
m.submodules.mainpll = Instance("PLLE2_ADV",
|
||||
p_CLKIN1_PERIOD = 20,
|
||||
p_BANDWIDTH = "OPTIMIZED",
|
||||
p_COMPENSATION = "ZHOLD",
|
||||
p_STARTUP_WAIT = "FALSE",
|
||||
|
||||
p_DIVCLK_DIVIDE = 1,
|
||||
p_CLKFBOUT_MULT = 30,
|
||||
p_CLKFBOUT_PHASE = self.NO_PHASE_SHIFT,
|
||||
|
||||
# 60MHz
|
||||
p_CLKOUT0_DIVIDE = 25,
|
||||
p_CLKOUT0_PHASE = self.NO_PHASE_SHIFT,
|
||||
p_CLKOUT0_DUTY_CYCLE = self.DUTY_CYCLE,
|
||||
|
||||
i_CLKFBIN = mainpll_feedback,
|
||||
o_CLKFBOUT = mainpll_feedback,
|
||||
i_CLKIN1 = clk,
|
||||
o_CLKOUT0 = main_clocks,
|
||||
o_LOCKED = sys_locked,
|
||||
)
|
||||
|
||||
m.submodules.audiopll = Instance("PLLE2_ADV",
|
||||
p_CLKIN1_PERIOD = 16.666,
|
||||
p_BANDWIDTH = "OPTIMIZED",
|
||||
p_COMPENSATION = "ZHOLD",
|
||||
p_STARTUP_WAIT = "FALSE",
|
||||
p_DIVCLK_DIVIDE = 1,
|
||||
p_CLKFBOUT_MULT = self.ADAT_MULT_48k,
|
||||
p_CLKFBOUT_PHASE = self.NO_PHASE_SHIFT,
|
||||
|
||||
# ADAT clock = 12.288 MHz = 48 kHz * 256
|
||||
p_CLKOUT2_DIVIDE = self.ADAT_DIV_48k,
|
||||
p_CLKOUT2_PHASE = self.NO_PHASE_SHIFT,
|
||||
p_CLKOUT2_DUTY_CYCLE = self.DUTY_CYCLE,
|
||||
|
||||
|
||||
# ADAT sampling clock = 48 kHz * 256 * 8 times oversampling
|
||||
p_CLKOUT0_DIVIDE = self.ADAT_DIV_48k / 8,
|
||||
p_CLKOUT0_PHASE = self.NO_PHASE_SHIFT,
|
||||
p_CLKOUT0_DUTY_CYCLE = self.DUTY_CYCLE,
|
||||
|
||||
# ADAT transmit domain clock = 48 kHz * 256 * 5 output terminals
|
||||
p_CLKOUT3_DIVIDE = self.ADAT_DIV_48k / 5,
|
||||
p_CLKOUT3_PHASE = self.NO_PHASE_SHIFT,
|
||||
p_CLKOUT3_DUTY_CYCLE = self.DUTY_CYCLE,
|
||||
|
||||
i_CLKFBIN = audiopll_feedback,
|
||||
o_CLKFBOUT = audiopll_feedback,
|
||||
i_CLKIN1 = main_clocks[0],
|
||||
o_CLKOUT0 = audio_clocks[2],
|
||||
o_CLKOUT2 = audio_clocks[0],
|
||||
o_CLKOUT3 = audio_clocks[3],
|
||||
o_LOCKED = audio_locked,
|
||||
)
|
||||
|
||||
VCO_SCALER_FAST = 1
|
||||
|
||||
m.submodules.fastpll = Instance("PLLE2_ADV",
|
||||
p_CLKIN1_PERIOD = 10.172,
|
||||
p_BANDWIDTH = "OPTIMIZED",
|
||||
p_COMPENSATION = "ZHOLD",
|
||||
p_STARTUP_WAIT = "FALSE",
|
||||
p_DIVCLK_DIVIDE = 1,
|
||||
p_CLKFBOUT_MULT = VCO_SCALER_FAST * platform.fast_multiplier,
|
||||
p_CLKFBOUT_PHASE = self.NO_PHASE_SHIFT,
|
||||
|
||||
# Fast clock = 48 kHz * 256 * 9
|
||||
p_CLKOUT0_DIVIDE = VCO_SCALER_FAST,
|
||||
p_CLKOUT0_PHASE = self.NO_PHASE_SHIFT,
|
||||
p_CLKOUT0_DUTY_CYCLE = self.DUTY_CYCLE,
|
||||
|
||||
# I2S DAC clock 48k = 3.072 MHz = 48 kHz * 32 bit * 2 channels
|
||||
p_CLKOUT1_DIVIDE = VCO_SCALER_FAST * platform.fast_multiplier * 4,
|
||||
p_CLKOUT1_PHASE = self.NO_PHASE_SHIFT,
|
||||
p_CLKOUT1_DUTY_CYCLE = self.DUTY_CYCLE,
|
||||
|
||||
i_CLKFBIN = fastpll_feedback,
|
||||
o_CLKFBOUT = fastpll_feedback,
|
||||
i_CLKIN1 = audio_clocks[2],
|
||||
o_CLKOUT0 = fast_clock_48k,
|
||||
o_CLKOUT1 = audio_clocks[1],
|
||||
o_LOCKED = fast_locked,
|
||||
)
|
||||
|
||||
|
||||
m.d.comb += [
|
||||
reset.eq(~(sys_locked & audio_locked & fast_locked)),
|
||||
ClockSignal("fast").eq(fast_clock_48k),
|
||||
ClockSignal("usb") .eq(main_clocks[0]),
|
||||
ClockSignal("adat").eq(audio_clocks[0]),
|
||||
ClockSignal("dac").eq(audio_clocks[1]),
|
||||
ClockSignal("sync").eq(audio_clocks[3]),
|
||||
]
|
||||
|
||||
self.wire_up_reset(m, reset)
|
||||
|
||||
return m
|
||||
|
||||
class ColorlightDomainGenerator(Elaboratable, ClockDomainGeneratorBase):
|
||||
""" Clock generator for the Colorlight I5 board. """
|
||||
FastDomainDivider = 7
|
||||
FastClockFreq = 25e6 * 29 / FastDomainDivider
|
||||
|
||||
def __init__(self, clock_frequencies=None):
|
||||
pass
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
# Create our domains.
|
||||
m.domains.sync = ClockDomain("sync")
|
||||
m.domains.usb = ClockDomain("usb")
|
||||
m.domains.fast = ClockDomain("fast")
|
||||
m.domains.adat = ClockDomain("adat")
|
||||
m.domains.dac = ClockDomain("dac")
|
||||
|
||||
|
||||
# Grab our clock and global reset signals.
|
||||
clk25 = flipped(platform.request(platform.default_clk))
|
||||
|
||||
main_clocks = Signal(5)
|
||||
audio_clocks = Signal(4)
|
||||
fast_clock_48k = Signal()
|
||||
|
||||
main_locked = Signal()
|
||||
audio_locked = Signal()
|
||||
fast_locked = Signal()
|
||||
reset = Signal()
|
||||
|
||||
# USB PLL
|
||||
main_feedback = Signal()
|
||||
m.submodules.main_pll = Instance("EHXPLLL",
|
||||
|
||||
# Status.
|
||||
o_LOCK=main_locked,
|
||||
|
||||
# PLL parameters...
|
||||
p_PLLRST_ENA="DISABLED",
|
||||
p_INTFB_WAKE="DISABLED",
|
||||
p_STDBY_ENABLE="DISABLED",
|
||||
p_DPHASE_SOURCE="DISABLED",
|
||||
p_OUTDIVIDER_MUXA="DIVA",
|
||||
p_OUTDIVIDER_MUXB="DIVB",
|
||||
p_OUTDIVIDER_MUXC="DIVC",
|
||||
p_OUTDIVIDER_MUXD="DIVD",
|
||||
|
||||
# 60 MHz
|
||||
p_CLKI_DIV = 5,
|
||||
p_CLKOP_ENABLE = "ENABLED",
|
||||
p_CLKOP_DIV = 10,
|
||||
p_CLKOP_CPHASE = 15,
|
||||
p_CLKOP_FPHASE = 0,
|
||||
|
||||
p_FEEDBK_PATH = "CLKOP",
|
||||
p_CLKFB_DIV = 12,
|
||||
|
||||
# Clock in.
|
||||
i_CLKI=clk25,
|
||||
|
||||
# Internal feedback.
|
||||
i_CLKFB=main_feedback,
|
||||
|
||||
# Control signals.
|
||||
i_RST=reset,
|
||||
i_PHASESEL0=0,
|
||||
i_PHASESEL1=0,
|
||||
i_PHASEDIR=1,
|
||||
i_PHASESTEP=1,
|
||||
i_PHASELOADREG=1,
|
||||
i_STDBY=0,
|
||||
i_PLLWAKESYNC=0,
|
||||
|
||||
# Output Enables.
|
||||
i_ENCLKOP=0,
|
||||
i_ENCLKOS=0,
|
||||
|
||||
# Generated clock outputs.
|
||||
o_CLKOP=main_feedback,
|
||||
|
||||
# Synthesis attributes.
|
||||
a_FREQUENCY_PIN_CLKI="25",
|
||||
a_FREQUENCY_PIN_CLKOP="60",
|
||||
|
||||
a_ICP_CURRENT="6",
|
||||
a_LPF_RESISTOR="16",
|
||||
a_MFG_ENABLE_FILTEROPAMP="1",
|
||||
a_MFG_GMCREF_SEL="2"
|
||||
)
|
||||
|
||||
audio_feedback = Signal()
|
||||
audio_locked = Signal()
|
||||
m.submodules.audio_pll = Instance("EHXPLLL",
|
||||
|
||||
# Status.
|
||||
o_LOCK=audio_locked,
|
||||
|
||||
# PLL parameters...
|
||||
p_PLLRST_ENA="DISABLED",
|
||||
p_INTFB_WAKE="DISABLED",
|
||||
p_STDBY_ENABLE="DISABLED",
|
||||
p_DPHASE_SOURCE="DISABLED",
|
||||
p_OUTDIVIDER_MUXA="DIVA",
|
||||
p_OUTDIVIDER_MUXB="DIVB",
|
||||
p_OUTDIVIDER_MUXC="DIVC",
|
||||
p_OUTDIVIDER_MUXD="DIVD",
|
||||
|
||||
# 25MHz * 29 = 725 MHz PLL freqency
|
||||
p_CLKI_DIV = 1,
|
||||
p_CLKOP_ENABLE = "ENABLED",
|
||||
p_CLKOP_DIV = 29,
|
||||
p_CLKOP_CPHASE = 0,
|
||||
p_CLKOP_FPHASE = 0,
|
||||
|
||||
# 12.288 MHz = 725 MHz / 59
|
||||
p_CLKOS_ENABLE = "ENABLED",
|
||||
p_CLKOS_DIV = 59,
|
||||
p_CLKOS_CPHASE = 0,
|
||||
p_CLKOS_FPHASE = 0,
|
||||
|
||||
# fast domain clock
|
||||
p_CLKOS3_ENABLE = "ENABLED",
|
||||
p_CLKOS3_DIV = self.FastDomainDivider,
|
||||
p_CLKOS3_CPHASE = 0,
|
||||
p_CLKOS3_FPHASE = 0,
|
||||
|
||||
p_FEEDBK_PATH = "CLKOP",
|
||||
p_CLKFB_DIV = 1,
|
||||
|
||||
# Clock in.
|
||||
i_CLKI=clk25,
|
||||
|
||||
# Internal feedback.
|
||||
i_CLKFB=audio_feedback,
|
||||
|
||||
# Control signals.
|
||||
i_RST=reset,
|
||||
i_PHASESEL0=0,
|
||||
i_PHASESEL1=0,
|
||||
i_PHASEDIR=1,
|
||||
i_PHASESTEP=1,
|
||||
i_PHASELOADREG=1,
|
||||
i_STDBY=0,
|
||||
i_PLLWAKESYNC=0,
|
||||
|
||||
# Output Enables.
|
||||
i_ENCLKOP=0,
|
||||
i_ENCLKOS=0,
|
||||
i_ENCLKOS3=0,
|
||||
|
||||
# Generated clock outputs.
|
||||
o_CLKOP=audio_feedback,
|
||||
o_CLKOS=ClockSignal("adat"),
|
||||
o_CLKOS3=ClockSignal("fast"),
|
||||
|
||||
# Synthesis attributes.
|
||||
a_FREQUENCY_PIN_CLKI="25",
|
||||
a_FREQUENCY_PIN_CLKOS="12.288",
|
||||
|
||||
a_ICP_CURRENT="6",
|
||||
a_LPF_RESISTOR="16",
|
||||
a_MFG_ENABLE_FILTEROPAMP="1",
|
||||
a_MFG_GMCREF_SEL="2"
|
||||
)
|
||||
|
||||
debug0 = platform.request("debug", 0)
|
||||
debug1 = platform.request("debug", 1)
|
||||
debug2 = platform.request("debug", 2)
|
||||
debug3 = platform.request("debug", 3)
|
||||
|
||||
m.submodules.bclk_div = clk_div = DomainRenamer("adat")(SimpleClockDivider(4))
|
||||
|
||||
reset = Signal()
|
||||
# Control our resets.
|
||||
m.d.comb += [
|
||||
ClockSignal("usb") .eq(main_feedback),
|
||||
ClockSignal("sync") .eq(ClockSignal("usb")),
|
||||
ClockSignal("dac") .eq(clk_div.clock_out),
|
||||
clk_div.clock_enable_in.eq(1),
|
||||
|
||||
reset.eq(~(main_locked & audio_locked)),
|
||||
|
||||
debug0.eq(ClockSignal("usb")),
|
||||
debug1.eq(ClockSignal("adat")),
|
||||
debug2.eq(ClockSignal("fast")),
|
||||
debug3.eq(reset),
|
||||
|
||||
]
|
||||
|
||||
self.wire_up_reset(m, reset)
|
||||
|
||||
return m
|
||||
153
gateware/channel_stream_combiner.py
Normal file
153
gateware/channel_stream_combiner.py
Normal file
@@ -0,0 +1,153 @@
|
||||
from amaranth import *
|
||||
from amaranth.build import Platform
|
||||
|
||||
from amlib.stream import StreamInterface
|
||||
from amlib.test import GatewareTestCase, sync_test_case
|
||||
|
||||
class ChannelStreamCombiner(Elaboratable):
|
||||
SAMPLE_WIDTH = 24
|
||||
|
||||
def __init__(self, no_lower_channels, no_upper_channels):
|
||||
self.no_lower_channels = no_lower_channels
|
||||
self.no_upper_channels = no_upper_channels
|
||||
self.lower_channel_bits = Shape.cast(range(no_lower_channels)).width
|
||||
self.upper_channel_bits = Shape.cast(range(no_upper_channels)).width
|
||||
self.combined_channel_bits = Shape.cast(range(no_lower_channels + no_upper_channels)).width
|
||||
|
||||
self.lower_channel_stream_in = StreamInterface(name="lower_channels",
|
||||
payload_width=self.SAMPLE_WIDTH,
|
||||
extra_fields=[("channel_nr", self.lower_channel_bits)])
|
||||
|
||||
self.upper_channels_active_in = Signal()
|
||||
self.upper_channel_stream_in = StreamInterface(name="upper_channels",
|
||||
payload_width=self.SAMPLE_WIDTH,
|
||||
extra_fields=[("channel_nr", self.lower_channel_bits)])
|
||||
|
||||
self.combined_channel_stream_out = StreamInterface(name="combined_channels",
|
||||
payload_width=self.SAMPLE_WIDTH,
|
||||
extra_fields=[("channel_nr", self.combined_channel_bits)])
|
||||
|
||||
# debug signals
|
||||
self.state = Signal(range(3))
|
||||
self.upper_channel_counter = Signal(self.upper_channel_bits)
|
||||
|
||||
# debug signals
|
||||
self.state = Signal(range(3))
|
||||
|
||||
def elaborate(self, platform: Platform) -> Module:
|
||||
m = Module()
|
||||
|
||||
upper_channel_counter = Signal(self.upper_channel_bits)
|
||||
m.d.comb += self.upper_channel_counter.eq(upper_channel_counter)
|
||||
|
||||
with m.FSM() as fsm:
|
||||
m.d.comb += self.state.eq(fsm.state)
|
||||
|
||||
with m.State("LOWER_CHANNELS"):
|
||||
with m.If( self.combined_channel_stream_out.ready
|
||||
& self.lower_channel_stream_in.valid):
|
||||
m.d.comb += [
|
||||
self.combined_channel_stream_out.payload.eq(self.lower_channel_stream_in.payload),
|
||||
self.combined_channel_stream_out.channel_nr.eq(self.lower_channel_stream_in.channel_nr),
|
||||
self.lower_channel_stream_in.ready.eq(1),
|
||||
self.combined_channel_stream_out.valid.eq(1),
|
||||
self.combined_channel_stream_out.first.eq(self.lower_channel_stream_in.first),
|
||||
self.combined_channel_stream_out.last.eq(0),
|
||||
]
|
||||
|
||||
with m.If(self.lower_channel_stream_in.last):
|
||||
m.d.sync += upper_channel_counter.eq(0)
|
||||
with m.If(self.upper_channels_active_in):
|
||||
m.next = "UPPER_CHANNELS"
|
||||
with m.Else():
|
||||
m.next = "FILL_UPPER"
|
||||
|
||||
with m.State("UPPER_CHANNELS"):
|
||||
with m.If(self.combined_channel_stream_out.ready):
|
||||
with m.If(self.upper_channels_active_in):
|
||||
with m.If(self.upper_channel_stream_in.valid):
|
||||
m.d.comb += [
|
||||
self.combined_channel_stream_out.payload.eq(self.upper_channel_stream_in.payload),
|
||||
self.combined_channel_stream_out.channel_nr.eq(\
|
||||
self.no_lower_channels +
|
||||
self.upper_channel_stream_in.channel_nr),
|
||||
self.upper_channel_stream_in.ready.eq(1),
|
||||
self.combined_channel_stream_out.valid.eq(1),
|
||||
self.combined_channel_stream_out.first.eq(0),
|
||||
self.combined_channel_stream_out.last.eq(self.upper_channel_stream_in.last),
|
||||
]
|
||||
|
||||
with m.If(self.upper_channel_stream_in.last):
|
||||
with m.If(self.upper_channel_stream_in.channel_nr == 1):
|
||||
m.d.sync += upper_channel_counter.eq(2)
|
||||
m.next = "FILL_UPPER"
|
||||
with m.Else():
|
||||
m.next = "LOWER_CHANNELS"
|
||||
|
||||
# USB is not active: Fill in zeros
|
||||
with m.Else():
|
||||
m.next = "FILL_UPPER"
|
||||
|
||||
with m.State("FILL_UPPER"):
|
||||
with m.If(self.combined_channel_stream_out.ready):
|
||||
with m.If( ~self.upper_channels_active_in
|
||||
& self.upper_channel_stream_in.valid):
|
||||
# we just drain all stale data from the upstream FIFOs
|
||||
# if the upper channels are not active
|
||||
m.d.comb += self.upper_channel_stream_in.ready.eq(1)
|
||||
|
||||
m.d.sync += upper_channel_counter.eq(upper_channel_counter + 1)
|
||||
m.d.comb += [
|
||||
self.combined_channel_stream_out.payload.eq(0),
|
||||
self.combined_channel_stream_out.channel_nr.eq(\
|
||||
self.no_lower_channels +
|
||||
upper_channel_counter),
|
||||
self.combined_channel_stream_out.valid.eq(1),
|
||||
self.combined_channel_stream_out.first.eq(0),
|
||||
self.combined_channel_stream_out.last.eq(0),
|
||||
]
|
||||
|
||||
last_channel = upper_channel_counter >= (self.no_upper_channels - 1)
|
||||
with m.If(last_channel):
|
||||
m.d.comb += self.combined_channel_stream_out.last.eq(1)
|
||||
m.next = "LOWER_CHANNELS"
|
||||
|
||||
return m
|
||||
|
||||
class ChannelStreamCombinerTest(GatewareTestCase):
|
||||
FRAGMENT_UNDER_TEST = ChannelStreamCombiner
|
||||
FRAGMENT_ARGUMENTS = dict(no_lower_channels=32, no_upper_channels=6)
|
||||
|
||||
def send_lower_frame(self, sample: int, channel: int, wait=False):
|
||||
yield self.dut.lower_channel_stream_in.channel_nr.eq(channel)
|
||||
yield self.dut.lower_channel_stream_in.payload.eq(sample)
|
||||
yield self.dut.lower_channel_stream_in.valid.eq(1)
|
||||
yield self.dut.lower_channel_stream_in.first.eq(channel == 0)
|
||||
yield self.dut.lower_channel_stream_in.last.eq(channel == 31)
|
||||
yield
|
||||
yield self.dut.lower_channel_stream_in.valid.eq(0)
|
||||
if wait:
|
||||
yield
|
||||
|
||||
def send_upper_frame(self, sample: int, channel: int, last_channel: int=6, wait=False):
|
||||
yield self.dut.upper_channel_stream_in.channel_nr.eq(channel)
|
||||
yield self.dut.upper_channel_stream_in.payload.eq(sample)
|
||||
yield self.dut.upper_channel_stream_in.valid.eq(1)
|
||||
yield self.dut.upper_channel_stream_in.first.eq(channel == 0)
|
||||
yield self.dut.upper_channel_stream_in.last.eq(channel == last_channel)
|
||||
yield
|
||||
yield self.dut.upper_channel_stream_in.valid.eq(0)
|
||||
if wait:
|
||||
yield
|
||||
|
||||
@sync_test_case
|
||||
def test_smoke(self):
|
||||
dut = self.dut
|
||||
yield
|
||||
yield dut.combined_channel_stream_out.ready.eq(1)
|
||||
for channel in range(32):
|
||||
yield from self.send_lower_frame(channel, channel)
|
||||
yield from self.advance_cycles(13)
|
||||
for channel in range(32):
|
||||
yield from self.send_lower_frame(channel, channel)
|
||||
yield from self.advance_cycles(13)
|
||||
52
gateware/channel_stream_splitter-bench.gtkw
Normal file
52
gateware/channel_stream_splitter-bench.gtkw
Normal file
@@ -0,0 +1,52 @@
|
||||
[*]
|
||||
[*] GTKWave Analyzer v3.4.0 (w)1999-2022 BSI
|
||||
[*] Thu Jan 6 02:29:19 2022
|
||||
[*]
|
||||
[dumpfile] "test_ChannelStreamSplitterTest_test_smoke.vcd"
|
||||
[dumpfile_mtime] "Thu Jan 6 02:28:21 2022"
|
||||
[dumpfile_size] 5390
|
||||
[savefile] "gateware/channel_stream_splitter-bench.gtkw"
|
||||
[timestart] 0
|
||||
[size] 3828 2090
|
||||
[pos] -1 -1
|
||||
*-16.000000 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
|
||||
[treeopen] bench.
|
||||
[sst_width] 65
|
||||
[signals_width] 540
|
||||
[sst_expanded] 0
|
||||
[sst_vpaned_height] 658
|
||||
@28
|
||||
bench.top.clk
|
||||
@200
|
||||
-Input
|
||||
@22
|
||||
bench.top.combined_channels__channel_nr[5:0]
|
||||
bench.top.combined_channels__payload[23:0]
|
||||
@28
|
||||
bench.top.combined_channels__first
|
||||
bench.top.combined_channels__last
|
||||
bench.top.combined_channels__ready
|
||||
bench.top.combined_channels__valid
|
||||
@200
|
||||
-Output 1
|
||||
@22
|
||||
bench.top.lower_channels__channel_nr[4:0]
|
||||
bench.top.lower_channels__payload[23:0]
|
||||
@28
|
||||
bench.top.lower_channels__first
|
||||
bench.top.lower_channels__last
|
||||
bench.top.lower_channels__ready
|
||||
bench.top.lower_channels__valid
|
||||
@200
|
||||
-Output 2
|
||||
@22
|
||||
bench.top.upper_channels__channel_nr[4:0]
|
||||
@23
|
||||
bench.top.upper_channels__payload[23:0]
|
||||
@28
|
||||
bench.top.upper_channels__first
|
||||
bench.top.upper_channels__last
|
||||
bench.top.upper_channels__ready
|
||||
bench.top.upper_channels__valid
|
||||
[pattern_trace] 1
|
||||
[pattern_trace] 0
|
||||
111
gateware/channel_stream_splitter.py
Normal file
111
gateware/channel_stream_splitter.py
Normal file
@@ -0,0 +1,111 @@
|
||||
from amaranth import *
|
||||
from amaranth.build import Platform
|
||||
|
||||
from amlib.stream import StreamInterface
|
||||
from amlib.test import GatewareTestCase, sync_test_case
|
||||
|
||||
class ChannelStreamSplitter(Elaboratable):
|
||||
SAMPLE_WIDTH = 24
|
||||
|
||||
def __init__(self, no_lower_channels, no_upper_channels, test=False):
|
||||
self.test = test
|
||||
self.no_lower_channels = no_lower_channels
|
||||
self.no_upper_channels = no_upper_channels
|
||||
self.lower_channel_bits = Shape.cast(range(no_lower_channels)).width
|
||||
self.upper_channel_bits = Shape.cast(range(no_upper_channels)).width
|
||||
self.combined_channel_bits = Shape.cast(range(no_lower_channels + no_upper_channels)).width
|
||||
|
||||
self.lower_channel_stream_out = StreamInterface(name="lower_channels",
|
||||
payload_width=self.SAMPLE_WIDTH,
|
||||
extra_fields=[("channel_nr", self.lower_channel_bits)])
|
||||
|
||||
self.upper_channel_stream_out = StreamInterface(name="upper_channels",
|
||||
payload_width=self.SAMPLE_WIDTH,
|
||||
extra_fields=[("channel_nr", self.lower_channel_bits)])
|
||||
|
||||
self.combined_channel_stream_in = StreamInterface(name="combined_channels",
|
||||
payload_width=self.SAMPLE_WIDTH,
|
||||
extra_fields=[("channel_nr", self.combined_channel_bits)])
|
||||
|
||||
# debug signals
|
||||
|
||||
def elaborate(self, platform: Platform) -> Module:
|
||||
m = Module()
|
||||
|
||||
if (self.test):
|
||||
dummy = Signal()
|
||||
m.d.sync += dummy.eq(1)
|
||||
|
||||
input_stream = self.combined_channel_stream_in
|
||||
|
||||
m.d.comb += [
|
||||
input_stream.ready.eq(self.lower_channel_stream_out.ready & self.upper_channel_stream_out.ready),
|
||||
]
|
||||
|
||||
with m.If(input_stream.valid & input_stream.ready):
|
||||
with m.If(input_stream.channel_nr < self.no_lower_channels):
|
||||
m.d.comb += [
|
||||
self.lower_channel_stream_out.payload.eq(input_stream.payload),
|
||||
self.lower_channel_stream_out.channel_nr.eq(input_stream.channel_nr),
|
||||
self.lower_channel_stream_out.first.eq(input_stream.first),
|
||||
self.lower_channel_stream_out.last.eq(input_stream.channel_nr == (self.no_lower_channels - 1)),
|
||||
self.lower_channel_stream_out.valid.eq(1),
|
||||
]
|
||||
with m.Else():
|
||||
m.d.comb += [
|
||||
self.upper_channel_stream_out.payload.eq(input_stream.payload),
|
||||
self.upper_channel_stream_out.channel_nr.eq(input_stream.channel_nr - self.no_lower_channels),
|
||||
self.upper_channel_stream_out.first.eq(input_stream.channel_nr == self.no_lower_channels),
|
||||
self.upper_channel_stream_out.last.eq(input_stream.last),
|
||||
self.upper_channel_stream_out.valid.eq(1),
|
||||
]
|
||||
|
||||
return m
|
||||
|
||||
class ChannelStreamSplitterTest(GatewareTestCase):
|
||||
FRAGMENT_UNDER_TEST = ChannelStreamSplitter
|
||||
FRAGMENT_ARGUMENTS = dict(no_lower_channels=32, no_upper_channels=6, test=True)
|
||||
|
||||
def send_frame(self, sample: int, channel: int, wait=False):
|
||||
yield self.dut.combined_channel_stream_in.channel_nr.eq(channel)
|
||||
yield self.dut.combined_channel_stream_in.payload.eq(sample)
|
||||
yield self.dut.combined_channel_stream_in.valid.eq(1)
|
||||
yield self.dut.combined_channel_stream_in.first.eq(channel == 0)
|
||||
yield self.dut.combined_channel_stream_in.last.eq(channel == 35)
|
||||
yield
|
||||
yield self.dut.combined_channel_stream_in.valid.eq(0)
|
||||
if wait:
|
||||
yield
|
||||
|
||||
@sync_test_case
|
||||
def test_smoke(self):
|
||||
dut = self.dut
|
||||
yield
|
||||
channels = list(range(36))
|
||||
yield from self.advance_cycles(3)
|
||||
|
||||
yield self.dut.lower_channel_stream_out.ready.eq(1)
|
||||
yield self.dut.upper_channel_stream_out.ready.eq(1)
|
||||
|
||||
for channel in channels:
|
||||
yield from self.send_frame(channel, channel)
|
||||
yield from self.advance_cycles(3)
|
||||
|
||||
|
||||
for channel in channels[:20]:
|
||||
yield from self.send_frame(channel, channel)
|
||||
yield self.dut.lower_channel_stream_out.ready.eq(0)
|
||||
yield from self.send_frame(channels[20], channels[20])
|
||||
yield self.dut.lower_channel_stream_out.ready.eq(1)
|
||||
for channel in channels[20:32]:
|
||||
yield from self.send_frame(channel, channel)
|
||||
yield
|
||||
for channel in channels[32:34]:
|
||||
yield from self.send_frame(channel, channel)
|
||||
yield self.dut.upper_channel_stream_out.ready.eq(0)
|
||||
yield from self.send_frame(channels[34], channels[34])
|
||||
yield self.dut.upper_channel_stream_out.ready.eq(1)
|
||||
for channel in channels[34:]:
|
||||
yield from self.send_frame(channel, channel)
|
||||
yield
|
||||
|
||||
63
gateware/channels_to_usb_stream.gtkw
Normal file
63
gateware/channels_to_usb_stream.gtkw
Normal file
@@ -0,0 +1,63 @@
|
||||
[*]
|
||||
[*] GTKWave Analyzer v3.4.0 (w)1999-2022 BSI
|
||||
[*] Wed Jan 19 00:02:36 2022
|
||||
[*]
|
||||
[dumpfile] "test_ChannelsToUSBStreamTest_test_smoke.vcd"
|
||||
[dumpfile_mtime] "Tue Jan 18 23:57:47 2022"
|
||||
[dumpfile_size] 44363
|
||||
[savefile] "channels_to_usb_stream.gtkw"
|
||||
[timestart] 0
|
||||
[size] 3828 2090
|
||||
[pos] -1921 27
|
||||
*-16.270115 48200 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
|
||||
[treeopen] bench.
|
||||
[treeopen] bench.top.
|
||||
[treeopen] bench.top.out_fifo.
|
||||
[sst_width] 447
|
||||
[signals_width] 495
|
||||
[sst_expanded] 1
|
||||
[sst_vpaned_height] 650
|
||||
@28
|
||||
bench.top.clk
|
||||
bench.top.channel_stream__ready
|
||||
bench.top.channel_stream__valid
|
||||
@22
|
||||
bench.top.channel_stream__payload[23:0]
|
||||
@24
|
||||
bench.top.channel_stream__channel_nr[2:0]
|
||||
@22
|
||||
bench.top.no_channels_in[3:0]
|
||||
@28
|
||||
bench.top.data_requested_in
|
||||
@200
|
||||
-Channels To USB
|
||||
@28
|
||||
bench.top.current_byte[1:0]
|
||||
bench.top.current_channel[2:0]
|
||||
@22
|
||||
bench.top.current_sample[31:0]
|
||||
@28
|
||||
bench.top.usb_byte_pos[1:0]
|
||||
bench.top.feeder_state
|
||||
@200
|
||||
-Output FIFO
|
||||
@28
|
||||
bench.top.fifo_full
|
||||
bench.top.fifo_level_insufficient
|
||||
bench.top.fifo_level_sufficient
|
||||
bench.top.fifo_postprocess_state
|
||||
@200
|
||||
-USB Stream
|
||||
@28
|
||||
bench.top.skipping
|
||||
bench.top.filling
|
||||
bench.top.usb_byte_pos[1:0]
|
||||
bench.top.usb_channel[2:0]
|
||||
@22
|
||||
bench.top.usb_stream__payload[7:0]
|
||||
@29
|
||||
bench.top.usb_stream__valid
|
||||
@28
|
||||
bench.top.usb_stream__ready
|
||||
[pattern_trace] 1
|
||||
[pattern_trace] 0
|
||||
293
gateware/channels_to_usb_stream.py
Normal file
293
gateware/channels_to_usb_stream.py
Normal 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
|
||||
587
gateware/debug.py
Normal file
587
gateware/debug.py
Normal file
@@ -0,0 +1,587 @@
|
||||
from amaranth import *
|
||||
from amaranth.lib.cdc import FFSynchronizer, PulseSynchronizer
|
||||
|
||||
from amlib.debug.ila import StreamILA, ILACoreParameters
|
||||
from amlib.stream import StreamInterface
|
||||
from amlib.io.led import NumberToBitBar
|
||||
from amlib.io.max7219 import SerialLEDArray
|
||||
|
||||
from luna.gateware.usb.usb2.endpoints.stream import USBMultibyteStreamInEndpoint
|
||||
|
||||
def add_debug_led_array(v):
|
||||
self = v['self']
|
||||
m = v['m']
|
||||
platform = v['platform']
|
||||
channels_to_usb1_stream = v['channels_to_usb1_stream']
|
||||
input_to_usb_fifo = v['input_to_usb_fifo']
|
||||
usb1_to_output_fifo_level = v['usb1_to_output_fifo_level']
|
||||
usb1_to_output_fifo_depth = v['usb1_to_output_fifo_depth']
|
||||
usb2_to_usb1_fifo_level = v['usb2_to_usb1_fifo_level']
|
||||
usb2_to_usb1_fifo_depth = v['usb2_to_usb1_fifo_depth']
|
||||
channels_to_usb2_stream = v['channels_to_usb2_stream']
|
||||
usb2_audio_out_active = v['usb2_audio_out_active']
|
||||
usb2_audio_in_active = v['usb2_audio_in_active']
|
||||
bundle_multiplexer = v['bundle_multiplexer']
|
||||
adat_transmitters = v['adat_transmitters']
|
||||
usb1_to_usb2_midi_fifo = v['usb1_to_usb2_midi_fifo']
|
||||
usb2_to_usb1_midi_fifo = v['usb2_to_usb1_midi_fifo']
|
||||
usb_midi_fifo_depth = v['usb_midi_fifo_depth']
|
||||
|
||||
|
||||
adat1_underflow_count = Signal(16)
|
||||
with m.If(adat_transmitters[0].underflow_out):
|
||||
m.d.sync += adat1_underflow_count.eq(adat1_underflow_count + 1)
|
||||
|
||||
spi = platform.request("spi")
|
||||
m.submodules.led_display = led_display = SerialLEDArray(divisor=10, init_delay=24e6, no_modules=2)
|
||||
|
||||
rx_level_bars = []
|
||||
for i in range(1, 5):
|
||||
rx_level_bar = NumberToBitBar(0, bundle_multiplexer.FIFO_DEPTH, 8)
|
||||
setattr(m.submodules, f"rx{i}_level_bar", rx_level_bar)
|
||||
m.d.comb += rx_level_bar.value_in.eq(bundle_multiplexer.levels[i - 1])
|
||||
rx_level_bars.append(rx_level_bar)
|
||||
|
||||
m.submodules.in_bar = in_to_usb_fifo_bar = NumberToBitBar(0, self.INPUT_CDC_FIFO_DEPTH, 8)
|
||||
m.submodules.in_fifo_bar = channels_to_usb_bar = NumberToBitBar(0, 2 * self.USB1_MAX_PACKET_SIZE, 8)
|
||||
m.submodules.out_fifo_bar = out_fifo_bar = NumberToBitBar(0, usb1_to_output_fifo_depth, 8)
|
||||
|
||||
m.d.comb += [
|
||||
# LED bar displays
|
||||
in_to_usb_fifo_bar.value_in.eq(input_to_usb_fifo.r_level),
|
||||
channels_to_usb_bar.value_in.eq(channels_to_usb1_stream.level >> 3),
|
||||
out_fifo_bar.value_in.eq(usb1_to_output_fifo_level >> 1),
|
||||
|
||||
*[led_display.digits_in[i].eq(Cat(reversed(rx_level_bars[i].bitbar_out))) for i in range(4)],
|
||||
led_display.digits_in[4].eq(Cat(reversed(in_to_usb_fifo_bar.bitbar_out))),
|
||||
led_display.digits_in[5].eq(Cat(reversed(channels_to_usb_bar.bitbar_out))),
|
||||
led_display.digits_in[6].eq(Cat(reversed(out_fifo_bar.bitbar_out))),
|
||||
led_display.digits_in[7].eq(adat1_underflow_count),
|
||||
]
|
||||
|
||||
usb2 = lambda x: 8 + x
|
||||
|
||||
m.submodules.usb2_output_fifo_bar = usb2_output_fifo_bar = NumberToBitBar(0, usb2_to_usb1_fifo_depth, 8)
|
||||
m.submodules.usb2_input_fifo_bar = usb2_input_fifo_bar = NumberToBitBar(0, channels_to_usb2_stream._fifo_depth, 8)
|
||||
m.submodules.usb2_to_usb1_bar = usb2_to_usb1_bar = NumberToBitBar(0, usb_midi_fifo_depth, 8)
|
||||
m.submodules.usb1_to_usb2_bar = usb1_to_usb2_bar = NumberToBitBar(0, usb_midi_fifo_depth, 8)
|
||||
|
||||
m.d.comb += [
|
||||
usb2_output_fifo_bar.value_in.eq(usb2_to_usb1_fifo_level),
|
||||
usb2_input_fifo_bar.value_in.eq(channels_to_usb2_stream.level),
|
||||
led_display.digits_in[usb2(0)][0].eq(usb2_audio_out_active),
|
||||
led_display.digits_in[usb2(0)][7].eq(usb2_audio_in_active),
|
||||
led_display.digits_in[usb2(1)].eq(Cat(usb2_output_fifo_bar.bitbar_out)),
|
||||
led_display.digits_in[usb2(2)].eq(Cat(reversed(usb2_input_fifo_bar.bitbar_out))),
|
||||
led_display.digits_in[usb2(3)].eq(usb2_to_usb1_bar.bitbar_out),
|
||||
led_display.digits_in[usb2(4)].eq(usb1_to_usb2_bar.bitbar_out),
|
||||
]
|
||||
|
||||
m.d.comb += [
|
||||
*led_display.connect_to_resource(spi),
|
||||
led_display.valid_in.eq(1),
|
||||
]
|
||||
|
||||
|
||||
def setup_ila(v, ila_max_packet_size, use_convolution):
|
||||
examined_usb = "usb1"
|
||||
m = v['m']
|
||||
usb1_sof_counter = v['usb1_sof_counter']
|
||||
usb1 = v['usb1']
|
||||
|
||||
usb1_ep3_in = v['usb1_ep3_in']
|
||||
usb1_ep3_out = v['usb1_ep3_out']
|
||||
usb2_ep3_in = v['usb2_ep3_in']
|
||||
usb2_ep3_out = v['usb2_ep3_out']
|
||||
ep1_out = v[f'{examined_usb}_ep1_out']
|
||||
ep2_in = v[f'{examined_usb}_ep2_in']
|
||||
channels_to_usb_stream = v[f'channels_to_{examined_usb}_stream']
|
||||
usb_to_channel_stream = v[f'{examined_usb}_to_channel_stream']
|
||||
|
||||
usb1_audio_in_active = v['usb1_audio_in_active']
|
||||
usb2_audio_out_active = v['usb2_audio_out_active']
|
||||
|
||||
input_to_usb_fifo = v['input_to_usb_fifo']
|
||||
usb1_to_output_fifo = v['usb1_to_output_fifo']
|
||||
usb1_to_output_fifo_level = v['usb1_to_output_fifo_level']
|
||||
usb1_to_output_fifo_depth = v['usb1_to_output_fifo_depth']
|
||||
|
||||
audio_in_frame_bytes = v['usb2_audio_in_frame_bytes']
|
||||
min_fifo_level = v['min_fifo_level']
|
||||
max_fifo_level = v['max_fifo_level']
|
||||
adat_transmitters = v['adat_transmitters']
|
||||
adat_receivers = v['adat_receivers']
|
||||
bundle_demultiplexer = v['bundle_demultiplexer']
|
||||
bundle_multiplexer = v['bundle_multiplexer']
|
||||
usb1_channel_stream_combiner = v['usb1_channel_stream_combiner']
|
||||
usb1_channel_stream_splitter = v['usb1_channel_stream_splitter']
|
||||
|
||||
usb1_to_usb2_midi_fifo = v['usb1_to_usb2_midi_fifo']
|
||||
usb2_to_usb1_midi_fifo = v['usb2_to_usb1_midi_fifo']
|
||||
|
||||
dac1_extractor = v['dac1_extractor']
|
||||
dac1 = v['dac1']
|
||||
convolver = v['convolver']
|
||||
enable_convolver = v['enable_convolver']
|
||||
|
||||
adat_clock = Signal()
|
||||
m.d.comb += adat_clock.eq(ClockSignal("adat"))
|
||||
sof_wrap = Signal()
|
||||
m.d.comb += sof_wrap.eq(usb1_sof_counter == 0)
|
||||
|
||||
usb_packet_counter = Signal(10)
|
||||
with m.If(ep1_out.stream.valid & ep1_out.stream.ready):
|
||||
m.d.usb += usb_packet_counter.eq(usb_packet_counter + 1)
|
||||
with m.If(ep1_out.stream.last):
|
||||
m.d.usb += usb_packet_counter.eq(0)
|
||||
|
||||
weird_packet = Signal()
|
||||
m.d.comb += weird_packet.eq(ep1_out.stream.last & (
|
||||
usb_packet_counter[0:2] != Const(0b11, 2)
|
||||
))
|
||||
|
||||
strange_input = Signal()
|
||||
input_active = Signal()
|
||||
output_active = Signal()
|
||||
input_or_output_active = Signal()
|
||||
garbage = Signal()
|
||||
usb_frame_borders = Signal()
|
||||
|
||||
m.d.comb += [
|
||||
input_active.eq ( channels_to_usb_stream.channel_stream_in.ready
|
||||
& channels_to_usb_stream.channel_stream_in.valid),
|
||||
output_active.eq( channels_to_usb_stream.usb_stream_out.ready
|
||||
& channels_to_usb_stream.usb_stream_out.valid),
|
||||
input_or_output_active.eq(input_active | output_active),
|
||||
|
||||
strange_input.eq( (channels_to_usb_stream.channel_stream_in.payload != 0)
|
||||
& (channels_to_usb_stream.channel_stream_in.channel_nr > 1)),
|
||||
garbage.eq(channels_to_usb_stream.skipping | channels_to_usb_stream.filling),
|
||||
usb_frame_borders.eq(ep2_in.data_requested | ep2_in.frame_finished),
|
||||
]
|
||||
|
||||
fill_count = Signal(16)
|
||||
with m.If(channels_to_usb_stream.filling):
|
||||
m.d.usb += fill_count.eq(fill_count + 1)
|
||||
|
||||
channels_to_usb_input_frame = [
|
||||
usb1.sof_detected,
|
||||
input_to_usb_fifo.r_level,
|
||||
channels_to_usb_stream.channel_stream_in.channel_nr,
|
||||
channels_to_usb_stream.channel_stream_in.first,
|
||||
channels_to_usb_stream.channel_stream_in.last,
|
||||
input_active,
|
||||
#channels_to_usb_stream.channel_stream_in.payload,
|
||||
]
|
||||
|
||||
weird_frame_size = Signal()
|
||||
usb_outputting = Signal()
|
||||
m.d.comb += usb_outputting.eq(ep1_out.stream.valid & ep1_out.stream.ready)
|
||||
|
||||
usb_out_level_maxed = Signal()
|
||||
m.d.comb += usb_out_level_maxed.eq(usb1_to_output_fifo_level >= (usb1_to_output_fifo_depth - 1))
|
||||
|
||||
m.d.comb += weird_frame_size.eq((audio_in_frame_bytes & 0b11) != 0)
|
||||
|
||||
channels_to_usb_debug = [
|
||||
usb2_audio_out_active,
|
||||
audio_in_frame_bytes,
|
||||
channels_to_usb_stream.current_channel,
|
||||
channels_to_usb_stream.channel_stream_in.ready,
|
||||
channels_to_usb_stream.level,
|
||||
channels_to_usb_stream.fifo_full,
|
||||
channels_to_usb_stream.fifo_level_insufficient,
|
||||
channels_to_usb_stream.out_channel,
|
||||
channels_to_usb_stream.fifo_read,
|
||||
channels_to_usb_stream.usb_channel,
|
||||
channels_to_usb_stream.done,
|
||||
channels_to_usb_stream.usb_byte_pos,
|
||||
channels_to_usb_stream.skipping,
|
||||
channels_to_usb_stream.filling,
|
||||
ep2_in.data_requested,
|
||||
ep2_in.frame_finished,
|
||||
channels_to_usb_stream.usb_stream_out.valid,
|
||||
channels_to_usb_stream.usb_stream_out.ready,
|
||||
channels_to_usb_stream.usb_stream_out.first,
|
||||
channels_to_usb_stream.usb_stream_out.last,
|
||||
channels_to_usb_stream.usb_stream_out.payload,
|
||||
]
|
||||
|
||||
usb_out_debug = [
|
||||
usb_to_channel_stream.channel_stream_out.payload,
|
||||
usb_to_channel_stream.channel_stream_out.channel_nr,
|
||||
usb_to_channel_stream.channel_stream_out.first,
|
||||
usb_to_channel_stream.channel_stream_out.last,
|
||||
#usb1_to_output_fifo_level,
|
||||
#usb_out_level_maxed
|
||||
]
|
||||
|
||||
usb_channel_outputting = Signal()
|
||||
m.d.comb += usb_channel_outputting.eq(
|
||||
usb_out_level_maxed |
|
||||
usb_to_channel_stream.channel_stream_out.first |
|
||||
usb_to_channel_stream.channel_stream_out.last |
|
||||
( usb_to_channel_stream.channel_stream_out.ready &
|
||||
usb_to_channel_stream.channel_stream_out.valid)
|
||||
)
|
||||
|
||||
ep1_out_fifo_debug = [
|
||||
audio_in_frame_bytes,
|
||||
min_fifo_level,
|
||||
usb1_to_output_fifo_level,
|
||||
max_fifo_level,
|
||||
usb1.sof_detected,
|
||||
]
|
||||
|
||||
adat_nr = 0
|
||||
receiver_debug = [
|
||||
adat_receivers[adat_nr].sample_out,
|
||||
adat_receivers[adat_nr].addr_out,
|
||||
adat_receivers[adat_nr].output_enable,
|
||||
#adat_receivers[adat_nr].recovered_clock_out,
|
||||
]
|
||||
|
||||
adat_first = Signal()
|
||||
m.d.comb += adat_first.eq(adat_receivers[adat_nr].output_enable & (adat_receivers[adat_nr].addr_out == 0))
|
||||
adat_clock = Signal()
|
||||
m.d.comb += adat_clock.eq(ClockSignal("adat"))
|
||||
|
||||
adat_debug = [
|
||||
adat_clock,
|
||||
adat_transmitters[adat_nr].adat_out,
|
||||
adat_receivers[adat_nr].recovered_clock_out,
|
||||
adat_receivers[adat_nr].adat_in,
|
||||
adat_first,
|
||||
adat_receivers[adat_nr].output_enable,
|
||||
]
|
||||
|
||||
adat_transmitter_debug = [
|
||||
adat_clock,
|
||||
bundle_demultiplexer.bundles_out[adat_nr].channel_nr,
|
||||
adat_transmitters[adat_nr].sample_in,
|
||||
adat_transmitters[adat_nr].valid_in,
|
||||
adat_transmitters[adat_nr].last_in,
|
||||
adat_transmitters[adat_nr].ready_out,
|
||||
adat_transmitters[adat_nr].fifo_level_out,
|
||||
adat_transmitters[adat_nr].underflow_out,
|
||||
adat_transmitters[adat_nr].adat_out,
|
||||
]
|
||||
|
||||
bundle0_active = Signal()
|
||||
bundle3_active = Signal()
|
||||
bundle_multiplexer_active = Signal()
|
||||
multiplexer_enable = Signal()
|
||||
|
||||
m.d.comb += [
|
||||
bundle0_active.eq((bundle_multiplexer.bundles_in[0].valid &
|
||||
bundle_multiplexer.bundles_in[0].ready)),
|
||||
bundle3_active.eq((bundle_multiplexer.bundles_in[3].valid &
|
||||
bundle_multiplexer.bundles_in[3].ready)),
|
||||
bundle_multiplexer_active.eq((bundle_multiplexer.channel_stream_out.valid &
|
||||
bundle_multiplexer.channel_stream_out.ready)),
|
||||
multiplexer_enable.eq(bundle0_active | bundle3_active | bundle_multiplexer_active),
|
||||
]
|
||||
|
||||
multiplexer_debug = [
|
||||
bundle_multiplexer.current_bundle,
|
||||
bundle_multiplexer.last_bundle,
|
||||
bundle0_active,
|
||||
#bundle_multiplexer.bundles_in[0].payload,
|
||||
bundle_multiplexer.bundles_in[0].channel_nr,
|
||||
bundle_multiplexer.bundles_in[0].last,
|
||||
bundle3_active,
|
||||
#bundle_multiplexer.bundles_in[3].payload,
|
||||
bundle_multiplexer.bundles_in[3].channel_nr,
|
||||
bundle_multiplexer.bundles_in[3].last,
|
||||
#bundle_multiplexer.channel_stream_out.payload,
|
||||
bundle_multiplexer_active,
|
||||
bundle_multiplexer.channel_stream_out.channel_nr,
|
||||
bundle_multiplexer.channel_stream_out.last,
|
||||
input_to_usb_fifo.w_level,
|
||||
]
|
||||
|
||||
demultiplexer_debug = [
|
||||
bundle_demultiplexer.channel_stream_in.ready,
|
||||
bundle_demultiplexer.channel_stream_in.valid,
|
||||
bundle_demultiplexer.channel_stream_in.channel_nr,
|
||||
#bundle_demultiplexer.channel_stream_in.payload,
|
||||
*[bundle_demultiplexer.bundles_out[i].ready for i in range(4)],
|
||||
*[bundle_demultiplexer.bundles_out[i].valid for i in range(4)],
|
||||
*[bundle_demultiplexer.bundles_out[i].channel_nr for i in range(4)],
|
||||
]
|
||||
|
||||
demultiplexer_enable = Signal()
|
||||
m.d.comb += demultiplexer_enable.eq(
|
||||
(bundle_demultiplexer.bundles_out[0].valid &
|
||||
bundle_demultiplexer.bundles_out[0].ready) |
|
||||
(bundle_demultiplexer.bundles_out[3].valid &
|
||||
bundle_demultiplexer.bundles_out[3].ready) |
|
||||
(bundle_demultiplexer.channel_stream_in.valid &
|
||||
bundle_demultiplexer.channel_stream_in.ready)
|
||||
)
|
||||
|
||||
levels = [
|
||||
input_to_usb_fifo.r_level,
|
||||
channels_to_usb_stream.level,
|
||||
]
|
||||
|
||||
adat_transmit_count = Signal(8)
|
||||
adat_transmit_frames = Signal.like(adat_transmit_count)
|
||||
adat_receiver0_count = Signal.like(adat_transmit_count)
|
||||
adat_receiver0_frames = Signal.like(adat_transmit_count)
|
||||
adat_receiver3_count = Signal.like(adat_transmit_count)
|
||||
adat_receiver3_frames = Signal.like(adat_transmit_count)
|
||||
adat_multiplexer_count = Signal.like(adat_transmit_count)
|
||||
adat_multiplexer_frames = Signal.like(adat_transmit_count)
|
||||
adat_channels2usb_count = Signal.like(adat_transmit_count)
|
||||
adat_channels2usb_frames = Signal.like(adat_transmit_count)
|
||||
usb_receive_frames = Signal.like(adat_transmit_count)
|
||||
|
||||
m.submodules.sof_synchronizer = sof_synchronizer = PulseSynchronizer("usb", "fast")
|
||||
sof_fast = Signal()
|
||||
adat_receiver0_fast = Signal()
|
||||
adat_receiver3_fast = Signal()
|
||||
adat_multiplexer_out_fast = Signal()
|
||||
|
||||
m.d.comb += [
|
||||
sof_synchronizer.i.eq(usb1.sof_detected),
|
||||
sof_fast.eq(sof_synchronizer.o),
|
||||
|
||||
adat_receiver0_fast.eq((adat_receivers[0].addr_out == 7) & adat_receivers[0].output_enable),
|
||||
adat_receiver3_fast.eq((adat_receivers[3].addr_out == 7) & adat_receivers[3].output_enable),
|
||||
adat_multiplexer_out_fast.eq(bundle_multiplexer.channel_stream_out.ready & bundle_multiplexer.channel_stream_out.valid & bundle_multiplexer.channel_stream_out.last),
|
||||
]
|
||||
|
||||
with m.If(sof_fast):
|
||||
m.d.fast += [
|
||||
adat_receiver0_frames.eq(adat_receiver0_count),
|
||||
adat_receiver0_count.eq(0),
|
||||
adat_receiver3_frames.eq(adat_receiver3_count),
|
||||
adat_receiver3_count.eq(0),
|
||||
adat_multiplexer_frames.eq(adat_multiplexer_count),
|
||||
adat_multiplexer_count.eq(0),
|
||||
]
|
||||
|
||||
with m.If(adat_receiver0_fast):
|
||||
m.d.fast += adat_receiver0_count.eq(adat_receiver0_count + 1)
|
||||
|
||||
with m.If(adat_receiver3_fast):
|
||||
m.d.fast += adat_receiver3_count.eq(adat_receiver3_count + 1)
|
||||
|
||||
with m.If(adat_multiplexer_out_fast):
|
||||
m.d.fast += adat_multiplexer_count.eq(adat_multiplexer_count + 1)
|
||||
|
||||
frame_counts = [
|
||||
adat_transmit_frames,
|
||||
adat_receiver0_frames,
|
||||
adat_receiver3_frames,
|
||||
adat_multiplexer_frames,
|
||||
adat_channels2usb_frames,
|
||||
usb_receive_frames,
|
||||
]
|
||||
|
||||
with m.If(channels_to_usb_stream.channel_stream_in.last & channels_to_usb_stream.channel_stream_in.valid & channels_to_usb_stream.channel_stream_in.ready):
|
||||
m.d.usb += adat_channels2usb_count.eq(adat_channels2usb_count + 1)
|
||||
|
||||
with m.If(usb_to_channel_stream.channel_stream_out.last & usb_to_channel_stream.channel_stream_out.valid & usb_to_channel_stream.channel_stream_out.ready):
|
||||
m.d.usb += adat_transmit_count.eq(adat_transmit_count + 1)
|
||||
|
||||
with m.If(usb1.sof_detected):
|
||||
m.d.usb += [
|
||||
adat_transmit_frames.eq(adat_transmit_count),
|
||||
adat_transmit_count.eq(0),
|
||||
adat_channels2usb_frames.eq(adat_channels2usb_count),
|
||||
adat_channels2usb_count.eq(0),
|
||||
usb_receive_frames.eq(audio_in_frame_bytes >> 7),
|
||||
]
|
||||
|
||||
channel_stream_combiner_debug = [
|
||||
#usb1_channel_stream_combiner.lower_channel_stream_in.valid,
|
||||
#usb1_channel_stream_combiner.lower_channel_stream_in.ready,
|
||||
#usb1_channel_stream_combiner.lower_channel_stream_in.payload,
|
||||
#usb1_channel_stream_combiner.lower_channel_stream_in.channel_nr,
|
||||
#usb1_channel_stream_combiner.lower_channel_stream_in.first,
|
||||
#usb1_channel_stream_combiner.lower_channel_stream_in.last,
|
||||
usb2_audio_out_active,
|
||||
usb1_channel_stream_combiner.upper_channel_stream_in.valid,
|
||||
usb1_channel_stream_combiner.upper_channel_stream_in.ready,
|
||||
usb1_channel_stream_combiner.upper_channel_stream_in.payload,
|
||||
usb1_channel_stream_combiner.upper_channel_stream_in.channel_nr,
|
||||
usb1_channel_stream_combiner.upper_channel_stream_in.first,
|
||||
usb1_channel_stream_combiner.upper_channel_stream_in.last,
|
||||
usb1_channel_stream_combiner.upper_channel_counter,
|
||||
usb1_channel_stream_combiner.state,
|
||||
#usb1_channel_stream_combiner.combined_channel_stream_out.valid,
|
||||
#usb1_channel_stream_combiner.combined_channel_stream_out.ready,
|
||||
#usb1_channel_stream_combiner.combined_channel_stream_out.payload,
|
||||
#usb1_channel_stream_combiner.combined_channel_stream_out.channel_nr,
|
||||
#usb1_channel_stream_combiner.combined_channel_stream_out.first,
|
||||
#usb1_channel_stream_combiner.combined_channel_stream_out.last,
|
||||
]
|
||||
|
||||
upper_channel_active = Signal()
|
||||
channel_stream_combiner_active = Signal()
|
||||
m.d.comb += [
|
||||
upper_channel_active.eq(usb1_channel_stream_combiner.upper_channel_stream_in.valid &
|
||||
usb1_channel_stream_combiner.upper_channel_stream_in.ready),
|
||||
channel_stream_combiner_active.eq(
|
||||
upper_channel_active |
|
||||
(usb1_channel_stream_combiner.combined_channel_stream_out.valid &
|
||||
usb1_channel_stream_combiner.combined_channel_stream_out.ready) |
|
||||
(usb1_channel_stream_combiner.lower_channel_stream_in.valid &
|
||||
usb1_channel_stream_combiner.lower_channel_stream_in.ready))
|
||||
]
|
||||
|
||||
channel_stream_splitter_debug = [
|
||||
usb1_channel_stream_splitter.lower_channel_stream_out.valid,
|
||||
usb1_channel_stream_splitter.lower_channel_stream_out.ready,
|
||||
usb1_channel_stream_splitter.lower_channel_stream_out.payload,
|
||||
usb1_channel_stream_splitter.lower_channel_stream_out.channel_nr,
|
||||
usb1_channel_stream_splitter.lower_channel_stream_out.first,
|
||||
usb1_channel_stream_splitter.lower_channel_stream_out.last,
|
||||
usb2_audio_out_active,
|
||||
usb1_channel_stream_splitter.upper_channel_stream_out.valid,
|
||||
usb1_channel_stream_splitter.upper_channel_stream_out.ready,
|
||||
usb1_channel_stream_splitter.upper_channel_stream_out.payload,
|
||||
usb1_channel_stream_splitter.upper_channel_stream_out.channel_nr,
|
||||
usb1_channel_stream_splitter.upper_channel_stream_out.first,
|
||||
usb1_channel_stream_splitter.upper_channel_stream_out.last,
|
||||
usb1_channel_stream_splitter.combined_channel_stream_in.valid,
|
||||
usb1_channel_stream_splitter.combined_channel_stream_in.ready,
|
||||
usb1_channel_stream_splitter.combined_channel_stream_in.payload,
|
||||
usb1_channel_stream_splitter.combined_channel_stream_in.channel_nr,
|
||||
usb1_channel_stream_splitter.combined_channel_stream_in.first,
|
||||
usb1_channel_stream_splitter.combined_channel_stream_in.last,
|
||||
]
|
||||
|
||||
splitter_upper_channel_active = Signal()
|
||||
channel_stream_splitter_active = Signal()
|
||||
m.d.comb += [
|
||||
splitter_upper_channel_active.eq(usb1_channel_stream_splitter.upper_channel_stream_out.valid &
|
||||
usb1_channel_stream_splitter.upper_channel_stream_out.ready),
|
||||
channel_stream_splitter_active.eq(
|
||||
splitter_upper_channel_active |
|
||||
(usb1_channel_stream_splitter.combined_channel_stream_in.valid &
|
||||
usb1_channel_stream_splitter.combined_channel_stream_in.ready) |
|
||||
(usb1_channel_stream_splitter.lower_channel_stream_out.valid &
|
||||
usb1_channel_stream_splitter.lower_channel_stream_out.ready))
|
||||
]
|
||||
|
||||
midi_in_active = Signal()
|
||||
m.d.comb += midi_in_active.eq(usb2_ep3_in.stream.valid & usb2_ep3_in.stream.ready)
|
||||
midi_out_active = Signal()
|
||||
m.d.comb += midi_out_active.eq(usb2_ep3_out.stream.valid & usb2_ep3_out.stream.ready)
|
||||
midi_active = Signal()
|
||||
m.d.comb += midi_active.eq(midi_in_active | midi_out_active)
|
||||
|
||||
midi_out_stream = StreamInterface(name="midi_out")
|
||||
m.d.comb += midi_out_stream.stream_eq(usb2_ep3_out.stream, omit="ready")
|
||||
midi_in_stream = StreamInterface(name="midi_in")
|
||||
m.d.comb += midi_in_stream.stream_eq(usb1_ep3_in.stream, omit="ready")
|
||||
in_ready = Signal()
|
||||
m.d.comb += in_ready.eq(usb1_ep3_in.stream.ready)
|
||||
|
||||
midi_out = [
|
||||
usb2_ep3_out.stream.ready,
|
||||
usb2_ep3_out.stream.valid,
|
||||
midi_out_stream.payload,
|
||||
midi_out_stream.first,
|
||||
midi_out_stream.last,
|
||||
usb2_to_usb1_midi_fifo.r_level,
|
||||
in_ready,
|
||||
midi_in_stream.valid,
|
||||
midi_in_stream.payload,
|
||||
midi_in_stream.first,
|
||||
midi_in_stream.last,
|
||||
]
|
||||
|
||||
dac_extractor_debug = [
|
||||
#dac1_extractor.selected_channel_in,
|
||||
dac1_extractor.level,
|
||||
dac1_extractor.channel_stream_in.channel_nr,
|
||||
#dac1_extractor.channel_stream_in.payload,
|
||||
dac1_extractor.channel_stream_in.valid,
|
||||
#dac1_extractor.channel_stream_out.payload,
|
||||
#dac1_extractor.channel_stream_out.valid,
|
||||
dac1_extractor.channel_stream_out.ready,
|
||||
dac1_extractor.channel_stream_out.first,
|
||||
dac1_extractor.channel_stream_out.last,
|
||||
dac1.underflow_out,
|
||||
dac1.fifo_level_out,
|
||||
enable_convolver,
|
||||
]
|
||||
|
||||
convolver_signal_in_valid = Signal()
|
||||
convolver_signal_in_first = Signal()
|
||||
convolver_signal_in_last = Signal()
|
||||
convolver_signal_in_ready = Signal()
|
||||
convolver_signal_in_payload = Signal(signed(24))
|
||||
convolver_signal_out_valid = Signal()
|
||||
convolver_signal_out_first = Signal()
|
||||
convolver_signal_out_last = Signal()
|
||||
convolver_signal_out_payload = Signal(24)
|
||||
convolver_signal_out_ready = Signal()
|
||||
|
||||
if use_convolution:
|
||||
m.d.comb += [
|
||||
convolver_signal_in_valid.eq(convolver.signal_in.valid),
|
||||
convolver_signal_in_first.eq(convolver.signal_in.first),
|
||||
convolver_signal_in_last.eq(convolver.signal_in.last),
|
||||
convolver_signal_in_payload.eq(convolver.signal_in.payload),
|
||||
convolver_signal_in_ready.eq(convolver.signal_in.ready),
|
||||
convolver_signal_out_valid.eq(convolver.signal_out.valid),
|
||||
convolver_signal_out_first.eq(convolver.signal_out.first),
|
||||
convolver_signal_out_last.eq(convolver.signal_out.last),
|
||||
convolver_signal_out_payload.eq(convolver.signal_out.payload),
|
||||
convolver_signal_out_ready.eq(convolver.signal_out.ready),
|
||||
]
|
||||
|
||||
convolution_debug = [
|
||||
convolver_signal_in_ready,
|
||||
convolver_signal_in_valid,
|
||||
convolver_signal_in_first,
|
||||
convolver_signal_in_last,
|
||||
convolver_signal_in_payload,
|
||||
convolver_signal_out_valid,
|
||||
convolver_signal_out_first,
|
||||
convolver_signal_out_last,
|
||||
convolver_signal_out_payload,
|
||||
convolver_signal_out_ready,
|
||||
dac1_extractor.level,
|
||||
enable_convolver,
|
||||
]
|
||||
|
||||
#
|
||||
# signals to trace
|
||||
#
|
||||
signals = adat_transmitter_debug
|
||||
|
||||
signals_bits = sum([s.width for s in signals])
|
||||
m.submodules.ila = ila = \
|
||||
StreamILA(
|
||||
domain="usb", o_domain="usb",
|
||||
#sample_rate=60e6, # usb domain
|
||||
#sample_rate=48e3 * 256 * 5, # sync domain
|
||||
#sample_rate=48e3 * 256 * 9, # fast domain
|
||||
#sample_rate=25e6 * 29 / 7, # fast domain, ECP5
|
||||
signals=signals,
|
||||
sample_depth = int(10 * 8 * 1024 / signals_bits),
|
||||
samples_pretrigger = 2, #int(78 * 8 * 1024 / signals_bits),
|
||||
with_enable=True)
|
||||
|
||||
stream_ep = USBMultibyteStreamInEndpoint(
|
||||
endpoint_number=4, # EP 4 IN
|
||||
max_packet_size=ila_max_packet_size,
|
||||
byte_width=ila.bytes_per_sample
|
||||
)
|
||||
usb1.add_endpoint(stream_ep)
|
||||
|
||||
m.d.comb += [
|
||||
stream_ep.stream.stream_eq(ila.stream),
|
||||
# ila.enable.eq(input_or_output_active | garbage | usb_frame_borders),
|
||||
ila.trigger.eq(1),
|
||||
ila.enable .eq(input_or_output_active),
|
||||
]
|
||||
|
||||
ILACoreParameters(ila).pickle()
|
||||
|
||||
4
gateware/initialize-python-environment.sh
Executable file
4
gateware/initialize-python-environment.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
python3 -m venv venv
|
||||
. venv/bin/activate
|
||||
pip3 install -r requirements.txt
|
||||
134
gateware/platforms.py
Normal file
134
gateware/platforms.py
Normal file
@@ -0,0 +1,134 @@
|
||||
from amaranth import *
|
||||
from amaranth.build import *
|
||||
|
||||
from amaranth_boards.resources import *
|
||||
from amaranth_boards.qmtech_ep4ce import QMTechEP4CEPlatform
|
||||
from amaranth_boards.qmtech_5cefa2 import QMTech5CEFA2Platform
|
||||
from amaranth_boards.qmtech_10cl006 import QMTech10CL006Platform
|
||||
from amaranth_boards.qmtech_xc7a35t import QMTechXC7A35TPlatform
|
||||
from amaranth_boards.colorlight_qmtech import ColorlightQMTechPlatform
|
||||
|
||||
from luna.gateware.platform.core import LUNAPlatform
|
||||
|
||||
from car import ColorlightDomainGenerator, IntelCycloneIVClockDomainGenerator, IntelCycloneVClockDomainGenerator, Xilinx7SeriesClockDomainGenerator
|
||||
from adatface_rev0_baseboard import ADATFaceRev0Baseboard
|
||||
|
||||
class IntelFPGAParameters:
|
||||
QSF_ADDITIONS = r"""
|
||||
set_global_assignment -name OPTIMIZATION_MODE "Aggressive Performance"
|
||||
set_global_assignment -name FITTER_EFFORT "Standard Fit"
|
||||
set_global_assignment -name PHYSICAL_SYNTHESIS_EFFORT "Extra"
|
||||
set_instance_assignment -name DECREASE_INPUT_DELAY_TO_INPUT_REGISTER OFF -to *ulpi*
|
||||
set_instance_assignment -name INCREASE_DELAY_TO_OUTPUT_PIN OFF -to *ulpi*
|
||||
set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL
|
||||
"""
|
||||
|
||||
SDC_ADDITIONS = r"""
|
||||
derive_pll_clocks
|
||||
derive_clock_uncertainty
|
||||
# sync clock domain crossing to ADAT clock domain crossing
|
||||
set_max_delay -from [get_clocks {car|audiopll|auto_generated|pll1|clk[3]}] -to [get_clocks {car|audiopll|auto_generated|pll1|clk[0]}] 5
|
||||
|
||||
# USB to fast clock domain crossing
|
||||
set_max_delay -from [get_clocks {car|mainpll|auto_generated|pll1|clk[0]}] -to [get_clocks {car|fastopll|auto_generated|pll1|clk[0]}] 5
|
||||
"""
|
||||
|
||||
class ADATFaceCycloneV(QMTech5CEFA2Platform, LUNAPlatform):
|
||||
fast_multiplier = 9
|
||||
clock_domain_generator = IntelCycloneVClockDomainGenerator
|
||||
fast_domain_clock_freq = int(48e3 * 256 * fast_multiplier)
|
||||
|
||||
@property
|
||||
def file_templates(self):
|
||||
templates = super().file_templates
|
||||
templates["{{name}}.qsf"] += IntelFPGAParameters.QSF_ADDITIONS
|
||||
templates["{{name}}.sdc"] += IntelFPGAParameters.SDC_ADDITIONS
|
||||
return templates
|
||||
|
||||
def __init__(self):
|
||||
self.resources += ADATFaceRev0Baseboard.resources(Attrs(io_standard="3.3-V LVCMOS"))
|
||||
# swap connector numbers, because on ADATface the connector
|
||||
# names are swapped compared to the QMTech daughterboard
|
||||
self.connectors[0].number = 3
|
||||
self.connectors[1].number = 2
|
||||
super().__init__(standalone=False)
|
||||
|
||||
class ADATFaceCycloneIV(QMTechEP4CEPlatform, LUNAPlatform):
|
||||
fast_multiplier = 9
|
||||
clock_domain_generator = IntelCycloneIVClockDomainGenerator
|
||||
fast_domain_clock_freq = int(48e3 * 256 * fast_multiplier)
|
||||
|
||||
@property
|
||||
def file_templates(self):
|
||||
templates = super().file_templates
|
||||
templates["{{name}}.qsf"] += IntelFPGAParameters.QSF_ADDITIONS
|
||||
templates["{{name}}.sdc"] += IntelFPGAParameters.SDC_ADDITIONS
|
||||
return templates
|
||||
|
||||
def __init__(self):
|
||||
self.resources += ADATFaceRev0Baseboard.resources(Attrs(io_standard="3.3-V LVCMOS"))
|
||||
# swap connector numbers, because on ADATface the connector
|
||||
# names are swapped compared to the QMTech daughterboard
|
||||
self.connectors[0].number = 3
|
||||
self.connectors[1].number = 2
|
||||
super().__init__(no_kluts=55, standalone=False)
|
||||
|
||||
# This is here just for experimental reasons.
|
||||
# right now the design probably would not fit into this device anymore
|
||||
class ADATFaceCyclone10(QMTech10CL006Platform, LUNAPlatform):
|
||||
clock_domain_generator = IntelCycloneIVClockDomainGenerator
|
||||
fast_multiplier = 9
|
||||
fast_domain_clock_freq = int(48e3 * 256 * fast_multiplier)
|
||||
|
||||
@property
|
||||
def file_templates(self):
|
||||
templates = super().file_templates
|
||||
templates["{{name}}.qsf"] += IntelFPGAParameters.QSF_ADDITIONS
|
||||
templates["{{name}}.sdc"] += IntelFPGAParameters.SDC_ADDITIONS
|
||||
return templates
|
||||
|
||||
def __init__(self):
|
||||
self.resources += ADATFaceRev0Baseboard.resources(Attrs(io_standard="3.3-V LVCMOS"))
|
||||
# swap connector numbers, because on ADATface the connector
|
||||
# names are swapped compared to the QMTech daughterboard
|
||||
self.connectors[0].number = 3
|
||||
self.connectors[1].number = 2
|
||||
|
||||
super().__init__(standalone=False)
|
||||
|
||||
class ADATFaceArtix7(QMTechXC7A35TPlatform, LUNAPlatform):
|
||||
clock_domain_generator = Xilinx7SeriesClockDomainGenerator
|
||||
fast_multiplier = 9
|
||||
fast_domain_clock_freq = int(48e3 * 256 * fast_multiplier)
|
||||
|
||||
@property
|
||||
def file_templates(self):
|
||||
templates = super().file_templates
|
||||
return templates
|
||||
|
||||
def __init__(self):
|
||||
self.resources += ADATFaceRev0Baseboard.resources(Attrs(IOSTANDARD="LVCMOS33"))
|
||||
# swap connector numbers, because on ADATface the connector
|
||||
# names are swapped compared to the QMTech daughterboard
|
||||
self.connectors[0].number = 3
|
||||
self.connectors[1].number = 2
|
||||
|
||||
super().__init__(standalone=False)
|
||||
|
||||
class ADATFaceColorlight(ColorlightQMTechPlatform, LUNAPlatform):
|
||||
clock_domain_generator = ColorlightDomainGenerator
|
||||
fast_domain_clock_freq = ColorlightDomainGenerator.FastClockFreq
|
||||
|
||||
@property
|
||||
def file_templates(self):
|
||||
templates = super().file_templates
|
||||
return templates
|
||||
|
||||
def __init__(self):
|
||||
adatface_resources = ADATFaceRev0Baseboard.resources(Attrs(IO_TYPE="LVCMOS33"), colorlight=True)
|
||||
# swap connector numbers, because on ADATface the connector
|
||||
# names are swapped compared to the QMTech daughterboard
|
||||
self.connectors[0].number = 3
|
||||
self.connectors[1].number = 2
|
||||
from amaranth_boards.colorlight_i9 import ColorLightI9Platform
|
||||
super().__init__(colorlight=ColorLightI9Platform, daughterboard=False, extra_resources=adatface_resources)
|
||||
148
gateware/requesthandlers.py
Normal file
148
gateware/requesthandlers.py
Normal file
@@ -0,0 +1,148 @@
|
||||
from enum import IntEnum
|
||||
from amaranth import *
|
||||
from luna.gateware.usb.usb2.request import USBRequestHandler
|
||||
from luna.gateware.stream.generator import StreamSerializer
|
||||
|
||||
from usb_protocol.types import USBRequestType, USBRequestRecipient, USBStandardRequests
|
||||
from usb_protocol.types.descriptors.uac2 import AudioClassSpecificRequestCodes, ClockSourceControlSelectors
|
||||
from luna.gateware.usb.stream import USBInStreamInterface
|
||||
|
||||
from usb_descriptors import USBDescriptors
|
||||
|
||||
class VendorRequests(IntEnum):
|
||||
ILA_STOP_CAPTURE = 0
|
||||
TOGGLE_CONVOLUTION = 1
|
||||
|
||||
class UAC2RequestHandlers(USBRequestHandler):
|
||||
""" request handlers to implement UAC2 functionality. """
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.output_interface_altsetting_nr = Signal(3)
|
||||
self.input_interface_altsetting_nr = Signal(3)
|
||||
self.interface_settings_changed = Signal()
|
||||
self.enable_convolution = Signal()
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
interface = self.interface
|
||||
setup = self.interface.setup
|
||||
|
||||
m.submodules.transmitter = transmitter = \
|
||||
StreamSerializer(data_length=14, domain="usb", stream_type=USBInStreamInterface, max_length_width=14)
|
||||
|
||||
m.d.usb += self.interface_settings_changed.eq(0)
|
||||
m.d.comb += self.enable_convolution.eq(0)
|
||||
|
||||
#
|
||||
# Class request handlers.
|
||||
#
|
||||
with m.If(setup.type == USBRequestType.STANDARD):
|
||||
with m.If((setup.recipient == USBRequestRecipient.INTERFACE) &
|
||||
(setup.request == USBStandardRequests.SET_INTERFACE)):
|
||||
|
||||
interface_nr = setup.index
|
||||
alt_setting_nr = setup.value
|
||||
|
||||
m.d.usb += [
|
||||
self.output_interface_altsetting_nr.eq(0),
|
||||
self.input_interface_altsetting_nr.eq(0),
|
||||
self.interface_settings_changed.eq(1),
|
||||
]
|
||||
|
||||
with m.Switch(interface_nr):
|
||||
with m.Case(1):
|
||||
m.d.usb += self.output_interface_altsetting_nr.eq(alt_setting_nr)
|
||||
with m.Case(2):
|
||||
m.d.usb += self.input_interface_altsetting_nr.eq(alt_setting_nr)
|
||||
|
||||
# Always ACK the data out...
|
||||
with m.If(interface.rx_ready_for_response):
|
||||
m.d.comb += interface.handshakes_out.ack.eq(1)
|
||||
|
||||
# ... and accept whatever the request was.
|
||||
with m.If(interface.status_requested):
|
||||
m.d.comb += self.send_zlp()
|
||||
|
||||
clock_freq = (setup.value == Const(ClockSourceControlSelectors.CS_SAM_FREQ_CONTROL << 8, 16)) \
|
||||
& (setup.index == Const(USBDescriptors.CLOCK_ID << 8, 16))
|
||||
|
||||
request_clock_freq = clock_freq & setup.is_in_request
|
||||
set_clock_freq = clock_freq & ~setup.is_in_request
|
||||
|
||||
SRATE_44_1k = Const(44100, 32)
|
||||
SRATE_48k = Const(48000, 32)
|
||||
ZERO = Const(0, 32)
|
||||
|
||||
with m.Elif(setup.type == USBRequestType.CLASS):
|
||||
with m.Switch(setup.request):
|
||||
with m.Case(AudioClassSpecificRequestCodes.RANGE):
|
||||
m.d.comb += transmitter.stream.attach(self.interface.tx)
|
||||
|
||||
with m.If(request_clock_freq):
|
||||
m.d.comb += [
|
||||
Cat(transmitter.data).eq(
|
||||
Cat(Const(0x1, 16), # no triples
|
||||
SRATE_48k, # MIN
|
||||
SRATE_48k, # MAX
|
||||
ZERO)), # RES
|
||||
transmitter.max_length.eq(setup.length)
|
||||
]
|
||||
with m.Else():
|
||||
m.d.comb += interface.handshakes_out.stall.eq(1)
|
||||
|
||||
# ... trigger it to respond when data's requested...
|
||||
with m.If(interface.data_requested):
|
||||
m.d.comb += transmitter.start.eq(1)
|
||||
|
||||
# ... and ACK our status stage.
|
||||
with m.If(interface.status_requested):
|
||||
m.d.comb += interface.handshakes_out.ack.eq(1)
|
||||
|
||||
with m.Case(AudioClassSpecificRequestCodes.CUR):
|
||||
m.d.comb += transmitter.stream.attach(self.interface.tx)
|
||||
with m.If(request_clock_freq & (setup.length == 4)):
|
||||
m.d.comb += [
|
||||
Cat(transmitter.data[0:4]).eq(Const(48000, 32)),
|
||||
transmitter.max_length.eq(4)
|
||||
]
|
||||
with m.Else():
|
||||
m.d.comb += interface.handshakes_out.stall.eq(1)
|
||||
|
||||
# ... trigger it to respond when data's requested...
|
||||
with m.If(interface.data_requested):
|
||||
m.d.comb += transmitter.start.eq(1)
|
||||
|
||||
# ... and ACK our status stage.
|
||||
with m.If(interface.status_requested):
|
||||
m.d.comb += interface.handshakes_out.ack.eq(1)
|
||||
|
||||
with m.Default():
|
||||
#
|
||||
# Stall unhandled requests.
|
||||
#
|
||||
with m.If(interface.status_requested | interface.data_requested):
|
||||
m.d.comb += interface.handshakes_out.stall.eq(1)
|
||||
|
||||
with m.Elif(setup.type == USBRequestType.VENDOR):
|
||||
with m.Switch(setup.request):
|
||||
with m.Case(VendorRequests.TOGGLE_CONVOLUTION):
|
||||
m.d.comb += self.enable_convolution.eq(1)
|
||||
# m.d.usb += self.enable_convolution.eq(~self.enable_convolution)
|
||||
# ... and ACK our status stage.
|
||||
with m.If(interface.status_requested | interface.data_requested):
|
||||
m.d.comb += interface.handshakes_out.ack.eq(1)
|
||||
# m.d.comb += self.interface.handshakes_out.stall.eq(1)
|
||||
# pass
|
||||
with m.Case(VendorRequests.ILA_STOP_CAPTURE):
|
||||
# TODO - will be implemented when needed
|
||||
pass
|
||||
|
||||
with m.Default():
|
||||
m.d.comb += self.interface.handshakes_out.stall.eq(1)
|
||||
|
||||
with m.Else():
|
||||
m.d.comb += self.interface.handshakes_out.stall.eq(1)
|
||||
|
||||
return m
|
||||
9
gateware/requirements.txt
Normal file
9
gateware/requirements.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
wheel
|
||||
setuptools
|
||||
git+https://github.com/amaranth-lang/amaranth.git@v0.4.5
|
||||
git+https://github.com/amaranth-lang/amaranth-soc.git@59223a82399df45addf1db362ad6fb9670e72b51
|
||||
git+https://github.com/amaranth-farm/python-usb-descriptors.git
|
||||
git+https://github.com/amaranth-farm/usb2-highspeed-core.git
|
||||
git+https://github.com/amaranth-farm/adat-core.git
|
||||
git+https://github.com/amaranth-farm/amlib.git
|
||||
git+https://github.com/amaranth-farm/amaranth-boards.git
|
||||
45
gateware/stereopair_extractor.py
Normal file
45
gateware/stereopair_extractor.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from amaranth import *
|
||||
from amaranth.build import Platform
|
||||
from amaranth.lib.fifo import SyncFIFOBuffered
|
||||
from amlib.stream import StreamInterface, connect_fifo_to_stream
|
||||
|
||||
class StereoPairExtractor(Elaboratable):
|
||||
def __init__(self, max_no_channels: int, fifo_depth):
|
||||
self._channel_bits = Shape.cast(range(max_no_channels)).width
|
||||
self._fifo_depth = fifo_depth
|
||||
|
||||
# I/O
|
||||
self.channel_stream_in = StreamInterface(name="channel_stream_in", payload_width=24, extra_fields=[("channel_nr", self._channel_bits)])
|
||||
self.selected_channel_in = Signal(range(max_no_channels))
|
||||
# the first=left and last=right signals mark the channel on the output stream
|
||||
self.channel_stream_out = StreamInterface(name="channel_stream_out", payload_width=24)
|
||||
self.level = Signal(range(fifo_depth))
|
||||
|
||||
def elaborate(self, platform: Platform) -> Module:
|
||||
m = Module()
|
||||
|
||||
m.submodules.fifo = fifo = SyncFIFOBuffered(width=24+1, depth=self._fifo_depth)
|
||||
|
||||
in_channel_nr = self.channel_stream_in.channel_nr
|
||||
out_channel_nr = Signal(self._channel_bits)
|
||||
|
||||
# the ready signal is not wired in the input stream because this
|
||||
# module must not exert upstream back pressure
|
||||
with m.If( self.channel_stream_in.valid
|
||||
& ( (in_channel_nr == self.selected_channel_in)
|
||||
| (in_channel_nr == (self.selected_channel_in + 1)))):
|
||||
|
||||
m.d.comb += [
|
||||
fifo.w_data.eq(Cat(self.channel_stream_in.payload, out_channel_nr[0])),
|
||||
fifo.w_en.eq(1)
|
||||
]
|
||||
|
||||
m.d.comb += [
|
||||
self.level.eq(fifo.r_level),
|
||||
out_channel_nr.eq(in_channel_nr - self.selected_channel_in),
|
||||
*connect_fifo_to_stream(fifo, self.channel_stream_out),
|
||||
self.channel_stream_out.first.eq(~fifo.r_data[-1]),
|
||||
self.channel_stream_out.last.eq(fifo.r_data[-1]),
|
||||
]
|
||||
|
||||
return m
|
||||
302
gateware/usb_descriptors.py
Normal file
302
gateware/usb_descriptors.py
Normal file
@@ -0,0 +1,302 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2021 Hans Baier <hansfbaier@gmail.com>
|
||||
# SPDX-License-Identifier: CERN-OHL-W-2.0
|
||||
|
||||
from amaranth import *
|
||||
|
||||
from usb_protocol.types import USBRequestType, USBRequestRecipient, USBTransferType, USBSynchronizationType, USBUsageType, USBDirection, USBStandardRequests
|
||||
from usb_protocol.types.descriptors.uac2 import AudioClassSpecificRequestCodes
|
||||
from usb_protocol.emitters import DeviceDescriptorCollection
|
||||
from usb_protocol.emitters.descriptors import uac2, standard
|
||||
from usb_protocol.emitters.descriptors import midi1
|
||||
|
||||
class USBDescriptors():
|
||||
MAX_PACKET_SIZE_MIDI = 64
|
||||
CLOCK_ID = 1
|
||||
|
||||
def __init__(self, *, ila_max_packet_size: int, use_ila=False) -> None:
|
||||
|
||||
# ILA
|
||||
self.USE_ILA = use_ila
|
||||
self.ILA_MAX_PACKET_SIZE = ila_max_packet_size
|
||||
|
||||
|
||||
def create_usb1_descriptors(self, no_channels: int, max_packet_size: int):
|
||||
""" Creates the descriptors for the main USB interface """
|
||||
|
||||
return self.create_descriptors("ADATface (USB1)", no_channels, max_packet_size, self.USE_ILA)
|
||||
|
||||
|
||||
def create_usb2_descriptors(self, no_channels: int, max_packet_size: int):
|
||||
""" Creates the descriptors for the secondary USB interface """
|
||||
return self.create_descriptors("ADATface (USB2)", no_channels, max_packet_size)
|
||||
|
||||
|
||||
def create_descriptors(self, product_id: str, no_channels: int, max_packet_size: int, create_ila=False):
|
||||
""" Creates the descriptors for the main USB interface """
|
||||
|
||||
descriptors = DeviceDescriptorCollection()
|
||||
|
||||
with descriptors.DeviceDescriptor() as d:
|
||||
d.bcdUSB = 2.00
|
||||
d.bDeviceClass = 0xEF
|
||||
d.bDeviceSubclass = 0x02
|
||||
d.bDeviceProtocol = 0x01
|
||||
d.idVendor = 0x1209
|
||||
d.idProduct = 0xADA1 if "USB1" in product_id else 0xADA2
|
||||
d.iManufacturer = "OpenAudioGear"
|
||||
d.iProduct = product_id
|
||||
d.iSerialNumber = "0"
|
||||
d.bcdDevice = 0.01
|
||||
|
||||
d.bNumConfigurations = 1
|
||||
|
||||
with descriptors.ConfigurationDescriptor() as configDescr:
|
||||
# Interface Association
|
||||
interfaceAssociationDescriptor = uac2.InterfaceAssociationDescriptorEmitter()
|
||||
interfaceAssociationDescriptor.bInterfaceCount = 3 # Audio Control + Inputs + Outputs
|
||||
configDescr.add_subordinate_descriptor(interfaceAssociationDescriptor)
|
||||
|
||||
# Interface Descriptor (Control)
|
||||
interfaceDescriptor = uac2.StandardAudioControlInterfaceDescriptorEmitter()
|
||||
interfaceDescriptor.bInterfaceNumber = 0
|
||||
configDescr.add_subordinate_descriptor(interfaceDescriptor)
|
||||
|
||||
# AudioControl Interface Descriptor
|
||||
audioControlInterface = self.create_audio_control_interface_descriptor(no_channels)
|
||||
configDescr.add_subordinate_descriptor(audioControlInterface)
|
||||
|
||||
self.create_output_channels_descriptor(configDescr, no_channels, max_packet_size)
|
||||
|
||||
self.create_input_channels_descriptor(configDescr, no_channels, max_packet_size)
|
||||
|
||||
midi_interface, midi_streaming_interface = self.create_midi_interface_descriptor()
|
||||
configDescr.add_subordinate_descriptor(midi_interface)
|
||||
configDescr.add_subordinate_descriptor(midi_streaming_interface)
|
||||
|
||||
if create_ila:
|
||||
with configDescr.InterfaceDescriptor() as i:
|
||||
i.bInterfaceNumber = 4
|
||||
|
||||
with i.EndpointDescriptor() as e:
|
||||
e.bEndpointAddress = USBDirection.IN.to_endpoint_address(4)
|
||||
e.wMaxPacketSize = self.ILA_MAX_PACKET_SIZE
|
||||
|
||||
return descriptors
|
||||
|
||||
|
||||
def create_audio_control_interface_descriptor(self, number_of_channels):
|
||||
audioControlInterface = uac2.ClassSpecificAudioControlInterfaceDescriptorEmitter()
|
||||
|
||||
# AudioControl Interface Descriptor (ClockSource)
|
||||
clockSource = uac2.ClockSourceDescriptorEmitter()
|
||||
clockSource.bClockID = self.CLOCK_ID
|
||||
clockSource.bmAttributes = uac2.ClockAttributes.INTERNAL_FIXED_CLOCK
|
||||
clockSource.bmControls = uac2.ClockFrequencyControl.HOST_READ_ONLY
|
||||
audioControlInterface.add_subordinate_descriptor(clockSource)
|
||||
|
||||
# streaming input port from the host to the USB interface
|
||||
inputTerminal = uac2.InputTerminalDescriptorEmitter()
|
||||
inputTerminal.bTerminalID = 2
|
||||
inputTerminal.wTerminalType = uac2.USBTerminalTypes.USB_STREAMING
|
||||
# The number of channels needs to be 2 here in order to be recognized
|
||||
# default audio out device by Windows. We provide an alternate
|
||||
# setting with the full channel count, which also references
|
||||
# this terminal ID
|
||||
inputTerminal.bNrChannels = 2
|
||||
inputTerminal.bCSourceID = 1
|
||||
audioControlInterface.add_subordinate_descriptor(inputTerminal)
|
||||
|
||||
# audio output port from the USB interface to the outside world
|
||||
outputTerminal = uac2.OutputTerminalDescriptorEmitter()
|
||||
outputTerminal.bTerminalID = 3
|
||||
outputTerminal.wTerminalType = uac2.OutputTerminalTypes.SPEAKER
|
||||
outputTerminal.bSourceID = 2
|
||||
outputTerminal.bCSourceID = 1
|
||||
audioControlInterface.add_subordinate_descriptor(outputTerminal)
|
||||
|
||||
# audio input port from the outside world to the USB interface
|
||||
inputTerminal = uac2.InputTerminalDescriptorEmitter()
|
||||
inputTerminal.bTerminalID = 4
|
||||
inputTerminal.wTerminalType = uac2.InputTerminalTypes.MICROPHONE
|
||||
inputTerminal.bNrChannels = number_of_channels
|
||||
inputTerminal.bCSourceID = 1
|
||||
audioControlInterface.add_subordinate_descriptor(inputTerminal)
|
||||
|
||||
# audio output port from the USB interface to the host
|
||||
outputTerminal = uac2.OutputTerminalDescriptorEmitter()
|
||||
outputTerminal.bTerminalID = 5
|
||||
outputTerminal.wTerminalType = uac2.USBTerminalTypes.USB_STREAMING
|
||||
outputTerminal.bSourceID = 4
|
||||
outputTerminal.bCSourceID = 1
|
||||
audioControlInterface.add_subordinate_descriptor(outputTerminal)
|
||||
|
||||
return audioControlInterface
|
||||
|
||||
|
||||
def create_output_streaming_interface(self, c, *, no_channels: int, alt_setting_nr: int, max_packet_size):
|
||||
# Interface Descriptor (Streaming, OUT, active setting)
|
||||
activeAudioStreamingInterface = uac2.AudioStreamingInterfaceDescriptorEmitter()
|
||||
activeAudioStreamingInterface.bInterfaceNumber = 1
|
||||
activeAudioStreamingInterface.bAlternateSetting = alt_setting_nr
|
||||
activeAudioStreamingInterface.bNumEndpoints = 2
|
||||
c.add_subordinate_descriptor(activeAudioStreamingInterface)
|
||||
|
||||
# AudioStreaming Interface Descriptor (General)
|
||||
audioStreamingInterface = uac2.ClassSpecificAudioStreamingInterfaceDescriptorEmitter()
|
||||
audioStreamingInterface.bTerminalLink = 2
|
||||
audioStreamingInterface.bFormatType = uac2.FormatTypes.FORMAT_TYPE_I
|
||||
audioStreamingInterface.bmFormats = uac2.TypeIFormats.PCM
|
||||
audioStreamingInterface.bNrChannels = no_channels
|
||||
c.add_subordinate_descriptor(audioStreamingInterface)
|
||||
|
||||
# AudioStreaming Interface Descriptor (Type I)
|
||||
typeIStreamingInterface = uac2.TypeIFormatTypeDescriptorEmitter()
|
||||
typeIStreamingInterface.bSubslotSize = 4
|
||||
typeIStreamingInterface.bBitResolution = 24
|
||||
c.add_subordinate_descriptor(typeIStreamingInterface)
|
||||
|
||||
# Endpoint Descriptor (Audio out)
|
||||
audioOutEndpoint = standard.EndpointDescriptorEmitter()
|
||||
audioOutEndpoint.bEndpointAddress = USBDirection.OUT.to_endpoint_address(1) # EP 1 OUT
|
||||
audioOutEndpoint.bmAttributes = USBTransferType.ISOCHRONOUS | \
|
||||
(USBSynchronizationType.ASYNC << 2) | \
|
||||
(USBUsageType.DATA << 4)
|
||||
audioOutEndpoint.wMaxPacketSize = max_packet_size
|
||||
audioOutEndpoint.bInterval = 1
|
||||
c.add_subordinate_descriptor(audioOutEndpoint)
|
||||
|
||||
# AudioControl Endpoint Descriptor
|
||||
audioControlEndpoint = uac2.ClassSpecificAudioStreamingIsochronousAudioDataEndpointDescriptorEmitter()
|
||||
c.add_subordinate_descriptor(audioControlEndpoint)
|
||||
|
||||
# Endpoint Descriptor (Feedback IN)
|
||||
feedbackInEndpoint = standard.EndpointDescriptorEmitter()
|
||||
feedbackInEndpoint.bEndpointAddress = USBDirection.IN.to_endpoint_address(1) # EP 1 IN
|
||||
feedbackInEndpoint.bmAttributes = USBTransferType.ISOCHRONOUS | \
|
||||
(USBSynchronizationType.NONE << 2) | \
|
||||
(USBUsageType.FEEDBACK << 4)
|
||||
feedbackInEndpoint.wMaxPacketSize = 4
|
||||
feedbackInEndpoint.bInterval = 4
|
||||
c.add_subordinate_descriptor(feedbackInEndpoint)
|
||||
|
||||
|
||||
def create_output_channels_descriptor(self, c, no_channels: int, max_packet_size: int):
|
||||
#
|
||||
# Interface Descriptor (Streaming, OUT, quiet setting)
|
||||
#
|
||||
quietAudioStreamingInterface = uac2.AudioStreamingInterfaceDescriptorEmitter()
|
||||
quietAudioStreamingInterface.bInterfaceNumber = 1
|
||||
quietAudioStreamingInterface.bAlternateSetting = 0
|
||||
c.add_subordinate_descriptor(quietAudioStreamingInterface)
|
||||
|
||||
# we need the default alternate setting to be stereo
|
||||
# out for windows to automatically recognize
|
||||
# and use this audio interface
|
||||
self.create_output_streaming_interface(c, no_channels=2, alt_setting_nr=1, max_packet_size=max_packet_size)
|
||||
if no_channels > 2:
|
||||
self.create_output_streaming_interface(c, no_channels=no_channels, alt_setting_nr=2, max_packet_size=max_packet_size)
|
||||
|
||||
|
||||
def create_input_streaming_interface(self, c, *, no_channels: int, alt_setting_nr: int, channel_config: int=0, max_packet_size: int):
|
||||
# Interface Descriptor (Streaming, IN, active setting)
|
||||
activeAudioStreamingInterface = uac2.AudioStreamingInterfaceDescriptorEmitter()
|
||||
activeAudioStreamingInterface.bInterfaceNumber = 2
|
||||
activeAudioStreamingInterface.bAlternateSetting = alt_setting_nr
|
||||
activeAudioStreamingInterface.bNumEndpoints = 1
|
||||
c.add_subordinate_descriptor(activeAudioStreamingInterface)
|
||||
|
||||
# AudioStreaming Interface Descriptor (General)
|
||||
audioStreamingInterface = uac2.ClassSpecificAudioStreamingInterfaceDescriptorEmitter()
|
||||
audioStreamingInterface.bTerminalLink = 5
|
||||
audioStreamingInterface.bFormatType = uac2.FormatTypes.FORMAT_TYPE_I
|
||||
audioStreamingInterface.bmFormats = uac2.TypeIFormats.PCM
|
||||
audioStreamingInterface.bNrChannels = no_channels
|
||||
audioStreamingInterface.bmChannelConfig = channel_config
|
||||
c.add_subordinate_descriptor(audioStreamingInterface)
|
||||
|
||||
# AudioStreaming Interface Descriptor (Type I)
|
||||
typeIStreamingInterface = uac2.TypeIFormatTypeDescriptorEmitter()
|
||||
typeIStreamingInterface.bSubslotSize = 4
|
||||
typeIStreamingInterface.bBitResolution = 24 # we use all 24 bits
|
||||
c.add_subordinate_descriptor(typeIStreamingInterface)
|
||||
|
||||
# Endpoint Descriptor (Audio out)
|
||||
audioOutEndpoint = standard.EndpointDescriptorEmitter()
|
||||
audioOutEndpoint.bEndpointAddress = USBDirection.IN.to_endpoint_address(2) # EP 2 IN
|
||||
audioOutEndpoint.bmAttributes = USBTransferType.ISOCHRONOUS | \
|
||||
(USBSynchronizationType.ASYNC << 2) | \
|
||||
(USBUsageType.DATA << 4)
|
||||
audioOutEndpoint.wMaxPacketSize = max_packet_size
|
||||
audioOutEndpoint.bInterval = 1
|
||||
c.add_subordinate_descriptor(audioOutEndpoint)
|
||||
|
||||
# AudioControl Endpoint Descriptor
|
||||
audioControlEndpoint = uac2.ClassSpecificAudioStreamingIsochronousAudioDataEndpointDescriptorEmitter()
|
||||
c.add_subordinate_descriptor(audioControlEndpoint)
|
||||
|
||||
|
||||
def create_input_channels_descriptor(self, c, no_channels: int, max_packet_size: int):
|
||||
#
|
||||
# Interface Descriptor (Streaming, IN, quiet setting)
|
||||
#
|
||||
quietAudioStreamingInterface = uac2.AudioStreamingInterfaceDescriptorEmitter()
|
||||
quietAudioStreamingInterface.bInterfaceNumber = 2
|
||||
quietAudioStreamingInterface.bAlternateSetting = 0
|
||||
c.add_subordinate_descriptor(quietAudioStreamingInterface)
|
||||
|
||||
# Windows wants a stereo pair as default setting, so let's have it
|
||||
self.create_input_streaming_interface(c, no_channels=2, alt_setting_nr=1, channel_config=0x3, max_packet_size=max_packet_size)
|
||||
if no_channels > 2:
|
||||
self.create_input_streaming_interface(c, no_channels=no_channels, alt_setting_nr=2, channel_config=0x0, max_packet_size=max_packet_size)
|
||||
|
||||
|
||||
def create_midi_interface_descriptor(self):
|
||||
midi_interface = midi1.StandardMidiStreamingInterfaceDescriptorEmitter()
|
||||
midi_interface.bInterfaceNumber = 3
|
||||
midi_interface.bNumEndpoints = 2
|
||||
|
||||
midi_streaming_interface = midi1.ClassSpecificMidiStreamingInterfaceDescriptorEmitter()
|
||||
|
||||
outToHostJack = midi1.MidiOutJackDescriptorEmitter()
|
||||
outToHostJack.bJackID = 1
|
||||
outToHostJack.bJackType = midi1.MidiStreamingJackTypes.EMBEDDED
|
||||
outToHostJack.add_source(2)
|
||||
midi_streaming_interface.add_subordinate_descriptor(outToHostJack)
|
||||
|
||||
inToDeviceJack = midi1.MidiInJackDescriptorEmitter()
|
||||
inToDeviceJack.bJackID = 2
|
||||
inToDeviceJack.bJackType = midi1.MidiStreamingJackTypes.EXTERNAL
|
||||
midi_streaming_interface.add_subordinate_descriptor(inToDeviceJack)
|
||||
|
||||
inFromHostJack = midi1.MidiInJackDescriptorEmitter()
|
||||
inFromHostJack.bJackID = 3
|
||||
inFromHostJack.bJackType = midi1.MidiStreamingJackTypes.EMBEDDED
|
||||
midi_streaming_interface.add_subordinate_descriptor(inFromHostJack)
|
||||
|
||||
outFromDeviceJack = midi1.MidiOutJackDescriptorEmitter()
|
||||
outFromDeviceJack.bJackID = 4
|
||||
outFromDeviceJack.bJackType = midi1.MidiStreamingJackTypes.EXTERNAL
|
||||
outFromDeviceJack.add_source(3)
|
||||
midi_streaming_interface.add_subordinate_descriptor(outFromDeviceJack)
|
||||
|
||||
outEndpoint = midi1.StandardMidiStreamingBulkDataEndpointDescriptorEmitter()
|
||||
outEndpoint.bEndpointAddress = USBDirection.OUT.to_endpoint_address(3)
|
||||
outEndpoint.wMaxPacketSize = self.MAX_PACKET_SIZE_MIDI
|
||||
midi_streaming_interface.add_subordinate_descriptor(outEndpoint)
|
||||
|
||||
outMidiEndpoint = midi1.ClassSpecificMidiStreamingBulkDataEndpointDescriptorEmitter()
|
||||
outMidiEndpoint.add_associated_jack(3)
|
||||
midi_streaming_interface.add_subordinate_descriptor(outMidiEndpoint)
|
||||
|
||||
inEndpoint = midi1.StandardMidiStreamingBulkDataEndpointDescriptorEmitter()
|
||||
inEndpoint.bEndpointAddress = USBDirection.IN.to_endpoint_address(3)
|
||||
inEndpoint.wMaxPacketSize = self.MAX_PACKET_SIZE_MIDI
|
||||
midi_streaming_interface.add_subordinate_descriptor(inEndpoint)
|
||||
|
||||
inMidiEndpoint = midi1.ClassSpecificMidiStreamingBulkDataEndpointDescriptorEmitter()
|
||||
inMidiEndpoint.add_associated_jack(1)
|
||||
midi_streaming_interface.add_subordinate_descriptor(inMidiEndpoint)
|
||||
|
||||
return (midi_interface, midi_streaming_interface)
|
||||
49
gateware/usb_stream_to_channels-bench.gtkw
Normal file
49
gateware/usb_stream_to_channels-bench.gtkw
Normal file
@@ -0,0 +1,49 @@
|
||||
[*]
|
||||
[*] GTKWave Analyzer v3.3.109 (w)1999-2020 BSI
|
||||
[*] Tue Aug 31 23:46:03 2021
|
||||
[*]
|
||||
[dumpfile] "usb_stream_to_channels.vcd"
|
||||
[dumpfile_mtime] "Tue Aug 31 23:45:35 2021"
|
||||
[dumpfile_size] 7952
|
||||
[savefile] "gateware/usb_stream_to_channels-bench.gtkw"
|
||||
[timestart] 0
|
||||
[size] 3828 2090
|
||||
[pos] -1 -1
|
||||
*-17.716705 169900 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
|
||||
[sst_width] 357
|
||||
[signals_width] 345
|
||||
[sst_expanded] 1
|
||||
[sst_vpaned_height] 628
|
||||
@28
|
||||
top.clk
|
||||
@200
|
||||
-Input Stream
|
||||
@28
|
||||
top.usb_first
|
||||
top.usb_valid
|
||||
top.out_ready
|
||||
@22
|
||||
top.usb_payload[7:0]
|
||||
@200
|
||||
-Core
|
||||
@28
|
||||
top.fsm_state
|
||||
@24
|
||||
top.out_channel_nr[2:0]
|
||||
@200
|
||||
-Output Stream
|
||||
@25
|
||||
[color] 2
|
||||
top.channel_nr[2:0]
|
||||
@22
|
||||
[color] 2
|
||||
top.payload$1[23:0]
|
||||
@28
|
||||
[color] 2
|
||||
top.valid$1
|
||||
[color] 2
|
||||
top.first$1
|
||||
[color] 2
|
||||
top.last
|
||||
[pattern_trace] 1
|
||||
[pattern_trace] 0
|
||||
49
gateware/usb_stream_to_channels-bench.py
Normal file
49
gateware/usb_stream_to_channels-bench.py
Normal file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
from usb_stream_to_channels import USBStreamToChannels
|
||||
from amaranth.sim import Simulator, Tick
|
||||
|
||||
if __name__ == "__main__":
|
||||
dut = USBStreamToChannels(8)
|
||||
|
||||
def send_one_frame(seamless=False, drop_valid=False, drop_ready=False):
|
||||
data = [n % 4 + (n//4 << 4) for n in range(32)]
|
||||
yield dut.no_channels_in.eq(8)
|
||||
yield dut.usb_stream_in.valid.eq(1)
|
||||
yield dut.usb_stream_in.first.eq(1)
|
||||
yield dut.channel_stream_out.ready.eq(1)
|
||||
for pos, byte in enumerate(data):
|
||||
yield dut.usb_stream_in.payload.eq(byte)
|
||||
yield Tick()
|
||||
yield dut.usb_stream_in.first.eq(0)
|
||||
if drop_valid and pos == 7 * 4 + 2:
|
||||
yield dut.usb_stream_in.valid.eq(0)
|
||||
for _ in range(4): yield Tick()
|
||||
yield dut.usb_stream_in.valid.eq(1)
|
||||
if drop_ready and pos == 7 * 2 + 3:
|
||||
yield dut.channel_stream_out.ready.eq(0)
|
||||
for _ in range(7): yield Tick()
|
||||
yield dut.channel_stream_out.ready.eq(1)
|
||||
yield dut.usb_stream_in.last.eq(1)
|
||||
yield dut.usb_stream_in.valid.eq(0)
|
||||
if not seamless:
|
||||
for _ in range(10): yield Tick()
|
||||
yield dut.usb_stream_in.first.eq(1)
|
||||
yield dut.usb_stream_in.payload.eq(data[0])
|
||||
yield dut.usb_stream_in.last.eq(0)
|
||||
|
||||
def process():
|
||||
yield dut.usb_stream_in.payload.eq(0xff)
|
||||
yield Tick()
|
||||
yield from send_one_frame()
|
||||
yield Tick()
|
||||
yield Tick()
|
||||
yield from send_one_frame(seamless=True, drop_valid=True)
|
||||
yield from send_one_frame(seamless=True, drop_ready=True)
|
||||
for _ in range(5): yield Tick()
|
||||
|
||||
sim = Simulator(dut)
|
||||
sim.add_clock(1.0/60e6,)
|
||||
sim.add_sync_process(process)
|
||||
|
||||
with sim.write_vcd(f'usb_stream_to_channels.vcd'):
|
||||
sim.run()
|
||||
91
gateware/usb_stream_to_channels.py
Normal file
91
gateware/usb_stream_to_channels.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from amaranth import *
|
||||
from amaranth.build import Platform
|
||||
|
||||
from amlib.stream import StreamInterface
|
||||
|
||||
class USBStreamToChannels(Elaboratable):
|
||||
def __init__(self, max_no_channels=2):
|
||||
# parameters
|
||||
self._max_nr_channels = max_no_channels
|
||||
self._channel_bits = Shape.cast(range(max_no_channels)).width
|
||||
|
||||
# ports
|
||||
self.no_channels_in = Signal(self._channel_bits + 1)
|
||||
self.usb_stream_in = StreamInterface()
|
||||
self.channel_stream_out = StreamInterface(payload_width=24, extra_fields=[("channel_nr", self._channel_bits)])
|
||||
self.garbage_seen_out = Signal()
|
||||
|
||||
def elaborate(self, platform: Platform) -> Module:
|
||||
m = Module()
|
||||
|
||||
out_channel_nr = Signal(self._channel_bits)
|
||||
out_sample = Signal(16)
|
||||
usb_valid = Signal()
|
||||
usb_first = Signal()
|
||||
usb_payload = Signal(8)
|
||||
out_ready = Signal()
|
||||
|
||||
last_channel = Signal(self._channel_bits)
|
||||
|
||||
m.d.comb += [
|
||||
usb_first.eq(self.usb_stream_in.first),
|
||||
usb_valid.eq(self.usb_stream_in.valid),
|
||||
usb_payload.eq(self.usb_stream_in.payload),
|
||||
out_ready.eq(self.channel_stream_out.ready),
|
||||
self.usb_stream_in.ready.eq(out_ready),
|
||||
last_channel.eq(self.no_channels_in - 1),
|
||||
]
|
||||
|
||||
m.d.sync += [
|
||||
self.channel_stream_out.valid.eq(0),
|
||||
self.channel_stream_out.first.eq(0),
|
||||
self.channel_stream_out.last.eq(0),
|
||||
]
|
||||
|
||||
with m.If(usb_valid & out_ready):
|
||||
with m.FSM() as fsm:
|
||||
with m.State("B0"):
|
||||
with m.If(usb_first):
|
||||
m.d.sync += out_channel_nr.eq(0)
|
||||
with m.Else():
|
||||
m.d.sync += out_channel_nr.eq(out_channel_nr + 1)
|
||||
m.next = "B1"
|
||||
|
||||
with m.State("B1"):
|
||||
with m.If(usb_first):
|
||||
m.d.sync += out_channel_nr.eq(0)
|
||||
m.d.comb += self.garbage_seen_out.eq(1)
|
||||
m.next = "B1"
|
||||
with m.Else():
|
||||
m.d.sync += out_sample[:8].eq(usb_payload)
|
||||
m.next = "B2"
|
||||
|
||||
with m.State("B2"):
|
||||
with m.If(usb_first):
|
||||
m.d.sync += out_channel_nr.eq(0)
|
||||
m.d.comb += self.garbage_seen_out.eq(1)
|
||||
m.next = "B1"
|
||||
with m.Else():
|
||||
m.d.sync += out_sample[8:16].eq(usb_payload)
|
||||
m.next = "B3"
|
||||
|
||||
with m.State("B3"):
|
||||
with m.If(usb_first):
|
||||
m.d.sync += out_channel_nr.eq(0)
|
||||
m.d.comb += self.garbage_seen_out.eq(1)
|
||||
m.next = "B1"
|
||||
with m.Else():
|
||||
m.d.sync += [
|
||||
self.channel_stream_out.payload.eq(Cat(out_sample, usb_payload)),
|
||||
self.channel_stream_out.valid.eq(1),
|
||||
self.channel_stream_out.channel_nr.eq(out_channel_nr),
|
||||
self.channel_stream_out.first.eq(out_channel_nr == 0),
|
||||
self.channel_stream_out.last.eq(out_channel_nr == last_channel),
|
||||
]
|
||||
|
||||
with m.If(out_channel_nr == last_channel):
|
||||
m.d.sync += out_channel_nr.eq(-1)
|
||||
|
||||
m.next = "B0"
|
||||
|
||||
return m
|
||||
Reference in New Issue
Block a user