Skip to content

Commit

Permalink
WIP: Add HyperRAM buffer to output pipeline.
Browse files Browse the repository at this point in the history
  • Loading branch information
martinling committed Jun 27, 2024
1 parent f9d19e8 commit 496cb9a
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 7 deletions.
2 changes: 1 addition & 1 deletion cynthion/python/src/gateware/analyzer/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class USBAnalyzer(Elaboratable):
# Support a maximum payload size of 1024B, plus a 1-byte PID and a 2-byte CRC16.
MAX_PACKET_SIZE_BYTES = 1024 + 1 + 2

def __init__(self, *, utmi_interface, mem_depth=32768):
def __init__(self, *, utmi_interface, mem_depth=4096):
"""
Parameters:
utmi_interface -- A record or elaboratable that presents a UTMI interface.
Expand Down
133 changes: 133 additions & 0 deletions cynthion/python/src/gateware/analyzer/fifo.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,141 @@
# SPDX-License-Identifier: BSD-3-Clause

from amaranth import Elaboratable, Module, Signal, Cat
from amaranth.lib.fifo import SyncFIFO

from luna.gateware.stream import StreamInterface
from luna.gateware.interface.psram import HyperRAMInterface, HyperRAMPHY


class StreamFIFO(Elaboratable):
def __init__(self, fifo):
self.fifo = fifo
self.input = StreamInterface(payload_width=fifo.width)
self.output = StreamInterface(payload_width=fifo.width)

def elaborate(self, platform):
m = Module()

m.submodules.fifo = self.fifo

m.d.comb += [
self.fifo.w_data .eq(self.input.payload),
self.fifo.w_en .eq(self.input.valid),
self.input.ready .eq(self.fifo.w_rdy),

self.output.payload .eq(self.fifo.r_data),
self.output.valid .eq(self.fifo.r_rdy),
self.fifo.r_en .eq(self.output.ready),
]

return m


class HyperRAMPacketFIFO(Elaboratable):
def __init__(self):
self.input = StreamInterface(payload_width=16)
self.output = StreamInterface(payload_width=16)

def elaborate(self, platform):
m = Module()

# HyperRAM submodules
ram_bus = platform.request('ram')
psram_phy = HyperRAMPHY(bus=ram_bus)
psram = HyperRAMInterface(phy=psram_phy.phy)
m.submodules += [psram_phy, psram]

# HyperRAM status
depth = 2 ** 22
write_address = Signal(range(depth))
read_address = Signal(range(depth))
word_count = Signal(range(depth + 1))
empty = Signal()
full = Signal()
m.d.comb += [
empty .eq(word_count == 0),
full .eq(word_count == depth),
]

# Update word count and pointers using the write and read strobes.
m.d.sync += word_count.eq(word_count - psram.read_ready + psram.write_ready)
with m.If(psram.read_ready):
m.d.sync += read_address.eq(read_address + 1)
with m.If(psram.write_ready):
m.d.sync += write_address.eq(write_address + 1)

# This tiny output buffer prevents data loss during consumer stalls
m.submodules.out_fifo = out_fifo = SyncFIFO(width=16, depth=2)

# Hook up our PSRAM.
m.d.comb += [
ram_bus.reset.o .eq(0),
psram.single_page .eq(0),
psram.register_space .eq(0),
psram.write_data .eq(self.input.payload),
self.input.ready .eq(psram.write_ready),

# Wire PSRAM -> output FIFO -> output stream
out_fifo.w_data .eq(psram.read_data),
out_fifo.w_en .eq(psram.read_ready),
self.output.payload .eq(out_fifo.r_data),
self.output.valid .eq(out_fifo.r_rdy),
out_fifo.r_en .eq(self.output.ready),
]

#
# HyperRAM Packet FIFO state machine
#
with m.FSM(domain="sync"):

# IDLE: Begin a write / read burst operation when ready.
with m.State("IDLE"):
with m.If(self.input.valid & ~full):
m.d.comb += [
psram.address .eq(write_address),
psram.perform_write .eq(1),
psram.start_transfer .eq(1),
psram.final_word .eq(self.input.last),
]
with m.If(psram.final_word):
m.next = "FINISH"
with m.Else():
m.next = "WRITE"

with m.Elif((out_fifo.level == 0) & ~empty):
m.d.comb += [
psram.address .eq(read_address),
psram.perform_write .eq(0),
psram.start_transfer .eq(1),
psram.final_word .eq(word_count == 1),
]
with m.If(psram.final_word):
m.next = "FINISH"
with m.Else():
m.next = "READ"

# WRITE: End the operation when there's no space or incoming data.
with m.State("WRITE"):
m.d.comb += [
psram.final_word.eq((word_count == (depth-1)) | self.input.last),
]
with m.If(psram.final_word):
m.next = "FINISH"

# READ: End the operation when PSRAM is empty or the consumer stalls the output stream.
with m.State("READ"):
m.d.comb += [
psram.final_word.eq((word_count == 1 + psram.read_ready) | ~self.output.ready),
]
with m.If(psram.final_word):
m.next = "FINISH"

# FINISH: Wait for the PSRAM to recover before a new transaction
with m.State("FINISH"):
with m.If(psram.idle):
m.next = "IDLE"

return m


class Stream16to8(Elaboratable):
Expand Down
22 changes: 16 additions & 6 deletions cynthion/python/src/gateware/analyzer/top.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
from datetime import datetime
from enum import IntEnum, IntFlag

from amaranth import Signal, Elaboratable, Module, DomainRenamer
from amaranth import Signal, Elaboratable, Module, DomainRenamer, ResetInserter
from amaranth.lib.fifo import AsyncFIFO
from amaranth.build.res import ResourceError
from usb_protocol.emitters import DeviceDescriptorCollection
from usb_protocol.types import USBRequestType, USBRequestRecipient
Expand All @@ -41,7 +42,7 @@
from usb_protocol.types.descriptors.microsoft10 import RegistryTypes

from .analyzer import USBAnalyzer
from .fifo import Stream16to8
from .fifo import Stream16to8, StreamFIFO, HyperRAMPacketFIFO

import cynthion

Expand Down Expand Up @@ -328,10 +329,16 @@ def elaborate(self, platform):
# Create a USB analyzer, and connect a register up to its output.
m.submodules.analyzer = analyzer = USBAnalyzer(utmi_interface=utmi)

# Convert the 16-bit stream to an 8-bit one for output.
# Follow this with a HyperRAM FIFO for additional buffering.
reset_on_start = ResetInserter(analyzer.discarding)
m.submodules.psram_fifo = psram_fifo = reset_on_start(HyperRAMPacketFIFO())

# Add an additional FIFO for 'sync' to 'usb' crossing.
m.submodules.out_fifo = out_fifo = StreamFIFO(reset_on_start(
AsyncFIFO(width=16, depth=4096, r_domain="usb", w_domain="sync")))

# And a 16-bit to 8-bit conversion.
m.submodules.s16to8 = s16to8 = DomainRenamer("usb")(Stream16to8())
# The converter input is the analyzer output.
m.d.comb += s16to8.input.stream_eq(analyzer.stream)

m.d.comb += [
# Connect enable signal to host-controlled state register.
Expand All @@ -343,7 +350,10 @@ def elaborate(self, platform):
# Discard data buffered by endpoint when the analyzer discards its data.
stream_ep.discard .eq(analyzer.discarding),

# USB stream uplink.
# USB stream pipeline.
psram_fifo.input .stream_eq(analyzer.stream),
out_fifo.input .stream_eq(psram_fifo.output),
s16to8.input .stream_eq(out_fifo.output),
stream_ep.stream .stream_eq(s16to8.output),

usb.connect .eq(1),
Expand Down

0 comments on commit 496cb9a

Please sign in to comment.