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

Binary file not shown.

View 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)

View 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")),
]

View 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.

View 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])

View 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")

View 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

View 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

View 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

View 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

View 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
View 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

View 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)

View 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

View 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

View 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

View File

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

587
gateware/debug.py Normal file
View 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()

View 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
View 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
View 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

View 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

View 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
View 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)

View 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

View 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()

View 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