From ce0678bde41a59a18be207a64f6f3adb6263985b Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Mon, 26 Jun 2023 15:30:42 +0200 Subject: [PATCH] repo: move soc components to luna-soc repository --- README.md | 3 +- applets/selftest/Makefile | 89 --- applets/selftest/minerva.h | 0 applets/selftest/platform.c | 63 -- applets/selftest/platform.h | 31 - applets/selftest/psram.c | 34 - applets/selftest/psram.h | 15 - applets/selftest/selftest.c | 251 ------- applets/selftest/selftest.ld | 45 -- applets/selftest/selftest_soc.py | 265 ------- applets/selftest/start.S | 81 -- applets/selftest/start.c | 99 --- applets/selftest/uart.c | 76 -- applets/selftest/uart.h | 38 - applets/selftest/ulpi.c | 96 --- applets/selftest/ulpi.h | 28 - applets/tinyusb_soc/Makefile | 39 - applets/tinyusb_soc/README.md | 15 - applets/tinyusb_soc/tinyusb_soc.py | 144 ---- docs/intro.rst | 12 +- examples/soc/bios/Makefile | 70 -- examples/soc/bios/bios_example.py | 96 --- examples/soc/bios/blinky.c | 51 -- examples/soc/bios/riscv_application.ld | 39 - examples/soc/bios/start.S | 34 - examples/soc/hello/Makefile | 59 -- examples/soc/hello/hello_world.c | 60 -- examples/soc/hello/hello_world.ld | 39 - examples/soc/hello/hello_world_soc.py | 103 --- examples/soc/hello/minerva.h | 0 examples/soc/hello/openocd.cfg | 10 - examples/soc/hello/start.S | 81 -- examples/usb/eptri/Makefile | 64 -- examples/usb/eptri/README.md | 19 - examples/usb/eptri/eptri_device.py | 109 --- examples/usb/eptri/eptri_example.c | 438 ----------- examples/usb/eptri/riscv_application.ld | 39 - examples/usb/eptri/riscv_standalone.ld | 44 -- examples/usb/eptri/start.S | 35 - luna/__init__.py | 40 +- luna/gateware/soc/__init__.py | 8 - luna/gateware/soc/cpu.py | 23 - luna/gateware/soc/memory.py | 163 ---- luna/gateware/soc/peripheral.py | 125 ---- luna/gateware/soc/simplesoc.py | 596 --------------- luna/gateware/soc/uart.py | 135 ---- luna/gateware/usb/usb2/device.py | 104 --- luna/gateware/usb/usb2/interfaces/__init__.py | 0 luna/gateware/usb/usb2/interfaces/eptri.py | 708 ------------------ pyproject.toml | 1 - requirements.txt | 1 - 51 files changed, 8 insertions(+), 4710 deletions(-) delete mode 100644 applets/selftest/Makefile delete mode 100644 applets/selftest/minerva.h delete mode 100644 applets/selftest/platform.c delete mode 100644 applets/selftest/platform.h delete mode 100644 applets/selftest/psram.c delete mode 100644 applets/selftest/psram.h delete mode 100644 applets/selftest/selftest.c delete mode 100644 applets/selftest/selftest.ld delete mode 100755 applets/selftest/selftest_soc.py delete mode 100644 applets/selftest/start.S delete mode 100644 applets/selftest/start.c delete mode 100644 applets/selftest/uart.c delete mode 100644 applets/selftest/uart.h delete mode 100644 applets/selftest/ulpi.c delete mode 100644 applets/selftest/ulpi.h delete mode 100644 applets/tinyusb_soc/Makefile delete mode 100644 applets/tinyusb_soc/README.md delete mode 100755 applets/tinyusb_soc/tinyusb_soc.py delete mode 100644 examples/soc/bios/Makefile delete mode 100755 examples/soc/bios/bios_example.py delete mode 100644 examples/soc/bios/blinky.c delete mode 100644 examples/soc/bios/riscv_application.ld delete mode 100644 examples/soc/bios/start.S delete mode 100644 examples/soc/hello/Makefile delete mode 100644 examples/soc/hello/hello_world.c delete mode 100644 examples/soc/hello/hello_world.ld delete mode 100755 examples/soc/hello/hello_world_soc.py delete mode 100644 examples/soc/hello/minerva.h delete mode 100644 examples/soc/hello/openocd.cfg delete mode 100644 examples/soc/hello/start.S delete mode 100644 examples/usb/eptri/Makefile delete mode 100644 examples/usb/eptri/README.md delete mode 100755 examples/usb/eptri/eptri_device.py delete mode 100644 examples/usb/eptri/eptri_example.c delete mode 100644 examples/usb/eptri/riscv_application.ld delete mode 100644 examples/usb/eptri/riscv_standalone.ld delete mode 100644 examples/usb/eptri/start.S delete mode 100644 luna/gateware/soc/__init__.py delete mode 100644 luna/gateware/soc/cpu.py delete mode 100644 luna/gateware/soc/memory.py delete mode 100644 luna/gateware/soc/peripheral.py delete mode 100644 luna/gateware/soc/simplesoc.py delete mode 100644 luna/gateware/soc/uart.py delete mode 100644 luna/gateware/usb/usb2/interfaces/__init__.py delete mode 100644 luna/gateware/usb/usb2/interfaces/eptri.py diff --git a/README.md b/README.md index 33f1c0066..f7509ed0f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # LUNA: an Amaranth HDL library for USB ![Simulation Status](https://github.com/greatscottgadgets/luna/workflows/simulations/badge.svg) [![Documentation Status](https://readthedocs.org/projects/luna/badge/?version=latest)](https://luna.readthedocs.io/en/latest/?badge=latest) LUNA is a toolkit for working with USB using FPGA technology, providing gateware and software to enable USB applications. @@ -7,7 +6,7 @@ Some things you can use LUNA for, currently: - **Protocol analysis for Low-, Full-, or High- speed USB.** LUNA provides gateware that allow passive USB monitoring when combined with [Cynthion](https://github.com/greatscottgadgets/cynthion-hardware) and [Packetry](https://github.com/greatscottgadgets/packetry). - **Creating your own Low-, Full-, High-, or (experimentally) Super- speed USB device.** LUNA provides a collection of Amaranth gateware that allows you to easily create USB devices in gateware, software, or a combination of the two. -- **Building USB functionality into a new or existing System-on-a-Chip (SoC).** LUNA is capable of generating custom peripherals targeting the common Wishbone bus; allowing it to easily be integrated into SoC designs; and the library provides simple automation for developing simple SoC designs. +- **Building USB functionality into a new or existing System-on-a-Chip (SoC).** LUNA is capable of generating custom peripherals targeting the common Wishbone bus; allowing it to easily be integrated into SoC designs; and the [luna-soc](https://github.com/greatscottgadgets/luna-soc) library provides simple automation for developing simple SoC designs. Some things you'll be able to use LUNA for in the future: diff --git a/applets/selftest/Makefile b/applets/selftest/Makefile deleted file mode 100644 index f59b4368e..000000000 --- a/applets/selftest/Makefile +++ /dev/null @@ -1,89 +0,0 @@ -# -# This file is part of LUNA. -# - -TARGET = selftest -PORT ?= /dev/ttyACM0 - -CROSS ?= riscv64-unknown-elf- - -CC = python -m ziglang cc -LLD = python -m ziglang ld.lld -OBJCOPY = $(CROSS)objcopy - -CFLAGS = \ - -target riscv32-freestanding-eabi \ - -march=generic_rv32 \ - -mabi=ilp32 \ - -g \ - -Os \ - -Iinclude \ - -ffreestanding \ - -nostdlib \ - -ffunction-sections \ - -fdata-sections \ - -T$(TARGET).ld - -LLDFLAGS = \ - -T$(TARGET).ld \ - --gc-sections \ - --oformat binary \ - -nostdlib \ - -static - -SOC = $(TARGET)_soc.py -#ASM_SOURCES = start.S -SOURCES = \ - start.c \ - $(TARGET).c \ - platform.c \ - uart.c \ - ulpi.c \ - psram.c - - -# By default, build our binary. -all: $(TARGET).bit - -# -# Generated files. -# - -soc.ld: $(SOC) - ./$(SOC) --generate-ld-script > $@ - -resources.h: $(SOC) - ./$(SOC) --generate-c-header > $@ - - -# -# Firmware binary. -# -# -$(TARGET).o: $(SOURCES) soc.ld resources.h - $(CC) $(CFLAGS) $(SOURCES) -c -o $@ - -$(TARGET).bin: $(TARGET).o - $(LLD) $(LLDFLAGS) $< -o $@ - -$(TARGET).bit: $(TARGET).bin $(SOC) - ./$(SOC) --dry-run -o $(TARGET).bit - - -# -# Virtual/command targets. -# - -.PHONY: clean program console - -clean: - rm -f $(TARGET).elf $(TARGET).o $(TARGET).bin $(SOC).bit soc.ld resources.h - -# Loads the self-test program onto our FPGA. -program: $(TARGET).bin $(SOC) - ./$(SOC) - -# Loads our "Hello world" program onto the FPGA. -run: $(TARGET).bit - apollo configure $(TARGET).bit - pyserial-miniterm $(PORT) 115200 diff --git a/applets/selftest/minerva.h b/applets/selftest/minerva.h deleted file mode 100644 index e69de29bb..000000000 diff --git a/applets/selftest/platform.c b/applets/selftest/platform.c deleted file mode 100644 index 9e5d27a24..000000000 --- a/applets/selftest/platform.c +++ /dev/null @@ -1,63 +0,0 @@ -/** - * This file is part of LUNA. - * - * Copyright (c) 2021 Great Scott Gadgets - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include "platform.h" - -void sleep_ms(uint16_t milliseconds) -{ - // Set our timer to count down from the relevant value... - timer_ctr_write(60 * 1000 * milliseconds); - - // And block until that time has passed. - while(timer_ctr_read()); -} - - -void platform_bringup(void) -{ - // Enable our timer for use as a simple, software count-down. - // We'll disable its event, and disable it from reloading, so it stays 0 when it's supposed to be. - timer_interrupt_disable(); - timer_reload_write(0); - timer_en_write(1); - - // Give the platform a few ms to start up before we enable the UART. - // This is useful on newer LUNA platforms, which multiplex their JTAG and UART. - sleep_ms(10); - - uart_interrupt_disable(); - uart_enabled_write(1); - uart_divisor_write(520); -} - -/** - * Waits for a given conditional to be False, or for a given timeout to pass. - * @returns 1 if the conditional timed out; or 0 otherwise - */ -int while_with_timeout(simple_conditional conditional, uint16_t timeout_ms) -{ - // Set our timer to count down from the timeout value. - timer_ctr_write(60 * 1000 * timeout_ms); - - while (1) { - - // If our conditional has become false, abort with success. - if (!conditional()) { - return 0; - } - - - // If our timer has run out, abort with failure. - if (!timer_ctr_read()) { - return 1; - } - } -} - -void dispatch_isr(void) -{ -} diff --git a/applets/selftest/platform.h b/applets/selftest/platform.h deleted file mode 100644 index cab918f9d..000000000 --- a/applets/selftest/platform.h +++ /dev/null @@ -1,31 +0,0 @@ -/** - * This file is part of LUNA. - * - * Copyright (c) 2021 Great Scott Gadgets - * SPDX-License-Identifier: BSD-3-Clause - */ - -#pragma once -#include "resources.h" - -/** - * Function pointer type for simple conditionals. - */ -typedef uint32_t (*simple_conditional)(void); - - -/** - * Sleeps for the provided number of milliseconds. - */ -void sleep_ms(uint16_t milliseconds); - -/** - * Waits for a given conditional to be False, or for a given timeout to pass. - * @returns 1 if the conditional timed out; or 0 otherwise - */ -int while_with_timeout(simple_conditional conditional, uint16_t timeout_ms); - -/** - * Performs initial platform bringup. - */ -void platform_bringup(void); diff --git a/applets/selftest/psram.c b/applets/selftest/psram.c deleted file mode 100644 index 544ede10f..000000000 --- a/applets/selftest/psram.c +++ /dev/null @@ -1,34 +0,0 @@ -/** - * This file is part of LUNA. - * - * Copyright (c) 2021 Great Scott Gadgets - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include "platform.h" -#include "psram.h" - -// FIXME: remove -#include "uart.h" - -/** - * Reads a value from a ULPI PHY register. - */ -uint32_t read_psram_register(uint32_t address) -{ - // Wait for things to become ready. - if(while_with_timeout(psram_busy_read, 100)) { - return -1; - } - - // Apply the address we're targeting. - psram_address_write(address); - - // Wait for things to be come ready. - if(while_with_timeout(psram_busy_read, 100)) { - return -1; - } - - // Finally, read the value back. - return psram_value_read(); -} \ No newline at end of file diff --git a/applets/selftest/psram.h b/applets/selftest/psram.h deleted file mode 100644 index 32d3ff3af..000000000 --- a/applets/selftest/psram.h +++ /dev/null @@ -1,15 +0,0 @@ -/** - * This file is part of LUNA. - * - * Copyright (c) 2021 Great Scott Gadgets - * SPDX-License-Identifier: BSD-3-Clause - */ - -#pragma once - -#include "resources.h" - -/** - * Reads a value from a ULPI PHY register. - */ -uint32_t read_psram_register(uint32_t address); diff --git a/applets/selftest/selftest.c b/applets/selftest/selftest.c deleted file mode 100644 index 84eb846fc..000000000 --- a/applets/selftest/selftest.c +++ /dev/null @@ -1,251 +0,0 @@ -/** - * This file is part of LUNA. - * - * Copyright (c) 2021 Great Scott Gadgets - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include "platform.h" -#include "uart.h" -#include "ulpi.h" -#include "psram.h" - -// Create a type alias for our tests. -typedef bool (*simple_test)(void); - - -/** - * Runs a named test. - */ -uint32_t run_test(char *description, simple_test test) -{ - // Identify which test we're running. - uart_puts(description); - - // Run the test, and print its results. - if (test()) { - uart_puts("✅ OK\n"); - return 0; - } else { - return 1; - } -} - - - -/** - * Core tests. - */ -bool debug_controller_tests(void) -{ - return true; -} - - -/** - * ULPI PHY tests. - */ -bool ulpi_phy_tests(enum ulpi_phy phy) -{ - int16_t scratch; - - // - // Check that the ULPI PHY matches the VID/PID for a Microchip USB3343. - // - const bool id_matches = - (read_ulpi_register(phy, 0) == 0x24) && - (read_ulpi_register(phy, 1) == 0x04) && - (read_ulpi_register(phy, 2) == 0x09) && - (read_ulpi_register(phy, 3) == 0x00); - if (!id_matches) { - uart_puts("❌ FAIL: PHY ID read failure! "); - return false; - } - - // - // Check that we can set the scratch register to every binary-numbered value. - // This checks each of the lines connected to the scratch register. - // - for (uint16_t i = 0; i < 8; i++) { - uint8_t mask = (1 << i); - - // Perform a write followed by a read, to make sure the write took. - // - // For now, we seem to have an issue somwhere in timing that makes it - // so these writes only take if multiply written. This doesn't affect actual - // gateware, so for now, we're duplicating the writes. - write_ulpi_register(phy, 0x16, mask); - write_ulpi_register(phy, 0x16, mask); - write_ulpi_register(phy, 0x16, mask); - - //write_ulpi_register(phy, 0x16, mask); - scratch = read_ulpi_register(phy, 0x16); - - if (scratch != mask) { - uart_puts("❌ FAIL: Scratch register readback failure (bit "); - print_char('0' + i); - uart_puts(")!\n"); - return false; - } - } - - return true; -} - - -bool target_phy_tests(void) -{ - return ulpi_phy_tests(TARGET_PHY); -} - -bool host_phy_tests(void) -{ - return ulpi_phy_tests(HOST_PHY); -} - -bool sideband_phy_tests(void) -{ - return ulpi_phy_tests(SIDEBAND_PHY); -} - - -/** - * RAM tests. - */ -bool ram_tests(void) -{ - // - // Check that the ULPI PHY matches the VID/PID for a Winbond or Cypress PSRAM. - // - const uint32_t psram_id = read_psram_register(0); - const bool id_matches = - (psram_id == 0x0c81) || - (psram_id == 0x0c86); - - - if (psram_id == 0xFFFF) { - uart_puts("❌ FAIL: RAM ID read failure! (RAM did not respond)\n"); - return false; - } - - if (!id_matches) { - uart_puts("❌ FAIL: RAM ID read failure! (was: "); - uart_print_word(psram_id); - uart_puts(")\n"); - return false; - } - - return true; -} - - -/** - * Identifies itself to the user. - */ -void print_greeting(void) -{ - uart_puts("\n _ _ _ _ _ ___ \n"); - uart_puts("| | | | | | \\ | | / _ \\ \n"); - uart_puts("| | | | | | \\| |/ /_\\ \\\n"); - uart_puts("| | | | | | . ` || _ |\n"); - uart_puts("| |___| |_| | |\\ || | | |\n"); - uart_puts("\\_____/\\___/\\_| \\_/\\_| |_/\n\n\b"); - - uart_puts("Self-test firmware booted. 🌙\n"); - uart_puts("Running on a Minerva RISC-V softcore on a "); - uart_puts(PLATFORM_NAME); - uart_puts(" board.\n\n"); -} - - -/** - * Core self-test routine. - */ -int main(void) -{ - uint32_t failures = 0; - - // Perform our platform initialization. - platform_bringup(); - - // Turn on the yellow LED, indicating that we're performing the tests. - leds_output_write(0b001000); - - // Wait for a bit, so we know the other side is listening and ready. - // FIXME: remove this? - sleep_ms(1000); - - // Print a nice header for our tests. - print_greeting(); - - while (1) { - char command = 0; - - //uart_puts("\n\n"); - //uart_puts("Press 's' to run the simple self-test (no connections required)."); - //uart_puts("Press 'f' to run the factory self-test (setup required).\n"); - //uart_puts("Press 'a' to run the full self-test (setup required).\n"); - //uart_puts("Press Ctrl+] to terminate test.\n"); - - // FIXME: enable test switching - - // Wait for input from the user. - //while (!command) { - // command = uart_getchar(); - //} - command = 's'; - - switch(command) { - - // Run all tests. - case 'a': - // Falls through. - - // Run factory tests. - case 'f': - // Falls through. - - // Run our core tests. - case 's': - command = 0; - - failures += run_test("Debug controller & communications: ", debug_controller_tests); - failures += run_test("Target ULPI PHY: ", target_phy_tests); - failures += run_test("Host ULPI PHY: ", host_phy_tests); - failures += run_test("Sideband ULPI PHY: ", sideband_phy_tests); - failures += run_test("External RAM: ", ram_tests); - - uart_puts("\n\n"); - - if (failures) { - - // Indicate our failure via serial... - uart_puts("❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌\n"); - uart_puts("------------------------------------------------\n"); - uart_puts("--------------- TESTS FAILED! ------------------\n"); - uart_puts("------------------------------------------------\n"); - uart_puts("❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌\n"); - - // ... and turn on the red LED. - leds_output_write(0b100000); - } - - else { - // Indicate success, and turn on the green LED. - leds_output_write(0b000100); - uart_puts("All tests passed. ✅ \n\n"); - } - - // FIXME: remove this - uart_puts("Press Ctrl+] to terminate test.\n"); - while(1); - - break; - - default: - uart_puts("Unknown command.\n"); - } - - } -} - diff --git a/applets/selftest/selftest.ld b/applets/selftest/selftest.ld deleted file mode 100644 index c3838aab3..000000000 --- a/applets/selftest/selftest.ld +++ /dev/null @@ -1,45 +0,0 @@ -OUTPUT_FORMAT("elf32-littleriscv") -OUTPUT_ARCH("riscv") -ENTRY(_start) - -MEMORY -{ - rom : ORIGIN = 0x00000000, LENGTH = 0x00004000 - ram : ORIGIN = 0x00004000, LENGTH = 0x00004000 -} - -SECTIONS -{ - . = 0x00000000; - - /* Start of day code. */ - .init : - { - *(.init) *(.init.*) - } > rom - .text : - { - *(.text) *(.text.*) - } > rom - - .rodata : - { - *(.rodata) *(.rodata.*) - } > rom - .sdata : - { - PROVIDE(__global_pointer$ = .); - *(.sdata) *(.sdata.*) - } > ram - .data : - { - *(.data) *(.data.*) - } > ram - .bss : - { - *(.bss) *(.bss.*) - } > ram - -} - -PROVIDE(__stack_top = ORIGIN(ram) + LENGTH(ram)); diff --git a/applets/selftest/selftest_soc.py b/applets/selftest/selftest_soc.py deleted file mode 100755 index 43d20f5e1..000000000 --- a/applets/selftest/selftest_soc.py +++ /dev/null @@ -1,265 +0,0 @@ -#!/usr/bin/env python3 -# -# This file is part of LUNA. -# -# Copyright (c) 2020 Great Scott Gadgets -# SPDX-License-Identifier: BSD-3-Clause - -from amaranth import * -from amaranth.hdl.xfrm import DomainRenamer - -from lambdasoc.periph import Peripheral -from lambdasoc.periph.timer import TimerPeripheral - -from luna import top_level_cli -from luna.gateware.soc import SimpleSoC, UARTPeripheral -from luna.gateware.interface.ulpi import ULPIRegisterWindow -from luna.gateware.interface.psram import HyperRAMInterface - - -# Run our tests at a slower clock rate, for now. -# TODO: bump up the fast clock rate, to test the HyperRAM at speed? -CLOCK_FREQUENCIES_MHZ = { - "fast": 120, - "sync": 60, - "usb": 60 -} - - -class LEDPeripheral(Peripheral, Elaboratable): - """ Simple peripheral that controls the board's LEDs. """ - - def __init__(self, name="leds"): - super().__init__(name=name) - - # Create our LED register. - bank = self.csr_bank() - self._output = bank.csr(6, "rw") - - # ... and convert our register into a Wishbone peripheral. - self._bridge = self.bridge(data_width=32, granularity=8, alignment=2) - self.bus = self._bridge.bus - - - def elaborate(self, platform): - m = Module() - m.submodules.bridge = self._bridge - - # Grab our LEDS... - leds = Cat(platform.request("led", i) for i in range(6)) - - # ... and update them on each register write. - with m.If(self._output.w_stb): - m.d.sync += [ - self._output.r_data .eq(self._output.w_data), - leds .eq(self._output.w_data), - ] - - return m - - - -class ULPIRegisterPeripheral(Peripheral, Elaboratable): - """ Peripheral that provides access to a ULPI PHY, and its registers. """ - - def __init__(self, name="ulpi", io_resource_name="usb"): - super().__init__(name=name) - self._io_resource = io_resource_name - - # Create our registers... - bank = self.csr_bank() - self._address = bank.csr(8, "w") - self._value = bank.csr(8, "rw") - self._busy = bank.csr(1, "r") - - # ... and convert our register into a Wishbone peripheral. - self._bridge = self.bridge(data_width=32, granularity=8, alignment=2) - self.bus = self._bridge.bus - - - def elaborate(self, platform): - m = Module() - m.submodules.bridge = self._bridge - - # Grab a connection to our ULPI PHY. - target_ulpi = platform.request(self._io_resource) - - # - # ULPI Register Window - # - ulpi_reg_window = ULPIRegisterWindow() - m.submodules += ulpi_reg_window - - # Connect up the window. - m.d.comb += [ - ulpi_reg_window.ulpi_data_in .eq(target_ulpi.data.i), - ulpi_reg_window.ulpi_dir .eq(target_ulpi.dir.i), - ulpi_reg_window.ulpi_next .eq(target_ulpi.nxt.i), - - target_ulpi.clk .eq(ClockSignal("usb")), - target_ulpi.rst .eq(ResetSignal("usb")), - target_ulpi.stp .eq(ulpi_reg_window.ulpi_stop), - target_ulpi.data.o .eq(ulpi_reg_window.ulpi_data_out), - target_ulpi.data.oe .eq(~target_ulpi.dir.i) - ] - - # - # Address register logic. - # - - # Perform a read request whenever the user writes to ULPI address... - m.d.sync += ulpi_reg_window.read_request.eq(self._address.w_stb) - - # And update the register address accordingly. - with m.If(self._address.w_stb): - m.d.sync += ulpi_reg_window.address.eq(self._address.w_data) - - - # - # Value register logic. - # - - # Always report back the last read data. - m.d.comb += self._value.r_data.eq(ulpi_reg_window.read_data) - - # Perform a write whenever the user writes to our ULPI value. - m.d.sync += ulpi_reg_window.write_request.eq(self._value.w_stb) - with m.If(self._address.w_stb): - m.d.sync += ulpi_reg_window.write_data.eq(self._value.w_data) - - - # - # Busy register logic. - # - m.d.comb += self._busy.r_data.eq(ulpi_reg_window.busy) - - return m - - -class PSRAMRegisterPeripheral(Peripheral, Elaboratable): - """ Peripheral that provides access to a ULPI PHY, and its registers. """ - - def __init__(self, name="ram"): - super().__init__(name=name) - - # Create our registers... - bank = self.csr_bank() - self._address = bank.csr(32, "w") - self._value = bank.csr(32, "r") - self._busy = bank.csr(1, "r") - - # ... and convert our register into a Wishbone peripheral. - self._bridge = self.bridge(data_width=32, granularity=8, alignment=2) - self.bus = self._bridge.bus - - - def elaborate(self, platform): - m = Module() - m.submodules.bridge = self._bridge - - # - # HyperRAM interface window. - # - ram_bus = platform.request('ram') - m.submodules.psram = psram = HyperRAMInterface(bus=ram_bus, clock_skew=platform.ram_timings['clock_skew']) - - # Hook up our PSRAM. - m.d.comb += [ - ram_bus.reset .eq(0), - psram.single_page .eq(0), - psram.perform_write .eq(0), - psram.register_space .eq(1), - psram.final_word .eq(1), - ] - - # - # Address register logic. - # - - # Perform a read request whenever the user writes the address register... - m.d.sync += psram.start_transfer.eq(self._address.w_stb) - - # And update the register address accordingly. - with m.If(self._address.w_stb): - m.d.sync += psram.address.eq(self._address.w_data) - - - # - # Value register logic. - # - - # Always report back the last read data. - with m.If(psram.new_data_ready): - m.d.sync += self._value.r_data.eq(psram.read_data) - - - # - # Busy register logic. - # - m.d.comb += self._busy.r_data.eq(~psram.idle) - - return m - - - -class SelftestCore(Elaboratable): - """ Simple soft-core that executes the LUNA factory tests. """ - - def __init__(self): - clock_freq = 60e6 - - # Create our SoC... - self.soc = soc = SimpleSoC() - - # ... add a ROM for firmware... - soc.add_rom('selftest.bin', size=0x4000) - - # ... and a RAM for execution. - soc.add_ram(0x4000) - - # ... add our UART peripheral... - self.uart = uart = UARTPeripheral(divisor=int(clock_freq // 115200)) - soc.add_peripheral(uart) - - # ... add a timer, so our software can get precise timing. - self.timer = TimerPeripheral(32) - soc.add_peripheral(self.timer) - - # ... and add our peripherals under test. - peripherals = ( - LEDPeripheral(name="leds"), - ULPIRegisterPeripheral(name="target_ulpi", io_resource_name="target_phy"), - ULPIRegisterPeripheral(name="host_ulpi", io_resource_name="host_phy"), - ULPIRegisterPeripheral(name="sideband_ulpi", io_resource_name="sideband_phy"), - PSRAMRegisterPeripheral(name="psram"), - ) - - for peripheral in peripherals: - soc.add_peripheral(peripheral) - - - - def elaborate(self, platform): - m = Module() - - m.submodules.car = platform.clock_domain_generator(clock_frequencies=CLOCK_FREQUENCIES_MHZ) - - # Add our SoC to the design... - m.submodules.soc = self.soc - - # ... and connect up its UART. - uart_io = platform.request("uart", 0) - m.d.comb += [ - uart_io.tx .eq(self.uart.tx), - self.uart.rx .eq(uart_io.rx), - ] - - if hasattr(uart_io.tx, 'oe'): - m.d.comb += uart_io.tx.oe.eq(self.uart.driving & self.uart.enabled), - - return m - - -if __name__ == "__main__": - design = SelftestCore() - top_level_cli(design, cli_soc=design.soc) diff --git a/applets/selftest/start.S b/applets/selftest/start.S deleted file mode 100644 index 71859f50e..000000000 --- a/applets/selftest/start.S +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2020 Great Scott Gadgets - * SPDX-License-Identifier: BSD-3-Clause - */ - -#define MIE_MEIE 0x800 - -.section .init, "ax" - -.global _start -_start: - /* Set up our global pointer. */ - .option push - .option norelax - la gp, __global_pointer$ - .option pop - - /* Set up our primary interrut dispatcher. */ - la t0, _interrupt_handler - csrw mtvec, t0 - - /* Set up our stack. */ - la sp, __stack_top - add s0, sp, zero - - /* - * NOTE: In most cases, we'd clear the BSS, here. - * - * In our case, our FPGA automaticaly starts with all of our RAM - * initialized to zero; so our BSS comes pre-cleared. We'll skip the - * formality of re-clearing it. - */ - - /* Enable interrupts. */ - li t0, MIE_MEIE - csrs mie, t0 - - /* Finally, start our main routine. */ - jal zero, main - - -/** - * Re-entry point for interrupts. - */ -_interrupt_handler: - addi sp, sp, -16 * 4 - sw ra, 0 * 4(sp) - sw t0, 1 * 4(sp) - sw t1, 2 * 4(sp) - sw t2, 3 * 4(sp) - sw a0, 4 * 4(sp) - sw a1, 5 * 4(sp) - sw a2, 6 * 4(sp) - sw a3, 7 * 4(sp) - sw a4, 8 * 4(sp) - sw a5, 9 * 4(sp) - sw a6, 10 * 4(sp) - sw a7, 11 * 4(sp) - sw t3, 12 * 4(sp) - sw t4, 13 * 4(sp) - sw t5, 14 * 4(sp) - sw t6, 15 * 4(sp) - call dispatch_isr - lw ra, 0 * 4(sp) - lw t0, 1 * 4(sp) - lw t1, 2 * 4(sp) - lw t2, 3 * 4(sp) - lw a0, 4 * 4(sp) - lw a1, 5 * 4(sp) - lw a2, 6 * 4(sp) - lw a3, 7 * 4(sp) - lw a4, 8 * 4(sp) - lw a5, 9 * 4(sp) - lw a6, 10 * 4(sp) - lw a7, 11 * 4(sp) - lw t3, 12 * 4(sp) - lw t4, 13 * 4(sp) - lw t5, 14 * 4(sp) - lw t6, 15 * 4(sp) - addi sp, sp, 16*4 - mret diff --git a/applets/selftest/start.c b/applets/selftest/start.c deleted file mode 100644 index 005270fa6..000000000 --- a/applets/selftest/start.c +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2020 Great Scott Gadgets - * SPDX-License-Identifier: BSD-3-Clause - */ - - -__attribute__((naked,section(".init"))) void _start(void) -{ - asm( - /* Set up our global pointer. */ - ".option push\n\t" - ".option norelax\n\t" - "la gp, __global_pointer$\n\t" - ".option pop\n\t" - - /* Set up our primary interrut dispatcher. */ - "la t0, _interrupt_handler \n\t" - "csrw mtvec, t0\n\t" - - /* Set up our stack. */ - "la sp, __stack_top\n\t" - "add s0, sp, zero\n\t" - - /* - * NOTE: In most cases, we'd clear the BSS, here. - * - * In our case, our FPGA automaticaly starts with all of our RAM - * initialized to zero; so our BSS comes pre-cleared. We'll skip the - * formality of re-clearing it. - */ - - /* Enable interrupts. */ - "li t0, 0x800\n\t" - "csrs mie, t0\n\t" - - /* Finally, start our main routine. */ - "jal zero, main \n\t" - ); -} - - -__attribute__((naked,section(".init"))) void _interrupt_handler(void) -{ - asm( - "addi sp, sp, -16 * 4\n\t" - "sw ra, 0 * 4(sp)\n\t" - "sw t0, 1 * 4(sp)\n\t" - "sw t1, 2 * 4(sp)\n\t" - "sw t2, 3 * 4(sp)\n\t" - "sw a0, 4 * 4(sp)\n\t" - "sw a1, 5 * 4(sp)\n\t" - "sw a2, 6 * 4(sp)\n\t" - "sw a3, 7 * 4(sp)\n\t" - "sw a4, 8 * 4(sp)\n\t" - "sw a5, 9 * 4(sp)\n\t" - "sw a6, 10 * 4(sp)\n\t" - "sw a7, 11 * 4(sp)\n\t" - "sw t3, 12 * 4(sp)\n\t" - "sw t4, 13 * 4(sp)\n\t" - "sw t5, 14 * 4(sp)\n\t" - "sw t6, 15 * 4(sp)\n\t" - "call dispatch_isr\n\t" - "lw ra, 0 * 4(sp)\n\t" - "lw t0, 1 * 4(sp)\n\t" - "lw t1, 2 * 4(sp)\n\t" - "lw t2, 3 * 4(sp)\n\t" - "lw a0, 4 * 4(sp)\n\t" - "lw a1, 5 * 4(sp)\n\t" - "lw a2, 6 * 4(sp)\n\t" - "lw a3, 7 * 4(sp)\n\t" - "lw a4, 8 * 4(sp)\n\t" - "lw a5, 9 * 4(sp)\n\t" - "lw a6, 10 * 4(sp)\n\t" - "lw a7, 11 * 4(sp)\n\t" - "lw t3, 12 * 4(sp)\n\t" - "lw t4, 13 * 4(sp)\n\t" - "lw t5, 14 * 4(sp)\n\t" - "lw t6, 15 * 4(sp)\n\t" - "addi sp, sp, 16*4\n\t" - "mret\n\t" - ); -} - - -asm( - ".global __mulsi3\n\t" - "__mulsi3:\n\t" - "li a2,0\n\t" - "beqz a0,24\n\t" - "andi a3,a0,1\n\t" - "neg a3,a3\n\t" - "and a3,a3,a1\n\t" - "add a2,a3,a2\n\t" - "srli a0,a0,0x1\n\t" - "slli a1,a1,0x1\n\t" - "bnez a0,8\n\t" - "mv a0,a2\n\t" - "ret\n\t" -); diff --git a/applets/selftest/uart.c b/applets/selftest/uart.c deleted file mode 100644 index 838320153..000000000 --- a/applets/selftest/uart.c +++ /dev/null @@ -1,76 +0,0 @@ -/** - * This file is part of LUNA. - * - * Copyright (c) 2021 Great Scott Gadgets - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include "uart.h" - -/** - * Transmits a single charater over our example UART. - */ -void print_char(char c) -{ - while(!uart_tx_rdy_read()); - uart_tx_data_write(c); -} - - -/** - * Receives a single character from the UART. Blocking. - */ -char uart_getchar(void) -{ - while(!uart_rx_rdy_read()); - return uart_rx_data_read(); -} - - -/** - * Transmits a string over our UART. - */ -void uart_puts(char *str) -{ - for (char *c = str; *c; ++c) { - if (*c == '\n') { - print_char('\r'); - } - - print_char(*c); - } -} - - -static void uart_put_hexit(uint8_t hexit) -{ - if (hexit < 10) { - print_char(hexit + '0'); - } - else { - print_char((hexit - 10) + 'A'); - } -} - -/** - * Prints the hex value of a byte to the UART console. - */ -void uart_print_byte(uint8_t value) -{ - uart_puts("0x"); - uart_put_hexit(value >> 4); - uart_put_hexit(value & 0x0f); -} - - -/** - * Prints the hex value of a byte to the UART console. - */ -void uart_print_word(uint16_t value) -{ - uart_puts("0x"); - uart_put_hexit((value >> 12) & 0x0f); - uart_put_hexit((value >> 8) & 0x0f); - uart_put_hexit((value >> 4) & 0x0f); - uart_put_hexit((value >> 0) & 0x0f); -} \ No newline at end of file diff --git a/applets/selftest/uart.h b/applets/selftest/uart.h deleted file mode 100644 index e0cf5ff8e..000000000 --- a/applets/selftest/uart.h +++ /dev/null @@ -1,38 +0,0 @@ -/** - * This file is part of LUNA. - * - * Copyright (c) 2021 Great Scott Gadgets - * SPDX-License-Identifier: BSD-3-Clause - */ - -#pragma once - -#include "resources.h" - -/** - * Transmits a single charater over our example UART. - */ -void print_char(char c); - -/** - * Receives a single character from the UART. Blocking. - */ -char uart_getchar(void); - - -/** - * Transmits a string over our UART. - */ -void uart_puts(char *str); - - -/** - * Prints the hex value of a byte to the UART console. - */ -void uart_print_byte(uint8_t value); - -/** - * - * Prints the hex value of a uint16_t to the UART console. - */ -void uart_print_word(uint16_t value); \ No newline at end of file diff --git a/applets/selftest/ulpi.c b/applets/selftest/ulpi.c deleted file mode 100644 index ea53b7f43..000000000 --- a/applets/selftest/ulpi.c +++ /dev/null @@ -1,96 +0,0 @@ -/** - * This file is part of LUNA. - * - * Copyright (c) 2021 Great Scott Gadgets - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include "platform.h" -#include "ulpi.h" - -/** - * Reads a value from a ULPI PHY register. - */ -int16_t read_ulpi_register(enum ulpi_phy phy, uint8_t address) -{ - switch (phy) { - case TARGET_PHY: - if (while_with_timeout(target_ulpi_busy_read, 100)) { - return -1; - } - target_ulpi_address_write(address); - - if (while_with_timeout(target_ulpi_busy_read, 100)) { - return -1; - } - return target_ulpi_value_read(); - - case HOST_PHY: - if (while_with_timeout(host_ulpi_busy_read, 100)) { - return -1; - } - host_ulpi_address_write(address); - - if (while_with_timeout(host_ulpi_busy_read, 100)) { - return -1; - } - return host_ulpi_value_read(); - case SIDEBAND_PHY: - if (while_with_timeout(sideband_ulpi_busy_read, 100)) { - return -1; - } - sideband_ulpi_address_write(address); - - if (while_with_timeout(sideband_ulpi_busy_read, 100)) { - return -1; - } - return sideband_ulpi_value_read(); - } -} - - - -/** - * Writes a value to a ULPI PHY register. - */ -int write_ulpi_register(enum ulpi_phy phy, uint8_t address, uint8_t value) -{ - switch (phy) { - case TARGET_PHY: - if (while_with_timeout(target_ulpi_busy_read, 100)) { - return -1; - } - target_ulpi_address_write(address); - - if (while_with_timeout(target_ulpi_busy_read, 100)) { - return -1; - } - target_ulpi_value_write(value); - break; - - case HOST_PHY: - if (while_with_timeout(host_ulpi_busy_read, 100)) { - return -1; - } - host_ulpi_address_write(address); - - if (while_with_timeout(host_ulpi_busy_read, 100)) { - return -1; - } - host_ulpi_value_write(value); - break; - case SIDEBAND_PHY: - if (while_with_timeout(sideband_ulpi_busy_read, 100)) { - return -1; - } - sideband_ulpi_address_write(address); - - if (while_with_timeout(sideband_ulpi_busy_read, 100)) { - return -1; - } - sideband_ulpi_value_write(value); - break; - } - - return 0; -} diff --git a/applets/selftest/ulpi.h b/applets/selftest/ulpi.h deleted file mode 100644 index 6413ff75b..000000000 --- a/applets/selftest/ulpi.h +++ /dev/null @@ -1,28 +0,0 @@ -/** - * This file is part of LUNA. - * - * Copyright (c) 2021 Great Scott Gadgets - * SPDX-License-Identifier: BSD-3-Clause - */ - -#pragma once - -#include "resources.h" - -// Type representing each of our PHYs. -enum ulpi_phy { - TARGET_PHY, - HOST_PHY, - SIDEBAND_PHY -}; - - -/** - * Reads a value from a ULPI PHY register. - */ -int16_t read_ulpi_register(enum ulpi_phy phy, uint8_t address); - -/** - * Writes a value to a ULPI PHY register. - */ -int write_ulpi_register(enum ulpi_phy phy, uint8_t address, uint8_t value); diff --git a/applets/tinyusb_soc/Makefile b/applets/tinyusb_soc/Makefile deleted file mode 100644 index c4b82c58a..000000000 --- a/applets/tinyusb_soc/Makefile +++ /dev/null @@ -1,39 +0,0 @@ -# -# This file is part of LUNA. -# - -SOC = tinyusb_soc.py - -# By default, build our SDK. -all: soc.bit soc.ld resources.h - -.PHONY: clean program reprogram flash-soc configure - -# -# SDK contents. -# - -soc.ld: $(SOC) - ./$(SOC) --generate-ld-script > $@ - -resources.h: $(SOC) - ./$(SOC) --generate-c-header > $@ - -soc.bit: $(SOC) - ./$(SOC) -o soc.bit - -# -# Helpers. -# - -# Load our SoC onto the FPGA... -configure: $(SOC) - ./$(SOC) - -# Flash the FPGA's ROM with our SoC. -flash-soc: $(SOC) - ./$(SOC) --flash - -clean: - rm -f $(TARGET).elf $(TARGET).bin soc.ld resources.h soc.bit - diff --git a/applets/tinyusb_soc/README.md b/applets/tinyusb_soc/README.md deleted file mode 100644 index af8e9d464..000000000 --- a/applets/tinyusb_soc/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# TinyUSB SoC Example - -This folder contains a minimal SoC usable with TinyUSB. Typically, you'll build this SoC once, flash it -onto your LUNA device, and then move on to using TinyUSB to load programs onto your SoC. - -To flash this SoC onto your LUNA: - -```sh -# Build and flash: -$ make flash-soc -``` - -## Using TinyUSB - -Not yet supported -- check back soon! diff --git a/applets/tinyusb_soc/tinyusb_soc.py b/applets/tinyusb_soc/tinyusb_soc.py deleted file mode 100755 index 7649be8a9..000000000 --- a/applets/tinyusb_soc/tinyusb_soc.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env python3 -# -# This file is part of LUNA. -# -# Copyright (c) 2020 Great Scott Gadgets -# SPDX-License-Identifier: BSD-3-Clause - -import sys -import logging -import os.path - -from amaranth import Elaboratable, Module, Cat -from amaranth.hdl.rec import Record - -from lambdasoc.periph import Peripheral -from lambdasoc.periph.serial import AsyncSerialPeripheral -from lambdasoc.periph.timer import TimerPeripheral - -from luna import top_level_cli -from luna.gateware.soc import SimpleSoC - -from luna.gateware.usb.usb2.device import USBDevice, USBDeviceController -from luna.gateware.usb.usb2.interfaces.eptri import SetupFIFOInterface, InFIFOInterface, OutFIFOInterface - - -CLOCK_FREQUENCIES_MHZ = { - 'sync': 60 -} - - -class LEDPeripheral(Peripheral, Elaboratable): - """ Example peripheral that controls the board's LEDs. """ - - def __init__(self): - super().__init__() - - # Create our LED register. - # Note that there's a bunch of 'magic' that goes on behind the scenes, here: - # a memory address will automatically be reserved for this register in the address - # space it's attached to; and the SoC utilities will automatically generate header - # entires and stub functions for it. - bank = self.csr_bank() - self._output = bank.csr(6, "w") - - # ... and convert our register into a Wishbone peripheral. - self._bridge = self.bridge(data_width=32, granularity=8, alignment=2) - self.bus = self._bridge.bus - - - def elaborate(self, platform): - m = Module() - m.submodules.bridge = self._bridge - - # Grab our LEDS... - leds = Cat(platform.request("led", i) for i in range(6)) - - # ... and update them on each register write. - with m.If(self._output.w_stb): - m.d.sync += leds.eq(self._output.w_data) - - return m - - - -class TinyUSBSoC(Elaboratable): - """ Simple SoC for hosting TinyUSB. """ - - RAM_SIZE = 0x0001_0000 - - RAM_ADDRESS = 0x0004_0000 - USB_CORE_ADDRESS = 0x0005_0000 - USB_SETUP_ADDRESS = 0x0006_0000 - USB_IN_ADDRESS = 0x0007_0000 - USB_OUT_ADDRESS = 0x0008_0000 - LEDS_ADDRESS = 0x0009_0000 - - def __init__(self): - - # Create a stand-in for our UART. - self.uart_pins = Record([ - ('rx', [('i', 1)]), - ('tx', [('o', 1)]) - ]) - - # Create our SoC... - self.soc = soc = SimpleSoC() - soc.add_bios_and_peripherals(uart_pins=self.uart_pins, fixed_addresses=True) - - # ... add some bulk RAM ... - soc.add_ram(self.RAM_SIZE, addr=self.RAM_ADDRESS) - - # ... a core USB controller ... - self.usb_device_controller = USBDeviceController() - soc.add_peripheral(self.usb_device_controller, addr=self.USB_CORE_ADDRESS) - - # ... our eptri peripherals. - self.usb_setup = SetupFIFOInterface() - soc.add_peripheral(self.usb_setup, as_submodule=False, addr=self.USB_SETUP_ADDRESS) - - self.usb_in_ep = InFIFOInterface() - soc.add_peripheral(self.usb_in_ep, as_submodule=False, addr=self.USB_IN_ADDRESS) - - self.usb_out_ep = OutFIFOInterface() - soc.add_peripheral(self.usb_out_ep, as_submodule=False, addr=self.USB_OUT_ADDRESS) - - # ... and our LED peripheral, for simple output. - leds = LEDPeripheral() - soc.add_peripheral(leds, addr=self.LEDS_ADDRESS) - - - def elaborate(self, platform): - m = Module() - m.submodules.soc = self.soc - - # Generate our domain clocks/resets. - m.submodules.car = platform.clock_domain_generator(clock_frequencies=CLOCK_FREQUENCIES_MHZ) - - # Connect up our UART. - uart_io = platform.request("uart", 0) - m.d.comb += [ - uart_io.tx .eq(self.uart_pins.tx), - self.uart_pins.rx .eq(uart_io.rx) - ] - - if hasattr(uart_io.tx, 'oe'): - m.d.comb += uart_io.tx.oe.eq(~self.soc.uart._phy.tx.rdy), - - # Create our USB device. - ulpi = platform.request(platform.default_usb_connection) - m.submodules.usb = usb = USBDevice(bus=ulpi) - - # Connect up our device controller. - m.d.comb += self.usb_device_controller.attach(usb) - - # Add our eptri endpoint handlers. - usb.add_endpoint(self.usb_setup) - usb.add_endpoint(self.usb_in_ep) - usb.add_endpoint(self.usb_out_ep) - return m - - -if __name__ == "__main__": - design = TinyUSBSoC() - top_level_cli(design, cli_soc=design.soc) diff --git a/docs/intro.rst b/docs/intro.rst index fcafe9cf9..0c8404cef 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -1,16 +1,15 @@ - ============ Introduction ============ - *Note: LUNA is still a work in progress; and while much of the technology is in a usable state, - much of its feature-set is still being built. Consider LUNA an 'unstable' library, for the time being.* + *Note: LUNA is still a work in progress; and while much of the technology is in a usable state, + much of its feature-set is still being built. Consider LUNA an 'unstable' library, for the time being.* Welcome to the LUNA project! LUNA is a full toolkit for working with USB using FPGA technology; and provides hardware, gateware, and software to enable USB applications. .. image:: images/board_readme.jpg - :align: center + :align: center .. Some things you can use LUNA for, currently: @@ -21,8 +20,9 @@ Some things you can use LUNA for, currently: - **Creating your own Low, Full or High speed USB device.** LUNA provides a collection of Amaranth gateware that allows you to easily create USB devices in gateware, software, or a combination of the two. - **Building USB functionality into a new or existing System-on-a-Chip (SoC).** LUNA is capable of generating custom - peripherals targeting the common Wishbone bus; allowing it to easily be integrated into SoC designs; and the library - provides simple automation for developing simple SoC designs. + peripherals targeting the common Wishbone bus; allowing it to easily be integrated into SoC designs; and the + [luna-soc](https://github.com/greatscottgadgets/luna-soc) library provides simple automation for developing simple + SoC designs. Some things you'll be able to use LUNA for in the future: diff --git a/examples/soc/bios/Makefile b/examples/soc/bios/Makefile deleted file mode 100644 index 4a3d800fb..000000000 --- a/examples/soc/bios/Makefile +++ /dev/null @@ -1,70 +0,0 @@ -# -# This file is part of LUNA. -# - -TARGET = blinky -BAUDRATE = 115200 -SERIALPORT ?= /dev/ttyACM0 - -CROSS ?= riscv64-unknown-elf- - -CC = $(CROSS)gcc -OBJCOPY = $(CROSS)objcopy - -CFLAGS = -march=rv32i -mabi=ilp32 -g -Os -LDFLAGS = -Tsoc.ld -Triscv_application.ld -nostdlib - -SOC = bios_example.py -SOURCES = \ - start.S \ - $(TARGET).c - - -# By default, build our binary. -all: $(TARGET).bin - - -# -# Generated files. -# - -soc.ld: $(SOC) - ./$(SOC) --generate-ld-script > $@ - -resources.h: $(SOC) - ./$(SOC) --generate-c-header > $@ - - -# -# Firmware binary. -# - -$(TARGET).elf: $(SOURCES) soc.ld resources.h - $(CC) $(CFLAGS) $(LDFLAGS) $(SOURCES) -o $@ - -$(TARGET).bin: $(TARGET).elf - $(OBJCOPY) -O binary $< $@ - - -# -# Virtual/command targets. -# - -.PHONY: clean program - -clean: - rm -f $(TARGET).elf $(TARGET).bin soc.ld resources.h - - -# Load our SoC onto the FPGA... -configure: $(SOC) - ./$(SOC) - -# Flash the FPGA's ROM with our SoC. -flash-soc: $(SOC) - ./$(SOC) --flash - -# Program the SoC with our application. -program: $(TARGET).bin - echo -e "\nserialboot" | script -q /dev/null -c \ - "flterm --speed $(BAUDRATE) --kernel $< --kernel-addr $(shell ./$(SOC) --get-fw-address) $(SERIALPORT)" diff --git a/examples/soc/bios/bios_example.py b/examples/soc/bios/bios_example.py deleted file mode 100755 index dad156fd4..000000000 --- a/examples/soc/bios/bios_example.py +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env python3 -# -# This file is part of LUNA. -# -# Copyright (c) 2020 Great Scott Gadgets -# SPDX-License-Identifier: BSD-3-Clause - -from amaranth import Elaboratable, Module, Cat -from amaranth.hdl.rec import Record - -from lambdasoc.periph import Peripheral -from lambdasoc.periph.serial import AsyncSerialPeripheral -from lambdasoc.periph.timer import TimerPeripheral - -from luna import top_level_cli -from luna.gateware.soc import SimpleSoC -from luna.gateware.interface.uart import UARTTransmitterPeripheral - - -class LEDPeripheral(Peripheral, Elaboratable): - """ Example peripheral that controls the board's LEDs. """ - - def __init__(self): - super().__init__() - - # Create our LED register. - # Note that there's a bunch of 'magic' that goes on behind the scenes, here: - # a memory address will automatically be reserved for this register in the address - # space it's attached to; and the SoC utilities will automatically generate header - # entires and stub functions for it. - bank = self.csr_bank() - self._output = bank.csr(6, "w") - - # ... and convert our register into a Wishbone peripheral. - self._bridge = self.bridge(data_width=32, granularity=8, alignment=2) - self.bus = self._bridge.bus - - - def elaborate(self, platform): - m = Module() - m.submodules.bridge = self._bridge - - # Grab our LEDS... - leds = Cat(platform.request("led", i) for i in range(6)) - - # ... and update them on each register write. - with m.If(self._output.w_stb): - m.d.sync += leds.eq(self._output.w_data) - - return m - - -class LunaCPUExample(Elaboratable): - """ Simple example of building a simple SoC around LUNA. """ - - def __init__(self): - - # Create a stand-in for our UART. - self.uart_pins = Record([ - ('rx', [('i', 1)]), - ('tx', [('o', 1)]) - ]) - - # Create our SoC... - self.soc = soc = SimpleSoC() - soc.add_bios_and_peripherals(uart_pins=self.uart_pins) - - # ... add some bulk RAM ... - soc.add_ram(0x4000) - - # ... and add our LED peripheral. - leds = LEDPeripheral() - soc.add_peripheral(leds) - - - def elaborate(self, platform): - m = Module() - m.submodules.soc = self.soc - - # Connect up our UART. - uart_io = platform.request("uart", 0) - m.d.comb += [ - uart_io.tx.o .eq(self.uart_pins.tx), - self.uart_pins.rx .eq(uart_io.rx) - ] - - if hasattr(uart_io.tx, 'oe'): - m.d.comb += uart_io.tx.oe.eq(~self.soc.uart._phy.tx.rdy), - - - return m - - -if __name__ == "__main__": - design = LunaCPUExample() - top_level_cli(design, cli_soc=design.soc) diff --git a/examples/soc/bios/blinky.c b/examples/soc/bios/blinky.c deleted file mode 100644 index 85d95526c..000000000 --- a/examples/soc/bios/blinky.c +++ /dev/null @@ -1,51 +0,0 @@ -/** - * This file is part of LUNA. - * - * Copyright (c) 2020 Great Scott Gadgets - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include - -// Include our automatically generated resource file. -// This allows us to work with e.g. our registers no matter gt -#include "resources.h" - - -int main(void) -{ - bool shifting_right = true; - uint8_t led_value = 0b110000; - - // Set up our timer to periodically move the LEDs. - timer_en_write(1); - timer_reload_write(0x0C0000); - - // And blink our LEDs. - while(1) { - - // Skip all iterations that aren't our main one... - if (timer_ctr_read()) { - continue; - } - - // ... compute our pattern ... - if (shifting_right) { - led_value >>= 1; - - if (led_value == 0b000011) { - shifting_right = false; - } - } else { - led_value <<= 1; - - if (led_value == 0b110000) { - shifting_right = true; - } - - } - - // ... and output it to the LEDs. - leds_output_write(led_value); - } -} diff --git a/examples/soc/bios/riscv_application.ld b/examples/soc/bios/riscv_application.ld deleted file mode 100644 index 824cdd00e..000000000 --- a/examples/soc/bios/riscv_application.ld +++ /dev/null @@ -1,39 +0,0 @@ -OUTPUT_FORMAT("elf32-littleriscv") -OUTPUT_ARCH("riscv") -ENTRY(_start) - -SECTIONS -{ - . = ORIGIN(ram); - - /* Start of day code. */ - .init : - { - *(.init) *(.init.*) - } > ram - .text : - { - *(.text) *(.text.*) - } > ram - - .rodata : - { - *(.rodata) *(.rodata.*) - } > ram - .sdata : - { - PROVIDE(__global_pointer$ = .); - *(.sdata) *(.sdata.*) - } - .data : - { - *(.data) *(.data.*) - } > ram - .bss : - { - *(.bss) *(.bss.*) - } > ram - -} - -PROVIDE(__stack_top = ORIGIN(ram) + LENGTH(ram)); diff --git a/examples/soc/bios/start.S b/examples/soc/bios/start.S deleted file mode 100644 index 5ff554ffa..000000000 --- a/examples/soc/bios/start.S +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2020 Great Scott Gadgets - * SPDX-License-Identifier: BSD-3-Clause - */ -.section .init, "ax" - -.global _start -_start: - .cfi_startproc - .cfi_undefined ra - - /* Set up our global pointer. */ - .option push - .option norelax - la gp, __global_pointer$ - .option pop - - /* Set up our stack. */ - la sp, __stack_top - add s0, sp, zero - - /* - * NOTE: In most cases, we'd clear the BSS, here. - * - * In our case, our FPGA automaticaly starts with all of our RAM - * initialized to zero; so our BSS comes pre-cleared. We'll skip the - * formality of re-clearing it. - */ - - /* Finally, start our main routine. */ - jal zero, main - - .cfi_endproc - .end diff --git a/examples/soc/hello/Makefile b/examples/soc/hello/Makefile deleted file mode 100644 index e2d317da5..000000000 --- a/examples/soc/hello/Makefile +++ /dev/null @@ -1,59 +0,0 @@ -# -# This file is part of LUNA. -# - -TARGET = hello_world - -CROSS ?= riscv64-unknown-elf- - -CC = $(CROSS)gcc -OBJCOPY = $(CROSS)objcopy - -CFLAGS = -march=rv32i -mabi=ilp32 -g -Os -Iinclude -LDFLAGS = -Tsoc.ld -T$(TARGET).ld -nostdlib - -SOC = $(TARGET)_soc.py -SOURCES = \ - start.S \ - hello_world.c - - -# By default, build our binary. -all: $(TARGET).bin - - -# -# Generated files. -# - -soc.ld: $(SOC) - ./$(SOC) --generate-ld-script > $@ - -resources.h: $(SOC) - ./$(SOC) --generate-c-header > $@ - - -# -# Firmware binary. -# - -$(TARGET).elf: $(SOURCES) soc.ld resources.h - $(CC) $(CFLAGS) $(LDFLAGS) $(SOURCES) -o $@ - -$(TARGET).bin: $(TARGET).elf - $(OBJCOPY) -O binary $< $@ - - -# -# Virtual/command targets. -# - -.PHONY: clean program - -clean: - rm -f $(TARGET).elf $(TARGET).bin soc.ld resources.h - -# Loads our "Hello world" program onto the FPGA. -program: $(TARGET).bin $(SOC) - ./$(SOC) - diff --git a/examples/soc/hello/hello_world.c b/examples/soc/hello/hello_world.c deleted file mode 100644 index 95a6df6b6..000000000 --- a/examples/soc/hello/hello_world.c +++ /dev/null @@ -1,60 +0,0 @@ -/** - * This file is part of LUNA. - * - * Copyright (c) 2020 Great Scott Gadgets - * SPDX-License-Identifier: BSD-3-Clause - */ - -// Include our automatically generated resource file. -// This allows us to work with e.g. our registers no matter what address they're assigned. -#include "resources.h" - - -/** - * Transmits a single charater over our example UART. - */ -void print_char(char c) -{ - while(!uart_tx_rdy_read()); - uart_tx_data_write(c); -} - - -/** - * Transmits a string over our UART. - */ -void uart_puts(char *str) -{ - for (char *c = str; *c; ++c) { - print_char(*c); - } -} - - -void dispatch_isr(void) -{ - if(timer_interrupt_pending()) { - timer_ev_pending_write(timer_ev_pending_read()); - leds_output_write(~leds_output_read()); - } -} - - -int main(void) -{ - uint8_t led_value = 0b101010; - leds_output_write(led_value); - - // Set up our timer to generate LED blinkies. - timer_reload_write(0xA00000); - timer_en_write(1); - timer_ev_enable_write(1); - - // Enable our timer's interrupt. - irq_setie(1); - timer_interrupt_enable(); - - // Say hello, on our UART. - uart_puts("Hello, world!\r\n"); - while(1); -} diff --git a/examples/soc/hello/hello_world.ld b/examples/soc/hello/hello_world.ld deleted file mode 100644 index a890e0e23..000000000 --- a/examples/soc/hello/hello_world.ld +++ /dev/null @@ -1,39 +0,0 @@ -OUTPUT_FORMAT("elf32-littleriscv") -OUTPUT_ARCH("riscv") -ENTRY(_start) - -SECTIONS -{ - . = 0x00000000; - - /* Start of day code. */ - .init : - { - *(.init) *(.init.*) - } > rom - .text : - { - *(.text) *(.text.*) - } > rom - - .rodata : - { - *(.rodata) *(.rodata.*) - } > rom - .sdata : - { - PROVIDE(__global_pointer$ = .); - *(.sdata) *(.sdata.*) - } - .data : - { - *(.data) *(.data.*) - } > ram - .bss : - { - *(.bss) *(.bss.*) - } > ram - -} - -PROVIDE(__stack_top = ORIGIN(ram) + LENGTH(ram)); diff --git a/examples/soc/hello/hello_world_soc.py b/examples/soc/hello/hello_world_soc.py deleted file mode 100755 index f337bd375..000000000 --- a/examples/soc/hello/hello_world_soc.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python3 -# -# This file is part of LUNA. -# -# Copyright (c) 2020 Great Scott Gadgets -# SPDX-License-Identifier: BSD-3-Clause - -from amaranth import Elaboratable, Module, Cat -from amaranth.hdl.rec import Record - -from lambdasoc.periph import Peripheral -from lambdasoc.periph.serial import AsyncSerialPeripheral -from lambdasoc.periph.timer import TimerPeripheral - -from luna import top_level_cli -from luna.gateware.soc import SimpleSoC -from luna.gateware.interface.uart import UARTTransmitterPeripheral - - -class LEDPeripheral(Peripheral, Elaboratable): - """ Example peripheral that controls the board's LEDs. """ - - def __init__(self): - super().__init__() - - # Create our LED register. - # Note that there's a bunch of 'magic' that goes on behind the scenes, here: - # a memory address will automatically be reserved for this register in the address - # space it's attached to; and the SoC utilities will automatically generate header - # entires and stub functions for it. - bank = self.csr_bank() - self._output = bank.csr(6, "rw") - - # ... and convert our register into a Wishbone peripheral. - self._bridge = self.bridge(data_width=32, granularity=8, alignment=2) - self.bus = self._bridge.bus - - - def elaborate(self, platform): - m = Module() - m.submodules.bridge = self._bridge - - # Grab our LEDS... - leds = Cat(platform.request("led", i) for i in range(6)) - - # ... and update them on each register write. - with m.If(self._output.w_stb): - m.d.sync += [ - self._output.r_data .eq(self._output.w_data), - leds .eq(self._output.w_data), - ] - - return m - - - -class LunaCPUExample(Elaboratable): - """ Simple example of building a simple SoC around LUNA. """ - - def __init__(self): - clock_freq = 60e6 - - # Create our SoC... - self.soc = soc = SimpleSoC() - - soc.add_rom('hello_world.bin', size=0x1000) - soc.add_ram(0x1000) - - - # ... add our UART peripheral... - self.uart_pins = Record([ - ('rx', [('i', 1)]), - ('tx', [('o', 1)]) - ]) - self.uart = uart = AsyncSerialPeripheral(divisor=int(clock_freq // 115200), pins=self.uart_pins) - soc.add_peripheral(uart) - - # ... add a timer, to control our LED blinkies... - self.timer = timer = TimerPeripheral(24) - soc.add_peripheral(timer) - - # ... and add our LED peripheral. - leds = LEDPeripheral() - soc.add_peripheral(leds) - - - def elaborate(self, platform): - m = Module() - m.submodules.soc = self.soc - - # Connect up our UART. - uart_io = platform.request("uart", 0) - m.d.comb += [ - uart_io.tx .eq(self.uart_pins.tx), - self.uart_pins.rx .eq(uart_io.rx) - ] - - return m - - -if __name__ == "__main__": - design = LunaCPUExample() - top_level_cli(design, cli_soc=design.soc) diff --git a/examples/soc/hello/minerva.h b/examples/soc/hello/minerva.h deleted file mode 100644 index e69de29bb..000000000 diff --git a/examples/soc/hello/openocd.cfg b/examples/soc/hello/openocd.cfg deleted file mode 100644 index 9ec5b3925..000000000 --- a/examples/soc/hello/openocd.cfg +++ /dev/null @@ -1,10 +0,0 @@ -transport select jtag -adapter_khz 1000 - -set _CHIPNAME riscv -jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x10e31913 - -set _TARGETNAME $_CHIPNAME.cpu -target create $_TARGETNAME riscv -chain-position $_TARGETNAME -$_TARGETNAME configure -work-area-phys 0x00001E00 -work-area-size 0x200 -work-area-backup 1 - diff --git a/examples/soc/hello/start.S b/examples/soc/hello/start.S deleted file mode 100644 index 71859f50e..000000000 --- a/examples/soc/hello/start.S +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2020 Great Scott Gadgets - * SPDX-License-Identifier: BSD-3-Clause - */ - -#define MIE_MEIE 0x800 - -.section .init, "ax" - -.global _start -_start: - /* Set up our global pointer. */ - .option push - .option norelax - la gp, __global_pointer$ - .option pop - - /* Set up our primary interrut dispatcher. */ - la t0, _interrupt_handler - csrw mtvec, t0 - - /* Set up our stack. */ - la sp, __stack_top - add s0, sp, zero - - /* - * NOTE: In most cases, we'd clear the BSS, here. - * - * In our case, our FPGA automaticaly starts with all of our RAM - * initialized to zero; so our BSS comes pre-cleared. We'll skip the - * formality of re-clearing it. - */ - - /* Enable interrupts. */ - li t0, MIE_MEIE - csrs mie, t0 - - /* Finally, start our main routine. */ - jal zero, main - - -/** - * Re-entry point for interrupts. - */ -_interrupt_handler: - addi sp, sp, -16 * 4 - sw ra, 0 * 4(sp) - sw t0, 1 * 4(sp) - sw t1, 2 * 4(sp) - sw t2, 3 * 4(sp) - sw a0, 4 * 4(sp) - sw a1, 5 * 4(sp) - sw a2, 6 * 4(sp) - sw a3, 7 * 4(sp) - sw a4, 8 * 4(sp) - sw a5, 9 * 4(sp) - sw a6, 10 * 4(sp) - sw a7, 11 * 4(sp) - sw t3, 12 * 4(sp) - sw t4, 13 * 4(sp) - sw t5, 14 * 4(sp) - sw t6, 15 * 4(sp) - call dispatch_isr - lw ra, 0 * 4(sp) - lw t0, 1 * 4(sp) - lw t1, 2 * 4(sp) - lw t2, 3 * 4(sp) - lw a0, 4 * 4(sp) - lw a1, 5 * 4(sp) - lw a2, 6 * 4(sp) - lw a3, 7 * 4(sp) - lw a4, 8 * 4(sp) - lw a5, 9 * 4(sp) - lw a6, 10 * 4(sp) - lw a7, 11 * 4(sp) - lw t3, 12 * 4(sp) - lw t4, 13 * 4(sp) - lw t5, 14 * 4(sp) - lw t6, 15 * 4(sp) - addi sp, sp, 16*4 - mret diff --git a/examples/usb/eptri/Makefile b/examples/usb/eptri/Makefile deleted file mode 100644 index be1aa53ba..000000000 --- a/examples/usb/eptri/Makefile +++ /dev/null @@ -1,64 +0,0 @@ -# -# This file is part of LUNA. -# - -TARGET = eptri_example -BAUDRATE = 115200 -SERIALPORT ?= /dev/ttyACM0 - -CROSS ?= riscv64-unknown-elf- - -CC = $(CROSS)gcc -OBJCOPY = $(CROSS)objcopy - -CFLAGS = -march=rv32i -mabi=ilp32 -g -Os -Wall -Werror -LDFLAGS = -Tsoc.ld -Triscv_standalone.ld -nostdlib - -SOC = eptri_device.py -SOURCES = \ - start.S \ - $(TARGET).c - - -# By default, build our binary. -all: $(TARGET).bin - - -# -# Generated files. -# - -soc.ld: $(SOC) - ./$(SOC) --generate-ld-script > $@ - -resources.h: $(SOC) - ./$(SOC) --generate-c-header > $@ - - -# -# Firmware binary. -# - -$(TARGET).elf: $(SOURCES) soc.ld resources.h - $(CC) $(CFLAGS) $(LDFLAGS) $(SOURCES) -o $@ - -$(TARGET).bin: $(TARGET).elf - $(OBJCOPY) -O binary $< $@ - - -# -# Virtual/command targets. -# - -.PHONY: clean program - -clean: - rm -f $(TARGET).elf $(TARGET).bin soc.ld resources.h soc.bit - - -soc.bit: $(SOC) $(TARGET).bin - ./$(SOC) -o soc.bit - -# Load our SoC onto the FPGA... -program: soc.bit - apollo configure soc.bit diff --git a/examples/usb/eptri/README.md b/examples/usb/eptri/README.md deleted file mode 100644 index 1f12a341e..000000000 --- a/examples/usb/eptri/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Tri-Endpoint (eptri) Example - -You'll need to build both firmware and the SoC gateware in order to use this example. - -The easiest way to do both at once is to - -```sh - -# Build and program: -$ make program - -# Or just build: -$ make soc.bit -``` - -### Firmware - -The included firmware is heavily intended as a quick example, and doesn't go as far as a full USB stack -should. Accordingly, it e.g. may fail the first (non-compliant) enumeration attempt on Linux. diff --git a/examples/usb/eptri/eptri_device.py b/examples/usb/eptri/eptri_device.py deleted file mode 100755 index d4735a26e..000000000 --- a/examples/usb/eptri/eptri_device.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python3 -# -# This file is part of LUNA. -# -# Copyright (c) 2020 Great Scott Gadgets -# SPDX-License-Identifier: BSD-3-Clause - -import sys -import logging -import os.path - -from amaranth import Elaboratable, Module, Cat -from amaranth.hdl.rec import Record - -from lambdasoc.periph.serial import AsyncSerialPeripheral -from lambdasoc.periph.timer import TimerPeripheral - -from luna import top_level_cli -from luna.gateware.soc import SimpleSoC - -from luna.gateware.usb.usb2.device import USBDevice, USBDeviceController -from luna.gateware.usb.usb2.interfaces.eptri import SetupFIFOInterface, InFIFOInterface, OutFIFOInterface - - -CLOCK_FREQUENCIES_MHZ = { - 'sync': 60 -} - - -class EptriDeviceExample(Elaboratable): - """ Example of an Eptri-equivalent USB device built with LUNA. """ - - def __init__(self): - - # Create a stand-in for our UART. - self.uart_pins = Record([ - ('rx', [('i', 1)]), - ('tx', [('o', 1)]) - ]) - - # Create our SoC... - self.soc = soc = SimpleSoC() - soc.add_rom("eptri_example.bin", 0x4000) - - # ... add some bulk RAM ... - soc.add_ram(0x4000) - - # ... add a UART ... - self.uart = uart = AsyncSerialPeripheral(divisor=int(60e6 // 115200), pins=self.uart_pins) - soc.add_peripheral(uart) - - # ... add a timer, to control our LED blinkies... - self.timer = timer = TimerPeripheral(24) - soc.add_peripheral(timer) - - - # ... a core USB controller ... - self.controller = USBDeviceController() - soc.add_peripheral(self.controller) - - # ... and add our eptri peripherals. - self.setup = SetupFIFOInterface() - soc.add_peripheral(self.setup, as_submodule=False) - - self.in_ep = InFIFOInterface() - soc.add_peripheral(self.in_ep, as_submodule=False) - - self.out_ep = OutFIFOInterface() - soc.add_peripheral(self.out_ep, as_submodule=False) - - - - def elaborate(self, platform): - m = Module() - m.submodules.soc = self.soc - - # Check for our prerequisites before building. - if not os.path.exists("eptri_example.bin"): - logging.error("Firmware binary not found -- you may want to build this with `make program`.") - sys.exit(-1) - - # Generate our domain clocks/resets. - m.submodules.car = platform.clock_domain_generator(clock_frequencies=CLOCK_FREQUENCIES_MHZ) - - # Connect up our UART. - uart_io = platform.request("uart", 0) - m.d.comb += [ - uart_io.tx .eq(self.uart_pins.tx), - self.uart_pins.rx .eq(uart_io.rx) - ] - - # Create our USB device. - ulpi = platform.request(platform.default_usb_connection) - m.submodules.usb = usb = USBDevice(bus=ulpi) - - # Connect up our device controller. - m.d.comb += self.controller.attach(usb) - - # Add our eptri endpoint handlers. - usb.add_endpoint(self.setup) - usb.add_endpoint(self.in_ep) - usb.add_endpoint(self.out_ep) - return m - - -if __name__ == "__main__": - - design = EptriDeviceExample() - top_level_cli(design, cli_soc=design.soc) diff --git a/examples/usb/eptri/eptri_example.c b/examples/usb/eptri/eptri_example.c deleted file mode 100644 index f57b4a12d..000000000 --- a/examples/usb/eptri/eptri_example.c +++ /dev/null @@ -1,438 +0,0 @@ -/** - * This file is part of LUNA. - * - * Copyright (c) 2020 Great Scott Gadgets - * SPDX-License-Identifier: BSD-3-Clause - * - * Minimal example for the LUNA `eptri`-equivalent interface. - * - * Note that this example is minimal, and meant to offer an example of how to use the - * LUNA `eptri` interface; and not a complete and correct `eptri` based USB stack. - */ - - -#include -#include "resources.h" - -#define ARRAY_SIZE(array) (sizeof(array) / sizeof(*array)) - - -/** - * Control request constants. - */ -enum { - - // Request flags. - DIRECTION_IN_MASK = 0x80, - - REQUEST_TYPE_STANDARD = 0x00, - - // Request types. - REQUEST_SET_ADDRESS = 0x05, - REQUEST_GET_DESCRIPTOR = 0x06, - REQUEST_SET_CONFIGURATION = 0x09, - - // Descriptor types - DESCRIPTOR_DEVICE = 0x01, - DESCRIPTOR_CONFIGURATION = 0x02, - DESCRIPTOR_STRING = 0x03 -}; - - -/** - * Struct representing a USB setup request. - */ -union usb_setup_request -{ - struct - { - union { - struct - { - uint8_t bmRequestType; - uint8_t bRequest; - }; - - uint16_t wRequestAndType; - }; - - uint16_t wValue; - uint16_t wIndex; - uint16_t wLength; - }; - - // Window that allows us to capture raw data into the setup request, easily. - uint8_t raw_data[8]; -}; -typedef union usb_setup_request usb_setup_request_t; - - -// -// Globals -// -usb_setup_request_t last_setup_packet; - - -// -// Descriptors. -// - -static const uint8_t usb_device_descriptor[] = { - 0x12, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x40, - 0xd0, 0x16, 0x3b, 0x0f, 0x01, 0x01, 0x01, 0x02, - 0x00, 0x01 -}; - - -static const uint8_t usb_config_descriptor[] = { - 0x09, 0x02, 0x12, 0x00, 0x01, 0x01, 0x01, 0x80, - 0x32, 0x09, 0x04, 0x00, 0x00, 0x00, 0xfe, 0x00, - 0x00, 0x02 -}; - -static const uint8_t usb_string0_descriptor[] = { - 0x04, 0x03, 0x09, 0x04, -}; - -static const uint8_t usb_string1_descriptor[] = { - 0x0a, 0x03, 'L', 0x00, 'U', 0x00, 'N', 0x00, 'A', 0x00 -}; - -static const uint8_t usb_string2_descriptor[] = { - 0x22, 0x03, - 'T', 0, 'r', 0, 'i', 0, '-', 0, 'F', 0, 'I', 0, 'F', 0, 'O', 0, - ' ', 0, 'E', 0, 'x', 0, 'a', 0, 'm', 0, 'p', 0, 'l', 0, 'e', 0 -}; - - -// -// Support functions. -// - - -/** - * Transmits a single charater over our example UART. - */ -void print_char(char c) -{ - while(!uart_tx_rdy_read()); - uart_tx_data_write(c); -} - - -/** - * Transmits a string over our UART. - */ -void uart_puts(char *str) -{ - for (char *c = str; *c; ++c) { - if (*c == '\n') { - print_char('\r'); - } - - print_char(*c); - } -} - - -/** - * Prints a hex character over our UART. - */ -void print_nibble(uint8_t nibble) -{ - static const char hexits[] = "0123456789abcdef"; - print_char(hexits[nibble & 0xf]); -} - - -/** - * Prints a single byte, in hex, over our UART. - */ -void print_byte(uint8_t byte) -{ - print_nibble(byte >> 4); - print_nibble(byte & 0xf); -} - - -/** - * Reads a setup request from our interface, populating our SETUP request field. - */ -void read_setup_request(void) -{ - for (uint8_t i = 0; i < 8; ++i) { - - // Block until we have setup data to read. - while(!setup_have_read()); - - // Once it's available, read the setup field for our packet. - uint8_t byte = setup_data_read(); - last_setup_packet.raw_data[i] = byte; - } -} - -/** - * Transmits a single data packet on an IN endpoint. - * - * @param endpoint The endpoint -number- on which we should respond. - * @param data The data packet to respond with. - * @param length The total length of the data to send. - */ - void send_packet(uint8_t endpoint, const void *data, uint16_t length) - { - const uint8_t *buffer = data; - - // Clear our output FIFO, ensuring we start fresh. - in_ep_reset_write(1); - - // Send data until we run out of bytes. - while (length) { - in_ep_data_write(*buffer); - - ++buffer; - --length; - } - - // And prime our IN endpoint. - in_ep_epno_write(endpoint); - - } - - -/** - * Transmits a single data packet in response to an control request. - * - * @param data The data packet to respond with. - * @param data_length The length of the data object that can be sent. If this is longer - * than the last request length, the response will automatically be - * truncated to the requested length. - * - */ - void send_control_response(const void *data, uint16_t data_length) - { - uint16_t length = data_length; - - // If the host is requesting less than the maximum amount of data, - // only respond with the amount of data requested. - if (last_setup_packet.wLength < data_length) { - length = last_setup_packet.wLength; - } - - send_packet(0, data, length); - } - - -/** - * Clears the contents of the Receive buffer. - */ -void flush_receive_buffer(void) -{ - out_ep_reset_write(1); -} - - -/** - * Prepares an endpoint to receive a single OUT packet. - */ -void prime_receive(uint8_t endpoint) -{ - flush_receive_buffer(); - - // Select our endpoint, and enable it to prime a read. - out_ep_epno_write(endpoint); - out_ep_enable_write(1); -} - - -/** - * Handles acknowledging the status stage of an incoming control request. - */ -void ack_status_stage() -{ - - // If this is an IN request, read a zero-length packet (ZLP) from the host.. - if (last_setup_packet.bmRequestType & DIRECTION_IN_MASK) { - prime_receive(0); - } - // ... otherwise, send a ZLP. - else { - send_packet(0, 0, 0); - } -} - - -/** - * Stalls the current control request - * For this example, we'll assume we're always targeting EP0. - */ -void stall_request(void) -{ - in_ep_stall_write(1); - out_ep_stall_write(1); -} - -// -// Request handlers. -// - -/** - * Handle SET_ADDRESS requests. - */ -void handle_set_address(void) -{ - ack_status_stage(); - - // FIXME: we should wait to get our final ACK on the status stage before applying this address - - // Apply our address. - setup_address_write(last_setup_packet.wValue); -} - - -/** - * Handle SET_CONFIGURATIOn requests. - */ -void handle_set_configuration(uint8_t configuration) -{ - // We only have a single configuration; so only accept configuration number '1', - // or configuration '0' (unconfigured). - if (configuration > 1) { - stall_request(); - return; - } - - // TODO: apply our configuration to the device state - ack_status_stage(); -} - - - -/** - * Sends a string descriptor, by number. - */ -void handle_string_descriptor(uint8_t number) -{ - switch (number) { - - case 0: - send_control_response(usb_string0_descriptor, ARRAY_SIZE(usb_string0_descriptor)); - break; - - case 1: - send_control_response(usb_string1_descriptor, ARRAY_SIZE(usb_string1_descriptor)); - break; - - case 2: - send_control_response(usb_string2_descriptor, ARRAY_SIZE(usb_string2_descriptor)); - break; - - default: - stall_request(); - return; - } - - ack_status_stage(); -} - - -/** - * Handle GET_DESCRIPTOR requests. - */ -void handle_get_descriptor(void) -{ - uint8_t descriptor_type = last_setup_packet.wValue >> 8; - uint8_t descriptor_number = last_setup_packet.wValue & 0xFF; - - switch (descriptor_type) { - - case DESCRIPTOR_DEVICE: - send_control_response(usb_device_descriptor, ARRAY_SIZE(usb_device_descriptor)); - break; - - case DESCRIPTOR_CONFIGURATION: - if (descriptor_number != 0) { - stall_request(); - return; - } - - send_control_response(usb_config_descriptor, ARRAY_SIZE(usb_config_descriptor)); - break; - - case DESCRIPTOR_STRING: - handle_string_descriptor(descriptor_number); - return; - - default: - stall_request(); - return; - - } - - ack_status_stage(); -} - - -/** - * Unhandled request handler. - */ -void unhandled_request(void) -{ - stall_request(); -} - - -void handle_setup_request(void) -{ - // Extract our type (e.g. standard/class/vendor) from our SETUP request. - uint8_t type = (last_setup_packet.bmRequestType >> 5) & 0b11; - - // TODO: Get rid of this once we move to be fully compatible with ValentyUSB. - in_ep_pid_write(1); - - // If this isn't a standard request, STALL it. - if (type != REQUEST_TYPE_STANDARD) { - stall_request(); - return; - } - - // Handle a subset of standard requests. - switch (last_setup_packet.bRequest) { - - case REQUEST_SET_ADDRESS: - handle_set_address(); - break; - - case REQUEST_GET_DESCRIPTOR: - handle_get_descriptor(); - break; - - case REQUEST_SET_CONFIGURATION: - handle_set_configuration(last_setup_packet.wValue); - break; - - default: - unhandled_request(); - break; - } -} - -// -// Core application. -// - -int main(void) -{ - uart_puts("eptri demo started! (built: " __TIME__ ")\n"); - uart_puts("Connecting USB device...\n"); - controller_connect_write(1); - uart_puts("Connected.\n"); - - - while (1) { - - // Loop constantly between reading setup packets and handling them. - read_setup_request(); - handle_setup_request(); - - } -} diff --git a/examples/usb/eptri/riscv_application.ld b/examples/usb/eptri/riscv_application.ld deleted file mode 100644 index 824cdd00e..000000000 --- a/examples/usb/eptri/riscv_application.ld +++ /dev/null @@ -1,39 +0,0 @@ -OUTPUT_FORMAT("elf32-littleriscv") -OUTPUT_ARCH("riscv") -ENTRY(_start) - -SECTIONS -{ - . = ORIGIN(ram); - - /* Start of day code. */ - .init : - { - *(.init) *(.init.*) - } > ram - .text : - { - *(.text) *(.text.*) - } > ram - - .rodata : - { - *(.rodata) *(.rodata.*) - } > ram - .sdata : - { - PROVIDE(__global_pointer$ = .); - *(.sdata) *(.sdata.*) - } - .data : - { - *(.data) *(.data.*) - } > ram - .bss : - { - *(.bss) *(.bss.*) - } > ram - -} - -PROVIDE(__stack_top = ORIGIN(ram) + LENGTH(ram)); diff --git a/examples/usb/eptri/riscv_standalone.ld b/examples/usb/eptri/riscv_standalone.ld deleted file mode 100644 index 837a44f39..000000000 --- a/examples/usb/eptri/riscv_standalone.ld +++ /dev/null @@ -1,44 +0,0 @@ -OUTPUT_FORMAT("elf32-littleriscv") -OUTPUT_ARCH("riscv") -ENTRY(_start) - -SECTIONS -{ - . = 0x00000000; - - /* Start of day code. */ - .init : - { - *(.init) *(.init.*) - } > rom - .text : - { - *(.text) *(.text.*) - } > rom - - .rodata : - { - *(.rodata) *(.rodata.*) - } > rom - .sdata : - { - PROVIDE(__global_pointer$ = .); - *(.sdata) *(.sdata.*) - } - .data : - { - *(.data) *(.data.*) - } > ram - .bss : - { - *(.bss) *(.bss.*) - } > ram - - /DISCARD/ : - { - *(.eh_frame) *(.eh_frame.*) - } - -} - -PROVIDE(__stack_top = ORIGIN(ram) + LENGTH(ram)); diff --git a/examples/usb/eptri/start.S b/examples/usb/eptri/start.S deleted file mode 100644 index a2b2ed668..000000000 --- a/examples/usb/eptri/start.S +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2020 Great Scott Gadgets - * SPDX-License-Identifier: BSD-3-Clause - */ - -.section .init, "ax" - -.global _start -_start: - .cfi_startproc - .cfi_undefined ra - - /* Set up our global pointer. */ - .option push - .option norelax - la gp, __global_pointer$ - .option pop - - /* Set up our stack. */ - la sp, __stack_top - add s0, sp, zero - - /* - * NOTE: In most cases, we'd clear the BSS, here. - * - * In our case, our FPGA automaticaly starts with all of our RAM - * initialized to zero; so our BSS comes pre-cleared. We'll skip the - * formality of re-clearing it. - */ - - /* Finally, start our main routine. */ - jal zero, main - - .cfi_endproc - .end diff --git a/luna/__init__.py b/luna/__init__.py index c285140df..453b9d716 100644 --- a/luna/__init__.py +++ b/luna/__init__.py @@ -28,7 +28,7 @@ def configure_default_logging(level=logging.INFO, logger=logging): logger.basicConfig(level=level, format=log_format) -def top_level_cli(fragment, *pos_args, cli_soc=None, **kwargs): +def top_level_cli(fragment, *pos_args, **kwargs): from .gateware.platform import get_appropriate_platform """ Runs a default CLI that assists in building and running gateware. @@ -40,8 +40,6 @@ def top_level_cli(fragment, *pos_args, cli_soc=None, **kwargs): fragment -- The fragment instance to be built; or a callable that returns a fragment, such as a Elaborable type. If the latter is provided, any keyword or positional arguments not specified here will be passed to this callable. - cli_soc -- Optional. If a SoC design provides a SimpleSoc, options will be provided for generating - build artifacts, such as header or linker files; instead of elaborating a design. """ name = fragment.__name__ if callable(fragment) else fragment.__class__.__name__ @@ -63,16 +61,6 @@ def top_level_cli(fragment, *pos_args, cli_soc=None, **kwargs): parser.add_argument('--console', metavar="port", help="Attempts to open a convenience 115200 8N1 UART console on the specified port immediately after uploading.") - # If we have SoC options, print them to the command line. - if cli_soc: - parser.add_argument('--generate-c-header', action='store_true', - help="If provided, a C header file for this design's SoC will be printed to the stdout. Other options ignored.") - parser.add_argument('--generate-ld-script', action='store_true', - help="If provided, a linker script for design's SoC memory regions be printed to the stdout. Other options ignored.") - parser.add_argument('--get-fw-address', action='store_true', - help="If provided, the utility will print the address firmware should be loaded to to stdout. Other options ignored.") - - # Disable UnusedElaboarable warnings until we decide to build things. # This is sort of cursed, but it keeps us categorically from getting UnusedElaborable warnings # if we're not actually buliding. @@ -97,20 +85,6 @@ def top_level_cli(fragment, *pos_args, cli_soc=None, **kwargs): args.upload = False - # If we've been asked to generate a C header, generate -only- that. - if cli_soc and args.generate_c_header: - cli_soc.generate_c_header(platform_name=get_appropriate_platform().name) - sys.exit(0) - - # If we've been asked to generate linker region info, generate -only- that. - if cli_soc and args.generate_ld_script: - cli_soc.generate_ld_script() - sys.exit(0) - - if cli_soc and args.get_fw_address: - print(f"0x{cli_soc.main_ram_address():08x}") - sys.exit(0) - # Build the relevant gateware, uploading if requested. build_dir = "build" if args.keep_files else tempfile.mkdtemp() @@ -134,18 +108,6 @@ def top_level_cli(fragment, *pos_args, cli_soc=None, **kwargs): join_text = "and uploading gateware to attached" if args.upload else "for" logging.info(f"Building {join_text} {platform.name}...") - # TODO fix litex build - thirdparty = os.path.join(build_dir, "soc/lambdasoc.soc.cpu/bios/3rdparty/litex") - if not os.path.exists(thirdparty): - logging.info("Fixing build, creating output directory: {}".format(thirdparty)) - os.makedirs(thirdparty) - - # If we have an SoC, allow it to perform any pre-elaboration steps it wants. - # This allows it to e.g. build a BIOS or equivalent firmware. - if cli_soc and hasattr(cli_soc, 'build'): - cli_soc.build(build_dir=build_dir) - - # Now that we're actually building, re-enable Unused warnings. MustUse._MustUse__silence = False products = platform.build(fragment, diff --git a/luna/gateware/soc/__init__.py b/luna/gateware/soc/__init__.py deleted file mode 100644 index 9ec1d99bf..000000000 --- a/luna/gateware/soc/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# -# This file is part of LUNA. -# -# Copyright (c) 2020 Great Scott Gadgets -# SPDX-License-Identifier: BSD-3-Clause - -from .simplesoc import SimpleSoC -from .uart import UARTPeripheral diff --git a/luna/gateware/soc/cpu.py b/luna/gateware/soc/cpu.py deleted file mode 100644 index 6763952c0..000000000 --- a/luna/gateware/soc/cpu.py +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file is part of LUNA. -# -# Copyright (c) 2020 Great Scott Gadgets -# SPDX-License-Identifier: BSD-3-Clause - -from minerva.core import Minerva -from amaranth_soc import wishbone - -class Processor(Minerva): - """ Compatibility subclass around the Minerva RISC-V (riscv32i) processor. """ - - # List of features supported by the Minerva processor's wishbone busses. - MINERVA_BUS_FEATURES = {'cti', 'bte', 'err'} - - def __init__(self, *args, **kwargs): - - # Create the basic Minerva processor... - super().__init__(*args, **kwargs) - - # ... and replace its Record-based busses with amaranth-soc ones. - self.ibus = wishbone.Interface(addr_width=30, data_width=32, features=self.MINERVA_BUS_FEATURES) - self.dbus = wishbone.Interface(addr_width=30, data_width=32, features=self.MINERVA_BUS_FEATURES) diff --git a/luna/gateware/soc/memory.py b/luna/gateware/soc/memory.py deleted file mode 100644 index 39e28c9d0..000000000 --- a/luna/gateware/soc/memory.py +++ /dev/null @@ -1,163 +0,0 @@ -# -# This file is part of LUNA. -# -# Copyright (c) 2020 Great Scott Gadgets -# SPDX-License-Identifier: BSD-3-Clause - -import math - -from enum import Enum -from functools import reduce -from operator import or_ - -from amaranth import Elaboratable, Record, Module, Cat, Array, Repl, Signal, Memory -from amaranth_soc import wishbone, memory - - -class WishboneRAM(Elaboratable): - """ Simple Wishbone-connected RAM. """ - - - @staticmethod - def _initialization_value(value, data_width, granularity, byteorder): - """ Converts a provided value into a valid Memory-initializer array. - - Parameters should match those provied to __init__ - """ - - # If this is a filename, read the file's contents before processing. - if isinstance(value, str): - with open(value, "rb") as f: - value = f.read() - - # If we don't have bytes, read this direction. - if not isinstance(value, bytes): - return value - - bytes_per_chunk = data_width // granularity - - words = (value[pos:pos + bytes_per_chunk] for pos in range(0, len(value), bytes_per_chunk)) - return [int.from_bytes(word, byteorder=byteorder) for word in words] - - - - def __init__(self, *, addr_width, data_width=32, granularity=8, init=None, - read_only=False, byteorder="little", name="ram"): - """ - Parameters: - addr_width -- The -bus- address width for the relevant memory. Determines the size - of the memory. - data_width -- The width of each memory word. - granularity -- The number of bits of data per each address. - init -- Optional. The initial value of the relevant memory. Should be an array of integers, a - filename, or a bytes-like object. If bytes are provided, the byteorder parametera allows - control over their interpretation. If a filename is provided, this filename will not be read - until elaboration; this allows reading the file to be deferred until the very last minute in - e.g. systems that generate the relevant file during build. - read_only -- If true, this will ignore writes to this memory, so it effectively - acts as a ROM fixed to its initialization value. - byteorder -- Sets the byte order of the initializer value. Ignored unless a bytes-type initializer is provided. - name -- A descriptive name for the given memory. - """ - - self.name = name - self.read_only = read_only - self.data_width = data_width - self.initial_value = init - self.byteorder = byteorder - - # Our granularity determines how many bits of data exist per single address. - # Often, this isn't the same as our data width; which means we'll wind up with - # two different address widths: a 'local' one where each address corresponds to a - # data value in memory; and a 'bus' one where each address corresponds to a granularity- - # sized chunk of memory. - self.granularity = granularity - self.bus_addr_width = addr_width - - # Our bus addresses are more granular than our local addresses. - # Figure out how many more bits exist in our bus addresses, and use - # that to figure out our local bus size. - self.bytes_per_word = data_width // granularity - self.bits_in_bus_only = int(math.log2(self.bytes_per_word)) - self.local_addr_width = self.bus_addr_width - self.bits_in_bus_only - - # Create our wishbone interface. - # Note that we provide the -local- address to the Interface object; as it automatically factors - # in our extra bits as it computes our granularity. - self.bus = wishbone.Interface(addr_width=self.local_addr_width, data_width=data_width, granularity=granularity) - memory_map = memory.MemoryMap(addr_width=self.bus_addr_width, data_width=granularity, name=self.name) - memory_map.add_resource(self, size=2 ** addr_width, name=self.name) - self.bus.memory_map = memory_map - - def elaborate(self, platform): - m = Module() - - # Create our memory initializer from our initial value. - initial_value = self._initialization_value(self.initial_value, self.data_width, self.granularity, self.byteorder) - - # Create the the memory used to store our data. - memory_depth = 2 ** self.local_addr_width - memory = Memory(width=self.data_width, depth=memory_depth, init=initial_value, name=self.name) - - # Grab a reference to the bits of our Wishbone bus that are relevant to us. - local_address_bits = self.bus.adr[:self.local_addr_width] - - # Create a read port, and connect it to our Wishbone bus. - m.submodules.rdport = read_port = memory.read_port() - m.d.comb += [ - read_port.addr.eq(local_address_bits), - self.bus.dat_r.eq(read_port.data) - ] - - # If this is a read/write memory, create a write port, as well. - if not self.read_only: - m.submodules.wrport = write_port = memory.write_port(granularity=self.granularity) - m.d.comb += [ - write_port.addr.eq(local_address_bits), - write_port.data.eq(self.bus.dat_w) - ] - - # Generate the write enables for each of our words. - for i in range(self.bytes_per_word): - m.d.comb += write_port.en[i].eq( - self.bus.cyc & # Transaction is active. - self.bus.stb & # Valid data is being provided. - self.bus.we & # This is a write. - self.bus.sel[i] # The relevant setion of the datum is being targeted. - ) - - - # We can handle any transaction request in a single cycle, when our RAM handles - # the read or write. Accordingly, we'll ACK the cycle after any request. - m.d.sync += self.bus.ack.eq( - self.bus.cyc & - self.bus.stb & - ~self.bus.ack - ) - - return m - - -class WishboneROM(WishboneRAM): - """ Wishbone-attached ROM. """ - - def __init__(self, data, *, addr_width, data_width=32, granularity=8, name="rom"): - """ - Parameters: - data -- The data to fill the ROM with. - - addr_width -- The -bus- address width for the relevant memory. Determines the address size of the memory. - Physical size is based on the data provided, as unused elements will be optimized away. - data_width -- The width of each memory word. - granularity -- The number of bits of data per each address. - name -- A descriptive name for the ROM. - """ - - super().__init__( - addr_width=addr_width, - data_width=data_width, - granularity=8, - init=data, - read_only=True, - name=name - ) diff --git a/luna/gateware/soc/peripheral.py b/luna/gateware/soc/peripheral.py deleted file mode 100644 index d74de1ea8..000000000 --- a/luna/gateware/soc/peripheral.py +++ /dev/null @@ -1,125 +0,0 @@ -# -# This file is part of LUNA. -# -# Adapted from lambdasoc. -# This file includes content Copyright (C) 2020 LambdaConcept. -# -# Per our BSD license, derivative files must include this license disclaimer. -# -# Copyright (c) 2020 Great Scott Gadgets -# SPDX-License-Identifier: BSD-3-Clause - -""" Peripheral helpers for LUNA devices. """ - -from contextlib import contextmanager - -from amaranth import Module, Elaboratable -from amaranth import tracer -from amaranth.utils import log2_int - -from amaranth_soc import csr, wishbone -from amaranth_soc.memory import MemoryMap -from amaranth_soc.csr.wishbone import WishboneCSRBridge - -from lambdasoc.periph.base import PeripheralBridge -from lambdasoc.periph.event import EventSource - -import lambdasoc - -__all__ = ["Peripheral", "CSRBank", "PeripheralBridge"] - -# Note: -# -# The following are thin wrappers around LambdaSoC's Peripheral and -# CSRBank classes. -# -# The primary reason this abstraction exists is to allow us to support -# auto-generation of register documentation from Peripherals. -# -# The intention is to either upstream this at a future point in time -# or use LambdaSoC's facilities if/when it should gain them. - -class Peripheral(lambdasoc.periph.base.Peripheral): - def csr_bank(self, *, name=None, addr=None, alignment=None, desc=None): - """Request a CSR bank. - - Arguments - --------- - name : str - Optional. Bank name. - addr : int or None - Address of the bank. If ``None``, the implicit next address will be used. - Otherwise, the exact specified address (which must be a multiple of - ``2 ** max(alignment, bridge_alignment)``) will be used. - alignment : int or None - Alignment of the bank. If not specified, the bridge alignment is used. - See :class:`amaranth_soc.csr.Multiplexer` for details. - desc : str - Optional. Documentation for the given CSR bank. - - Return value - ------------ - An instance of :class:`CSRBank`. - """ - bank = CSRBank(name=name) - bank.desc = desc - self._csr_banks.append((bank, addr, alignment)) - - return bank - - def event(self, *, mode="level", name=None, src_loc_at=0, desc=None): - """Request an event source. - - Arguments - --------- - desc : str - Optional. Documentation for the given event. - - See :class:`EventSource` for details. - - Return value - ------------ - An instance of :class:`EventSource`. - """ - if name is None: - name = tracer.get_var_name(depth=2 + src_loc_at).lstrip("_") - - event = super().event(mode=mode, name=name, src_loc_at=src_loc_at) - - event.desc = desc - return event - - -class CSRBank(lambdasoc.periph.base.CSRBank): - def csr(self, width, access, *, addr=None, alignment=None, name=None, - src_loc_at=0, desc=None): - """Request a CSR register. - - Parameters - ---------- - width : int - Width of the register. See :class:`amaranth_soc.csr.Element`. - access : :class:`Access` - Register access mode. See :class:`amaranth_soc.csr.Element`. - addr : int - Address of the register. See :meth:`amaranth_soc.csr.Multiplexer.add`. - alignment : int - Register alignment. See :class:`amaranth_soc.csr.Multiplexer`. - name : str - Name of the register. If ``None`` (default) the name is inferred from the variable - name this register is assigned to. - desc : str - Optional. Documentation for the given register. - Used to generate register documentation automatically. - - Return value - ------------ - An instance of :class:`amaranth_soc.csr.Element`. - """ - if name is None: - name = tracer.get_var_name(depth=2 + src_loc_at).lstrip("_") - - elem = super().csr(width, access, addr=addr, alignment=alignment, name=name, src_loc_at=src_loc_at) - elem.desc = desc - - return elem diff --git a/luna/gateware/soc/simplesoc.py b/luna/gateware/soc/simplesoc.py deleted file mode 100644 index c9ff22570..000000000 --- a/luna/gateware/soc/simplesoc.py +++ /dev/null @@ -1,596 +0,0 @@ -# -# This file is part of LUNA. -# -# Copyright (c) 2020 Great Scott Gadgets -# SPDX-License-Identifier: BSD-3-Clause - -""" Simple SoC abstraction for LUNA examples.""" - -import os -import datetime -import logging - -from amaranth import Elaboratable, Module -from amaranth_soc import wishbone -from amaranth_stdio.serial import AsyncSerial - -from lambdasoc.soc.cpu import CPUSoC -from lambdasoc.cpu.minerva import MinervaCPU -from lambdasoc.periph.intc import GenericInterruptController -from lambdasoc.periph.serial import AsyncSerialPeripheral -from lambdasoc.periph.sram import SRAMPeripheral -from lambdasoc.periph.timer import TimerPeripheral - -from .memory import WishboneRAM, WishboneROM -from ..utils.cdc import synchronize - - -class SimpleSoC(CPUSoC, Elaboratable): - """ Class used for building simple, example system-on-a-chip architectures. - - Intended to facilitate demonstrations (and very simple USB devices) by providing - a wrapper that can be updated as the Amaranth-based-SoC landscape changes. Hopefully, - this will eventually be filled by e.g. Amaranth-compatible-LiteX. :) - - SimpleSoC devices intergrate: - - A simple riscv32i processor. - - One or more read-only or read-write memories. - - A number of amaranth-soc peripherals. - - - The current implementation uses a single, 32-bit wide Wishbone bus - as the system's backend; and uses lambdasoc as its backing technology. - This is subject to change. - """ - - BUS_ADDRESS_WIDTH = 30 - - def __init__(self, clock_frequency=int(60e6)): - """ - Parameters: - clock_frequency -- The frequency of our `sync` domain, in MHz. - """ - - self.sync_clk_freq = clock_frequency - - self._main_rom = None - self._main_ram = None - self._uart_baud = None - - # Keep track of our created peripherals and interrupts. - self._submodules = [] - self._irqs = {} - self._next_irq_index = 0 - - # By default, don't attach any debug hardware; or build a BIOS. - self._auto_debug = False - self._build_bios = False - - # - # Create our core hardware. - # We'll create this hardware early, so it can be used for e.g. code generation without - # fully elaborating our design. - # - - # Create our CPU. - self.cpu = MinervaCPU(with_debug=False) - - # Create our interrupt controller. - self.intc = GenericInterruptController(width=32) - - # Create our bus decoder - self.bus_decoder = wishbone.Decoder(addr_width=30, data_width=32, granularity=8, features={"cti", "bte"}) - # Things we don't have but lambdasoc's jinja2 templates expect - self.sdram = None - self.ethmac = None - - @property - def memory_map(self): - return self.bus_decoder.bus.memory_map - - def add_rom(self, data, size, addr=0, is_main_rom=True): - """ Creates a simple ROM and adds it to the design. - - Parameters: - data -- The data to fill the relevant ROM. - size -- The size for the rom that should be created. - addr -- The address at which the ROM should reside. - """ - - # Figure out how many address bits we'll need to address the given memory size. - addr_width = (size - 1).bit_length() - - rom = WishboneROM(data, addr_width=addr_width) - if self._main_rom is None and is_main_rom: - self._main_rom = rom - - return self.add_peripheral(rom, addr=addr) - - @property - def mainram(self): - return self.sram - - @property - def sram(self): - return self._main_ram - - def add_ram(self, size: int, addr: int = None, is_main_mem: bool = True): - """ Creates a simple RAM and adds it to our design. - - Parameters: - size -- The size of the RAM, in bytes. Will be rounded up to the nearest power of two. - addr -- The address at which to place the RAM. - """ - - # Figure out how many address bits we'll need to address the given memory size. - addr_width = (size - 1).bit_length() - - # ... and add it as a peripheral. - ram = WishboneRAM(addr_width=addr_width) - if self._main_ram is None and is_main_mem: - self._main_ram = ram - - return self.add_peripheral(ram, addr=addr) - - def add_peripheral(self, p, *, as_submodule=True, **kwargs): - """ Adds a peripheral to the SoC. - - For now, this is identical to adding a peripheral to the SoC's wishbone bus. - For convenience, returns the peripheral provided. - """ - - # Add the peripheral to our bus... - interface = getattr(p, 'bus') - self.bus_decoder.add(interface, **kwargs) - - # ... add its IRQs to the IRQ controller... - try: - irq_line = getattr(p, 'irq') - self.intc.add_irq(irq_line, self._next_irq_index) - - self._irqs[self._next_irq_index] = p - self._next_irq_index += 1 - except (AttributeError, NotImplementedError): - - # If the object has no associated IRQs, continue anyway. - # This allows us to add devices with only Wishbone interfaces to our SoC. - pass - - # ... and keep track of it for later. - if as_submodule: - self._submodules.append(p) - - return p - - - def add_debug_port(self): - """ Adds an automatically-connected Debug port to our SoC. """ - self._auto_debug = True - - - def add_bios_and_peripherals(self, uart_pins, uart_baud_rate=115200, fixed_addresses=False): - """ Adds a simple BIOS that allows loading firmware, and the requisite peripherals. - - Automatically adds the following peripherals: - self.uart -- An AsyncSerialPeripheral used for serial I/O. - self.timer -- A TimerPeripheral used for BIOS timing. - self.bootrom -- A ROM memory used for the BIOS. - self.scratchpad -- The RAM used by the BIOS; not typically the program RAM. - - Parameters: - uart_pins -- The UARTResource to be used for UART communications; or an equivalent record. - uart_baud_rate -- The baud rate to be used by the BIOS' uart. - """ - - self._build_bios = True - self._uart_baud = uart_baud_rate - - # Add our RAM and ROM. - # Note that these names are from CPUSoC, and thus must not be changed. - # - # Here, we're using SRAMPeripherals instead of our more flexible ones, - # as that's what the lambdasoc BIOS expects. These are effectively internal. - # - addr = 0x0000_0000 if fixed_addresses else None - self.bootrom = SRAMPeripheral(size=0x4000, writable=False) - self.add_peripheral(self.bootrom, addr=addr) - - addr = 0x0001_0000 if fixed_addresses else None - self.scratchpad = SRAMPeripheral(size=0x1000) - self.add_peripheral(self.scratchpad, addr=addr) - - # Add our UART and Timer. - # Again, names are fixed. - addr = 0x0002_0000 if fixed_addresses else None - self.timer = TimerPeripheral(width=32) - self.add_peripheral(self.timer, addr=addr) - - addr = 0x0003_0000 if fixed_addresses else None - uart_core = AsyncSerial( - data_bits = 8, - divisor = int(self.sync_clk_freq // uart_baud_rate), - pins = uart_pins, - ) - self.uart = AsyncSerialPeripheral(core=uart_core) - self.add_peripheral(self.uart, addr=addr) - - - - def elaborate(self, platform): - m = Module() - - # Add our core CPU, and create its main system bus. - # Note that our default implementation uses a single bus for code and data, - # so this is both the instruction bus (ibus) and data bus (dbus). - m.submodules.cpu = self.cpu - m.submodules.bus = self.bus_decoder - - # Create a basic programmable interrupt controller for our CPU. - m.submodules.pic = self.intc - - # Add each of our peripherals to the bus. - for peripheral in self._submodules: - m.submodules += peripheral - - # Merge the CPU's data and instruction busses. This essentially means taking the two - # separate bus masters (the CPU ibus master and the CPU dbus master), and connecting them - # to an arbiter, so they both share use of the single bus. - - # Create the arbiter around our main bus... - m.submodules.bus_arbiter = arbiter = wishbone.Arbiter(addr_width=30, data_width=32, granularity=8, features={"cti", "bte"}) - m.d.comb += arbiter.bus.connect(self.bus_decoder.bus) - - # ... and connect it to the CPU instruction and data busses. - arbiter.add(self.cpu.ibus) - arbiter.add(self.cpu.dbus) - - # Connect up our CPU interrupt lines. - m.d.comb += self.cpu.ip.eq(self.intc.ip) - - # If we're automatically creating a debug connection, do so. - if self._auto_debug: - m.d.comb += [ - self.cpu._cpu.jtag.tck .eq(synchronize(m, platform.request("user_io", 0, dir="i").i)), - self.cpu._cpu.jtag.tms .eq(synchronize(m, platform.request("user_io", 1, dir="i").i)), - self.cpu._cpu.jtag.tdi .eq(synchronize(m, platform.request("user_io", 2, dir="i").i)), - platform.request("user_io", 3, dir="o").o .eq(self.cpu._cpu.jtag.tdo) - ] - - return m - - - - def resources(self, omit_bios_mem=True): - """ Creates an iterator over each of the device's addressable resources. - - Yields (resource, address, size) for each resource. - - Parameters: - omit_bios_mem -- If True, BIOS-related memories are skipped when generating our - resource listings. This hides BIOS resources from the application. - """ - - # Grab the memory map for this SoC... - memory_map = self.bus_decoder.bus.memory_map - - # ... find each addressable peripheral... - window: amaranth_soc.memory.MemoryMap - for window, (window_start, _end, _granularity) in memory_map.windows(): - resources = window.all_resources() - - # ... find the peripheral's resources... - resource_info: amaranth_soc.memory.ResourceInfo - for resource_info in resources: - resource = resource_info.resource - register_offset = resource_info.start - register_end_offset = resource_info.end - _local_granularity = resource_info.width - - if self._build_bios and omit_bios_mem: - # If we're omitting bios resources, skip the BIOS ram/rom. - if (self.scratchpad._mem is resource) or (self.bootrom._mem is resource): - continue - - # ... and extract the peripheral's range/vitals... - size = register_end_offset - register_offset - yield window, resource, window_start + register_offset, size - - - def build(self, name=None, build_dir="build"): - """ Builds any internal artifacts necessary to create our CPU. - - This is usually used for e.g. building our BIOS. - - Parmeters: - name -- The name for the SoC design. - build_dir -- The directory where our main Amaranth build is being performed. - We'll build in a subdirectory of it. - """ - - # If we're building a BIOS, let our superclass build a BIOS for us. - if self._build_bios: - logging.info("Building SoC BIOS...") - super().build(name=name, build_dir=os.path.join(build_dir, 'soc'), do_build=True, do_init=True) - logging.info("BIOS build complete. Continuing with SoC build.") - - self.log_resources() - - - def _range_for_peripheral(self, target_peripheral): - """ Returns size information for the given peripheral. - - Returns: - addr, size -- if the given size is known; or - None, None if not - """ - - - # Grab the memory map for this SoC... - memory_map = self.bus_decoder.bus.memory_map - - # Search our memory map for the target peripheral. - resource_info: amaranth_soc.memory.ResourceInfo - for resource_info in memory_map.all_resources(): - if resource_info.name[0] is target_peripheral.name: - return resource_info.start, (resource_info.end - resource_info.start) - - return None, None - - - def _emit_minerva_basics(self, emit): - """ Emits the standard Minerva RISC-V CSR functionality. - - Parameters - ---------- - emit: callable(str) - The function used to print the code lines to the output stream. - """ - - - emit("#ifndef read_csr") - emit("#define read_csr(reg) ({ unsigned long __tmp; \\") - emit(" asm volatile (\"csrr %0, \" #reg : \"=r\"(__tmp)); \\") - emit(" __tmp; })") - emit("#endif") - emit("") - emit("#ifndef write_csr") - emit("#define write_csr(reg, val) ({ \\") - emit(" asm volatile (\"csrw \" #reg \", %0\" :: \"rK\"(val)); })") - emit("#endif") - emit("") - emit("#ifndef set_csr") - emit("#define set_csr(reg, bit) ({ unsigned long __tmp; \\") - emit(" asm volatile (\"csrrs %0, \" #reg \", %1\" : \"=r\"(__tmp) : \"rK\"(bit)); \\") - emit(" __tmp; })") - emit("#endif") - emit("") - emit("#ifndef clear_csr") - emit("#define clear_csr(reg, bit) ({ unsigned long __tmp; \\") - emit(" asm volatile (\"csrrc %0, \" #reg \", %1\" : \"=r\"(__tmp) : \"rK\"(bit)); \\") - emit(" __tmp; })") - emit("#endif") - emit("") - - emit("#ifndef MSTATUS_MIE") - emit("#define MSTATUS_MIE 0x00000008") - emit("#endif") - emit("") - - emit("//") - emit("// Minerva headers") - emit("//") - emit("") - emit("static inline uint32_t irq_getie(void)") - emit("{") - emit(" return (read_csr(mstatus) & MSTATUS_MIE) != 0;") - emit("}") - emit("") - emit("static inline void irq_setie(uint32_t ie)") - emit("{") - emit(" if (ie) {") - emit(" set_csr(mstatus, MSTATUS_MIE);") - emit(" } else {") - emit(" clear_csr(mstatus, MSTATUS_MIE);") - emit(" }") - emit("}") - emit("") - emit("static inline uint32_t irq_getmask(void)") - emit("{") - emit(" return read_csr(0x330);") - emit("}") - emit("") - emit("static inline void irq_setmask(uint32_t value)") - emit("{") - emit(" write_csr(0x330, value);") - emit("}") - emit("") - emit("static inline uint32_t pending_irqs(void)") - emit("{") - emit(" return read_csr(0x360);") - emit("}") - emit("") - - - - def generate_c_header(self, macro_name="SOC_RESOURCES", file=None, platform_name="Generic Platform"): - """ Generates a C header file that simplifies access to the platform's resources. - - Parameters: - macro_name -- Optional. The name of the guard macro for the C header, as a string without spaces. - file -- Optional. If provided, this will be treated as the file= argument to the print() - function. This can be used to generate file content instead of printing to the terminal. - """ - - def emit(content): - """ Utility function that emits a string to the targeted file. """ - print(content, file=file) - - # Create a mapping that maps our register sizes to C types. - types_for_size = { - 4: 'uint32_t', - 2: 'uint16_t', - 1: 'uint8_t' - } - - # Emit a warning header. - emit("/*") - emit(" * Automatically generated by LUNA; edits will be discarded on rebuild.") - emit(" * (Most header files phrase this 'Do not edit.'; be warned accordingly.)") - emit(" *") - emit(f" * Generated: {datetime.datetime.now()}.") - emit(" */") - emit("\n") - - emit(f"#ifndef __{macro_name}_H__") - emit(f"#define __{macro_name}_H__") - emit("") - emit("#include \n") - emit("#include ") - emit("") - - emit("//") - emit("// Environment Information") - emit("//") - - emit("") - emit(f"#define PLATFORM_NAME \"{platform_name}\"") - emit("") - - - # Emit our constant data for all Minerva CPUs. - self._emit_minerva_basics(emit) - - emit("//") - emit("// Peripherals") - emit("//") - for memory_map, resource, address, size in self.resources(): - # Get peripheral name - if memory_map.name is None: - name = resource.name - else: - name = "{}_{}".format(memory_map.name, resource.name) - - # Always generate a macro for the resource's ADDRESS and size. - emit(f"#define {name.upper()}_ADDRESS (0x{address:08x}U)") - emit(f"#define {name.upper()}_SIZE ({size})") - - # If we have information on how to access this resource, generate convenience - # macros for reading and writing it. - if hasattr(resource, 'access'): - c_type = types_for_size[size] - - # Generate a read stub, if useful... - if resource.access.readable(): - emit(f"static inline {c_type} {name}_read(void) {{") - emit(f" volatile {c_type} *reg = ({c_type} *){name.upper()}_ADDRESS;") - emit(f" return *reg;") - emit(f"}}") - - # ... and a write stub. - if resource.access.writable(): - emit(f"static inline void {name}_write({c_type} value) {{") - emit(f" volatile {c_type} *reg = ({c_type} *){name.upper()}_ADDRESS;") - emit(f" *reg = value;") - emit(f"}}") - - emit("") - - - emit("//") - emit("// Interrupts") - emit("//") - for irq, peripheral in self._irqs.items(): - - # Function that determines if a given unit has an IRQ pending. - emit(f"static inline bool {peripheral.name}_interrupt_pending(void) {{") - emit(f" return pending_irqs() & (1 << {irq});") - emit(f"}}") - - # IRQ masking - emit(f"static inline void {peripheral.name}_interrupt_enable(void) {{") - emit(f" irq_setmask(irq_getmask() | (1 << {irq}));") - emit(f"}}") - emit(f"static inline void {peripheral.name}_interrupt_disable(void) {{") - emit(f" irq_setmask(irq_getmask() & ~(1 << {irq}));") - emit(f"}}") - - emit("#endif") - emit("") - - - def generate_ld_script(self, file=None): - """ Generates an ldscript that holds our primary RAM and ROM regions. - - Parameters: - file -- Optional. If provided, this will be treated as the file= argument to the print() - function. This can be used to generate file content instead of printing to the terminal. - """ - - def emit(content): - """ Utility function that emits a string to the targeted file. """ - print(content, file=file) - - - # Insert our automatically generated header. - emit("/**") - emit(" * Linker memory regions.") - emit(" *") - emit(" * Automatically generated by LUNA; edits will be discarded on rebuild.") - emit(" * (Most header files phrase this 'Do not edit.'; be warned accordingly.)") - emit(" *") - emit(f" * Generated: {datetime.datetime.now()}.") - emit(" */") - emit("") - - emit("MEMORY") - emit("{") - - # Add regions for our main ROM and our main RAM. - for memory in [self.bootrom, self._main_ram]: - # Figure out our fields: a region name, our start, and our size. - name = "ram" if (memory is self._main_ram) else "rom" - start, size = self._range_for_peripheral(memory) - - if size: - emit(f" {name} : ORIGIN = 0x{start:08x}, LENGTH = 0x{size:08x}") - - emit("}") - emit("") - - - def log_resources(self): - """ Logs a summary of our resource utilization to our running logs. """ - - # Resource addresses: - logging.info("Physical address allocations:") - memory_map = self.bus_decoder.bus.memory_map - for resource_info in memory_map.all_resources(): - start = resource_info.start - end = resource_info.end - peripheral = resource_info.resource - logging.info(f" {start:08x}-{end:08x}: {peripheral}") - - logging.info("") - - # IRQ numbers - logging.info("IRQ allocations:") - for irq, peripheral in self._irqs.items(): - logging.info(f" {irq}: {peripheral.name}") - logging.info("") - - # Main memory. - if self._build_bios: - memory_location = self.main_ram_address() - - logging.info(f"Main memory at 0x{memory_location:08x}; upload using:") - logging.info(f" flterm --kernel --kernel-addr 0x{memory_location:08x} --speed {self._uart_baud}") - logging.info("or") - logging.info(f" lxterm --kernel --kernel-adr 0x{memory_location:08x} --speed {self._uart_baud}") - - logging.info("") - - - def main_ram_address(self): - """ Returns the address of the main system RAM. """ - start, _ = self._range_for_peripheral(self._main_ram) - return start diff --git a/luna/gateware/soc/uart.py b/luna/gateware/soc/uart.py deleted file mode 100644 index ef9ef3b2d..000000000 --- a/luna/gateware/soc/uart.py +++ /dev/null @@ -1,135 +0,0 @@ -from amaranth import * -from amaranth.lib.fifo import SyncFIFO - -from amaranth_stdio.serial import AsyncSerial - -from .peripheral import Peripheral - -__all__ = ["UARTPeripheral"] - -class UARTPeripheral(Peripheral, Elaboratable): - """Asynchronous serial transceiver peripheral. - - See :class:`amaranth_stdio.serial.AsyncSerial` for details. - - CSR registers - ------------- - divisor : read/write - Clock divisor. - rx_data : read-only - Receiver data. - rx_rdy : read-only - Receiver ready. The receiver FIFO is non-empty. - rx_err : read-only - Receiver error flags. See :class:`amaranth_stdio.serial.AsyncSerialRX` for layout. - tx_data : write-only - Transmitter data. - tx_rdy : read-only - Transmitter ready. The transmitter FIFO is non-full. - - Events - ------ - rx_rdy : level-triggered - Receiver ready. The receiver FIFO is non-empty. - rx_err : edge-triggered (rising) - Receiver error. Error cause is available in the ``rx_err`` register. - tx_mty : edge-triggered (rising) - Transmitter empty. The transmitter FIFO is empty. - - Parameters - ---------- - rx_depth : int - Depth of the receiver FIFO. - tx_depth : int - Depth of the transmitter FIFO. - divisor : int - Clock divisor reset value. Should be set to ``int(clk_frequency // baudrate)``. - divisor_bits : int - Optional. Clock divisor width. If omitted, ``bits_for(divisor)`` is used instead. - data_bits : int - Data width. - parity : ``"none"``, ``"mark"``, ``"space"``, ``"even"``, ``"odd"`` - Parity mode. - pins : :class:`Record` - Optional. UART pins. See :class:`amaranth_boards.resources.UARTResource`. - - Attributes - ---------- - bus : :class:`amaranth_soc.wishbone.Interface` - Wishbone bus interface. - irq : :class:`IRQLine` - Interrupt request line. - """ - def __init__(self, *, rx_depth=16, tx_depth=16, **kwargs): - super().__init__() - - self._phy = AsyncSerial(**kwargs) - self._rx_fifo = SyncFIFO(width=self._phy.rx.data.width, depth=rx_depth) - self._tx_fifo = SyncFIFO(width=self._phy.tx.data.width, depth=tx_depth) - - bank = self.csr_bank() - self._enabled = bank.csr(1, "w") - self._divisor = bank.csr(self._phy.divisor.width, "rw") - self._rx_data = bank.csr(self._phy.rx.data.width, "r") - self._rx_rdy = bank.csr(1, "r") - self._rx_err = bank.csr(len(self._phy.rx.err), "r") - self._tx_data = bank.csr(self._phy.tx.data.width, "w") - self._tx_rdy = bank.csr(1, "r") - - self._rx_rdy_ev = self.event(mode="level") - self._rx_err_ev = self.event(mode="rise") - self._tx_mty_ev = self.event(mode="rise") - - self._bridge = self.bridge(data_width=32, granularity=8, alignment=2) - self.bus = self._bridge.bus - self.irq = self._bridge.irq - - self.tx = Signal() - self.rx = Signal() - self.enabled = Signal() - self.driving = Signal() - - def elaborate(self, platform): - m = Module() - m.submodules.bridge = self._bridge - - m.submodules.phy = self._phy - m.submodules.rx_fifo = self._rx_fifo - m.submodules.tx_fifo = self._tx_fifo - - m.d.comb += self._divisor.r_data.eq(self._phy.divisor) - with m.If(self._divisor.w_stb): - m.d.sync += self._phy.divisor.eq(self._divisor.w_data) - - # Control our UART's enable state. - with m.If(self._enabled.w_stb): - m.d.sync += self.enabled.eq(self._enabled.w_data) - - m.d.comb += [ - self._rx_data.r_data .eq(self._rx_fifo.r_data), - self._rx_fifo.r_en .eq(self._rx_data.r_stb), - self._rx_rdy.r_data .eq(self._rx_fifo.r_rdy), - - self._rx_fifo.w_data .eq(self._phy.rx.data), - self._rx_fifo.w_en .eq(self._phy.rx.rdy), - self._phy.rx.ack .eq(self._rx_fifo.w_rdy), - self._rx_err.r_data .eq(self._phy.rx.err), - - self._tx_fifo.w_en .eq(self._tx_data.w_stb & self.enabled), - self._tx_fifo.w_data .eq(self._tx_data.w_data), - self._tx_rdy.r_data .eq(self._tx_fifo.w_rdy), - - self._phy.tx.data .eq(self._tx_fifo.r_data), - self._phy.tx.ack .eq(self._tx_fifo.r_rdy), - self._tx_fifo.r_en .eq(self._phy.tx.rdy), - - self._rx_rdy_ev.stb .eq(self._rx_fifo.r_rdy), - self._rx_err_ev.stb .eq(self._phy.rx.err.any()), - self._tx_mty_ev.stb .eq(~self._tx_fifo.r_rdy), - - self.tx .eq(self._phy.tx.o), - self._phy.rx.i .eq(self.rx), - self.driving .eq(~self._phy.tx.rdy) - ] - - return m diff --git a/luna/gateware/usb/usb2/device.py b/luna/gateware/usb/usb2/device.py index 67d77349e..e73b0f522 100644 --- a/luna/gateware/usb/usb2/device.py +++ b/luna/gateware/usb/usb2/device.py @@ -625,109 +625,5 @@ def test_descriptor_zlp(self): self.assertEqual(len(data), request_length) -# -# Section that requires our CPU framework. -# We'll very deliberately section that off, so it -# -try: - - from ...soc.peripheral import Peripheral - - class USBDeviceController(Peripheral, Elaboratable): - """ SoC controller for a USBDevice. - - Breaks our USBDevice control and status signals out into registers so a CPU / Wishbone master - can control our USB device. - - The attributes below are intended to connect to a USBDevice. Typically, they'd be created by - using the .controller() method on a USBDevice object, which will automatically connect all - relevant signals. - - Attributes - ---------- - - connect: Signal(), output - High when the USBDevice should be allowed to connect to a host. - - """ - - def __init__(self): - super().__init__() - - # - # I/O port - # - self.connect = Signal(reset=1) - self.bus_reset = Signal() - - - # - # Registers. - # - - regs = self.csr_bank() - self._connect = regs.csr(1, "rw", desc=""" - Set this bit to '1' to allow the associated USB device to connect to a host. - """) - - self._speed = regs.csr(2, "r", desc=""" - Indicates the current speed of the USB device. 0 indicates High; 1 => Full, - 2 => Low, and 3 => SuperSpeed (incl SuperSpeed+). - """) - - self._reset_irq = self.event(mode="rise", name="reset", desc=""" - Interrupt that occurs when a USB bus reset is received. - """) - - # Wishbone connection. - self._bridge = self.bridge(data_width=32, granularity=8, alignment=2) - self.bus = self._bridge.bus - self.irq = self._bridge.irq - - - def attach(self, device: USBDevice): - """ Returns a list of statements necessary to connect this to a USB controller. - - The returned values makes all of the connections necessary to provide control and fetch status - from the relevant USB device. These can be made either combinationally or synchronously, but - combinational is recommended; as these signals are typically fed from a register anyway. - - Parameters - ---------- - device: USBDevice - The :class:`USBDevice` object to be controlled. - """ - return [ - device.connect .eq(self.connect), - self.bus_reset .eq(device.reset_detected), - self._speed.r_data .eq(device.speed) - ] - - - def elaborate(self, platform): - m = Module() - m.submodules.bridge = self._bridge - - # Core connection register. - m.d.comb += self.connect.eq(self._connect.r_data) - with m.If(self._connect.w_stb): - m.d.usb += self._connect.r_data.eq(self._connect.w_data) - - # Reset-detection event. - m.d.comb += self._reset_irq.stb.eq(self.bus_reset) - - return m - - -except ImportError as e: - # Since this exception happens so early, top_level_cli won't have set up logging yet, - # so call the setup here to avoid getting stuck with Python's default config. - configure_default_logging() - - logging.warning("SoC framework components could not be imported; some functionality will be unavailable.") - logging.warning(e) - - - if __name__ == "__main__": unittest.main() diff --git a/luna/gateware/usb/usb2/interfaces/__init__.py b/luna/gateware/usb/usb2/interfaces/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/luna/gateware/usb/usb2/interfaces/eptri.py b/luna/gateware/usb/usb2/interfaces/eptri.py deleted file mode 100644 index 789a51d96..000000000 --- a/luna/gateware/usb/usb2/interfaces/eptri.py +++ /dev/null @@ -1,708 +0,0 @@ -# -# This file is part of LUNA. -# -# Copyright (c) 2020 Great Scott Gadgets -# SPDX-License-Identifier: BSD-3-Clause - -""" Implementation of a Triple-FIFO endpoint manager. - -Equivalent (but not binary-compatbile) implementation of ValentyUSB's ``eptri``. - -For an example, see ``examples/usb/eptri`` or TinyUSB's ``luna/dcd_eptri.c``. -""" - -from amaranth import * -from amaranth.lib.fifo import SyncFIFOBuffered -from amaranth.hdl.xfrm import ResetInserter, DomainRenamer - - -from ..endpoint import EndpointInterface -from ....soc.peripheral import Peripheral -from luna.gateware.usb.usb2 import endpoint - - -class SetupFIFOInterface(Peripheral, Elaboratable): - """ Setup component of our `eptri`-equivalent interface. - - Implements the USB Setup FIFO, which handles SETUP packets on any endpoint. - - This interface is similar to an :class:`OutFIFOInterface`, but always ACKs packets, - and does not allow for any flow control; as a USB device must always be ready to accept - control packets. [USB2.0: 8.6.1] - - Attributes - ----- - - interface: EndpointInterface - Our primary interface to the core USB device hardware. - """ - - def __init__(self): - super().__init__() - - # - # Registers - # - - regs = self.csr_bank() - - self.data = regs.csr(8, "r", desc=""" - A FIFO that returns the bytes from the most recently captured SETUP packet. - Reading a byte from this register advances the FIFO. The first eight bytes read - from this conain the core SETUP packet. - """) - - self.reset = regs.csr(1, "w", desc=""" - Local reset control for the SETUP handler; writing a '1' to this register clears the handler state. - """) - - self.epno = regs.csr(4, "r", desc="The number of the endpoint associated with the current SETUP packet.") - self.have = regs.csr(1, "r", desc="`1` iff data is available in the FIFO.") - self.pend = regs.csr(1, "r", desc="`1` iff an interrupt is pending") - - - # TODO: figure out where this should actually go to match ValentyUSB as much as possible - self._address = regs.csr(8, "rw", desc=""" - Controls the current device's USB address. Should be written after a SET_ADDRESS request is - received. Automatically resets back to zero on a USB reset. - """) - - # - # IRQ / Events - # - self.setup_received = self.event(mode="rise", desc=""" - Interrupt that triggers when a new SETUP packet is ready to be read. - """) - - # - # I/O port - # - self.interface = EndpointInterface() - - # - # Internals - # - - # Act as a Wishbone peripheral. - self._bridge = self.bridge(data_width=32, granularity=8, alignment=2) - self.bus = self._bridge.bus - self.irq = self._bridge.irq - - - def elaborate(self, platform): - m = Module() - m.submodules.bridge = self._bridge - - # Shortcuts to our components. - interface = self.interface - token = self.interface.tokenizer - rx = self.interface.rx - handshakes_out = self.interface.handshakes_out - - # Logic condition for getting a new setup packet. - new_setup = token.new_token & token.is_setup - reset_requested = self.reset.w_stb & self.reset.w_data - clear_fifo = new_setup | reset_requested - - # - # Core FIFO. - # - m.submodules.fifo = fifo = ResetInserter(clear_fifo)(SyncFIFOBuffered(width=8, depth=8)) - - m.d.comb += [ - - # We'll write to the active FIFO whenever the last received token is a SETUP - # token, and we have incoming data; and we'll always write the data received - fifo.w_en .eq(token.is_setup & rx.valid & rx.next), - fifo.w_data .eq(rx.payload), - - # We'll advance the FIFO whenever our CPU reads from the data CSR; - # and we'll always read our data from the FIFO. - fifo.r_en .eq(self.data.r_stb), - self.data.r_data .eq(fifo.r_data), - - # Pass the FIFO status on to our CPU. - self.have.r_data .eq(fifo.r_rdy), - - # Always acknowledge SETUP packets as they arrive. - handshakes_out.ack .eq(token.is_setup & interface.rx_ready_for_response), - - # Trigger a SETUP interrupt as we ACK the setup packet, since that's also the point - # where we know we're done receiving data. - self.setup_received.stb .eq(handshakes_out.ack) - ] - - # - # Control registers - # - - # Our address register always reads the current address of the device; - # but will generate a - m.d.comb += self._address.r_data.eq(interface.active_address) - with m.If(self._address.w_stb): - m.d.comb += [ - interface.address_changed .eq(1), - interface.new_address .eq(self._address.w_data), - ] - - - # - # Status and interrupts. - # - - with m.If(token.new_token): - m.d.usb += self.epno.r_data.eq(token.endpoint) - - # TODO: generate interrupts - - return DomainRenamer({"sync": "usb"})(m) - - - -class InFIFOInterface(Peripheral, Elaboratable): - """ IN component of our `eptri`-equivalent interface. - - Implements the FIFO that handles `eptri` IN requests. This FIFO collects USB data, and - transmits it in response to an IN token. Like all `eptri` interfaces; it can handle only one - pending packet at a time. - - - Attributes - ----- - - interface: EndpointInterface - Our primary interface to the core USB device hardware. - - """ - - - def __init__(self, max_packet_size=512): - """ - Parameters - ---------- - max_packet_size: int, optional - Sets the maximum packet size that can be transmitted on this endpoint. - This should match the value provided in the relevant endpoint descriptor. - """ - - super().__init__() - - self._max_packet_size = max_packet_size - - # - # Registers - # - - regs = self.csr_bank() - - self.data = regs.csr(8, "w", desc=""" - Write-only register. Each write enqueues a byte to be transmitted; gradually building - a single packet to be transmitted. This queue should only ever contain a single packet; - it is the software's responsibility to handle breaking requests down into packets. - """) - - self.epno = regs.csr(4, "rw", desc=""" - Contains the endpoint the enqueued packet is to be transmitted on. Writing this register - marks the relevant packet as ready to transmit; and thus should only be written after a - full packet has been written into the FIFO. If no data has been placed into the DATA FIFO, - a zero-length packet is generated. - - Note that any IN requests that do not match the endpoint number are automatically NAK'd. - """) - - self.reset = regs.csr(1, "w", desc="A write to this register clears the FIFO without transmitting.") - - self.stall = regs.csr(1, "rw", desc=""" - When this register contains '1', any IN tokens targeting `epno` will be responded to with a - STALL token, rather than DATA or a NAK. - - For EP0, this register will automatically be cleared when a new SETUP token is received. - """) - - self.idle = regs.csr(1, "r", desc="This value is `1` if no packet is actively being transmitted.") - self.have = regs.csr(1, "r", desc="This value is `1` if data is present in the transmit FIFO.") - self.pend = regs.csr(1, "r", desc="`1` iff an interrupt is pending") - self.pid = regs.csr(1, "rw", desc="Contains the current PID toggle bit for the given endpoint.") - - # - # Interrupts - # - - self._done_irq = self.event(mode="rise", name="done", desc=""" - Indicates that the host has successfully transferred an ``IN`` packet, - and that the FIFO is now empty. - """) - - # - # I/O port - # - self.interface = EndpointInterface() - - # - # Internals - # - - # Act as a Wishbone peripheral. - self._bridge = self.bridge(data_width=32, granularity=8, alignment=2) - self.bus = self._bridge.bus - self.irq = self._bridge.irq - - - - def elaborate(self, platform): - m = Module() - m.submodules.bridge = self._bridge - - # Shortcuts to our components. - token = self.interface.tokenizer - tx = self.interface.tx - handshakes_out = self.interface.handshakes_out - - # - # Core FIFO. - # - - - # Create our FIFO; and set it to be cleared whenever the user requests. - m.submodules.fifo = fifo = ResetInserter(self.reset.w_stb)( - SyncFIFOBuffered(width=8, depth=self._max_packet_size) - ) - - m.d.comb += [ - # Whenever the user DATA register is written to, add the relevant data to our FIFO. - fifo.w_en .eq(self.data.w_stb), - fifo.w_data .eq(self.data.w_data), - ] - - # Keep track of the amount of data in our FIFO. - bytes_in_fifo = Signal(range(0, self._max_packet_size + 1)) - - # If we're clearing the whole FIFO, reset our data count. - with m.If(self.reset.w_stb): - m.d.usb += bytes_in_fifo.eq(0) - - # Keep track of our FIFO's data count as data is added or removed. - increment = fifo.w_en & fifo.w_rdy - decrement = fifo.r_en & fifo.r_rdy - - with m.Elif(increment & ~decrement): - m.d.usb += bytes_in_fifo.eq(bytes_in_fifo + 1) - with m.Elif(decrement & ~increment): - m.d.usb += bytes_in_fifo.eq(bytes_in_fifo - 1) - - - # - # Register updates. - # - - # Active endpoint number. - with m.If(self.epno.w_stb): - m.d.usb += self.epno.r_data.eq(self.epno.w_data) - - # Keep track of which endpoints are stalled. - endpoint_stalled = Array(Signal() for _ in range(16)) - - # Keep track of the current DATA pid for each endpoint. - endpoint_data_pid = Array(Signal() for _ in range(16)) - - # Clear our system state on reset. - with m.If(self.reset.w_stb): - for i in range(16): - m.d.usb += [ - endpoint_stalled[i] .eq(0), - endpoint_data_pid[i] .eq(0), - ] - - - # Set the value of our endpoint `stall` based on our `stall` register... - with m.If(self.stall.w_stb): - m.d.usb += endpoint_stalled[self.epno.r_data].eq(self.stall.w_data) - - # Clear our endpoint `stall` when we get a SETUP packet, and reset the endpoint's - # data PID to DATA1, as per [USB2.0: 8.5.3], the first packet of the DATA or STATUS - # phase always carries a DATA1 PID. - with m.If(token.is_setup & token.new_token): - m.d.usb += [ - endpoint_stalled[token.endpoint] .eq(0), - endpoint_data_pid[token.endpoint] .eq(1) - ] - - - # - # Status registers. - # - m.d.comb += [ - self.have.r_data .eq(fifo.r_rdy), - self.pid.r_data .eq(endpoint_data_pid[self.epno.r_data]) - ] - - # - # Data toggle control. - # - endpoint_matches = (token.endpoint == self.epno.r_data) - packet_complete = self.interface.handshakes_in.ack & token.is_in & endpoint_matches - - # Always drive the DATA pid we're transmitting with our current data pid. - m.d.comb += self.interface.tx_pid_toggle.eq(endpoint_data_pid[token.endpoint]) - - # If our controller is overriding the data PID, accept the override. - with m.If(self.pid.w_stb): - m.d.usb += endpoint_data_pid[self.epno.r_data].eq(self.pid.w_data) - - # Otherwise, toggle our expected DATA PID once we receive a complete packet. - with m.Elif(packet_complete): - m.d.usb += endpoint_data_pid[token.endpoint].eq(~endpoint_data_pid[token.endpoint]) - - - # - # Control logic. - # - - # Logic shorthand. - new_in_token = (token.is_in & token.ready_for_response) - stalled = endpoint_stalled[token.endpoint] - - with m.FSM(domain='usb') as f: - - # Drive our IDLE line based on our FSM state. - m.d.comb += self.idle.r_data.eq(f.ongoing('IDLE')) - - # IDLE -- our CPU hasn't yet requested that we send data. - # We'll wait for it to do so, and NAK any packets that arrive. - with m.State("IDLE"): - - # If we get an IN token... - with m.If(new_in_token): - - # STALL it, if the endpoint is STALL'd... - with m.If(stalled): - m.d.comb += handshakes_out.stall.eq(1) - - # Otherwise, NAK. - with m.Else(): - m.d.comb += handshakes_out.nak.eq(1) - - - # If the user request that we send data, "prime" the endpoint. - # This means we have data to send, but are just waiting for an IN token. - with m.If(self.epno.w_stb & ~stalled): - m.next = "PRIMED" - - # Always return to IDLE on reset. - with m.If(self.reset.w_stb): - m.next = "IDLE" - - # PRIMED -- our CPU has provided data, but we haven't been sent an IN token, yet. - # Await that IN token. - with m.State("PRIMED"): - - with m.If(new_in_token): - - # If the target endpoint is STALL'd, reply with STALL no matter what. - with m.If(stalled): - m.d.comb += handshakes_out.stall.eq(1) - - # If we have a new IN token to our endpoint, move to responding to it. - with m.Elif(endpoint_matches): - - # If there's no data in our endpoint, send a ZLP. - with m.If(~fifo.r_rdy): - m.next = "SEND_ZLP" - - # Otherwise, send our data, starting with our first byte. - with m.Else(): - m.d.usb += tx.first.eq(1) - m.next = "SEND_DATA" - - # Otherwise, we don't have a response; NAK the packet. - with m.Else(): - m.d.comb += handshakes_out.nak.eq(1) - - # Always return to IDLE on reset. - with m.If(self.reset.w_stb): - m.next = "IDLE" - - # SEND_ZLP -- we're now now ready to respond to an IN token with a ZLP. - # Send our response. - with m.State("SEND_ZLP"): - m.d.comb += [ - tx.valid .eq(1), - tx.last .eq(1) - ] - m.d.comb += self._done_irq.stb.eq(1) - m.next = 'IDLE' - - # SEND_DATA -- we're now ready to respond to an IN token to our endpoint. - # Send our response. - with m.State("SEND_DATA"): - last_byte = (bytes_in_fifo == 1) - - m.d.comb += [ - tx.valid .eq(1), - tx.last .eq(last_byte), - - # Drive our transmit data directly from our FIFO... - tx.payload .eq(fifo.r_data), - - # ... and advance our FIFO each time a data byte is transmitted. - fifo.r_en .eq(tx.ready) - ] - - # After we've sent a byte, drop our first flag. - with m.If(tx.ready): - m.d.usb += tx.first.eq(0) - - # Once we transmit our last packet, we're done transmitting. Move back to IDLE. - with m.If(last_byte & tx.ready): - # Trigger our DONE interrupt. - m.d.comb += self._done_irq.stb.eq(1) - m.next = 'IDLE' - - # Always return to IDLE on reset. - with m.If(self.reset.w_stb): - m.next = "IDLE" - - return DomainRenamer({"sync": "usb"})(m) - - - -class OutFIFOInterface(Peripheral, Elaboratable): - """ OUT component of our `eptri` - - Implements the OUT FIFO, which handles receiving packets from our host. - - Attributes - ----- - - interface: EndpointInterface - Our primary interface to the core USB device hardware. - """ - - def __init__(self, max_packet_size=512): - super().__init__() - - self._max_packet_size = max_packet_size - - # - # Registers - # - - regs = self.csr_bank() - self.data = regs.csr(8, "r", desc=""" - A FIFO that returns the bytes from the most recently captured OUT transaction. - Reading a byte from this register advances the FIFO. - """) - self.data_ep = regs.csr(4, "r", desc=""" - Register that contains the endpoint number associated with the data in the FIFO -- that is, - the endpoint number on which the relevant data was received. - """) - - self.reset = regs.csr(1, "w", desc=""" - Local reset for the OUT handler; clears the out FIFO. - """) - - self.epno = regs.csr(4, "rw", desc=""" - Selects the endpoint number to prime. This interface only allows priming a single endpoint at once-- - that is, only one endpoint can be ready to receive data at a time. See the `enable` bit for usage. - """) - - self.enable = regs.csr(1, "rw", desc=""" - Controls whether any data can be received on any primed OUT endpoint. This bit is automatically cleared - on receive in order to give the controller time to read data from the FIFO. It must be re-enabled once - the FIFO has been emptied. - """) - - self.prime = regs.csr(1, "w", desc=""" - Controls "priming" an out endpoint. To receive data on any endpoint, the CPU must first select - the endpoint with the `epno` register; and then write a '1' into the prime and enable register. - This prepares our FIFO to receive data; and the next OUT transaction will be captured into the FIFO. - - When a transaction is complete, the `enable` bit is reset; the `prime` is not. This effectively means - that `enable` controls receiving on _any_ of the primed endpoints; while `prime` can be used to build - a collection of endpoints willing to participate in receipt. - - Only one transaction / data packet is captured per `enable` write; repeated enabling is necessary - to capture multiple packets. - """) - - self.stall = regs.csr(1, "rw", desc=""" - Controls STALL'ing the active endpoint. Setting or clearing this bit will set or clear STALL on - the provided endpoint. Endpoint STALLs persist even after `epno` is changed; so multiple endpoints - can be stalled at once by writing their respective endpoint numbers into `epno` register and then - setting their `stall` bits. - """) - - - self.have = regs.csr(1, "r", desc="`1` iff data is available in the FIFO.") - self.pend = regs.csr(1, "r", desc="`1` iff an interrupt is pending") - - # TODO: figure out where this should actually go to match ValentyUSB as much as possible - self._address = regs.csr(8, "rw", desc=""" - Controls the current device's USB address. Should be written after a SET_ADDRESS request is - received. Automatically resets back to zero on a USB reset. - """) - - self.pid = regs.csr(1, "rw", desc="Contains the current PID toggle bit for the given endpoint.") - - # - # Interrupts. - # - - self._done_irq = self.event(mode="rise", name="done", desc=""" - Indicates that an ``OUT`` packet has successfully been transferred - from the host. This bit must be cleared in order to receive - additional packets. - """) - - - # - # I/O port - # - self.interface = EndpointInterface() - - # - # Internals - # - - # Act as a Wishbone peripheral. - self._bridge = self.bridge(data_width=32, granularity=8, alignment=2) - self.bus = self._bridge.bus - self.irq = self._bridge.irq - - - def elaborate(self, platform): - m = Module() - m.submodules.bridge = self._bridge - - # Shortcuts to our components. - interface = self.interface - token = self.interface.tokenizer - rx = self.interface.rx - handshakes_out = self.interface.handshakes_out - - # - # Control registers - # - - # Active endpoint number. - with m.If(self.epno.w_stb): - m.d.usb += self.epno.r_data.eq(self.epno.w_data) - - # Keep track of which endpoints are primed. - endpoint_primed = Array(Signal() for _ in range(16)) - - # Keep track of which endpoints are stalled. - endpoint_stalled = Array(Signal() for _ in range(16)) - - # Keep track of the PIDs for each endpoint, which we'll toggle automatically. - endpoint_data_pid = Array(Signal() for _ in range(16)) - - # Keep track of whether we're enabled. - with m.If(self.enable.w_stb): - m.d.usb += self.enable.r_data.eq(self.enable.w_data) - - # If Prime is written to, mark the relevant endpoint as primed. - with m.If(self.prime.w_stb): - m.d.usb += endpoint_primed[self.epno.r_data].eq(self.prime.w_data) - - # If we've just ACK'd a receive, clear our enable and un-prime the given endpoint. - with m.If(interface.handshakes_out.ack & token.is_out): - m.d.usb += [ - self.enable.r_data .eq(0), - endpoint_primed[token.endpoint] .eq(0), - ] - - # Set the value of our endpoint `stall` based on our `stall` register... - with m.If(self.stall.w_stb): - m.d.usb += endpoint_stalled[self.epno.r_data].eq(self.stall.w_data) - - # Allow our controller to override our DATA pid, selectively. - with m.If(self.pid.w_stb): - m.d.usb += endpoint_data_pid[self.epno.r_data].eq(self.pid.w_data) - - # Clear our endpoint `stall` when we get a SETUP packet, and reset the endpoint's - # data PID to DATA1, as per [USB2.0: 8.5.3], the first packet of the DATA or STATUS - # phase always carries a DATA1 PID. - with m.If(token.is_setup & token.new_token): - m.d.usb += [ - endpoint_stalled[token.endpoint] .eq(0), - endpoint_data_pid[token.endpoint] .eq(1) - ] - - # - # Core FIFO. - # - m.submodules.fifo = fifo = ResetInserter(self.reset.w_stb)( - SyncFIFOBuffered(width=8, depth=self._max_packet_size) - ) - - # Shortcut for when we should allow a receive. We'll read when: - # - Our `epno` register matches the target register; and - # - We've primed the relevant endpoint. - # - Our most recent token is an OUT. - # - We're not stalled. - stalled = token.is_out & endpoint_stalled[token.endpoint] - endpoint_primed = endpoint_primed[token.endpoint] - ready_to_receive = endpoint_primed & self.enable.r_data & ~stalled - allow_receive = token.is_out & ready_to_receive - nak_receives = token.is_out & ~ready_to_receive & ~stalled - - # Shortcut for when we have a "redundant"/incorrect PID. In these cases, we'll assume - # the host missed our ACK, and per the USB spec, implicitly ACK the packet. - is_redundant_pid = (interface.rx_pid_toggle != endpoint_data_pid[token.endpoint]) - is_redundant_packet = endpoint_primed & token.is_out & is_redundant_pid - - # Shortcut conditions under which we'll ACK and NAK a receive. - ack_redundant_packet = (is_redundant_packet & interface.rx_ready_for_response) - ack_receive = allow_receive & interface.rx_ready_for_response - nak_receive = nak_receives & interface.rx_ready_for_response & ~ack_redundant_packet - - # Conditions under which we'll ACK or NAK a ping. - ack_ping = ready_to_receive & token.is_ping & token.ready_for_response - nak_ping = ~ready_to_receive & token.is_ping & token.ready_for_response - - m.d.comb += [ - # We'll write to the endpoint iff we've valid data, and we're allowed receive. - fifo.w_en .eq(allow_receive & rx.valid & rx.next & ~is_redundant_packet), - fifo.w_data .eq(rx.payload), - - # We'll advance the FIFO whenever our CPU reads from the data CSR; - # and we'll always read our data from the FIFO. - fifo.r_en .eq(self.data.r_stb), - self.data.r_data .eq(fifo.r_data), - - # Pass the FIFO status on to our CPU. - self.have.r_data .eq(fifo.r_rdy), - - # If we've just finished an allowed receive, ACK. - handshakes_out.ack .eq(ack_receive | ack_ping | ack_redundant_packet), - - # Trigger our DONE interrupt once we ACK a received/allowed packet. - self._done_irq.stb .eq(ack_receive), - - # If we were stalled, stall. - handshakes_out.stall .eq(stalled & interface.rx_ready_for_response), - - # If we're not ACK'ing or STALL'ing, NAK all packets. - handshakes_out.nak .eq(nak_receive | nak_ping), - - # Always indicate the current DATA PID in the PID register. - self.pid.r_data .eq(endpoint_data_pid[self.epno.r_data]) - ] - - # Whenever we capture data, update our associated endpoint number - # to match the endpoint on which we received the relevant data. - with m.If(fifo.w_en): - m.d.usb += self.data_ep.r_data.eq(token.endpoint) - - # Whenever we ACK a non-redundant receive, toggle our DATA PID. - # (unless the user happens to be overriding it by writing to the PID register). - with m.If(ack_receive & ~is_redundant_packet & ~self.pid.w_stb): - m.d.usb += endpoint_data_pid[token.endpoint].eq(~endpoint_data_pid[token.endpoint]) - - - # - # Interrupt/status - # - - return DomainRenamer({"sync": "usb"})(m) diff --git a/pyproject.toml b/pyproject.toml index f14df9c58..b19ef44aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,6 @@ usb-protocol = {git = "https://github.com/usb-tools/python-usb-protocol"} tox = "^3.22.0" minerva = {git = "https://github.com/lambdaconcept/minerva.git"} amaranth-stdio = {git = "https://github.com/amaranth-lang/amaranth-stdio.git", branch="main"} -lambdasoc = {git = "https://github.com/lambdaconcept/lambdasoc.git"} prompt-toolkit = "^3.0.16" [build-system] diff --git a/requirements.txt b/requirements.txt index 8c5b840d9..49e81419b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,6 @@ pyusb -e git+https://github.com/amaranth-lang/amaranth-soc.git#egg=amaranth-soc -e git+https://github.com/amaranth-lang/amaranth-boards.git#egg=amaranth_boards -e git+https://github.com/lambdaconcept/minerva.git#egg=minerva --e git+https://github.com/lambdaconcept/lambdasoc.git#egg=lambdasoc -e git+https://github.com/usb-tools/python-usb-protocol.git#egg=usb_protocol -e git+https://github.com/greatscottgadgets/apollo.git#egg=apollo-fpga pyvcd