├── .gitignore ├── .readthedocs.yaml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE.txt ├── README.md ├── docs ├── Makefile ├── README.md ├── bin │ └── svd2rst.py ├── images │ └── eptri │ │ ├── block-diagram.txt │ │ ├── registers-USB0.rst │ │ ├── registers-USB0_EP_CONTROL.rst │ │ ├── registers-USB0_EP_IN.rst │ │ ├── registers-USB0_EP_OUT.rst │ │ ├── seq-bulk-in-transfer.d2 │ │ ├── seq-bulk-in-transfer.svg │ │ ├── seq-bulk-out-transfer.d2 │ │ ├── seq-bulk-out-transfer.svg │ │ ├── seq-bus-reset.d2 │ │ ├── seq-bus-reset.svg │ │ ├── seq-connect.d2 │ │ ├── seq-connect.svg │ │ ├── seq-control-in-transfer.d2 │ │ ├── seq-control-in-transfer.svg │ │ ├── seq-control-out-transfer.d2 │ │ └── seq-control-out-transfer.svg ├── make.bat ├── requirements.txt └── source │ ├── conf.py │ ├── eptri.rst │ ├── index.rst │ └── lunasoc.svd ├── examples ├── .keep ├── README.md ├── hello-c │ ├── Makefile │ ├── main.c │ ├── riscv_application.ld │ ├── start.S │ └── top.py └── hello-rust │ ├── .cargo │ └── config.toml │ ├── Cargo.toml │ ├── LICENSE.txt │ ├── Makefile │ ├── README.md │ ├── firmware │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── log.rs │ │ └── main.rs │ ├── lunasoc-pac │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ ├── cpu.rs │ │ ├── csr.rs │ │ ├── lib.rs │ │ └── macros.rs │ └── top.py ├── luna_soc ├── __init__.py ├── gateware │ ├── __init__.py │ ├── core │ │ ├── __init__.py │ │ ├── blockram.py │ │ ├── ila.py │ │ ├── spiflash │ │ │ ├── __init__.py │ │ │ ├── controller.py │ │ │ ├── mmap.py │ │ │ ├── phy.py │ │ │ ├── port.py │ │ │ └── utils.py │ │ ├── timer.py │ │ ├── uart.py │ │ └── usb2 │ │ │ ├── __init__.py │ │ │ ├── device.py │ │ │ ├── ep_control.py │ │ │ ├── ep_in.py │ │ │ ├── ep_out.py │ │ │ └── ulpi.py │ ├── cpu │ │ ├── __init__.py │ │ ├── ic.py │ │ ├── minerva.py │ │ ├── verilog │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ └── vexriscv │ │ │ │ ├── scala │ │ │ │ ├── build.sbt │ │ │ │ ├── project │ │ │ │ │ ├── build.properties │ │ │ │ │ └── plugins.sbt │ │ │ │ └── src │ │ │ │ │ └── main │ │ │ │ │ └── scala │ │ │ │ │ ├── Config.scala │ │ │ │ │ ├── GenCoreCynthion.scala │ │ │ │ │ ├── GenCoreCynthionJtag.scala │ │ │ │ │ ├── GenCoreImacDcache.scala │ │ │ │ │ └── GenCoreImc.scala │ │ │ │ ├── vexriscv_cynthion+jtag.v │ │ │ │ ├── vexriscv_cynthion+jtag.yaml │ │ │ │ ├── vexriscv_cynthion.v │ │ │ │ ├── vexriscv_cynthion.yaml │ │ │ │ ├── vexriscv_imac+dcache.v │ │ │ │ ├── vexriscv_imac+dcache.yaml │ │ │ │ ├── vexriscv_imac+litex.v │ │ │ │ ├── vexriscv_imac+litex.yaml │ │ │ │ ├── vexriscv_imc.v │ │ │ │ └── vexriscv_imc.yaml │ │ └── vexriscv.py │ ├── interface │ │ └── .keep │ ├── provider │ │ ├── .keep │ │ └── cynthion.py │ └── vendor │ │ ├── README.md │ │ ├── __init__.py │ │ ├── amaranth_soc │ │ ├── __init__.py │ │ ├── csr │ │ │ ├── __init__.py │ │ │ ├── action.py │ │ │ ├── bus.py │ │ │ ├── event.py │ │ │ ├── reg.py │ │ │ └── wishbone.py │ │ ├── event.py │ │ ├── gpio.py │ │ ├── memory.py │ │ ├── periph.py │ │ └── wishbone │ │ │ ├── __init__.py │ │ │ ├── bus.py │ │ │ └── sram.py │ │ └── amaranth_stdio │ │ ├── __init__.py │ │ └── serial.py ├── generate │ ├── __init__.py │ ├── c.py │ ├── introspect.py │ ├── rust.py │ └── svd.py ├── top_level_cli.py └── util │ ├── __init__.py │ └── readbin.py └── pyproject.toml /.gitignore: -------------------------------------------------------------------------------- 1 | # Build outputs 2 | **/scala/project/target/ 3 | **/scala/target/ 4 | memory.x 5 | applets/**/build/ 6 | examples/**/build/ 7 | *.bit 8 | 9 | # Compiled python files 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | *.egg-info 14 | host/dist 15 | 16 | # C 17 | *.bin 18 | *.elf 19 | *.o 20 | 21 | # Rust 22 | **/target/ 23 | Cargo.lock 24 | **/*.rs.bk 25 | 26 | # Operating system spew 27 | .DS_Store 28 | .Spotlight-V100 29 | .Trashes 30 | ehthumbs.db 31 | Thumbs.db 32 | 33 | # Editor junk 34 | *.swp 35 | *.sublime-project 36 | *.sublime-workspace 37 | .vscode 38 | .idea 39 | 40 | # ctags output 41 | tags 42 | 43 | # ccls output 44 | .ccls-cache 45 | .ccls 46 | compile_commands.json 47 | 48 | # files involved in generating a release 49 | VERSION 50 | dist 51 | host-packages 52 | host/.eggs 53 | release-files 54 | deploy-files 55 | firmware-bin 56 | *.tar.xz 57 | *.zip 58 | 59 | # Never include unencrypted deploy keys. 60 | id_deploy 61 | id_deploy.pub 62 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the OS, Python version and other tools 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.12" 13 | 14 | # Build documentation in the "docs/" directory with Sphinx 15 | sphinx: 16 | configuration: docs/source/conf.py 17 | 18 | # Build PDF for docs 19 | formats: 20 | - pdf 21 | 22 | python: 23 | install: 24 | - requirements: docs/requirements.txt 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | 11 | 12 | ## [0.3.1] - 2025-05-16 13 | ### Fixed 14 | * EPTRI device would emit continual bus reset interrupts when unplugged. 15 | * Regression in 0.2.4 that prevented Facedancer ftdi-echo example from working. 16 | 17 | 18 | ## [0.3.0] - 2025-04-03 19 | > This is a breaking release which will require substantial changes in all useages of luna-soc cores. 20 | > 21 | > For migration information see: https://github.com/greatscottgadgets/luna-soc/pull/33 22 | > 23 | > Please refer to the [v0.2.x](https://github.com/greatscottgadgets/luna-soc/tree/v0.2.x) branch for older releases of luna-soc. 24 | 25 | ### Changed 26 | * All gateware has been ported from Lambdasoc to Amaranth `0.5.x` and Amaranth-SoC. 27 | 28 | --- 29 | 30 | ## [0.2.5] - 2025-05-16 31 | ### Fixed 32 | * Facedancer ftdi-echo example stopped working in the 0.2.4 release. 33 | 34 | 35 | ## [0.2.4] - 2025-04-03 36 | ### Fixed 37 | * Concurrent transactions could lock `OutFIFOInterface`. (Tx @thejh!) 38 | * Concurrent transactions could lock `InFIFOInterface`. 39 | 40 | 41 | ## [0.2.3] - 2025-02-26 42 | ### Fixed 43 | - Only set ep_control epno register if it's a setup packet. 44 | ### Removed 45 | * Dropped support for Python 3.8 46 | ### Security 47 | * Bump jinja2 from 3.1.4 to 3.1.5 48 | 49 | 50 | ## [0.2.2] - 2024-12-19 51 | ### Added 52 | - Support for yowasp when yosys is unavailable. 53 | 54 | 55 | ## [0.2.1] - 2024-11-25 56 | ### Fixed 57 | - Fix ambiguous documentation and implementation for usb2.interfaces.eptri.OutFIFOInterface priming behaviour. 58 | ### Changed 59 | - Non-control endpoints for eptri OutFIFOInterface are no longer unprimed after receiving a data packet. 60 | 61 | 62 | ## [0.2.0] - 2024-07-05 63 | ### Changed 64 | - Replaced `WishboneSPIFlashReader` with `SPIFlashPeripheral` 65 | 66 | 67 | ## [0.1.0] - 2024-06-12 68 | ### Added 69 | - Initial release 70 | 71 | 72 | [Unreleased]: https://github.com/greatscottgadgets/luna-soc/compare/0.3.1...HEAD 73 | [0.3.1]: https://github.com/greatscottgadgets/luna-soc/compare/0.3.0...0.3.1 74 | [0.3.0]: https://github.com/greatscottgadgets/luna-soc/compare/0.2.5...0.3.0 75 | [0.2.5]: https://github.com/greatscottgadgets/luna-soc/compare/0.2.4...0.2.5 76 | [0.2.4]: https://github.com/greatscottgadgets/luna-soc/compare/0.2.3...0.2.4 77 | [0.2.3]: https://github.com/greatscottgadgets/luna-soc/compare/0.2.2...0.2.3 78 | [0.2.2]: https://github.com/greatscottgadgets/luna-soc/compare/0.2.1...0.2.2 79 | [0.2.1]: https://github.com/greatscottgadgets/luna-soc/compare/0.2.0...0.2.1 80 | [0.2.0]: https://github.com/greatscottgadgets/luna-soc/compare/0.1.0...0.2.0 81 | [0.1.0]: https://github.com/greatscottgadgets/luna-soc/releases/tag/0.1.0 82 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | straithe@greatscottgadgets.com. 65 | 66 | ## Attribution 67 | 68 | This Code of Conduct is adapted from the Contributor Covenant, version 2.0, 69 | available at http://contributor-covenant.org/version/2/ 70 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) Katherine J. Temkin 4 | Copyright (c) 2019-2020, Great Scott Gadgets 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LUNA-SOC: Amaranth HDL library for building USB-capable SoC designs 2 | 3 | ## LUNA-SOC Library 4 | 5 | LUNA-SOC is a toolkit for building custom SoC (System on Chip) designs incorporating [LUNA](https://github.com/greatscottgadgets/luna) USB peripherals. 6 | 7 | Some things you can use LUNA-SOC for, currently: 8 | 9 | * Implement SoC designs using a [Minerva](https://github.com/minerva-cpu/minerva) or [VexRiscv](https://github.com/SpinalHDL/VexRiscv) RISC-V CPU. 10 | * Add a variety of Wishbone and CSR peripherals to your SoC design such as: SRAM, GPIO, UART and USB. 11 | * Implement firmware for your designs using Rust or C. 12 | 13 | > __NOTE__ 14 | > There are no official packages for Minerva at the time of writing but you can install it directly from the repository using: 15 | > 16 | > `pip install git+https://github.com/minerva-cpu/minerva` 17 | 18 | ## Project Structure 19 | 20 | This project is broken down into several directories: 21 | 22 | * [`luna_soc/`](luna_soc/) -- the primary LUNA-SOC library; generates gateware and provides peripherals. 23 | * [`examples/`](examples/) -- some simple LUNA-SOC examples demonstrating gateware design and firmware implementation. 24 | * [`docs/`](docs/) -- sources for the LUNA-SOC Sphinx documentation. 25 | 26 | 27 | ## Project Documentation 28 | 29 | LUNA-SOC's documentation is captured on [Read the Docs](https://luna-soc.readthedocs.io/en/latest/). Raw documentation sources are in the docs folder. 30 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | 17 | # To make everything: 18 | # 19 | # install d2: https://github.com/terrastruct/d2 20 | # 21 | # make d2 registers html 22 | 23 | # Compile all d2 diagrams 24 | d2: 25 | @for f in $(shell ls ./images/eptri/*.d2); do d2 $${f}; done 26 | 27 | # Compile all register maps 28 | SVD = source/lunasoc.svd 29 | registers: 30 | python bin/svd2rst.py usb0 $(SVD) images/eptri/registers-USB0.rst 31 | python bin/svd2rst.py usb0_ep_control $(SVD) images/eptri/registers-USB0_EP_CONTROL.rst 32 | python bin/svd2rst.py usb0_ep_in $(SVD) images/eptri/registers-USB0_EP_IN.rst 33 | python bin/svd2rst.py usb0_ep_out $(SVD) images/eptri/registers-USB0_EP_OUT.rst 34 | 35 | 36 | # Catch-all target: route all unknown targets to Sphinx using the new 37 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 38 | %: Makefile 39 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 40 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## luna-soc Documentation 2 | 3 | ### Rebuilding register diagrams 4 | 5 | After peripheral changes the register diagrams can be rebuilt with: 6 | 7 | make registers 8 | 9 | ### Rebuilding sequence diagrams 10 | 11 | Before rebuilding the sequence diagrams you will need to install the [d2](https://github.com/terrastruct/d2) compiler. 12 | 13 | After making any changes to the sequence diagrams they can be rebuilt with: 14 | 15 | make d2 16 | -------------------------------------------------------------------------------- /docs/bin/svd2rst.py: -------------------------------------------------------------------------------- 1 | import io, os, sys 2 | 3 | 4 | from xml.dom.minidom import parse 5 | 6 | import xml.etree.ElementTree as ET 7 | 8 | 9 | def svd2rst(peripheral_name, input, output=None): 10 | # parse svd file 11 | svd = ET.parse(input) 12 | 13 | # create output file 14 | if output is None: 15 | f = io.StringIO() 16 | else: 17 | f = open(output, 'w') 18 | 19 | peripheral = [p for p in svd.findall("peripherals/peripheral") if p.find("name").text == peripheral_name] 20 | if len(peripheral) == 0: 21 | print(f"Failed to find peripheral: {peripheral_name}") 22 | sys.exit(1) 23 | peripheral = peripheral[0] 24 | 25 | registers = peripheral.findall("registers/register") 26 | for register in registers: 27 | offset = register.find("addressOffset").text 28 | register_name = register.find("name").text.upper() 29 | description = register.find("description").text 30 | 31 | # output register name 32 | f.write("\n") 33 | f.write(f"**{register_name} Register**\n") 34 | f.write("\n") 35 | 36 | # output table headers 37 | f.write(f".. list-table::\n") 38 | f.write(f" :widths: 100 100 100 500\n") 39 | f.write(f" :header-rows: 1\n") 40 | f.write("\n") 41 | f.write(" * - Offset\n") 42 | f.write(" - Range\n") 43 | f.write(" - Access\n") 44 | f.write(" - Field\n") 45 | 46 | # output table rows 47 | fields = register.findall("fields/field") 48 | for field in fields: 49 | size = field.find("bitRange").text 50 | name = field.find("name").text 51 | access = field.find("access").text 52 | if name.startswith("_"): 53 | continue 54 | 55 | f.write(f" * - {offset}\n") 56 | f.write(f" - {size}\n") 57 | f.write(f" - {access}\n") 58 | f.write(f" - ``{name}``\n") 59 | 60 | # output register documentation 61 | if register_name.startswith("EV_"): 62 | continue 63 | f.write("\n") 64 | f.write(".. code-block:: markdown\n") 65 | f.write("\n") 66 | f.write(f" {description}\n") 67 | f.write("\n") 68 | 69 | if output is None: 70 | print(f.getvalue()) 71 | f.close() 72 | 73 | 74 | if __name__ == "__main__": 75 | if len(sys.argv) < 3: 76 | print("Usage: svd2rst [rst output file]") 77 | sys.exit(1) 78 | 79 | peripheral = sys.argv[1] 80 | input = sys.argv[2] 81 | 82 | if len(sys.argv) == 4: 83 | output = sys.argv[3] 84 | else: 85 | output = None 86 | 87 | svd2rst(peripheral, input, output) 88 | -------------------------------------------------------------------------------- /docs/images/eptri/block-diagram.txt: -------------------------------------------------------------------------------- 1 | ┌────────────────────────────────────────┐ ┌──────────────────────────────────────┐ 2 | │USBDevice │ │EPTRI │ 3 | │ │ │ │ 4 | │ │ ├───────────────────────────┬──────────┤ 5 | │ │ │USBDeviceController │Registers │◀─────────▶ 6 | ├─────────────┐ ┌───────────────────┤ │ ├──────────┤ 7 | │UTMIInterface│ │EndpointMultiplexer│ │ │IRQ │──────────▶ 8 | │ │ │ │ ├───────────────────────────┼──────────┤ 9 | ┌──────────┐ │ │ │ │ │SetupFIFOInterface┌───────▶│Registers │◀─────────▶ 10 | │USB PHY │ │ │ │ │ ├──────────┐ │ ├──────────┤ 11 | ◀─── D+ ──▶│ │ │ │ │ │ │8B FIFO │───────┘ │IRQ │──────────▶ 12 | │ │◀────▶│ │◀────▶│ │◀────▶├──────────┴────────────────┼──────────┤ SoC 13 | ◀─── D- ──▶│ │ │ │ │ │ │InFIFOInterface ┌────────│Registers │◀─────────▶ 14 | │ │ │ │ │ │ ├──────────┐ │ ├──────────┤ 15 | └──────────┘ │ │ │ │ │512B FIFO │◀──────┘ │IRQ │──────────▶ 16 | │ │ │ │ ├──────────┴────────────────┼──────────┤ 17 | │ │ │ │ │OutFIFOInterface ┌───────▶│Registers │◀─────────▶ 18 | │ │ │ │ ├──────────┐ │ ├──────────┤ 19 | │ │ │ │ │512B FIFO │───────┘ │IRQ │──────────▶ 20 | └─────────────┴──────┴───────────────────┘ └──────────┴────────────────┴──────────┘ -------------------------------------------------------------------------------- /docs/images/eptri/registers-USB0.rst: -------------------------------------------------------------------------------- 1 | 2 | **CONTROL Register** 3 | 4 | .. list-table:: 5 | :widths: 100 100 100 500 6 | :header-rows: 1 7 | 8 | * - Offset 9 | - Range 10 | - Access 11 | - Field 12 | * - 0x0000 13 | - [0:0] 14 | - read-write 15 | - ``connect`` 16 | * - 0x0000 17 | - [8:8] 18 | - read-write 19 | - ``low_speed_only`` 20 | * - 0x0000 21 | - [9:9] 22 | - read-write 23 | - ``full_speed_only`` 24 | 25 | .. code-block:: markdown 26 | 27 | Control register 28 | 29 | connect: Set this bit to '1' to allow the associated USB device to connect to a host. 30 | low_speed_only: Set this bit to '1' to force the device to operate at low speed. 31 | full_speed_only: Set this bit to '1' to force the device to operate at full speed. 32 | 33 | 34 | 35 | **STATUS Register** 36 | 37 | .. list-table:: 38 | :widths: 100 100 100 500 39 | :header-rows: 1 40 | 41 | * - Offset 42 | - Range 43 | - Access 44 | - Field 45 | * - 0x0002 46 | - [1:0] 47 | - read-only 48 | - ``speed`` 49 | 50 | .. code-block:: markdown 51 | 52 | Status register 53 | 54 | speed: Indicates the current speed of the USB device. 0 indicates High; 1 => Full, 55 | 2 => Low, and 3 => SuperSpeed (incl SuperSpeed+). 56 | 57 | 58 | 59 | **EV_ENABLE Register** 60 | 61 | .. list-table:: 62 | :widths: 100 100 100 500 63 | :header-rows: 1 64 | 65 | * - Offset 66 | - Range 67 | - Access 68 | - Field 69 | * - 0x0008 70 | - [0:0] 71 | - read-write 72 | - ``mask`` 73 | 74 | **EV_PENDING Register** 75 | 76 | .. list-table:: 77 | :widths: 100 100 100 500 78 | :header-rows: 1 79 | 80 | * - Offset 81 | - Range 82 | - Access 83 | - Field 84 | * - 0x0009 85 | - [0:0] 86 | - read-write 87 | - ``mask`` 88 | -------------------------------------------------------------------------------- /docs/images/eptri/registers-USB0_EP_CONTROL.rst: -------------------------------------------------------------------------------- 1 | 2 | **CONTROL Register** 3 | 4 | .. list-table:: 5 | :widths: 100 100 100 500 6 | :header-rows: 1 7 | 8 | * - Offset 9 | - Range 10 | - Access 11 | - Field 12 | * - 0x0000 13 | - [7:0] 14 | - write-only 15 | - ``address`` 16 | 17 | .. code-block:: markdown 18 | 19 | Control register 20 | 21 | address: Controls the current device's USB address. Should be written after a SET_ADDRESS 22 | request is received. Automatically resets back to zero on a USB reset. 23 | 24 | 25 | 26 | **STATUS Register** 27 | 28 | .. list-table:: 29 | :widths: 100 100 100 500 30 | :header-rows: 1 31 | 32 | * - Offset 33 | - Range 34 | - Access 35 | - Field 36 | * - 0x0002 37 | - [7:0] 38 | - read-only 39 | - ``address`` 40 | * - 0x0002 41 | - [11:8] 42 | - read-only 43 | - ``epno`` 44 | * - 0x0002 45 | - [12:12] 46 | - read-only 47 | - ``have`` 48 | 49 | .. code-block:: markdown 50 | 51 | Status register 52 | 53 | address: Holds the current device's active USB address. 54 | epno: The endpoint number associated with the most recently captured SETUP packet. 55 | have: `1` iff data is available in the FIFO. 56 | 57 | 58 | 59 | **RESET Register** 60 | 61 | .. list-table:: 62 | :widths: 100 100 100 500 63 | :header-rows: 1 64 | 65 | * - Offset 66 | - Range 67 | - Access 68 | - Field 69 | * - 0x0004 70 | - [0:0] 71 | - write-only 72 | - ``fifo`` 73 | 74 | .. code-block:: markdown 75 | 76 | Reset register 77 | 78 | fifo: Local reset control for the SETUP handler; writing a '1' to this register clears 79 | the handler state. 80 | 81 | 82 | 83 | **DATA Register** 84 | 85 | .. list-table:: 86 | :widths: 100 100 100 500 87 | :header-rows: 1 88 | 89 | * - Offset 90 | - Range 91 | - Access 92 | - Field 93 | * - 0x0005 94 | - [7:0] 95 | - read-only 96 | - ``byte`` 97 | 98 | .. code-block:: markdown 99 | 100 | Data register 101 | 102 | A FIFO that returns the bytes from the most recently captured SETUP packet. 103 | Reading a byte from this register advances the FIFO. The first eight bytes read 104 | from this contain the core SETUP packet. 105 | 106 | 107 | 108 | **EV_ENABLE Register** 109 | 110 | .. list-table:: 111 | :widths: 100 100 100 500 112 | :header-rows: 1 113 | 114 | * - Offset 115 | - Range 116 | - Access 117 | - Field 118 | * - 0x0010 119 | - [0:0] 120 | - read-write 121 | - ``mask`` 122 | 123 | **EV_PENDING Register** 124 | 125 | .. list-table:: 126 | :widths: 100 100 100 500 127 | :header-rows: 1 128 | 129 | * - Offset 130 | - Range 131 | - Access 132 | - Field 133 | * - 0x0011 134 | - [0:0] 135 | - read-write 136 | - ``mask`` 137 | -------------------------------------------------------------------------------- /docs/images/eptri/registers-USB0_EP_IN.rst: -------------------------------------------------------------------------------- 1 | 2 | **ENDPOINT Register** 3 | 4 | .. list-table:: 5 | :widths: 100 100 100 500 6 | :header-rows: 1 7 | 8 | * - Offset 9 | - Range 10 | - Access 11 | - Field 12 | * - 0x0000 13 | - [3:0] 14 | - write-only 15 | - ``number`` 16 | 17 | .. code-block:: markdown 18 | 19 | Endpoint register 20 | 21 | number: Contains the endpoint the enqueued packet is to be transmitted on. Writing to this field 22 | marks the relevant packet as ready to transmit; and thus should only be written after a 23 | full packet has been written into the FIFO. If no data has been placed into the DATA FIFO, 24 | a zero-length packet is generated. 25 | Note that any IN requests that do not match the endpoint number are automatically NAK'd. 26 | 27 | 28 | 29 | **STALL Register** 30 | 31 | .. list-table:: 32 | :widths: 100 100 100 500 33 | :header-rows: 1 34 | 35 | * - Offset 36 | - Range 37 | - Access 38 | - Field 39 | * - 0x0001 40 | - [0:0] 41 | - write-only 42 | - ``stalled`` 43 | 44 | .. code-block:: markdown 45 | 46 | Stall register 47 | 48 | stalled: When this field contains '1', any IN tokens targeting `epno` will be responded to with a 49 | STALL token, rather than DATA or a NAK. 50 | For EP0, this field will automatically be cleared when a new SETUP token is received. 51 | 52 | 53 | 54 | **PID Register** 55 | 56 | .. list-table:: 57 | :widths: 100 100 100 500 58 | :header-rows: 1 59 | 60 | * - Offset 61 | - Range 62 | - Access 63 | - Field 64 | * - 0x0002 65 | - [0:0] 66 | - write-only 67 | - ``toggle`` 68 | 69 | .. code-block:: markdown 70 | 71 | Pid register 72 | 73 | toggle: Sets the current PID toggle bit for the given endpoint. 74 | 75 | 76 | 77 | **STATUS Register** 78 | 79 | .. list-table:: 80 | :widths: 100 100 100 500 81 | :header-rows: 1 82 | 83 | * - Offset 84 | - Range 85 | - Access 86 | - Field 87 | * - 0x0004 88 | - [15:0] 89 | - read-only 90 | - ``nak`` 91 | * - 0x0004 92 | - [19:16] 93 | - read-only 94 | - ``epno`` 95 | * - 0x0004 96 | - [24:24] 97 | - read-only 98 | - ``idle`` 99 | * - 0x0004 100 | - [25:25] 101 | - read-only 102 | - ``have`` 103 | * - 0x0004 104 | - [26:26] 105 | - read-only 106 | - ``pid`` 107 | 108 | .. code-block:: markdown 109 | 110 | Status register 111 | 112 | nak: Contains a bitmask of endpoints that have responded with a NAK since the 113 | last read of this register. 114 | epno: Contains the endpoint being transmitted on. 115 | idle: This value is `1` if no packet is actively being transmitted. 116 | have: This value is `1` if data is present in the transmit FIFO. 117 | pid: Contains the current PID toggle bit for the given endpoint. 118 | 119 | 120 | 121 | **RESET Register** 122 | 123 | .. list-table:: 124 | :widths: 100 100 100 500 125 | :header-rows: 1 126 | 127 | * - Offset 128 | - Range 129 | - Access 130 | - Field 131 | * - 0x0008 132 | - [0:0] 133 | - write-only 134 | - ``fifo`` 135 | 136 | .. code-block:: markdown 137 | 138 | Reset register 139 | 140 | fifo: A write to this field Clears the FIFO without transmitting. 141 | 142 | 143 | 144 | **DATA Register** 145 | 146 | .. list-table:: 147 | :widths: 100 100 100 500 148 | :header-rows: 1 149 | 150 | * - Offset 151 | - Range 152 | - Access 153 | - Field 154 | * - 0x0009 155 | - [7:0] 156 | - write-only 157 | - ``byte`` 158 | 159 | .. code-block:: markdown 160 | 161 | Data register 162 | 163 | Each write enqueues a byte to be transmitted; gradually building a single packet to 164 | be transmitted. This queue should only ever contain a single packet; it is the software's 165 | responsibility to handle breaking requests down into packets. 166 | 167 | 168 | 169 | **EV_ENABLE Register** 170 | 171 | .. list-table:: 172 | :widths: 100 100 100 500 173 | :header-rows: 1 174 | 175 | * - Offset 176 | - Range 177 | - Access 178 | - Field 179 | * - 0x0010 180 | - [0:0] 181 | - read-write 182 | - ``mask`` 183 | 184 | **EV_PENDING Register** 185 | 186 | .. list-table:: 187 | :widths: 100 100 100 500 188 | :header-rows: 1 189 | 190 | * - Offset 191 | - Range 192 | - Access 193 | - Field 194 | * - 0x0011 195 | - [0:0] 196 | - read-write 197 | - ``mask`` 198 | -------------------------------------------------------------------------------- /docs/images/eptri/registers-USB0_EP_OUT.rst: -------------------------------------------------------------------------------- 1 | 2 | **CONTROL Register** 3 | 4 | .. list-table:: 5 | :widths: 100 100 100 500 6 | :header-rows: 1 7 | 8 | * - Offset 9 | - Range 10 | - Access 11 | - Field 12 | * - 0x0000 13 | - [7:0] 14 | - read-write 15 | - ``address`` 16 | 17 | .. code-block:: markdown 18 | 19 | Control register 20 | 21 | address: Controls the current device's USB address. Should be written after a SET_ADDRESS request 22 | is received. Automatically resets back to zero on a USB reset. 23 | 24 | 25 | 26 | **ENDPOINT Register** 27 | 28 | .. list-table:: 29 | :widths: 100 100 100 500 30 | :header-rows: 1 31 | 32 | * - Offset 33 | - Range 34 | - Access 35 | - Field 36 | * - 0x0001 37 | - [3:0] 38 | - read-write 39 | - ``number`` 40 | 41 | .. code-block:: markdown 42 | 43 | Endpoint register 44 | 45 | number: Selects the endpoint number to prime. This interface allows priming multiple endpoints 46 | at once. That is, multiple endpoints can be ready to receive data at a time. See the `prime` 47 | and `enable` bits for usage. 48 | 49 | 50 | 51 | **ENABLE Register** 52 | 53 | .. list-table:: 54 | :widths: 100 100 100 500 55 | :header-rows: 1 56 | 57 | * - Offset 58 | - Range 59 | - Access 60 | - Field 61 | * - 0x0002 62 | - [0:0] 63 | - write-only 64 | - ``enabled`` 65 | 66 | .. code-block:: markdown 67 | 68 | Enable register 69 | 70 | enabled: Controls whether any data can be received on any primed OUT endpoint. This bit is 71 | automatically cleared on receive in order to give the controller time to read data 72 | from the FIFO. It must be re-enabled once the FIFO has been emptied. 73 | 74 | 75 | 76 | **PRIME Register** 77 | 78 | .. list-table:: 79 | :widths: 100 100 100 500 80 | :header-rows: 1 81 | 82 | * - Offset 83 | - Range 84 | - Access 85 | - Field 86 | * - 0x0003 87 | - [0:0] 88 | - write-only 89 | - ``primed`` 90 | 91 | .. code-block:: markdown 92 | 93 | Prime register 94 | 95 | primed: Controls "priming" an out endpoint. To receive data on any endpoint, the CPU must first 96 | select the endpoint with the `epno` register; and then write a '1' into the prime and 97 | enable register. This prepares our FIFO to receive data; and the next OUT transaction will 98 | be captured into the FIFO. 99 | 100 | When a transaction is complete, the `enable` bit is reset; the `prime` is not. This 101 | effectively means that `enable` controls receiving on _any_ of the primed endpoints; 102 | while `prime` can be used to build a collection of endpoints willing to participate in 103 | receipt. 104 | 105 | Note that this does not apply to the control endpoint. Once the control endpoint has 106 | received a packet it will be un-primed and need to be re-primed before it can receive 107 | again. This is to ensure that we can establish an order on the receipt of the setup 108 | packet and any associated data. 109 | 110 | Only one transaction / data packet is captured per `enable` write; repeated enabling is 111 | necessary to capture multiple packets. 112 | 113 | 114 | 115 | **STALL Register** 116 | 117 | .. list-table:: 118 | :widths: 100 100 100 500 119 | :header-rows: 1 120 | 121 | * - Offset 122 | - Range 123 | - Access 124 | - Field 125 | * - 0x0004 126 | - [0:0] 127 | - write-only 128 | - ``stalled`` 129 | 130 | .. code-block:: markdown 131 | 132 | Stall register 133 | 134 | stalled: Controls STALL'ing the active endpoint. Setting or clearing this bit will set or clear 135 | STALL on the provided endpoint. Endpoint STALLs persist even after `epno` is changed; so 136 | multiple endpoints can be stalled at once by writing their respective endpoint numbers 137 | into `epno` register and then setting their `stall` bits. 138 | 139 | 140 | 141 | **PID Register** 142 | 143 | .. list-table:: 144 | :widths: 100 100 100 500 145 | :header-rows: 1 146 | 147 | * - Offset 148 | - Range 149 | - Access 150 | - Field 151 | * - 0x0005 152 | - [0:0] 153 | - write-only 154 | - ``toggle`` 155 | 156 | .. code-block:: markdown 157 | 158 | Pid register 159 | 160 | toggle: Sets the current PID toggle bit for the given endpoint. 161 | 162 | 163 | 164 | **STATUS Register** 165 | 166 | .. list-table:: 167 | :widths: 100 100 100 500 168 | :header-rows: 1 169 | 170 | * - Offset 171 | - Range 172 | - Access 173 | - Field 174 | * - 0x0006 175 | - [3:0] 176 | - read-only 177 | - ``epno`` 178 | * - 0x0006 179 | - [8:8] 180 | - read-only 181 | - ``have`` 182 | * - 0x0006 183 | - [9:9] 184 | - read-only 185 | - ``pid`` 186 | 187 | .. code-block:: markdown 188 | 189 | Status register 190 | 191 | epno: Contains the endpoint number associated with the data in the FIFO -- that is, 192 | the endpoint number on which the relevant data was received. 193 | have: `1` iff data is available in the FIFO. 194 | pid: Contains the current PID toggle bit for the given endpoint. 195 | 196 | 197 | 198 | **RESET Register** 199 | 200 | .. list-table:: 201 | :widths: 100 100 100 500 202 | :header-rows: 1 203 | 204 | * - Offset 205 | - Range 206 | - Access 207 | - Field 208 | * - 0x0008 209 | - [0:0] 210 | - write-only 211 | - ``fifo`` 212 | 213 | .. code-block:: markdown 214 | 215 | Reset register 216 | 217 | fifo: Local reset for the OUT handler; clears the out FIFO. 218 | 219 | 220 | 221 | **DATA Register** 222 | 223 | .. list-table:: 224 | :widths: 100 100 100 500 225 | :header-rows: 1 226 | 227 | * - Offset 228 | - Range 229 | - Access 230 | - Field 231 | * - 0x0009 232 | - [7:0] 233 | - read-only 234 | - ``byte`` 235 | 236 | .. code-block:: markdown 237 | 238 | Data register 239 | 240 | Read-only register. A FIFO that returns the bytes from the most recently captured OUT transaction. 241 | Reading a byte from this register advances the FIFO. 242 | 243 | byte: Contains the most recently received byte. 244 | 245 | 246 | 247 | **EV_ENABLE Register** 248 | 249 | .. list-table:: 250 | :widths: 100 100 100 500 251 | :header-rows: 1 252 | 253 | * - Offset 254 | - Range 255 | - Access 256 | - Field 257 | * - 0x0020 258 | - [0:0] 259 | - read-write 260 | - ``mask`` 261 | 262 | **EV_PENDING Register** 263 | 264 | .. list-table:: 265 | :widths: 100 100 100 500 266 | :header-rows: 1 267 | 268 | * - Offset 269 | - Range 270 | - Access 271 | - Field 272 | * - 0x0021 273 | - [0:0] 274 | - read-write 275 | - ``mask`` 276 | -------------------------------------------------------------------------------- /docs/images/eptri/seq-bulk-in-transfer.d2: -------------------------------------------------------------------------------- 1 | bulk_in_transfer: "Bulk IN Transfer" { 2 | shape: sequence_diagram 3 | 4 | host: "HOST" 5 | eptri: "EPTRI" 6 | cpu: "CPU / SoC" 7 | 8 | # usb transaction: NAK EP_IN 9 | usb1: "EPTRI EP_IN will NAK host until it is primed to transmit" { 10 | host -> eptri: "PID IN" 11 | host <- eptri: "PID NAK" 12 | } 13 | 14 | # cpu: poll EP_IN NAK status register 15 | cpu1: "Poll EP_IN NAK status register to check if host has\nrequested data on an endpoint." { 16 | cpu -> eptri: "r(EP_IN.nak)" 17 | cpu <- eptri: "bitmask: u16" 18 | } 19 | 20 | # cpu: write response data 21 | cpu2: "Write response data to EPTRI EP_IN." { 22 | write: "write response to EP_IN FIFO" { 23 | cpu -> eptri: "for byte in response {" 24 | cpu -> eptri: "w(EP_IN.data, byte)" 25 | } 26 | prime: "Prime EP_IN to transmit FIFO contents" { 27 | cpu -> eptri: "w(EP_IN.epno, endpoint_number)" 28 | } 29 | } 30 | 31 | # usb transaction: IN data transfer 32 | usb2: "EPTRI EP_IN transmits data to host" { 33 | host -> eptri: "PID IN" 34 | host <- eptri: "PID DATAn bytes=[u8]" 35 | host -> eptri: "PID ACK" 36 | } 37 | 38 | # trigger interrupt: IRQ_EP_IN (SendComplete) 39 | irq1: "Trigger Interrupt: IRQ_EP_IN (SendComplete)" { 40 | eptri -> cpu: "IRQ_EP_IN" 41 | } 42 | 43 | # usb transaction: NAK EP_IN 44 | usb3: "EPTRI EP_IN will NAK host until it is re-primed." { 45 | host -> eptri: "PID IN" 46 | host <- eptri: "PID NAK" 47 | } 48 | 49 | # handle interrupt: EP_IN (SendComplete) 50 | irq1: "Handle Interrupt: IRQ_EP_IN (SendComplete)" { 51 | get_ep: "get endpoint number" { 52 | cpu -> eptri: "r(EP_IN.epno)" 53 | cpu <- eptri: "endpoint_number" 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /docs/images/eptri/seq-bulk-out-transfer.d2: -------------------------------------------------------------------------------- 1 | bulk_out_transfer: "Bulk OUT Transfer" { 2 | shape: sequence_diagram 3 | 4 | host: "HOST" 5 | eptri: "EPTRI" 6 | cpu: "CPU / SoC" 7 | 8 | # usb transaction: NAK EP_OUT 9 | usb1: "EPTRI EP_OUT will NAK host until it is primed to receive." { 10 | host -> eptri: "PID OUT" 11 | host -> eptri: "PID DATAn bytes=[u8]" 12 | host <- eptri: "PID NAK" 13 | } 14 | 15 | # cpu: prime EP_OUT 16 | cpu1: "Prime EP_OUT when you're ready to receive data on an endpoint." { 17 | select: "select the endpoint to prime" { 18 | cpu -> eptri: "w(EP_OUT.epno, endpoint_number)" 19 | } 20 | prime: "prime the endpoint" { 21 | cpu -> eptri: "w(EP_OUT.prime, true)" 22 | } 23 | enable: "re-enable the interface" { 24 | cpu -> eptri: "w(EP_OUT.enable, true)" 25 | } 26 | } 27 | 28 | # usb transaction: OUT data transfer 29 | usb2: "EPTRI EP_OUT accepts data transfer from host." { 30 | host -> eptri: "PID OUT" 31 | host -> eptri: "PID DATAn bytes=[u8]" 32 | host <- eptri: "PID ACK" 33 | } 34 | 35 | # trigger interrupt: IRQ_EP_OUT (ReceivePacket) 36 | irq1: "Trigger Interrupt: IRQ_EP_OUT (ReceivePacket)" { 37 | eptri -> cpu: "IRQ_EP_OUT" 38 | } 39 | 40 | # usb transaction: NAK EP_OUT 41 | usb3: "EPTRI EP_OUT will NAK host until it is re-primed." { 42 | host -> eptri: "PID OUT" 43 | host -> eptri: "PID DATAn bytes=[u8]" 44 | host <- eptri: "PID NAK" 45 | } 46 | 47 | # handle interrupt: IRQ_EP_OUT (ReceivePacket) 48 | irq1: "Handle Interrupt: IRQ_EP_OUT (ReceivePacket)" { 49 | get_ep: "get transfer endpoint number" { 50 | cpu -> eptri: "r(EP_OUT.data_ep)" 51 | cpu <- eptri: "endpoint_number" 52 | } 53 | read_fifo: "while the FIFO has data, read the next byte" { 54 | cpu -> eptri: "while r(EP_OUT.have) {" 55 | cpu <- eptri: "bool" 56 | cpu -> eptri: "r(EP_OUT.data)" 57 | cpu <- eptri: "u8" 58 | } 59 | prime: "finally, re-prime EP_OUT if you're ready to\nreceive more data on the endpoint" { 60 | cpu -> eptri: "w(EP_OUT.epno, endpoint_number)" 61 | cpu -> eptri: "w(EP_OUT.prime, true)" 62 | cpu -> eptri: "w(EP_OUT.enable, true)" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /docs/images/eptri/seq-bus-reset.d2: -------------------------------------------------------------------------------- 1 | bus_reset: "Bus Reset" { 2 | shape: sequence_diagram 3 | 4 | host: "HOST" 5 | eptri: "EPTRI" 6 | cpu: "CPU / SoC" 7 | 8 | # usb: bus reset 9 | usb1: "Bus Reset" { 10 | host -> eptri: "RESET" 11 | } 12 | 13 | # trigger interrupt: IRQ_USBx (BusReset) 14 | irq1: "Trigger Interrupt: IRQ_USBx (BusReset)" { 15 | eptri -> cpu: "IRQ_USBx" 16 | } 17 | 18 | # handle interrupt: IRQ_USBx (BusReset) 19 | irq1: "Handle Interrupt: IRQ_USBx (BusReset)" { 20 | clear_pending_irqs: "clear pending interrupt events" { 21 | cpu -> eptri: "w(USBx.ev_pending, r(USBx.ev_pending))" 22 | cpu -> eptri: "w(USBx_EP_CONTROL.ev_pending, r(USBx_EP_CONTROL.ev_pending))" 23 | cpu -> eptri: "w(USBx_EP_IN.ev_pending, r(USBx_EP_IN.ev_pending))" 24 | cpu -> eptri: "w(USBx_EP_OUT.ev_pending, r(USBx_EP_OUT.ev_pending))" 25 | } 26 | 27 | disable_irqs: "disable interrupt events" { 28 | cpu -> eptri: "w(USBx.ev_enable, 0)" 29 | cpu -> eptri: "w(USBx_EP_CONTROL.ev_enable, 0)" 30 | cpu -> eptri: "w(USBx_EP_IN.ev_enable, 0)" 31 | cpu -> eptri: "w(USBx_EP_OUT.ev_enable, 0)" 32 | } 33 | 34 | reset_address: "reset device address to 0" { 35 | cpu -> eptri: "w(USBx_EP_OUT.address, 0)" 36 | cpu -> eptri: "w(USBx_EP_CONTROL.address, 0)" 37 | } 38 | 39 | reset_fifos: "reset FIFO's" { 40 | cpu -> eptri: "w(USBx_EP_CONTROL.reset, 1)" 41 | cpu -> eptri: "w(USBx_EP_IN.reset, 1)" 42 | cpu -> eptri: "w(USBx_EP_OUT.reset, 1)" 43 | } 44 | 45 | enable_irqs: "re-enable interrupt events" { 46 | cpu -> eptri: "w(USBx.ev_enable, 1)" 47 | cpu -> eptri: "w(USBx_EP_CONTROL.ev_enable, 1)" 48 | cpu -> eptri: "w(USBx_EP_IN.ev_enable, 1)" 49 | cpu -> eptri: "w(USBx_EP_OUT.ev_enable, 1)" 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /docs/images/eptri/seq-connect.d2: -------------------------------------------------------------------------------- 1 | grid-columns: 1 2 | 3 | disconnect: "Disconnect Device" { 4 | shape: sequence_diagram 5 | 6 | host: "HOST" 7 | eptri: "EPTRI" 8 | cpu: "CPU / SoC" 9 | 10 | disable_irqs: "Disable interrupt events." { 11 | cpu -> eptri: "w(USBx.ev_enable, 0)" 12 | cpu -> eptri: "w(USBx_EP_CONTROL.ev_enable, 0)" 13 | cpu -> eptri: "w(USBx_EP_IN.ev_enable, 0)" 14 | cpu -> eptri: "w(USBx_EP_OUT.ev_enable, 0)" 15 | } 16 | 17 | reset_fifos: "Reset FIFO's" { 18 | cpu -> eptri: "w(USBx_EP_CONTROL.reset, 1)" 19 | cpu -> eptri: "w(USBx_EP_IN.reset, 1)" 20 | cpu -> eptri: "w(USBx_EP_OUT.reset, 1)" 21 | } 22 | 23 | disconnect: "Disconnect device controller." { 24 | cpu -> eptri: "w(USBx.connect, 0)" 25 | } 26 | 27 | reset_address: "Reset device address to 0." { 28 | cpu -> eptri: "w(USBx_EP_OUT.address, 0)" 29 | cpu -> eptri: "w(USBx_EP_CONTROL.address, 0)" 30 | } 31 | } 32 | 33 | connect: "Connect Device" { 34 | shape: sequence_diagram 35 | 36 | host: "HOST" 37 | eptri: "EPTRI" 38 | cpu: "CPU / SoC" 39 | 40 | set_speed: "Set device speed." { 41 | cpu.| 42 | High speed: 0, 0 43 | Full speed: 1, 0 44 | Low speed: 0, 1 45 | | 46 | cpu -> eptri: "w(USBx.full_speed_only, 0)" 47 | cpu -> eptri: "w(USBx.low_speed_only, 0)" 48 | } 49 | 50 | connect: "Connect device controller." { 51 | cpu -> eptri: "w(USBx.connect, 1)" 52 | } 53 | 54 | enable_irqs: "Re-enable interrupt events." { 55 | cpu -> eptri: "w(USBx.ev_enable, 1)" 56 | cpu -> eptri: "w(USBx_EP_CONTROL.ev_enable, 1)" 57 | cpu -> eptri: "w(USBx_EP_IN.ev_enable, 1)" 58 | cpu -> eptri: "w(USBx_EP_OUT.ev_enable, 1)" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /docs/images/eptri/seq-control-in-transfer.d2: -------------------------------------------------------------------------------- 1 | control_in_transfer: "Control IN Transfer" { 2 | shape: sequence_diagram 3 | 4 | host: "HOST" 5 | eptri: "EPTRI" 6 | cpu: "CPU / SoC" 7 | 8 | setup_stage: "Setup Stage" { 9 | # usb transaction: SETUP 10 | usb1: "Host sends a SETUP packet." { 11 | host -> eptri: "PID SETUP" 12 | host -> eptri: "PID DATAn bytes=[u8; 8]" 13 | eptri -> host: "PID ACK" 14 | } 15 | 16 | # trigger interrupt: EP_CONTROL (ReceiveSetupPacket) 17 | irq1: "Trigger interrupt: EP_CONTROL (ReceiveSetupPacket)" { 18 | eptri -> cpu: "IRQ_EP_CONTROL" 19 | } 20 | 21 | # usb transaction: NAK EP_IN 22 | usb2: "EPTRI EP_IN will NAK host until it is primed to transmit" { 23 | host -> eptri: "PID IN" 24 | host <- eptri: "PID NAK" 25 | } 26 | 27 | # handle interrupt: ReceiveSetupPacket 28 | irq1: "Handle Interrupt: IRQ_EP_CONTROL (ReceiveSetupPacket)" { 29 | get_ep: "get endpoint number" { 30 | cpu -> eptri: "r(EP_CONTROL.epno)" 31 | cpu <- eptri: "endpoint_number" 32 | } 33 | read_fifo: "while the control endoint FIFO has data, read the 8 bytes of the setup packet" { 34 | cpu -> eptri: "while r(EP_CONTROL.have) {" 35 | cpu <- eptri: "bool" 36 | cpu -> eptri: "r(EP_CONTROL.data)" 37 | cpu <- eptri: "u8" 38 | } 39 | cpu."parse setup packet and get ready for data stage if len > 0" 40 | } 41 | } 42 | 43 | data_stage: "Data Stage (optional)" { 44 | # write response data 45 | irq1: "Handle Interrupt: IRQ_EP_CONTROL (ReceiveSetupPacket)" { 46 | write_fifo: "Write response data to EP_IN FIFO" { 47 | cpu -> eptri: "for byte in response {" 48 | cpu -> eptri: "w(EP_IN.data, byte)" 49 | } 50 | prime_ep: "Prime EP_IN to transmit FIFO contents" { 51 | cpu -> eptri: "w(EP_IN.epno, endpoint_number)" 52 | } 53 | } 54 | 55 | # usb transaction: IN data transfer 56 | usb3: "EPTRI EP_IN transmits data to host" { 57 | host -> eptri: "PID IN" 58 | host <- eptri: "PID DATAn bytes=[u8]" 59 | host -> eptri: "PID ACK" 60 | } 61 | 62 | # trigger interrupt: EP_IN (SendComplete) 63 | irq2: "Trigger Interrupt: EP_IN (SendComplete)" { 64 | eptri -> cpu: "IRQ_EP_IN" 65 | } 66 | } 67 | 68 | status_stage: "Status Stage" { 69 | # usb transaction - eptri ep_out will nak host until it is primed 70 | usb4: "EPTRI will NAK host until EP_OUT is primed to receive" { 71 | host -> eptri: "PID OUT" 72 | host -> eptri: "PID DATAn bytes=[]" 73 | host <- eptri: "PID NAK" 74 | } 75 | 76 | # handle interrupt: EP_IN (ReceivePacket) 77 | irq2: "Handle Interrupt: IRQ_EP_IN (SendComplete)" { 78 | zlp: "prime EP_OUT to receive ZLP from host" { 79 | # get ep that triggered interrupt 80 | get_ep: "get transfer endpoint number" { 81 | cpu -> eptri: "r(EP_IN.epno)" 82 | cpu <- eptri: "endpoint_number" 83 | } 84 | # select the endpoint to prime 85 | select: "select the EP_OUT endpoint to prime" { 86 | cpu -> eptri: "w(EP_OUT.epno, endpoint_number)" 87 | } 88 | # prime the endpoint 89 | prime: "prime the endpoint" { 90 | cpu -> eptri: "w(EP_OUT.prime, true)" 91 | } 92 | # re-enable the interface 93 | enable: "re-enable the interface" { 94 | cpu -> eptri: "w(EP_OUT.enable, true)" 95 | } 96 | } 97 | } 98 | 99 | # usb transaction: receive ZLP 100 | usb5: "EPTRI EP_OUT receives ZLP acknowledgement from host" { 101 | host -> eptri: "PID OUT" 102 | host -> eptri: "PID DATAn bytes=[]" 103 | host <- eptri: "PID ACK" 104 | } 105 | 106 | # trigger EP_OUT interrupt: ReceivePacket 107 | irq3: "Trigger EP_OUT interrupt: ReceivePacket" { 108 | eptri -> cpu: "IRQ_EP_OUT" 109 | } 110 | 111 | # soc interrupt handler 112 | irq3: "Handle Interrupt: ReceivePacket" { 113 | # get ep that triggered interrupt 114 | get_ep: "get transfer endpoint number" { 115 | cpu -> eptri: "r(EP_OUT.data_ep)" 116 | cpu <- eptri: "endpoint_number" 117 | } 118 | read_fifo: "check that the FIFO is empty (zlp)" { 119 | cpu -> eptri: "if r(EP_OUT.have) {" 120 | cpu <- eptri: "bool" 121 | } 122 | } 123 | 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /docs/images/eptri/seq-control-out-transfer.d2: -------------------------------------------------------------------------------- 1 | control_out_transfer: "Control OUT Transfer" { 2 | shape: sequence_diagram 3 | 4 | host: "HOST" 5 | eptri: "EPTRI" 6 | cpu: "CPU / SoC" 7 | 8 | setup_stage: "Setup Stage" { 9 | # usb transaction: SETUP 10 | usb1: "Host sends a SETUP packet." { 11 | host -> eptri: "PID SETUP" 12 | host -> eptri: "PID DATAn bytes=[u8; 8]" 13 | eptri -> host: "PID ACK" 14 | } 15 | 16 | # trigger interrupt: IRQ_EP_CONTROL (ReceiveSetupPacket) 17 | irq1: "Trigger Interrupt: IRQ_EP_CONTROL (ReceiveSetupPacket)" { 18 | eptri -> cpu: "IRQ_EP_CONTROL" 19 | } 20 | 21 | # usb transaction: NAK EP_OUT 22 | usb2: "EPTRI EP_OUT will NAK host until it is primed to receive." { 23 | host -> eptri: "PID OUT" 24 | host -> eptri: "PID DATAn bytes=[u8]" 25 | host <- eptri: "PID NAK" 26 | } 27 | 28 | # handle interrupt: IRQ_EP_CONTROL (ReceiveSetupPacket) 29 | irq1: "Handle Interrupt: IRQ_EP_CONTROL (ReceiveSetupPacket)" { 30 | get_ep: "get endpoint number" { 31 | cpu -> eptri: "r(EP_CONTROL.epno)" 32 | cpu <- eptri: "endpoint_number" 33 | } 34 | read_fifo: "while the control endoint FIFO has data, read the 8 bytes of the setup packet" { 35 | cpu -> eptri: "while r(EP_CONTROL.have) {" 36 | cpu <- eptri: "bool" 37 | cpu -> eptri: "r(EP_CONTROL.data)" 38 | cpu <- eptri: "u8" 39 | } 40 | cpu."parse setup packet and get ready for data stage if len > 0" 41 | } 42 | } 43 | 44 | data_stage: "Data Stage (optional)" { 45 | # prime EP_OUT 46 | irq1: "Handle Interrupt: IRQ_EP_CONTROL (ReceiveSetupPacket)" { 47 | prime: "prime EP_OUT to receive data" { 48 | cpu -> eptri: "w(EP_OUT.epno, endpoint_number)" 49 | cpu -> eptri: "w(EP_OUT.prime, true)" 50 | cpu -> eptri: "w(EP_OUT.enable, true)" 51 | } 52 | } 53 | 54 | # usb transaction: OUT data transfer 55 | usb3: "EPTRI EP_OUT accepts data transfer from host." { 56 | host -> eptri: "PID OUT address=0x13 endpoint_number=0x1" 57 | host -> eptri: "PID DATAn bytes=[u8]" 58 | host <- eptri: "PID ACK" 59 | } 60 | 61 | # trigger interrupt: IRQ_EP_OUT (ReceivePacket) 62 | irq2: "Trigger Interrupt: IRQ_EP_OUT (ReceivePacket)" { 63 | eptri -> cpu: "IRQ_EP_OUT" 64 | } 65 | 66 | # usb transaction: NAK EP_OUT 67 | usb4: "EPTRI EP_OUT will NAK host until it is re-primed." { 68 | host -> eptri: "PID IN" 69 | host <- eptri: "PID NAK" 70 | } 71 | 72 | # handle interrupt: IRQ_EP_OUT (ReceivePacket) 73 | irq2: "Handle Interrupt: IRQ_EP_OUT (ReceivePacket)" { 74 | get_ep: "get transfer endpoint number" { 75 | cpu -> eptri: "r(EP_OUT.data_ep)" 76 | cpu <- eptri: "endpoint_number" 77 | } 78 | read_fifo: "while the FIFO has data, read the next byte" { 79 | cpu -> eptri: "while r(EP_OUT.have) {" 80 | cpu <- eptri: "bool" 81 | cpu -> eptri: "r(EP_OUT.data)" 82 | cpu <- eptri: "u8" 83 | } 84 | } 85 | } 86 | 87 | status_stage: "Status Stage" { 88 | zlp: "Finally, send ZLP acknowledgement to host to end status stage." { 89 | cpu -> eptri: "w(EP_IN.epno, endpoint_number)" 90 | } 91 | 92 | # usb transaction: send ZLP 93 | usb5: "EPTRI EP_IN transmits ZLP" { 94 | host -> eptri: "PID IN" 95 | host <- eptri: "PID DATAn bytes=[u8; 0]" 96 | host -> eptri: "PID ACK" 97 | } 98 | 99 | # trigger interrupt: EP_IN (SendComplete) 100 | irq3: "Trigger Interrupt: EP_IN (SendComplete)" { 101 | eptri -> cpu: "IRQ_EP_IN" 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | setuptools 2 | sphinx==7.2.6 3 | sphinx_rtd_theme==2.0.0 4 | sphinxcontrib-apidoc 5 | readthedocs-sphinx-search==0.3.2 6 | recommonmark==0.7.1 7 | jinja2==3.1.6 8 | 9 | # needed to build api docs 10 | amaranth-stdio @ git+https://github.com/amaranth-lang/amaranth-stdio 11 | amaranth-soc @ git+https://github.com/amaranth-lang/amaranth-soc 12 | minerva @ git+https://github.com/minerva-cpu/minerva 13 | luna-usb @ git+https://github.com/greatscottgadgets/luna@main 14 | luna-soc @ git+https://github.com/greatscottgadgets/luna-soc@main 15 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | import os, pkg_resources, sys, time 2 | sys.path.insert(0, os.path.abspath("../../")) 3 | sys.path.insert(0, os.path.abspath("../../luna_soc")) 4 | 5 | import sphinx_rtd_theme 6 | 7 | extensions = [ 8 | 'sphinx_rtd_theme' 9 | ] 10 | 11 | # -- Project information ----------------------------------------------------- 12 | 13 | project = 'luna-soc' 14 | copyright = time.strftime('2018-%Y, Great Scott Gadgets') 15 | author = 'Great Scott Gadget' 16 | 17 | version = pkg_resources.get_distribution('luna_soc').version 18 | release = '' 19 | 20 | 21 | # -- General configuration --------------------------------------------------- 22 | 23 | templates_path = ['_templates'] 24 | exclude_patterns = ['build'] 25 | source_suffix = '.rst' 26 | master_doc = 'index' 27 | language = "en" 28 | exclude_patterns = [] 29 | pygments_style = None 30 | 31 | extensions = [ 32 | 'recommonmark', 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.coverage', 35 | 'sphinx.ext.doctest', 36 | 'sphinx.ext.extlinks', 37 | 'sphinx.ext.napoleon', 38 | 'sphinx.ext.todo', 39 | 'sphinx.ext.viewcode', 40 | 'sphinxcontrib.apidoc', 41 | ] 42 | 43 | # configure extension: sphinxcontrib.apidoc 44 | apidoc_module_dir = '../../luna_soc' 45 | apidoc_output_dir = 'api_docs' 46 | apidoc_excluded_paths = ['gateware/vendor/'] 47 | apidoc_separate_modules = True 48 | 49 | # configure extension: extlinks 50 | extlinks = { 51 | 'repo': ('https://github.com/greatscottgadgets/luna-soc/blob/main/%s', '%s'), 52 | 'example': ('https://github.com/greatscottgadgets/luna-soc/blob/main/examples/%s', '%s'), 53 | } 54 | 55 | # configure extension: napoleon 56 | napoleon_google_docstring = True 57 | napoleon_numpy_docstring = False 58 | napoleon_include_init_with_doc = True 59 | napoleon_use_ivar = True 60 | napoleon_include_private_with_doc = False 61 | napoleon_include_special_with_doc = True 62 | napoleon_use_param = False 63 | 64 | 65 | # -- Options for HTML output ------------------------------------------------- 66 | # run pip install sphinx_rtd_theme if you get sphinx_rtd_theme errors 67 | html_theme = "sphinx_rtd_theme" 68 | html_css_files = ['status.css'] 69 | -------------------------------------------------------------------------------- /docs/source/eptri.rst: -------------------------------------------------------------------------------- 1 | ============================================== 2 | eptri - SoC controller for the LUNA USB Device 3 | ============================================== 4 | 5 | **General Description** 6 | 7 | ``eptri`` (endpoint-tri) is a three-interface CSR controller that allows a CPU or Wishbone design to control the endpoints of a LUNA USB Device. 8 | 9 | **Features** 10 | 11 | * CONTROL peripheral for managing device connection, reset and connection speed. 12 | * SETUP interface peripheral for reading control transactions from the host. 13 | * OUT interface peripheral for reading data transfers from the host. 14 | * IN interface peripheral for writing data transactions to the host. 15 | 16 | 17 | 18 | Introduction 19 | ------------ 20 | 21 | Definitions 22 | ~~~~~~~~~~~ 23 | 24 | * *Controller* refers to the ``eptri`` controller as a whole, including all peripherals. 25 | * *Peripheral* refers to ``USBDeviceController``, ``SetupFIFOInterface``, ``InFIFOInterface`` or ``OutFIFOInterface``. 26 | 27 | 28 | Block Diagram 29 | ------------- 30 | 31 | .. literalinclude:: ../images/eptri/block-diagram.txt 32 | 33 | 34 | Usage 35 | ----- 36 | 37 | The exact details for integrating a LUNA eptri peripheral into a design will depend on the SoC platform you are using. 38 | 39 | For example, using ``luna-soc``: 40 | 41 | .. code-block:: python 42 | 43 | class Top(Elaboratable): 44 | def __init__(self): 45 | # instantiate the SoC 46 | self.soc = LunaSoC( 47 | cpu=VexRiscv(reset_addr=0x00000000, variant="cynthion"), 48 | clock_frequency=int(60e6), 49 | ) 50 | 51 | # instantiate the eptri device controller peripheral 52 | self.usb0 = USBDeviceController() 53 | 54 | # instantiate the three endpoint interface peripherals 55 | self.usb0_ep_control = SetupFIFOInterface() 56 | self.usb0_ep_in = InFIFOInterface() 57 | self.usb0_ep_out = OutFIFOInterface() 58 | 59 | # add the peripherals to the SoC 60 | self.soc.add_peripheral(self.usb0) 61 | self.soc.add_peripheral(self.usb0_ep_control, as_submodule=False) 62 | self.soc.add_peripheral(self.usb0_ep_in, as_submodule=False) 63 | self.soc.add_peripheral(self.usb0_ep_out, as_submodule=False) 64 | 65 | def elaborate(self, platform): 66 | m = Module() 67 | 68 | m.submodules.soc = self.soc 69 | 70 | # instantiate a LUNA USB device 71 | usb0_bus = platform.request(platform.default_usb_connection) 72 | usb0_device = USBDevice(bus=usb0_bus) 73 | 74 | # add the eptri endpoint interface peripherals to the LUNA USB device 75 | # as endpoint handlers 76 | usb0_device.add_endpoint(self.usb0_ep_control) 77 | usb0_device.add_endpoint(self.usb0_ep_in) 78 | usb0_device.add_endpoint(self.usb0_ep_out) 79 | 80 | # connect the eptri device controller to the LUNA USB device 81 | m.d.comb += self.usb0.attach(usb0_device) 82 | 83 | m.submodules.usb0_device = usb0_device 84 | 85 | return m 86 | 87 | 88 | 89 | Registers 90 | --------- 91 | 92 | The ``eptri`` controller provides four sets of registers corresponding to each peripheral. 93 | 94 | 95 | ``USBx`` - USBDeviceController 96 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 97 | 98 | .. include:: ../images/eptri/registers-USB0.rst 99 | 100 | 101 | ``USBx_EP_CONTROL`` - SetupFIFOInterface 102 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 103 | 104 | .. include:: ../images/eptri/registers-USB0_EP_CONTROL.rst 105 | 106 | 107 | ``USBx_EP_IN`` - InFIFOInterface 108 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 109 | 110 | .. include:: ../images/eptri/registers-USB0_EP_IN.rst 111 | 112 | 113 | ``USBx_EP_OUT`` - OutFIFOInterface 114 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 115 | 116 | .. include:: ../images/eptri/registers-USB0_EP_OUT.rst 117 | 118 | 119 | Interrupts 120 | ---------- 121 | 122 | Each of the ``eptri`` peripherals can generate the following interrupts: 123 | 124 | .. list-table:: USBDeviceController Registers 125 | :header-rows: 1 126 | 127 | * - Interrupt 128 | - Peripheral 129 | - Description 130 | * - USBx 131 | - USBDeviceController 132 | - Interrupt that triggers when the host issued a USB bus reset. 133 | * - USBx_EP_CONTROL 134 | - SetupFIFOInterface 135 | - Interrupt that triggers when the host wrote a new SETUP packet to the bus. 136 | * - USBx_EP_IN 137 | - InFIFOInterface 138 | - Interrupt that triggers after the peripheral has written a data packet to the bus and read back a PID ACK response from the host. 139 | * - USBx_EP_OUT 140 | - OutFIFOInterface 141 | - Interrupt that triggers when the peripheral has read a data packet from the host. 142 | 143 | 144 | Programming Guide 145 | ----------------- 146 | 147 | The programming guide provides sequence diagrams that detail the steps required to perform the various operations supported by the ``eptri`` controller. 148 | 149 | The following pseudo-code is used through-out to indicate register operations: 150 | 151 | .. list-table:: Register Operations 152 | :header-rows: 1 153 | 154 | * - Operation 155 | - Description 156 | * - ``r(PERIPHERAL.register)`` 157 | - Read a value from the ``register`` belonging to ``PERIPHERAL``. 158 | * - ``w(PERIPHERAL.register, bits)`` 159 | - Write ``bits`` to the ``register`` belonging to ``PERIPHERAL``. 160 | 161 | 162 | Device Connection 163 | ~~~~~~~~~~~~~~~~~~ 164 | 165 | .. image:: ../images/eptri/seq-connect.svg 166 | :alt: Sequence diagram for USB device connection 167 | 168 | Bus Reset 169 | ~~~~~~~~~ 170 | 171 | .. image:: ../images/eptri/seq-bus-reset.svg 172 | :alt: Sequence diagram for USB bus reset 173 | 174 | Control OUT Transfers 175 | ~~~~~~~~~~~~~~~~~~~~~ 176 | 177 | .. image:: ../images/eptri/seq-control-out-transfer.svg 178 | :alt: Sequence diagram for Control OUT transfers 179 | 180 | Control IN Transfers 181 | ~~~~~~~~~~~~~~~~~~~~ 182 | 183 | .. image:: ../images/eptri/seq-control-in-transfer.svg 184 | :alt: Sequence diagram for Control IN transfers 185 | 186 | Bulk OUT Transfers 187 | ~~~~~~~~~~~~~~~~~~ 188 | 189 | .. image:: ../images/eptri/seq-bulk-out-transfer.svg 190 | :alt: Sequence diagram for Bulk OUT transfers 191 | 192 | Bulk IN Transfers 193 | ~~~~~~~~~~~~~~~~~ 194 | 195 | .. image:: ../images/eptri/seq-bulk-in-transfer.svg 196 | :alt: Sequence diagram for Bulk IN transfers 197 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | luna-soc Documentation 3 | ====================== 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | :caption: Datasheets 8 | 9 | eptri 10 | 11 | .. toctree:: 12 | :maxdepth: 1 13 | :caption: API Documentation 14 | 15 | api_docs/modules 16 | 17 | * :ref:`genindex` 18 | * :ref:`modindex` 19 | -------------------------------------------------------------------------------- /examples/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greatscottgadgets/luna-soc/e7e742fc93187768ed7f2a0a557a95f9a0eab8e9/examples/.keep -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # LUNA-SOC Examples 2 | 3 | ## Dependencies 4 | 5 | ### Yosys Toolchain 6 | 7 | Grab and install the latest toolchain from: 8 | 9 | https://github.com/YosysHQ/oss-cad-suite-build/releases/latest 10 | 11 | Remember to mollify Gatekeeper if you're on macOS: 12 | 13 | oss-cad-suite/activate 14 | 15 | Enable environment with: 16 | 17 | source /oss-cad-suite/environment 18 | 19 | 20 | ### Rust 21 | 22 | If you'd like to use `rustup` to manage your Rust environment you can install it with: 23 | 24 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 25 | 26 | Install riscv target support: 27 | 28 | rustup target add riscv32imac-unknown-none-elf 29 | 30 | Install cargo binutils support: 31 | 32 | rustup component add llvm-tools-preview 33 | cargo install cargo-binutils 34 | 35 | 36 | ### RiscV GNU Toolchain 37 | 38 | This is needed to build the C examples: 39 | 40 | # macOS - https://github.com/riscv-software-src/homebrew-riscv 41 | brew tap riscv-software-src/riscv 42 | brew install riscv-gnu-toolchain 43 | 44 | # debian 45 | apt install gcc-riscv64-unknown-elf 46 | -------------------------------------------------------------------------------- /examples/hello-c/Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/zsh 2 | 3 | # - configuration ------------------------------------------------------------- 4 | 5 | UART ?= /dev/ttyACM0 6 | CROSS ?= riscv64-unknown-elf 7 | 8 | CC := $(CROSS)-gcc 9 | OBJCOPY := $(CROSS)-objcopy 10 | 11 | # - firmware ----------------------------------------------------------------- 12 | 13 | CFLAGS = -march=rv32i -mabi=ilp32 -g -Os -I./build/genc/ 14 | LDFLAGS = -Tbuild/genc/soc.ld -Triscv_application.ld -nostdlib -L./build/genc/ 15 | 16 | firmware.elf: main.c start.S riscv_application.ld build/genc/soc.ld 17 | $(CC) $(CFLAGS) $(LDFLAGS) start.S main.c -o $@ 18 | 19 | firmware.bin: firmware.elf 20 | $(OBJCOPY) -O binary $< $@ 21 | 22 | firmware: build/genc/resources.h build/genc/soc.ld firmware.bin 23 | 24 | # - generated files ----------------------------------------------------------- 25 | 26 | build/genc/resources.h: top.py 27 | mkdir -p build/genc/ 28 | python top.py --generate-c-header > $@ 29 | 30 | build/genc/soc.ld: top.py 31 | mkdir -p build/genc/ 32 | python top.py --generate-ld-script > $@ 33 | 34 | # - gateware ------------------------------------------------------------------ 35 | 36 | gateware: top.py 37 | python top.py --dry-run --output top.bit 38 | 39 | load: 40 | -apollo configure top.bit 41 | 42 | load-ulx3s: 43 | openFPGALoader --board ulx3s top.bit 44 | 45 | # - helpers ------------------------------------------------------------------- 46 | 47 | clean: 48 | -rm -rf build/ firmware.elf firmware.bin 49 | 50 | # Loads the SoC bitstream running the selftest firmware onto our FPGA. 51 | program: firmware gateware 52 | -apollo configure top.bit 53 | 54 | # Loads the SoC bitstream running the selftest firmware onto our FPGA and shows the output in a console. 55 | run: firmware gateware 56 | -apollo configure top.bit 57 | pyserial-miniterm $(UART) 115200 58 | -------------------------------------------------------------------------------- /examples/hello-c/main.c: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of LUNA. 3 | * 4 | * Copyright (c) 2020 Great Scott Gadgets 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | 8 | #include 9 | 10 | // Include our automatically generated resource file. 11 | // This allows us to work with e.g. our registers no matter gt 12 | #include "resources.h" 13 | 14 | /** 15 | * Transmits a single charater over our example UART. 16 | */ 17 | void print_char(char c) 18 | { 19 | while(!uart0_tx_ready_read()); 20 | uart0_tx_data_write(c); 21 | } 22 | 23 | /** 24 | * Transmits a string over our UART. 25 | */ 26 | void uart_puts(char *str) 27 | { 28 | for (char *c = str; *c; ++c) { 29 | print_char(*c); 30 | } 31 | } 32 | 33 | /** 34 | * Main firmware entry point 35 | */ 36 | int main(void) 37 | { 38 | bool shifting_right = true; 39 | uint8_t led_value = 0b110000; 40 | 41 | // Set up our timer to periodically move the LEDs. 42 | timer0_reload_write(6000000); 43 | timer0_enable_write(1); 44 | 45 | // And blink our LEDs. 46 | while(1) { 47 | 48 | // Skip all iterations that aren't our main one... 49 | if (timer0_counter_read()) { 50 | continue; 51 | } 52 | 53 | // Reset timer... 54 | timer0_enable_write(0); 55 | timer0_enable_write(1); 56 | 57 | // ... compute our pattern ... 58 | if (shifting_right) { 59 | led_value >>= 1; 60 | 61 | if (led_value == 0b000011) { 62 | shifting_right = false; 63 | uart_puts("left!\r\n"); 64 | } 65 | } else { 66 | led_value <<= 1; 67 | 68 | if (led_value == 0b110000) { 69 | shifting_right = true; 70 | uart_puts("right!\r\n"); 71 | } 72 | } 73 | 74 | // ... and output it to the LEDs. 75 | leds_output_write(led_value); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /examples/hello-c/riscv_application.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT("elf32-littleriscv") 2 | OUTPUT_ARCH("riscv") 3 | ENTRY(_start) 4 | 5 | SECTIONS 6 | { 7 | . = ORIGIN(blockram); 8 | 9 | /* Start of day code. */ 10 | .init : 11 | { 12 | *(.init) *(.init.*) 13 | } > blockram 14 | .text : 15 | { 16 | *(.text) *(.text.*) 17 | } > blockram 18 | 19 | .rodata : 20 | { 21 | *(.rodata) *(.rodata.*) 22 | } > blockram 23 | .sdata : 24 | { 25 | PROVIDE(__global_pointer$ = .); 26 | *(.sdata) *(.sdata.*) 27 | } 28 | .data : 29 | { 30 | *(.data) *(.data.*) 31 | } > blockram 32 | .bss : 33 | { 34 | *(.bss) *(.bss.*) 35 | } > blockram 36 | 37 | } 38 | 39 | PROVIDE(__stack_top = ORIGIN(blockram) + LENGTH(blockram)); 40 | -------------------------------------------------------------------------------- /examples/hello-c/start.S: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Great Scott Gadgets 3 | * SPDX-License-Identifier: BSD-3-Clause 4 | */ 5 | .section .init, "ax" 6 | 7 | .global _start 8 | _start: 9 | .cfi_startproc 10 | .cfi_undefined ra 11 | 12 | /* Set up our global pointer. */ 13 | .option push 14 | .option norelax 15 | la gp, __global_pointer$ 16 | .option pop 17 | 18 | /* Set up our stack. */ 19 | la sp, __stack_top 20 | add s0, sp, zero 21 | 22 | /* 23 | * NOTE: In most cases, we'd clear the BSS, here. 24 | * 25 | * In our case, our FPGA automaticaly starts with all of our RAM 26 | * initialized to zero; so our BSS comes pre-cleared. We'll skip the 27 | * formality of re-clearing it. 28 | */ 29 | 30 | /* Finally, start our main routine. */ 31 | jal zero, main 32 | 33 | .cfi_endproc 34 | .end 35 | -------------------------------------------------------------------------------- /examples/hello-rust/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.riscv32imac-unknown-none-elf] 2 | runner = "make run" 3 | rustflags = [ 4 | "-C", "link-arg=-Tmemory.x", 5 | "-C", "link-arg=-Tlink.x", 6 | ] 7 | 8 | [build] 9 | target = "riscv32imac-unknown-none-elf" 10 | -------------------------------------------------------------------------------- /examples/hello-rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "lunasoc-pac", 4 | "firmware", 5 | ] 6 | resolver = "2" 7 | 8 | exclude = [ 9 | ] 10 | 11 | # - profiles ------------------------------------------------------------------ 12 | 13 | [profile.release] 14 | codegen-units = 1 15 | debug = true 16 | debug-assertions = false 17 | incremental = false 18 | lto = true 19 | opt-level = "s" 20 | overflow-checks = false 21 | 22 | [profile.dev] 23 | codegen-units = 1 24 | debug = true 25 | debug-assertions = false 26 | incremental = false 27 | lto = true 28 | opt-level = "s" 29 | overflow-checks = false 30 | -------------------------------------------------------------------------------- /examples/hello-rust/LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) Katherine J. Temkin 4 | Copyright (c) 2019-2020, Great Scott Gadgets 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /examples/hello-rust/Makefile: -------------------------------------------------------------------------------- 1 | # - svd ---------------------------------------------------------------------- 2 | 3 | svd: 4 | python ./top.py --generate-svd > lunasoc-pac/svd/lunasoc.svd 5 | 6 | 7 | # - pac ---------------------------------------------------------------------- 8 | 9 | pac: svd 10 | mkdir -p build 11 | rm -rf lunasoc-pac/src/generated.rs lunasoc-pac/src/generated/ 12 | svd2rust -i lunasoc-pac/svd/lunasoc.svd -o build/ --target riscv --make_mod --ident-formats-theme legacy 13 | mv build/mod.rs lunasoc-pac/src/generated.rs 14 | mv build/device.x lunasoc-pac/ 15 | 16 | form -i lunasoc-pac/src/generated.rs -o lunasoc-pac/src/generated/ 17 | mv lunasoc-pac/src/generated/lib.rs lunasoc-pac/src/generated.rs 18 | cargo fmt -p lunasoc-pac -- --emit files 19 | 20 | 21 | # - firmware ----------------------------------------------------------------- 22 | 23 | firmware: pac firmware.bin 24 | 25 | firmware.bin: memory.x 26 | cargo objcopy --release -- -Obinary build/firmware.bin 27 | 28 | 29 | # - generated files ----------------------------------------------------------- 30 | 31 | memory.x: $(SOC) 32 | python ./top.py --generate-memory-x > $@ 33 | 34 | 35 | # - gateware ------------------------------------------------------------------ 36 | 37 | gateware: firmware top.py 38 | python ./top.py --dry-run --output build/top.bit 39 | rm build/firmware.bin 40 | 41 | 42 | # - helpers ------------------------------------------------------------------- 43 | 44 | # Loads the SoC bitstream running the selftest firmware onto our FPGA. 45 | program: firmware gateware 46 | -apollo configure build/top.bit 47 | 48 | program-ulx3s: 49 | openFPGALoader --board ulx3s build/top.bit 50 | 51 | # Loads the SoC bitstream running the selftest firmware onto our FPGA and shows the output in a console. 52 | run: firmware gateware 53 | -apollo configure build/top.bit 54 | pyserial-miniterm $(UART) 115200 55 | 56 | clean: 57 | -cargo clean 58 | -rm -rf memory.x build/ 59 | -rm -rf lunasoc-pac/device.x lunasoc-pac/svd/lunasoc.svd lunasoc-pac/src/generated.rs lunasoc-pac/src/generated/ 60 | -------------------------------------------------------------------------------- /examples/hello-rust/README.md: -------------------------------------------------------------------------------- 1 | # hello-rust 2 | 3 | A simple firmware example for LUNA-SOC written in Rust. 4 | 5 | 6 | ## Dependencies 7 | 8 | Please ensure you've installed any required dependencies referenced in [`../examples/README.md`](../examples/README.md). 9 | 10 | 11 | ## Running the example 12 | 13 | make run 14 | 15 | You should see a simple LED runner on your board with some logging output on the console. 16 | -------------------------------------------------------------------------------- /examples/hello-rust/firmware/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "firmware" 3 | version = "0.0.1" 4 | authors = ["Great Scott Gadgets "] 5 | license-file = "LICENSE.txt" 6 | edition = "2021" 7 | 8 | [dependencies] 9 | log = "0.4.17" 10 | panic-halt = "0.2.0" 11 | riscv = { version="=0.10.1", features=["critical-section-single-hart"] } 12 | riscv-rt = "=0.11.0" 13 | 14 | [dependencies.lunasoc-pac] 15 | path = "../lunasoc-pac/" 16 | default-features = false 17 | features = ["critical-section", "vexriscv"] 18 | 19 | [dependencies.lunasoc-hal] 20 | version = "0.2" 21 | default-features = false 22 | features = ["vexriscv"] 23 | -------------------------------------------------------------------------------- /examples/hello-rust/firmware/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | pub use lunasoc_pac as pac; 5 | 6 | pub mod log; 7 | pub mod hal { 8 | use crate::pac; 9 | 10 | lunasoc_hal::impl_serial! { 11 | Serial0: pac::UART0, 12 | } 13 | 14 | lunasoc_hal::impl_timer! { 15 | Timer0: pac::TIMER0, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/hello-rust/firmware/src/log.rs: -------------------------------------------------------------------------------- 1 | //! A simple logger for the `log` crate which can log to any object 2 | //! implementing `Write` 3 | 4 | #![allow(unused_imports, unused_mut, unused_variables)] 5 | 6 | use crate::{hal, pac}; 7 | 8 | use log::{Level, LevelFilter, Metadata, Record}; 9 | 10 | use core::cell::RefCell; 11 | use core::fmt::Write; 12 | 13 | // - initialization ----------------------------------------------------------- 14 | 15 | static LOGGER: WriteLogger = WriteLogger { 16 | writer: RefCell::new(None), 17 | level: Level::Trace, 18 | }; 19 | 20 | pub fn init(writer: hal::Serial0) { 21 | LOGGER.writer.replace(Some(writer)); 22 | match log::set_logger(&LOGGER).map(|()| log::set_max_level(LevelFilter::Trace)) { 23 | Ok(()) => (), 24 | Err(_e) => { 25 | panic!("Failed to set logger"); 26 | } 27 | } 28 | } 29 | 30 | // - implementation ----------------------------------------------------------- 31 | 32 | /// WriteLogger 33 | pub struct WriteLogger 34 | where 35 | W: Write + Send, 36 | { 37 | pub writer: RefCell>, 38 | pub level: Level, 39 | } 40 | 41 | impl log::Log for WriteLogger 42 | where 43 | W: Write + Send, 44 | { 45 | fn enabled(&self, metadata: &Metadata) -> bool { 46 | metadata.level() <= self.level 47 | } 48 | 49 | fn log(&self, record: &Record) { 50 | if !self.enabled(record.metadata()) { 51 | return; 52 | } 53 | 54 | match self.writer.borrow_mut().as_mut() { 55 | Some(writer) => match writeln!(writer, "{}\t{}", record.level(), record.args()) { 56 | Ok(()) => (), 57 | Err(_e) => { 58 | panic!("Logger failed to write to device"); 59 | } 60 | }, 61 | None => { 62 | panic!("Logger has not been initialized"); 63 | } 64 | } 65 | } 66 | 67 | fn flush(&self) {} 68 | } 69 | 70 | unsafe impl Sync for WriteLogger {} 71 | -------------------------------------------------------------------------------- /examples/hello-rust/firmware/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use log::info; 5 | use panic_halt as _; 6 | use riscv_rt::entry; 7 | use lunasoc_hal::hal::delay::DelayUs; 8 | 9 | use firmware::{pac, hal}; 10 | use hal::Serial0; 11 | use hal::Timer0; 12 | 13 | #[riscv_rt::pre_init] 14 | unsafe fn pre_main() { 15 | pac::cpu::vexriscv::flush_icache(); 16 | pac::cpu::vexriscv::flush_dcache(); 17 | } 18 | 19 | #[entry] 20 | fn main() -> ! { 21 | let peripherals = pac::Peripherals::take().unwrap(); 22 | let leds = &peripherals.LEDS; 23 | 24 | // initialize logging 25 | let serial = Serial0::new(peripherals.UART0); 26 | firmware::log::init(serial); 27 | 28 | let mut timer = Timer0::new(peripherals.TIMER0, pac::clock::sysclk()); 29 | let mut counter = 0; 30 | let mut direction = true; 31 | let mut led_state = 0b110000; 32 | 33 | info!("Peripherals initialized, entering main loop."); 34 | 35 | loop { 36 | timer.delay_ms(100).unwrap(); 37 | 38 | if direction { 39 | led_state >>= 1; 40 | if led_state == 0b000011 { 41 | direction = false; 42 | info!("left: {}", counter); 43 | } 44 | } else { 45 | led_state <<= 1; 46 | if led_state == 0b110000 { 47 | direction = true; 48 | info!("right: {}", counter); 49 | } 50 | } 51 | 52 | leds.output().write(|w| unsafe { w.bits(led_state) }); 53 | counter += 1; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/hello-rust/lunasoc-pac/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "riscv32imac-unknown-none-elf" 3 | -------------------------------------------------------------------------------- /examples/hello-rust/lunasoc-pac/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lunasoc-pac" 3 | version = "0.0.1" 4 | authors = ["Great Scott Gadgets "] 5 | license = "BSD-3-Clause" 6 | description = "A peripheral access crate for a luna-soc SoC" 7 | categories = ["embedded", "hardware-support", "no-std"] 8 | keywords = ["cynthion", "luna", "riscv", "peripheral", "usb"] 9 | repository = "https://github.com/greatscottgadgets/luna-soc" 10 | edition = "2021" 11 | rust-version = "1.68" 12 | include = ["src/**/*", "README.md", "device.x", "build.rs"] 13 | 14 | [package.metadata.docs.rs] 15 | default-target = "riscv32imac-unknown-none-elf" 16 | targets = [ 17 | "riscv32i-unknown-none-elf", 18 | "riscv32imac-unknown-none-elf", 19 | ] 20 | 21 | [lib] 22 | test = false 23 | bench = false 24 | 25 | [features] 26 | default = [ 27 | "vexriscv", 28 | ] 29 | rt = ["riscv-rt"] 30 | minerva = [] 31 | vexriscv = [] 32 | 33 | [dependencies] 34 | critical-section = { version = "=1.2.0", optional = true } 35 | riscv = "=0.10.1" 36 | riscv-rt = { version = "=0.11.0", optional = true } 37 | vcell = "=0.1.3" 38 | -------------------------------------------------------------------------------- /examples/hello-rust/lunasoc-pac/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | use std::io::Write; 4 | use std::path::PathBuf; 5 | 6 | fn main() { 7 | if env::var_os("CARGO_FEATURE_RT").is_some() { 8 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); 9 | File::create(out.join("device.x")) 10 | .unwrap() 11 | .write_all(include_bytes!("device.x")) 12 | .unwrap(); 13 | println!("cargo:rustc-link-search={}", out.display()); 14 | println!("cargo:rerun-if-changed=device.x"); 15 | } 16 | 17 | // TODO Tracking Issue: https://github.com/rust-lang/rust/issues/94039 18 | let Some(target) = rustc_target() else { return }; 19 | if target_has_atomic(&target) { 20 | println!("cargo:rustc-cfg=target_has_atomic"); 21 | } 22 | 23 | println!("cargo:rerun-if-changed=build.rs"); 24 | } 25 | 26 | fn rustc_target() -> Option { 27 | env::var("TARGET").ok() 28 | } 29 | 30 | fn target_has_atomic(target: &str) -> bool { 31 | match target { 32 | "riscv32imac-unknown-none-elf" => true, 33 | "riscv32i-unknown-none-elf" => false, 34 | _ => false, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/hello-rust/lunasoc-pac/src/cpu.rs: -------------------------------------------------------------------------------- 1 | //! Support for various vendor defined softcore extensions. 2 | 3 | pub mod minerva { 4 | 5 | pub mod register { 6 | //! Micro-architecture specific CSR extensions for the Minerva RISC-V 7 | //! soft processor. 8 | //! 9 | //! See: [ISA definition](https://github.com/minerva-cpu/minerva/blob/master/minerva/isa.py) 10 | //! 11 | //! These are somewhat weird because peripheral irq enable (0x330) 12 | //! overlaps with the Machine Counter Setup `mhpmevent16` 13 | //! performance-monitoring event selector. 14 | //! 15 | //! See: [Chapter 2 - Control and Status Registers](https://riscv.org/wp-content/uploads/2017/05/riscv-privileged-v1.10.pdf) 16 | 17 | /// Machine IRQ Mask 18 | pub mod mim { 19 | crate::macros::read_csr_as_usize!(0x330); 20 | crate::macros::write_csr_as_usize!(0x330); 21 | } 22 | 23 | /// Machine IRQ Pending 24 | pub mod mip { 25 | crate::macros::read_csr_as_usize!(0x360); 26 | } 27 | } 28 | } 29 | 30 | pub mod vexriscv { 31 | 32 | #[inline(always)] 33 | pub fn flush_icache() { 34 | unsafe { 35 | core::arch::asm!(".word(0x100f)", "nop", "nop", "nop", "nop", "nop",); 36 | } 37 | } 38 | #[inline(always)] 39 | pub fn flush_dcache() { 40 | unsafe { 41 | core::arch::asm!(".word(0x500f)"); 42 | } 43 | } 44 | 45 | pub mod register { 46 | //! Micro-architecture specific CSR extensions for the `VexRiscv` RISC-V 47 | //! soft processor. 48 | //! 49 | //! See: [ExternalInterruptArrayPlugin.scala](https://github.com/SpinalHDL/VexRiscv/blob/master/src/main/scala/vexriscv/plugin/ExternalInterruptArrayPlugin.scala) 50 | 51 | /// Machine IRQ Mask 52 | pub mod mim { 53 | crate::macros::read_csr_as_usize!(0xBC0); 54 | crate::macros::write_csr_as_usize!(0xBC0); 55 | } 56 | 57 | /// Machine IRQ Pending 58 | pub mod mip { 59 | crate::macros::read_csr_as_usize!(0xFC0); 60 | } 61 | 62 | /// Supervisor IRQ Mask 63 | pub mod sim { 64 | crate::macros::read_csr_as_usize!(0x9C0); 65 | crate::macros::write_csr_as_usize!(0x9C0); 66 | } 67 | 68 | /// Supervisor IRQ Pending 69 | pub mod sip { 70 | crate::macros::read_csr_as_usize!(0xDC0); 71 | } 72 | 73 | /// Data Cache Info 74 | pub mod dci { 75 | crate::macros::read_csr_as_usize!(0xCC0); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/hello-rust/lunasoc-pac/src/csr.rs: -------------------------------------------------------------------------------- 1 | pub mod interrupt { 2 | //! CSR access methods. 3 | 4 | use crate::register; 5 | use crate::Interrupt; 6 | 7 | /// Unmask the given [`Interrupt`] in the CPU's Machines IRQ Mask register. 8 | /// 9 | /// # Safety 10 | /// 11 | /// Passing incorrect value can cause undefined behaviour. See CPU reference manual. 12 | pub unsafe fn enable(interrupt: Interrupt) { 13 | let mask = register::mim::read(); 14 | let mask = mask | (1 << interrupt as usize); 15 | register::mim::write(mask); 16 | while register::mim::read() != mask {} 17 | } 18 | 19 | /// Mask the given [`Interrupt`] in the CPU's Machines IRQ Mask register. 20 | /// 21 | /// # Safety 22 | /// 23 | /// Passing incorrect value can cause undefined behaviour. See CPU reference manual. 24 | pub unsafe fn disable(interrupt: Interrupt) { 25 | let mask = register::mim::read(); 26 | let mask = mask & !(1 << interrupt as usize); 27 | register::mim::write(mask); 28 | while register::mim::read() != mask {} 29 | } 30 | 31 | /// Return the current value of the CPU's Machines IRQ Mask register. 32 | #[must_use] 33 | pub fn reg_mask() -> usize { 34 | register::mim::read() 35 | } 36 | 37 | /// Return the current bit value of the CPU's Machines IRQ Pending register. 38 | #[must_use] 39 | pub fn bits_pending() -> usize { 40 | register::mip::read() 41 | } 42 | 43 | /// Check if the given `Interrupt` is pending in the CPU's Machines IRQ Pending register. 44 | #[must_use] 45 | pub fn is_pending(interrupt: Interrupt) -> bool { 46 | let pending = register::mip::read(); 47 | (pending & (1 << interrupt as usize)) != 0 48 | } 49 | 50 | /// Returns the current `Interrupt` pending in the CPU's Machines IRQ Pending register. 51 | /// 52 | /// If there is no interrupt pending or an unknown interrupt 53 | /// pending it returns an `Err` containing the current bit value 54 | /// of the register. 55 | pub fn pending() -> Result { 56 | let bit = register::mip::read(); 57 | if bit == 0 { 58 | return Err(0); 59 | } 60 | let pending = bit.ilog2(); 61 | if let Ok(interrupt) = Interrupt::try_from(pending as u8) { 62 | Ok(interrupt) 63 | } else { 64 | Err(bit) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/hello-rust/lunasoc-pac/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Peripheral access API for Luna System-on-Chip designs generated using svd2rust. 2 | 3 | #![no_std] 4 | #![allow(clippy::inline_always)] 5 | 6 | #[cfg(all(feature = "minerva", feature = "vexriscv"))] 7 | compile_error!(r#"Only one of the "minerva" or "vexriscv" features can be selected"#); 8 | 9 | #[macro_use] 10 | mod macros; 11 | 12 | pub mod cpu; 13 | pub mod csr; 14 | pub mod register { 15 | #[cfg(feature = "minerva")] 16 | pub use crate::cpu::minerva::register::*; 17 | #[cfg(feature = "vexriscv")] 18 | pub use crate::cpu::vexriscv::register::*; 19 | } 20 | 21 | pub mod clock { 22 | const SYSTEM_CLOCK_FREQUENCY: u32 = 60_000_000; 23 | 24 | #[must_use] 25 | pub const fn sysclk() -> u32 { 26 | SYSTEM_CLOCK_FREQUENCY 27 | } 28 | } 29 | 30 | #[deny(dead_code)] 31 | #[deny(improper_ctypes)] 32 | #[deny(missing_docs)] 33 | #[deny(no_mangle_generic_items)] 34 | #[deny(non_shorthand_field_patterns)] 35 | #[deny(overflowing_literals)] 36 | #[deny(path_statements)] 37 | #[deny(patterns_in_fns_without_body)] 38 | #[deny(unconditional_recursion)] 39 | #[deny(unused_allocation)] 40 | #[deny(unused_comparisons)] 41 | #[deny(unused_parens)] 42 | #[deny(while_true)] 43 | #[allow(non_camel_case_types)] 44 | #[allow(non_snake_case)] 45 | #[allow(clippy::must_use_candidate)] 46 | #[allow(clippy::semicolon_if_nothing_returned)] 47 | mod generated; 48 | 49 | pub use generated::generic::*; 50 | pub use generated::*; 51 | -------------------------------------------------------------------------------- /examples/hello-rust/lunasoc-pac/src/macros.rs: -------------------------------------------------------------------------------- 1 | //! Register access functions for RISC-V system registers. 2 | 3 | macro_rules! read_csr { 4 | ($csr_number:literal) => { 5 | #[inline] 6 | unsafe fn _read() -> usize { 7 | match () { 8 | () => { 9 | let r: usize; 10 | core::arch::asm!(concat!("csrrs {0}, ", stringify!($csr_number), ", x0"), out(reg) r); 11 | r 12 | } 13 | } 14 | } 15 | }; 16 | } 17 | pub(crate) use read_csr; 18 | 19 | macro_rules! write_csr { 20 | ($csr_number:literal) => { 21 | #[inline] 22 | #[allow(unused_variables)] 23 | unsafe fn _write(bits: usize) { 24 | match () { 25 | () => core::arch::asm!(concat!("csrrw x0, ", stringify!($csr_number), ", {0}"), in(reg) bits), 26 | } 27 | } 28 | }; 29 | } 30 | pub(crate) use write_csr; 31 | 32 | macro_rules! read_csr_as_usize { 33 | ($csr_number:literal) => { 34 | crate::macros::read_csr!($csr_number); 35 | 36 | #[inline] 37 | #[allow(clippy::must_use_candidate)] 38 | pub fn read() -> usize { 39 | unsafe { _read() } 40 | } 41 | }; 42 | } 43 | pub(crate) use read_csr_as_usize; 44 | 45 | macro_rules! write_csr_as_usize { 46 | ($csr_number:literal) => { 47 | crate::macros::write_csr!($csr_number); 48 | 49 | #[inline] 50 | pub fn write(bits: usize) { 51 | unsafe { _write(bits) } 52 | } 53 | }; 54 | } 55 | pub(crate) use write_csr_as_usize; 56 | -------------------------------------------------------------------------------- /examples/hello-rust/top.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2020-2025 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import logging 8 | import os 9 | import sys 10 | 11 | from luna import configure_default_logging 12 | from luna.gateware.usb.usb2.device import USBDevice 13 | 14 | import luna_soc 15 | from luna_soc.gateware.cpu import InterruptController, VexRiscv 16 | from luna_soc.gateware.core import blockram, timer, uart 17 | from luna_soc.gateware.provider import cynthion as provider 18 | from luna_soc.util.readbin import get_mem_data 19 | 20 | from amaranth import * 21 | from amaranth.lib import wiring 22 | 23 | from amaranth_soc import csr, gpio, wishbone 24 | from amaranth_soc.csr.wishbone import WishboneCSRBridge 25 | 26 | CLOCK_FREQUENCIES_MHZ = { 27 | 'sync': 60 28 | } 29 | 30 | # - HelloSoc ------------------------------------------------------------------ 31 | 32 | class HelloSoc(wiring.Component): 33 | def __init__(self, clock_frequency_hz, domain): 34 | super().__init__({}) 35 | 36 | self.clock_frequency_hz = clock_frequency_hz 37 | self.domain = domain 38 | 39 | # configuration 40 | blockram_base = 0x00000000 41 | blockram_size = 32768 42 | 43 | csr_base = 0xf0000000 44 | leds_base = 0x00000000 45 | uart0_base = 0x00000300 46 | timer0_base = 0x00000500 47 | 48 | # cpu 49 | self.cpu = VexRiscv( 50 | reset_addr=blockram_base, 51 | variant="cynthion" 52 | ) 53 | 54 | # interrupt controller 55 | self.interrupt_controller = InterruptController(width=len(self.cpu.irq_external)) 56 | 57 | # bus 58 | self.wb_arbiter = wishbone.Arbiter( 59 | addr_width=30, 60 | data_width=32, 61 | granularity=8, 62 | features={"cti", "bte", "err"} 63 | ) 64 | self.wb_decoder = wishbone.Decoder( 65 | addr_width=30, 66 | data_width=32, 67 | granularity=8, 68 | features={"cti", "bte", "err"} 69 | ) 70 | 71 | # ... read our firmware binary ... 72 | filename = "build/firmware.bin" 73 | firmware = get_mem_data(filename, data_width=32, endianness="little") 74 | if not firmware: 75 | logging.warning(f"Firmware file '{filename}' could not be located.") 76 | firmware = [] 77 | 78 | # blockram 79 | self.blockram = blockram.Peripheral(size=blockram_size, init=firmware) 80 | self.wb_decoder.add(self.blockram.bus, addr=blockram_base, name="blockram") 81 | 82 | # csr decoder 83 | self.csr_decoder = csr.Decoder(addr_width=28, data_width=8) 84 | 85 | # leds 86 | self.led_count = 6 87 | self.leds = gpio.Peripheral(pin_count=self.led_count, addr_width=3, data_width=8) 88 | self.csr_decoder.add(self.leds.bus, addr=leds_base, name="leds") 89 | 90 | # uart0 91 | uart_baud_rate = 115200 92 | divisor = int(clock_frequency_hz // uart_baud_rate) 93 | self.uart0 = uart.Peripheral(divisor=divisor) 94 | self.csr_decoder.add(self.uart0.bus, addr=uart0_base, name="uart0") 95 | 96 | # timer0 97 | self.timer0 = timer.Peripheral(width=32) 98 | self.csr_decoder.add(self.timer0.bus, addr=timer0_base, name="timer0") 99 | self.interrupt_controller.add(self.timer0, number=0, name="timer0") 100 | 101 | # wishbone csr bridge 102 | self.wb_to_csr = WishboneCSRBridge(self.csr_decoder.bus, data_width=32) 103 | self.wb_decoder.add(self.wb_to_csr.wb_bus, addr=csr_base, sparse=False, name="wb_to_csr") 104 | 105 | 106 | def elaborate(self, platform): 107 | m = Module() 108 | 109 | # bus 110 | m.submodules += [self.wb_arbiter, self.wb_decoder] 111 | wiring.connect(m, self.wb_arbiter.bus, self.wb_decoder.bus) 112 | 113 | # cpu 114 | m.submodules += self.cpu 115 | self.wb_arbiter.add(self.cpu.ibus) 116 | self.wb_arbiter.add(self.cpu.dbus) 117 | 118 | # interrupt controller 119 | m.submodules += self.interrupt_controller 120 | m.d.comb += self.cpu.irq_external.eq(self.interrupt_controller.pending) 121 | 122 | # blockram 123 | m.submodules += self.blockram 124 | 125 | # csr decoder 126 | m.submodules += self.csr_decoder 127 | 128 | # leds 129 | led_provider = provider.LEDProvider("led", pin_count=self.led_count) 130 | m.submodules += [led_provider, self.leds] 131 | for n in range(self.led_count): 132 | wiring.connect(m, self.leds.pins[n], led_provider.pins[n]) 133 | 134 | # uart0 135 | uart0_provider = provider.UARTProvider("uart", 0) 136 | m.submodules += [uart0_provider, self.uart0] 137 | wiring.connect(m, self.uart0.pins, uart0_provider.pins) 138 | 139 | # timer0 140 | m.submodules += self.timer0 141 | 142 | # wishbone csr bridge 143 | m.submodules += self.wb_to_csr 144 | 145 | # wire up the cpu external reset signal 146 | try: 147 | user1_io = platform.request("button_user") 148 | m.d.comb += self.cpu.ext_reset.eq(user1_io.i) 149 | except: 150 | logging.warning("Platform does not support a user button for cpu reset") 151 | 152 | return DomainRenamer({ 153 | "sync": self.domain, 154 | })(m) 155 | 156 | 157 | # - module: Top --------------------------------------------------------------- 158 | 159 | class Top(Elaboratable): 160 | def __init__(self, clock_frequency_hz, domain="sync"): 161 | self.clock_frequency_hz = clock_frequency_hz 162 | self.domain = domain 163 | 164 | self.soc = HelloSoc(clock_frequency_hz=self.clock_frequency_hz, domain=self.domain) 165 | 166 | def elaborate(self, platform): 167 | m = Module() 168 | 169 | # generate our domain clocks/resets 170 | m.submodules.car = platform.clock_domain_generator(clock_frequencies=CLOCK_FREQUENCIES_MHZ) 171 | 172 | # add soc to design 173 | m.submodules += self.soc 174 | 175 | return m 176 | 177 | 178 | # - main ---------------------------------------------------------------------- 179 | 180 | if __name__ == "__main__": 181 | from luna import configure_default_logging 182 | from luna.gateware.platform import get_appropriate_platform 183 | from luna_soc import top_level_cli 184 | 185 | # configure logging 186 | configure_default_logging() 187 | logging.getLogger().setLevel(logging.DEBUG) 188 | 189 | # select platform 190 | platform = get_appropriate_platform() 191 | if platform is None: 192 | logging.error("Failed to identify a supported platform") 193 | sys.exit(1) 194 | 195 | # configure domain 196 | domain = "sync" 197 | 198 | # configure clock frequency 199 | clock_frequency_hz = int(CLOCK_FREQUENCIES_MHZ[domain] * 1e6) 200 | logging.info(f"Building for {platform} with domain {domain} and clock frequency: {clock_frequency_hz}") 201 | 202 | # create design 203 | design = Top(clock_frequency_hz=int(60e6), domain=domain) 204 | 205 | # invoke cli 206 | _overrides = { 207 | "debug_verilog": False, 208 | "verbose": False, 209 | } 210 | top_level_cli(design, **_overrides) 211 | -------------------------------------------------------------------------------- /luna_soc/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2023 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | from .top_level_cli import * 8 | 9 | # Mildly evil hack to vendor in amaranth_soc and amaranth_stdio if they're 10 | # not installed: 11 | try: 12 | try: 13 | import amaranth_soc 14 | import amaranth_stdio 15 | except: 16 | import os 17 | path = os.path.dirname(os.path.abspath(__file__)) 18 | path = os.path.join(path, "gateware", "vendor") 19 | sys.path.append(path) 20 | except: 21 | pass 22 | -------------------------------------------------------------------------------- /luna_soc/gateware/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2020-2024 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | -------------------------------------------------------------------------------- /luna_soc/gateware/core/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2023 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | -------------------------------------------------------------------------------- /luna_soc/gateware/core/blockram.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2023-2025 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | from amaranth import * 8 | from amaranth.lib import wiring, memory 9 | from amaranth.lib.wiring import In 10 | from amaranth.utils import exact_log2 11 | 12 | from amaranth_soc import wishbone 13 | from amaranth_soc.memory import MemoryMap 14 | from amaranth_soc.periph import ConstantMap 15 | 16 | 17 | class Peripheral(wiring.Component): 18 | """SRAM storage peripheral. 19 | 20 | Parameters 21 | ---------- 22 | size : int 23 | Memory size in bytes. 24 | data_width : int 25 | The width of each memory word. 26 | granularity : int 27 | The number of bits of data per each address. 28 | writable : bool 29 | Memory is writable. 30 | init : list[byte] Optional 31 | The initial value of the relevant memory. 32 | name : str 33 | A descriptive name for the given memory. 34 | 35 | Attributes 36 | ---------- 37 | bus : :class:`amaranth_soc.wishbone.Interface` 38 | Wishbone bus interface. 39 | """ 40 | 41 | def __init__(self, *, size, data_width=32, granularity=8, writable=True, init=[], name="blockram"): 42 | if not isinstance(size, int) or size <= 0 or size & size-1: 43 | raise ValueError("Size must be an integer power of two, not {!r}" 44 | .format(size)) 45 | if size < data_width // granularity: 46 | raise ValueError("Size {} cannot be lesser than the data width/granularity ratio " 47 | "of {} ({} / {})" 48 | .format(size, data_width // granularity, data_width, granularity)) 49 | 50 | self.size = size 51 | self.granularity = granularity 52 | self.writable = writable 53 | self.name = name 54 | 55 | depth = (size * granularity) // data_width 56 | self._mem = memory.Memory(shape=data_width, depth=depth, init=init) 57 | 58 | super().__init__({ 59 | "bus": In(wishbone.Signature(addr_width=exact_log2(depth), 60 | data_width=data_width, 61 | granularity=granularity, 62 | features={"cti", "bte"})), 63 | }) 64 | 65 | memory_map = MemoryMap(addr_width=exact_log2(size), data_width=granularity) 66 | memory_map.add_resource(name=("memory", self.name,), size=size, resource=self) 67 | self.bus.memory_map = memory_map 68 | 69 | @property 70 | def init(self): 71 | return self._mem.init 72 | 73 | @init.setter 74 | def init(self, init): 75 | self._mem.init = init 76 | 77 | @property 78 | def constant_map(self): 79 | return ConstantMap( 80 | SIZE = self.size, 81 | ) 82 | 83 | def elaborate(self, platform): 84 | m = Module() 85 | m.submodules.mem = self._mem 86 | 87 | incr = Signal.like(self.bus.adr) 88 | 89 | with m.Switch(self.bus.bte): 90 | with m.Case(wishbone.BurstTypeExt.LINEAR): 91 | m.d.comb += incr.eq(self.bus.adr + 1) 92 | with m.Case(wishbone.BurstTypeExt.WRAP_4): 93 | m.d.comb += incr[:2].eq(self.bus.adr[:2] + 1) 94 | m.d.comb += incr[2:].eq(self.bus.adr[2:]) 95 | with m.Case(wishbone.BurstTypeExt.WRAP_8): 96 | m.d.comb += incr[:3].eq(self.bus.adr[:3] + 1) 97 | m.d.comb += incr[3:].eq(self.bus.adr[3:]) 98 | with m.Case(wishbone.BurstTypeExt.WRAP_16): 99 | m.d.comb += incr[:4].eq(self.bus.adr[:4] + 1) 100 | m.d.comb += incr[4:].eq(self.bus.adr[4:]) 101 | 102 | mem_rp = self._mem.read_port() 103 | m.d.comb += self.bus.dat_r.eq(mem_rp.data) 104 | 105 | with m.If(self.bus.cyc & self.bus.stb): 106 | m.d.sync += self.bus.ack.eq(1) 107 | with m.If((self.bus.cti == wishbone.CycleType.INCR_BURST) & self.bus.ack): 108 | m.d.comb += mem_rp.addr.eq(incr) 109 | with m.Else(): 110 | m.d.comb += mem_rp.addr.eq(self.bus.adr) 111 | 112 | if self.writable: 113 | mem_wp = self._mem.write_port(granularity=self.granularity) 114 | m.d.comb += mem_wp.addr.eq(mem_rp.addr) 115 | m.d.comb += mem_wp.data.eq(self.bus.dat_w) 116 | with m.If(self.bus.cyc & self.bus.stb & self.bus.we): 117 | m.d.comb += mem_wp.en.eq(self.bus.sel) 118 | 119 | # We can handle any transaction request in a single cycle, when our RAM handles 120 | # the read or write. Accordingly, we'll ACK the cycle after any request. 121 | m.d.sync += self.bus.ack.eq( 122 | self.bus.cyc & 123 | self.bus.stb & 124 | ~self.bus.ack 125 | ) 126 | 127 | return m 128 | -------------------------------------------------------------------------------- /luna_soc/gateware/core/ila.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2020-2025 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | from amaranth import * 8 | from amaranth.lib import enum, wiring 9 | from amaranth.lib.wiring import In, Out, flipped, connect 10 | 11 | from amaranth_soc import csr 12 | 13 | 14 | class Peripheral(wiring.Component): 15 | """ A simple peripheral interface to the ila. """ 16 | class Control(csr.Register, access="w"): 17 | trigger: csr.Field(csr.action.W, unsigned(1)) 18 | 19 | class Trace(csr.Register, access="w"): 20 | a: csr.Field(csr.action.W, unsigned(8)) 21 | b: csr.Field(csr.action.W, unsigned(8)) 22 | c: csr.Field(csr.action.W, unsigned(8)) 23 | d: csr.Field(csr.action.W, unsigned(8)) 24 | 25 | def __init__(self): 26 | # registers 27 | regs = csr.Builder(addr_width=4, data_width=8) 28 | self._control = regs.add("control", self.Control()) 29 | self._trace = regs.add("trace", self.Trace()) 30 | self._bridge = csr.Bridge(regs.as_memory_map()) 31 | 32 | # csr decoder 33 | self._decoder = csr.Decoder(addr_width=5, data_width=8) 34 | self._decoder.add(self._bridge.bus) 35 | 36 | super().__init__({ 37 | "bus": Out(self._decoder.bus.signature), 38 | "trigger": Out(unsigned(1)), 39 | "a": Out(unsigned(8)), 40 | "b": Out(unsigned(8)), 41 | "c": Out(unsigned(8)), 42 | "d": Out(unsigned(8)), 43 | }) 44 | self.bus.memory_map = self._decoder.bus.memory_map 45 | 46 | def elaborate(self, platform): 47 | m = Module() 48 | m.submodules += [self._bridge, self._decoder] 49 | 50 | # connect bus 51 | connect(m, flipped(self.bus), self._decoder.bus) 52 | 53 | # logic 54 | with m.If(self._control.f.trigger.w_stb): 55 | m.d.comb += self.trigger .eq(self._control.f.trigger.w_data) 56 | with m.If(self._trace.f.a.w_stb): 57 | m.d.comb += self.a .eq(self._trace.f.a.w_data) 58 | with m.If(self._trace.f.b.w_stb): 59 | m.d.comb += self.b .eq(self._trace.f.b.w_data) 60 | with m.If(self._trace.f.c.w_stb): 61 | m.d.comb += self.c .eq(self._trace.f.c.w_data) 62 | with m.If(self._trace.f.d.w_stb): 63 | m.d.comb += self.d .eq(self._trace.f.d.w_data) 64 | 65 | return m 66 | -------------------------------------------------------------------------------- /luna_soc/gateware/core/spiflash/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2024 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | # Based on code from LiteSPI 8 | 9 | from amaranth import Elaboratable, Module, unsigned 10 | from amaranth.lib import wiring 11 | from amaranth.lib.wiring import connect, In, Out 12 | 13 | from .port import SPIControlPortCDC, SPIControlPortCrossbar 14 | from .mmap import SPIFlashMemoryMap 15 | from .controller import SPIController 16 | from .phy import SPIPHYController, ECP5ConfigurationFlashInterface 17 | 18 | __all__ = ["PinSignature", "Peripheral", "ECP5ConfigurationFlashInterface", "SPIPHYController"] 19 | 20 | 21 | class PinSignature(wiring.Signature): 22 | def __init__(self): 23 | super().__init__({ 24 | "dq" : In( 25 | wiring.Signature({ 26 | "i" : In (unsigned(4)), 27 | "o" : Out (unsigned(4)), 28 | "oe" : Out (unsigned(1)), 29 | }) 30 | ), 31 | "cs" : Out( 32 | wiring.Signature({ 33 | "o" : Out (unsigned(1)), 34 | }) 35 | ), 36 | }) 37 | 38 | 39 | class Peripheral(wiring.Component): 40 | """SPI Flash peripheral main module. 41 | 42 | This class provides a wrapper that can instantiate both ``SPIController`` and 43 | ``SPIFlashMemoryMap`` and connect them to the PHY. 44 | 45 | Both options share access to the PHY using a crossbar. 46 | Also, performs CDC if a different clock is used in the PHY. 47 | """ 48 | def __init__(self, phy, *, data_width=32, granularity=8, with_controller=True, controller_name=None, 49 | with_mmap=True, mmap_size=None, mmap_name=None, mmap_byteorder="little", domain="sync"): 50 | 51 | self._domain = domain 52 | self.data_width = data_width 53 | self.phy = phy 54 | self.cores = [] 55 | 56 | if with_controller: 57 | self.spi_controller = SPIController( 58 | data_width=data_width, 59 | granularity=granularity, 60 | name=controller_name, 61 | domain=domain, 62 | ) 63 | self.csr = self.spi_controller.bus 64 | self.cores.append(self.spi_controller) 65 | 66 | if with_mmap: 67 | self.spi_mmap = SPIFlashMemoryMap( 68 | size=mmap_size, 69 | data_width=data_width, 70 | granularity=granularity, 71 | name=mmap_name, 72 | domain=domain, 73 | byteorder=mmap_byteorder, 74 | ) 75 | self.bus = self.spi_mmap.bus 76 | self.cores.append(self.spi_mmap) 77 | 78 | def elaborate(self, platform): 79 | m = Module() 80 | 81 | phy = self.phy 82 | m.submodules += self.cores 83 | 84 | # Add crossbar when we need to share multiple cores with the same PHY. 85 | if len(self.cores) > 1: 86 | 87 | m.submodules.crossbar = crossbar = SPIControlPortCrossbar( 88 | data_width=self.data_width, 89 | num_ports=len(self.cores), 90 | domain=self._domain, 91 | ) 92 | 93 | for i, core in enumerate(self.cores): 94 | connect(m, core.source, crossbar.get_port(i).source) 95 | connect(m, core.sink, crossbar.get_port(i).sink) 96 | m.d.comb += crossbar.get_port(i).cs .eq(core.cs) 97 | 98 | phy_controller = crossbar.controller 99 | else: 100 | phy_controller = self.cores[0] 101 | 102 | # Add a clock domain crossing submodule if the PHY clock is different. 103 | if self._domain != phy._domain: 104 | m.submodules.cdc = cdc = SPIControlPortCDC( 105 | data_width=self.data_width, 106 | domain_a=self._domain, 107 | domain_b=phy._domain, 108 | ) 109 | connect(m, phy_controller, cdc.a) 110 | connect(m, cdc.b, phy) 111 | else: 112 | connect(m, phy_controller.source, phy.source) 113 | connect(m, phy_controller.sink, phy.sink) 114 | m.d.comb += phy.cs .eq(phy_controller.cs) 115 | 116 | return m 117 | -------------------------------------------------------------------------------- /luna_soc/gateware/core/spiflash/controller.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2024 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | # Based on code from LiteSPI 8 | 9 | from amaranth import Module, DomainRenamer, Signal, unsigned 10 | from amaranth.lib import wiring 11 | from amaranth.lib.fifo import SyncFIFO 12 | from amaranth.lib.data import StructLayout, View 13 | from amaranth.lib.wiring import In, Out, flipped, connect 14 | 15 | from amaranth_soc import csr 16 | 17 | from .port import SPIControlPort 18 | 19 | 20 | class SPIController(wiring.Component): 21 | """Wishbone generic SPI Flash Controller interface. 22 | 23 | Provides a generic SPI Controller that can be interfaced using CSRs. 24 | Supports multiple access modes with the help of ``width`` and ``mask`` registers which 25 | can be used to configure the PHY into any supported SDR mode (single/dual/quad/octal). 26 | """ 27 | 28 | class Phy(csr.Register, access="rw"): 29 | """PHY control register 30 | 31 | length : SPI transfer length in bits. 32 | width : SPI transfer bus width (1/2/4/8). 33 | mask : SPI DQ output enable mask. 34 | """ 35 | def __init__(self, source): 36 | super().__init__({ 37 | "length" : csr.Field(csr.action.RW, unsigned(len(source.len))), 38 | "width" : csr.Field(csr.action.RW, unsigned(len(source.width))), 39 | "mask" : csr.Field(csr.action.RW, unsigned(len(source.mask))), 40 | }) 41 | 42 | class Cs(csr.Register, access="w"): 43 | """SPI chip select register 44 | 45 | select : SPI chip select signal. 46 | """ 47 | select : csr.Field(csr.action.W, unsigned(1)) 48 | 49 | class Status(csr.Register, access="r"): 50 | """Status register 51 | 52 | rx_ready : RX FIFO contains data. 53 | tx_ready : TX FIFO ready to receive data. 54 | """ 55 | rx_ready : csr.Field(csr.action.R, unsigned(1)) 56 | tx_ready : csr.Field(csr.action.R, unsigned(1)) 57 | 58 | class Data(csr.Register, access="rw"): 59 | """Data register 60 | 61 | rx : Read the next byte in the RX FIFO 62 | tx : Write the given byte to the TX FIFO 63 | """ 64 | def __init__(self, width): 65 | super().__init__({ 66 | "rx" : csr.Field(csr.action.R, unsigned(width)), 67 | "tx" : csr.Field(csr.action.W, unsigned(width)) 68 | }) 69 | 70 | 71 | def __init__(self, *, data_width=32, granularity=8, rx_depth=16, tx_depth=16, name=None, domain="sync"): 72 | wiring.Component.__init__(self, SPIControlPort(data_width)) 73 | 74 | self._domain = domain 75 | 76 | # layout description for writing to the tx fifo 77 | self.tx_fifo_layout = StructLayout({ 78 | "data": len(self.source.data), 79 | "len": len(self.source.len), 80 | "width": len(self.source.width), 81 | "mask": len(self.source.mask), 82 | }) 83 | 84 | # fifos 85 | self._rx_fifo = DomainRenamer(domain)(SyncFIFO(width=len(self.sink.payload), depth=rx_depth)) 86 | self._tx_fifo = DomainRenamer(domain)(SyncFIFO(width=len(self.source.payload), depth=tx_depth)) 87 | 88 | # registers 89 | regs = csr.Builder(addr_width=5, data_width=8) 90 | self._phy = regs.add("phy", self.Phy(self.source)) 91 | self._cs = regs.add("cs", self.Cs()) 92 | self._status = regs.add("status", self.Status()) 93 | self._data = regs.add("data", self.Data(data_width)) 94 | 95 | # bridge 96 | self._bridge = csr.Bridge(regs.as_memory_map()) 97 | 98 | super().__init__({ 99 | "bus" : In(self._bridge.bus.signature), 100 | }) 101 | self.bus.memory_map = self._bridge.bus.memory_map 102 | 103 | 104 | def elaborate(self, platform): 105 | m = Module() 106 | m.submodules.bridge = self._bridge 107 | 108 | connect(m, self.bus, self._bridge.bus) 109 | 110 | # FIFOs. 111 | m.submodules.rx_fifo = rx_fifo = self._rx_fifo 112 | m.submodules.tx_fifo = tx_fifo = self._tx_fifo 113 | 114 | # Chip select generation. 115 | cs = Signal() 116 | with m.FSM(): 117 | with m.State("RISE"): 118 | # Enable chip select when the CSR is set to 1 and the TX FIFO contains something. 119 | m.d.comb += cs.eq(tx_fifo.r_rdy) 120 | with m.If(cs == 1): 121 | m.next = "FALL" 122 | with m.State("FALL"): 123 | # Only disable chip select after the current TX FIFO is emptied. 124 | m.d.comb += cs.eq(self._cs.f.select.w_data | tx_fifo.r_rdy) 125 | with m.If(cs == 0): 126 | m.next = "RISE" 127 | 128 | # Connect FIFOs to PHY streams. 129 | tx_fifo_payload = View(self.tx_fifo_layout, tx_fifo.w_data) 130 | m.d.comb += [ 131 | # CSRs to TX FIFO. 132 | tx_fifo.w_en .eq(self._data.f.tx.w_stb), 133 | tx_fifo_payload.data .eq(self._data.f.tx.w_data), 134 | tx_fifo_payload.len .eq(self._phy.f.length.data), 135 | tx_fifo_payload.width .eq(self._phy.f.width.data), 136 | tx_fifo_payload.mask .eq(self._phy.f.mask.data), 137 | 138 | # SPI chip select. 139 | self.cs .eq(cs), 140 | 141 | # TX FIFO to SPI PHY (PICO). 142 | self.source.payload .eq(tx_fifo.r_data), 143 | self.source.valid .eq(tx_fifo.r_rdy), 144 | tx_fifo.r_en .eq(self.source.ready), 145 | 146 | # SPI PHY (POCI) to RX FIFO. 147 | rx_fifo.w_data .eq(self.sink.payload), 148 | rx_fifo.w_en .eq(self.sink.valid), 149 | self.sink.ready .eq(rx_fifo.w_rdy), 150 | 151 | # RX FIFO to CSRs. 152 | rx_fifo.r_en .eq(self._data.f.rx.r_stb), 153 | self._data.f.rx.r_data .eq(rx_fifo.r_data), 154 | 155 | # FIFOs ready flags. 156 | self._status.f.rx_ready.r_data .eq(rx_fifo.r_rdy), 157 | self._status.f.tx_ready.r_data .eq(tx_fifo.w_rdy), 158 | ] 159 | 160 | # Convert our sync domain to the domain requested by the user, if necessary. 161 | if self._domain != "sync": 162 | m = DomainRenamer({"sync": self._domain})(m) 163 | 164 | return m 165 | -------------------------------------------------------------------------------- /luna_soc/gateware/core/spiflash/port.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2024 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | # Based on code from LiteSPI 8 | 9 | from amaranth import Module, Signal, Cat, EnableInserter 10 | from amaranth.lib.fifo import AsyncFIFO 11 | from amaranth.lib.cdc import FFSynchronizer 12 | from amaranth.lib import wiring 13 | from amaranth.lib.wiring import In, Out, connect 14 | 15 | from .utils import RoundRobin 16 | 17 | 18 | # 19 | # Signatures. 20 | # 21 | 22 | class StreamCore2PHY(wiring.Signature): 23 | def __init__(self, data_width): 24 | self._data_width = data_width 25 | members = { 26 | "data": Out(data_width), 27 | "len": Out(6), 28 | "width": Out(4), 29 | "mask": Out(8), 30 | "valid": Out(1), 31 | "ready": In(1), 32 | } 33 | super().__init__(members) 34 | 35 | @property 36 | def data_width(self): 37 | return self._data_width 38 | 39 | def __eq__(self, other): 40 | return isinstance(other, StreamCore2PHY) and self.data_width == other.data_width 41 | 42 | def __repr__(self): 43 | return f"StreamCore2PHY({self.data_width})" 44 | 45 | def create(self, *, path=None, src_loc_at=0): 46 | return StreamCore2PHYInterface(self, path=path, src_loc_at=1 + src_loc_at) 47 | 48 | class StreamCore2PHYInterface(wiring.PureInterface): 49 | @property 50 | def payload(self): 51 | """ Joint signal with all data. """ 52 | return Cat(self.data, self.len, self.width, self.mask) 53 | 54 | 55 | class StreamPHY2Core(wiring.Signature): 56 | def __init__(self, data_width): 57 | self._data_width = data_width 58 | members = { 59 | "data": Out(data_width), 60 | "valid": Out(1), 61 | "ready": In(1), 62 | } 63 | super().__init__(members) 64 | 65 | @property 66 | def data_width(self): 67 | return self._data_width 68 | 69 | def __eq__(self, other): 70 | return isinstance(other, StreamPHY2Core) and self.data_width == other.data_width 71 | 72 | def __repr__(self): 73 | return f"StreamPHY2Core({self.data_width})" 74 | 75 | def create(self, *, path=None, src_loc_at=0): 76 | return StreamPHY2CoreInterface(self, path=path, src_loc_at=1 + src_loc_at) 77 | 78 | class StreamPHY2CoreInterface(wiring.PureInterface): 79 | @property 80 | def payload(self): 81 | """ Joint signal with all data. """ 82 | return self.data 83 | 84 | 85 | class SPIControlPort(wiring.Signature): 86 | def __init__(self, data_width): 87 | super().__init__({ 88 | "source": Out(StreamCore2PHY(data_width)), 89 | "sink": In(StreamPHY2Core(data_width)), 90 | "cs": Out(1), 91 | }) 92 | 93 | 94 | # 95 | # SPI control port utilities. 96 | # 97 | 98 | class SPIControlPortCrossbar(wiring.Component): 99 | """ Merge multiple SPIControlPorts with a round-robin scheduler. """ 100 | 101 | def __init__(self, *, data_width=32, num_ports=1, domain="sync"): 102 | self._domain = domain 103 | self._num_ports = num_ports 104 | 105 | super().__init__(dict( 106 | controller=Out(SPIControlPort(data_width)), 107 | **{f"slave{i}": In(SPIControlPort(data_width)) for i in range(num_ports)} 108 | )) 109 | 110 | def get_port(self, index): 111 | return getattr(self, f"slave{index}") 112 | 113 | def elaborate(self, platform): 114 | m = Module() 115 | 116 | grant_update = Signal() 117 | m.submodules.rr = rr = EnableInserter(grant_update)(RoundRobin(count=self._num_ports)) 118 | m.d.comb += rr.requests.eq(Cat(self.get_port(i).cs for i in range(self._num_ports))) 119 | 120 | # Multiplexer. 121 | with m.Switch(rr.grant): 122 | for i in range(self._num_ports): 123 | with m.Case(i): 124 | connect(m, wiring.flipped(self.get_port(i)), wiring.flipped(self.controller)) 125 | m.d.comb += grant_update.eq(~rr.valid | ~rr.requests[i]) 126 | 127 | return m 128 | 129 | 130 | class SPIControlPortCDC(wiring.Component): 131 | """ Converts one SPIControlPort between clock domains. """ 132 | 133 | def __init__(self, *, data_width=32, domain_a="sync", domain_b="sync", depth=4): 134 | super().__init__({ 135 | "a": In(SPIControlPort(data_width)), 136 | "b": Out(SPIControlPort(data_width)), 137 | }) 138 | self.domain_a = domain_a 139 | self.domain_b = domain_b 140 | self.depth = depth 141 | 142 | def elaborate(self, platform): 143 | m = Module() 144 | a, b = self.a, self.b 145 | 146 | tx_cdc = AsyncFIFO(width=len(a.source.payload), depth=self.depth, w_domain=self.domain_a, r_domain=self.domain_b) 147 | rx_cdc = AsyncFIFO(width=len(b.sink.data), depth=self.depth, w_domain=self.domain_b, r_domain=self.domain_a) 148 | cs_cdc = FFSynchronizer(a.cs, b.cs, o_domain=self.domain_b) 149 | 150 | m.submodules.tx_cdc = tx_cdc 151 | m.submodules.rx_cdc = rx_cdc 152 | m.submodules.cs_cdc = cs_cdc 153 | 154 | m.d.comb += [ 155 | # Wire TX path. 156 | tx_cdc.w_data .eq(a.source.payload), 157 | tx_cdc.w_en .eq(a.source.valid), 158 | a.source.ready .eq(tx_cdc.w_rdy), 159 | b.source.payload .eq(tx_cdc.r_data), 160 | b.source.valid .eq(tx_cdc.r_rdy), 161 | tx_cdc.r_en .eq(b.source.ready), 162 | 163 | # Wire RX path. 164 | rx_cdc.w_data .eq(b.sink.data), 165 | rx_cdc.w_en .eq(b.sink.valid), 166 | b.sink.ready .eq(rx_cdc.w_rdy), 167 | a.sink.data .eq(rx_cdc.r_data), 168 | a.sink.valid .eq(rx_cdc.r_rdy), 169 | rx_cdc.r_en .eq(a.sink.ready), 170 | ] 171 | 172 | return m 173 | -------------------------------------------------------------------------------- /luna_soc/gateware/core/spiflash/utils.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2024 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | # Based on code from LiteSPI 8 | 9 | from amaranth import Elaboratable, Module, Signal, DomainRenamer 10 | from amaranth.utils import bits_for 11 | 12 | 13 | class RoundRobin(Elaboratable): 14 | """Round-robin scheduler. 15 | 16 | For a given set of requests, the round-robin scheduler will 17 | grant one request. Once it grants a request, if any other 18 | requests are active, it grants the next active request with 19 | a greater number, restarting from zero once it reaches the 20 | highest one. 21 | 22 | Use :class:`EnableInserter` to control when the scheduler 23 | is updated. 24 | 25 | Parameters 26 | ---------- 27 | count : int 28 | Number of requests. 29 | 30 | Attributes 31 | ---------- 32 | requests : Signal(count), in 33 | Set of requests. 34 | grant : Signal(range(count)), out 35 | Number of the granted request. Does not change if there are no 36 | active requests. 37 | valid : Signal(), out 38 | Asserted if grant corresponds to an active request. Deasserted 39 | otherwise, i.e. if no requests are active. 40 | """ 41 | def __init__(self, *, count): 42 | if not isinstance(count, int) or count < 0: 43 | raise ValueError("Count must be a non-negative integer, not {!r}" 44 | .format(count)) 45 | self.count = count 46 | 47 | self.requests = Signal(count) 48 | self.grant = Signal(range(count)) 49 | self.valid = Signal() 50 | 51 | def elaborate(self, platform): 52 | m = Module() 53 | 54 | with m.Switch(self.grant): 55 | for i in range(self.count): 56 | with m.Case(i): 57 | for pred in reversed(range(i)): 58 | with m.If(self.requests[pred]): 59 | m.d.sync += self.grant.eq(pred) 60 | for succ in reversed(range(i + 1, self.count)): 61 | with m.If(self.requests[succ]): 62 | m.d.sync += self.grant.eq(succ) 63 | 64 | m.d.sync += self.valid.eq(self.requests.any()) 65 | 66 | return m 67 | 68 | 69 | class WaitTimer(Elaboratable): 70 | def __init__(self, t, domain="sync"): 71 | self._domain = domain 72 | self.t = t 73 | self.wait = Signal() 74 | self.done = Signal() 75 | 76 | def elaborate(self, platform): 77 | m = Module() 78 | 79 | t = int(self.t) 80 | count = Signal(bits_for(t), init=t) 81 | 82 | m.d.comb += self.done.eq(count == 0) 83 | with m.If(self.wait): 84 | with m.If(~self.done): 85 | m.d.sync += count.eq(count - 1) 86 | with m.Else(): 87 | m.d.sync += count.eq(count.reset) 88 | 89 | # Convert our sync domain to the domain requested by the user, if necessary. 90 | if self._domain != "sync": 91 | m = DomainRenamer({"sync": self._domain})(m) 92 | 93 | return m 94 | -------------------------------------------------------------------------------- /luna_soc/gateware/core/timer.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2020-2025 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | """ A simple timer peripheral. """ 8 | 9 | from amaranth import * 10 | from amaranth.lib import enum, wiring 11 | from amaranth.lib.wiring import In, Out, flipped, connect 12 | 13 | from amaranth_soc import csr, event 14 | 15 | 16 | class Peripheral(wiring.Component): 17 | class Enable(csr.Register, access="rw"): 18 | """Counter enable""" 19 | enable: csr.Field(csr.action.RW, unsigned(1)) 20 | 21 | class Mode(csr.Register, access="rw"): 22 | """Timer mode. When ``periodic`` is set to 1 the counter will automatically be reset to the reload value.""" 23 | periodic: csr.Field(csr.action.RW, unsigned(1)) 24 | 25 | class Reload(csr.Register, access="rw"): 26 | """Reload value of counter.""" 27 | def __init__(self, width): 28 | super().__init__({ 29 | "value": csr.Field(csr.action.RW, unsigned(width)) 30 | }) 31 | 32 | class Counter(csr.Register, access="r"): 33 | def __init__(self, width): 34 | """Counter value""" 35 | super().__init__({ 36 | "value": csr.Field(csr.action.R, unsigned(width)) 37 | }) 38 | 39 | 40 | def __init__(self, *, width): 41 | if not isinstance(width, int) or width < 0: 42 | raise ValueError("Counter width must be a non-negative integer, not {!r}" 43 | .format(width)) 44 | if width > 32: 45 | raise ValueError("Counter width cannot be greater than 32 (was: {})" 46 | .format(width)) 47 | self.width = width 48 | 49 | # registers 50 | regs = csr.Builder(addr_width=4, data_width=8) 51 | self._enable = regs.add("enable", self.Enable()) 52 | self._mode = regs.add("mode", self.Mode()) 53 | self._reload = regs.add("reload", self.Reload(width)) 54 | self._counter = regs.add("counter", self.Counter(width)) 55 | self._bridge = csr.Bridge(regs.as_memory_map()) 56 | 57 | # events 58 | from typing import Annotated 59 | EventSource = Annotated[event.Source, "Interrupt that occurs when the timer reaches zero."] 60 | self._zero = EventSource(trigger="rise", path=("zero",)) 61 | event_map = event.EventMap() 62 | event_map.add(self._zero) 63 | self._events = csr.event.EventMonitor(event_map, data_width=8) 64 | 65 | # csr decoder 66 | self._decoder = csr.Decoder(addr_width=5, data_width=8) 67 | self._decoder.add(self._bridge.bus) 68 | self._decoder.add(self._events.bus, name="ev") 69 | 70 | super().__init__({ 71 | "bus": Out(self._decoder.bus.signature), 72 | "irq": Out(unsigned(1)), 73 | }) 74 | self.bus.memory_map = self._decoder.bus.memory_map 75 | 76 | def elaborate(self, platform): 77 | m = Module() 78 | m.submodules += [self._bridge, self._events, self._decoder] 79 | 80 | # connect bus 81 | connect(m, flipped(self.bus), self._decoder.bus) 82 | 83 | # peripheral logic 84 | zero = Signal() 85 | finish = Signal() 86 | with m.If(self._enable.f.enable.data): 87 | with m.If(self._counter.f.value.r_data == 0): 88 | m.d.comb += zero.eq(~finish) 89 | with m.If(self._mode.f.periodic.data): 90 | m.d.sync += self._counter.f.value.r_data.eq(self._reload.f.value.data) 91 | with m.Else(): 92 | m.d.sync += finish.eq(1) 93 | with m.Else(): 94 | m.d.sync += self._counter.f.value.r_data.eq(self._counter.f.value.r_data - 1) 95 | with m.Else(): 96 | m.d.sync += self._counter.f.value.r_data.eq(self._reload.f.value.data) 97 | m.d.sync += finish.eq(0) 98 | 99 | # connect events to irq line 100 | m.d.comb += [ 101 | self._zero.i .eq(zero), 102 | self.irq .eq(self._events.src.i), 103 | ] 104 | 105 | return m 106 | -------------------------------------------------------------------------------- /luna_soc/gateware/core/uart.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2020-2025 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | from amaranth import * 8 | from amaranth.lib import wiring 9 | from amaranth.lib.wiring import In, Out, flipped, connect 10 | 11 | from amaranth_soc import csr 12 | from amaranth_stdio.serial import AsyncSerialRX, AsyncSerialTX 13 | 14 | 15 | __all__ = ["PinSignature", "Peripheral"] 16 | 17 | 18 | class PinSignature(wiring.Signature): 19 | """UART pin signature. 20 | 21 | Interface attributes 22 | -------------------- 23 | tx : :class:`Signal` 24 | Output. 25 | rx : :class:`Signal` 26 | Input. 27 | """ 28 | def __init__(self): 29 | super().__init__({ 30 | "tx": Out( 31 | wiring.Signature({ 32 | "o" : Out(unsigned(1)), 33 | "oe" : Out(unsigned(1)), 34 | }) 35 | ), 36 | "rx": In(unsigned(1)), 37 | }) 38 | 39 | 40 | class Peripheral(wiring.Component): 41 | class TxData(csr.Register, access="w"): 42 | """valid to write to when tx_rdy is high, will trigger a transmit""" 43 | data: csr.Field(csr.action.W, unsigned(8)) 44 | 45 | class RxData(csr.Register, access="r"): 46 | """valid to read from when rx_avail is high, last received byte""" 47 | data: csr.Field(csr.action.R, unsigned(8)) 48 | 49 | class TxReady(csr.Register, access="r"): 50 | """is '1' when 1-byte transmit buffer is empty""" 51 | txe: csr.Field(csr.action.R, unsigned(1)) 52 | 53 | class RxAvail(csr.Register, access="r"): 54 | """is '1' when 1-byte receive buffer is full; reset by a read from rx_data""" 55 | rxe: csr.Field(csr.action.R, unsigned(1)) 56 | 57 | class BaudRate(csr.Register, access="rw"): 58 | """baud rate divider, defaults to init""" 59 | def __init__(self, init): 60 | super().__init__({ 61 | "div": csr.Field(csr.action.RW, unsigned(24), init=init), 62 | }) 63 | 64 | 65 | """A minimal UART.""" 66 | def __init__(self, *, divisor): 67 | self._init_divisor = divisor 68 | 69 | regs = csr.Builder(addr_width=5, data_width=8) 70 | 71 | self._tx_data = regs.add("tx_data", self.TxData(), offset=0x00) 72 | self._rx_data = regs.add("rx_data", self.RxData(), offset=0x04) 73 | self._tx_ready = regs.add("tx_ready", self.TxReady(), offset=0x08) 74 | self._rx_avail = regs.add("rx_avail", self.RxAvail(), offset=0x0c) 75 | self._divisor = regs.add("divisor", self.BaudRate(init=self._init_divisor), offset=0x10) 76 | 77 | # bridge 78 | self._bridge = csr.Bridge(regs.as_memory_map()) 79 | 80 | super().__init__({ 81 | "bus": In(csr.Signature(addr_width=regs.addr_width, data_width=regs.data_width)), 82 | "pins": Out(PinSignature()), 83 | }) 84 | self.bus.memory_map = self._bridge.bus.memory_map 85 | 86 | 87 | def elaborate(self, platform): 88 | m = Module() 89 | m.submodules.bridge = self._bridge 90 | 91 | connect(m, flipped(self.bus), self._bridge.bus) 92 | 93 | m.submodules.tx = tx = AsyncSerialTX(divisor=self._init_divisor, divisor_bits=24) 94 | m.d.comb += [ 95 | self.pins.tx.oe.eq(~self._tx_ready.f.txe.r_data), 96 | self.pins.tx.o.eq(tx.o), 97 | tx.data.eq(self._tx_data.f.data.w_data), 98 | tx.ack.eq(self._tx_data.f.data.w_stb), 99 | self._tx_ready.f.txe.r_data.eq(tx.rdy), 100 | tx.divisor.eq(self._divisor.f.div.data) 101 | ] 102 | 103 | rx_buf = Signal(unsigned(8)) 104 | rx_avail = Signal() 105 | 106 | m.submodules.rx = rx = AsyncSerialRX(divisor=self._init_divisor, divisor_bits=24) 107 | 108 | with m.If(self._rx_data.f.data.r_stb): 109 | m.d.sync += rx_avail.eq(0) 110 | 111 | with m.If(rx.rdy): 112 | m.d.sync += [ 113 | rx_buf.eq(rx.data), 114 | rx_avail.eq(1) 115 | ] 116 | 117 | m.d.comb += [ 118 | rx.i.eq(self.pins.rx), 119 | rx.ack.eq(~rx_avail), 120 | rx.divisor.eq(self._divisor.f.div.data), 121 | self._rx_data.f.data.r_data.eq(rx_buf), 122 | self._rx_avail.f.rxe.r_data.eq(rx_avail) 123 | ] 124 | 125 | return m 126 | -------------------------------------------------------------------------------- /luna_soc/gateware/core/usb2/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2023-2024 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | from . import device 8 | from . import ep_control 9 | from . import ep_in 10 | from . import ep_out 11 | 12 | from . import ulpi 13 | -------------------------------------------------------------------------------- /luna_soc/gateware/core/usb2/device.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2020-2024 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | """ 8 | Contains the organizing gateware used to add USB Device functionality 9 | to your own designs; including the core :class:`USBDevice` class. 10 | """ 11 | 12 | from typing import Annotated 13 | 14 | from amaranth import * 15 | from amaranth.lib import wiring 16 | from amaranth.lib.wiring import In, Out, connect, flipped 17 | 18 | from amaranth_soc import csr, event 19 | 20 | from luna.gateware.usb.usb2.device import USBDevice 21 | 22 | 23 | class Peripheral(wiring.Component): 24 | """ SoC controller for a USBDevice. 25 | 26 | Breaks our USBDevice control and status signals out into registers so a CPU / Wishbone master 27 | can control our USB device. 28 | 29 | The attributes below are intended to connect to a USBDevice. Typically, they'd be created by 30 | using the .controller() method on a USBDevice object, which will automatically connect all 31 | relevant signals. 32 | 33 | Attributes 34 | ---------- 35 | 36 | connect: Signal(), output 37 | High when the USBDevice should be allowed to connect to a host. 38 | 39 | """ 40 | 41 | class Control(csr.Register, access="rw"): 42 | """Control register 43 | 44 | connect: Set this bit to '1' to allow the associated USB device to connect to a host. 45 | low_speed_only: Set this bit to '1' to force the device to operate at low speed. 46 | full_speed_only: Set this bit to '1' to force the device to operate at full speed. 47 | """ 48 | connect : csr.Field(csr.action.RW, unsigned(1)) 49 | _0 : csr.Field(csr.action.ResRAW0, unsigned(7)) 50 | low_speed_only : csr.Field(csr.action.RW, unsigned(1)) 51 | full_speed_only : csr.Field(csr.action.RW, unsigned(1)) 52 | _1 : csr.Field(csr.action.ResRAW0, unsigned(6)) 53 | 54 | class Status(csr.Register, access="r"): 55 | """Status register 56 | 57 | speed: Indicates the current speed of the USB device. 0 indicates High; 1 => Full, 58 | 2 => Low, and 3 => SuperSpeed (incl SuperSpeed+). 59 | """ 60 | speed : csr.Field(csr.action.R, unsigned(2)) 61 | _0 : csr.Field(csr.action.ResRAW0, unsigned(6)) 62 | 63 | def __init__(self): 64 | # I/O ports FIXME ambiguity - private or signature ? 65 | self.connect = Signal(init=1) 66 | self.bus_reset = Signal() 67 | self.low_speed_only = Signal() 68 | self.full_speed_only = Signal() 69 | 70 | # registers 71 | regs = csr.Builder(addr_width=3, data_width=8) 72 | self._control = regs.add("control", self.Control()) 73 | self._status = regs.add("status", self.Status()) 74 | self._bridge = csr.Bridge(regs.as_memory_map()) 75 | 76 | # events 77 | EventSource = Annotated[event.Source, "Interrupt that occurs when a USB bus reset is received."] 78 | self._reset = EventSource(trigger="rise", path=("reset",)) 79 | event_map = event.EventMap() 80 | event_map.add(self._reset) 81 | self._events = csr.event.EventMonitor(event_map, data_width=8) 82 | 83 | # csr decoder 84 | self._decoder = csr.Decoder(addr_width=4, data_width=8) 85 | self._decoder.add(self._bridge.bus) 86 | self._decoder.add(self._events.bus, name="ev") 87 | 88 | super().__init__({ 89 | "bus": Out(self._decoder.bus.signature), 90 | "irq": Out(unsigned(1)), 91 | }) 92 | self.bus.memory_map = self._decoder.bus.memory_map 93 | 94 | 95 | def attach(self, device: USBDevice): 96 | """ Returns a list of statements necessary to connect this to a USB controller. 97 | 98 | The returned values makes all of the connections necessary to provide control and fetch status 99 | from the relevant USB device. These can be made either combinationally or synchronously, but 100 | combinational is recommended; as these signals are typically fed from a register anyway. 101 | 102 | Parameters 103 | ---------- 104 | device: USBDevice 105 | The :class:`USBDevice` object to be controlled. 106 | """ 107 | return [ 108 | device.connect .eq(self.connect), 109 | device.low_speed_only .eq(self.low_speed_only), 110 | device.full_speed_only .eq(self.full_speed_only), 111 | self.bus_reset .eq(device.reset_detected), 112 | self._status.f.speed.r_data .eq(device.speed) 113 | ] 114 | 115 | 116 | def elaborate(self, platform): 117 | m = Module() 118 | m.submodules += [self._bridge, self._events, self._decoder] 119 | 120 | # connect bus 121 | connect(m, flipped(self.bus), self._decoder.bus) 122 | 123 | # Core connection register. 124 | m.d.comb += self.connect.eq(self._control.f.connect.data) 125 | 126 | # Speed configuration registers. 127 | m.d.comb += self.low_speed_only.eq(self._control.f.low_speed_only.data) 128 | 129 | m.d.comb += self.full_speed_only.eq(self._control.f.full_speed_only.data) 130 | 131 | # event: bus reset detected 132 | m.d.comb += self._reset.i.eq(self.bus_reset) 133 | 134 | # connect events to irq line 135 | m.d.comb += self.irq.eq(self._events.src.i) 136 | 137 | return m 138 | -------------------------------------------------------------------------------- /luna_soc/gateware/core/usb2/ep_control.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2020-2025 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | """ Implementation of a Triple-FIFO endpoint manager. 8 | 9 | Equivalent (but not binary-compatbile) implementation of ValentyUSB's ``eptri``. 10 | 11 | For an example, see ``examples/usb/eptri`` or TinyUSB's ``luna/dcd_eptri.c``. 12 | """ 13 | 14 | from typing import Annotated 15 | 16 | from amaranth import * 17 | from amaranth.hdl.xfrm import ResetInserter, DomainRenamer 18 | from amaranth.lib import wiring 19 | from amaranth.lib.fifo import SyncFIFOBuffered 20 | from amaranth.lib.wiring import In, Out, connect, flipped 21 | 22 | from amaranth_soc import csr, event 23 | 24 | from luna.gateware.usb.usb2.endpoint import EndpointInterface 25 | 26 | 27 | class Peripheral(wiring.Component): 28 | """ Setup component of our `eptri`-equivalent interface. 29 | 30 | Implements the USB Setup FIFO, which handles SETUP packets on any endpoint. 31 | 32 | This interface is similar to an :class:`OutFIFOInterface`, but always ACKs packets, 33 | and does not allow for any flow control; as a USB device must always be ready to accept 34 | control packets. [USB2.0: 8.6.1] 35 | 36 | Attributes 37 | ---------- 38 | 39 | interface: EndpointInterface 40 | Our primary interface to the core USB device hardware. 41 | """ 42 | 43 | class Control(csr.Register, access="w"): 44 | """ Control register 45 | 46 | address: Controls the current device's USB address. Should be written after a SET_ADDRESS 47 | request is received. Automatically resets back to zero on a USB reset. 48 | """ 49 | address : csr.Field(csr.action.W, unsigned(8)) 50 | 51 | class Status(csr.Register, access="r"): 52 | """ Status register 53 | 54 | address: Holds the current device's active USB address. 55 | epno: The endpoint number associated with the most recently captured SETUP packet. 56 | have: `1` iff data is available in the FIFO. 57 | """ 58 | address : csr.Field(csr.action.R, unsigned(8)) 59 | epno : csr.Field(csr.action.R, unsigned(4)) 60 | have : csr.Field(csr.action.R, unsigned(1)) 61 | _0 : csr.Field(csr.action.ResRAW0, unsigned(3)) 62 | 63 | class Reset(csr.Register, access="w"): 64 | """ Reset register 65 | 66 | fifo: Local reset control for the SETUP handler; writing a '1' to this register clears 67 | the handler state. 68 | """ 69 | fifo : csr.Field(csr.action.W, unsigned(1)) 70 | _0 : csr.Field(csr.action.ResRAW0, unsigned(7)) 71 | 72 | class Data(csr.Register, access="r"): 73 | """ Data register 74 | 75 | A FIFO that returns the bytes from the most recently captured SETUP packet. 76 | Reading a byte from this register advances the FIFO. The first eight bytes read 77 | from this contain the core SETUP packet. 78 | """ 79 | byte : csr.Field(csr.action.R, unsigned(8)) 80 | 81 | 82 | def __init__(self): 83 | # I/O port FIXME ambiguity - private or signature ? 84 | self.interface = EndpointInterface() 85 | 86 | # registers 87 | regs = csr.Builder(addr_width=4, data_width=8) 88 | self._control = regs.add("control", self.Control()) 89 | self._status = regs.add("status", self.Status()) 90 | self._reset = regs.add("reset", self.Reset()) 91 | self._data = regs.add("data", self.Data()) 92 | self._bridge = csr.Bridge(regs.as_memory_map()) 93 | 94 | # events 95 | from typing import Annotated 96 | EventSource = Annotated[event.Source, "Interrupt that triggers when a new SETUP packet is ready to be read."] 97 | self._setup_received = EventSource(trigger="rise", path=("setup_received",)) 98 | event_map = event.EventMap() 99 | event_map.add(self._setup_received) 100 | self._events = csr.event.EventMonitor(event_map, data_width=8) 101 | 102 | # csr decoder 103 | self._decoder = csr.Decoder(addr_width=5, data_width=8) 104 | self._decoder.add(self._bridge.bus) 105 | self._decoder.add(self._events.bus, name="ev") 106 | 107 | super().__init__({ 108 | "bus": Out(self._decoder.bus.signature), 109 | "irq": Out(unsigned(1)), 110 | }) 111 | self.bus.memory_map = self._decoder.bus.memory_map 112 | 113 | 114 | def elaborate(self, platform): 115 | m = Module() 116 | m.submodules += [self._bridge, self._events, self._decoder] 117 | 118 | # connect bus 119 | connect(m, flipped(self.bus), self._decoder.bus) 120 | 121 | # Shortcuts to our components. 122 | interface = self.interface 123 | token = self.interface.tokenizer 124 | rx = self.interface.rx 125 | handshakes_out = self.interface.handshakes_out 126 | 127 | # Logic condition for getting a new setup packet. 128 | new_setup = token.new_token & token.is_setup 129 | reset_requested = self._reset.f.fifo.w_stb & self._reset.f.fifo.w_data 130 | clear_fifo = new_setup | reset_requested 131 | 132 | # 133 | # Core FIFO. 134 | # 135 | m.submodules.fifo = fifo = ResetInserter(clear_fifo)(SyncFIFOBuffered(width=8, depth=8)) 136 | 137 | m.d.comb += [ 138 | 139 | # We'll write to the active FIFO whenever the last received token is a SETUP 140 | # token, and we have incoming data; and we'll always write the data received 141 | fifo.w_en .eq(token.is_setup & rx.valid & rx.next), 142 | fifo.w_data .eq(rx.payload), 143 | 144 | # We'll advance the FIFO whenever our CPU reads from the data CSR; 145 | # and we'll always read our data from the FIFO. 146 | fifo.r_en .eq(self._data.f.byte.r_stb), 147 | self._data.f.byte.r_data .eq(fifo.r_data), 148 | 149 | # Pass the FIFO status on to our CPU. 150 | self._status.f.have.r_data .eq(fifo.r_rdy), 151 | 152 | # Always acknowledge SETUP packets as they arrive. 153 | handshakes_out.ack .eq(token.is_setup & interface.rx_ready_for_response), 154 | 155 | # Trigger a SETUP event as we ACK the setup packet, since that's also the point 156 | # where we know we're done receiving data. 157 | self._setup_received.i .eq(handshakes_out.ack) 158 | ] 159 | 160 | # control registers 161 | with m.If(self._control.f.address.w_stb): 162 | m.d.comb += [ 163 | interface.address_changed .eq(1), 164 | interface.new_address .eq(self._control.f.address.w_data), 165 | ] 166 | 167 | # status registers 168 | m.d.comb += self._status.f.address.r_data.eq(interface.active_address) 169 | with m.If(token.new_token & token.is_setup): 170 | m.d.usb += self._status.f.epno.r_data.eq(token.endpoint) 171 | 172 | # connect events to irq line 173 | m.d.comb += self.irq.eq(self._events.src.i) 174 | 175 | return DomainRenamer({"sync": "usb"})(m) 176 | -------------------------------------------------------------------------------- /luna_soc/gateware/core/usb2/ulpi.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2020-2025 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | """ ULPI interfacing hardware. """ 8 | 9 | from amaranth import * 10 | from amaranth.lib import wiring 11 | from amaranth.lib.wiring import In, Out 12 | 13 | class Signature(wiring.Signature): 14 | """ULPI bus signature. 15 | 16 | Interface attributes 17 | -------------------- 18 | i : :class:`Signal` 19 | Input value of the ULPI data lines. 20 | o : :class:`Signal` 21 | Output value of the ULPI data lines. 22 | oe : :class:`Signal` 23 | True iff we're trying to drive the ULPI data lines. 24 | clk : :class:`Signal` 25 | The ULPI clock signal. 26 | nxt : :class:`Signal` 27 | The ULPI 'next' throttle signal. 28 | stp : :class:`Signal` 29 | The ULPI 'stop' event signal. 30 | dir : :class:`Signal` 31 | The ULPI bus-direction signal. 32 | rst : :class:`Signal` 33 | The ULPI 'reset' signal. 34 | """ 35 | 36 | def __init__(self): 37 | super().__init__({ 38 | "data" : Out( 39 | wiring.Signature({ 40 | "i" : In (unsigned(8)), 41 | "o" : Out (unsigned(8)), 42 | "oe" : Out (unsigned(1)), 43 | }) 44 | ), 45 | # "clk" : Out (unsigned(1)), 46 | # "nxt" : In (unsigned(1)), 47 | # "stp" : Out (unsigned(1)), 48 | # "dir" : In (unsigned(1)), 49 | # "rst" : Out (unsigned(1)), 50 | 51 | # FIXME these are nested with i/o because luna is expecting Pins not the Signals that wiring provides 52 | "clk" : Out (wiring.Signature({ "o" : Out (unsigned(1)) })), 53 | "nxt" : Out (wiring.Signature({ "i" : In (unsigned(1)) })), 54 | "stp" : Out (wiring.Signature({ "o" : Out (unsigned(1)) })), 55 | "dir" : Out (wiring.Signature({ "i" : In (unsigned(1)) })), 56 | "rst" : Out (wiring.Signature({ "o" : Out (unsigned(1)) })), 57 | }) 58 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2020-2025 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | from .ic import * 8 | try: 9 | from .minerva import * 10 | except Exception as e: 11 | import logging 12 | logging.warning(e) 13 | from .vexriscv import * 14 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/ic.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2020-2025 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | """ The simplest interrupt controller. """ 8 | 9 | from amaranth import * 10 | from amaranth.lib import wiring 11 | from amaranth.lib.wiring import In, Out 12 | 13 | # type aliases only in py 3.12 :-( 14 | # type InterruptMap = dict[int, (str, wiring.Component)] 15 | InterruptMap = dict[int, (str, wiring.Component)] 16 | 17 | class InterruptController(wiring.Component): 18 | def __init__(self, *, width): 19 | super().__init__({ 20 | "pending": Out(unsigned(width)), 21 | }) 22 | 23 | self._interrupts: InterruptMap = dict() 24 | 25 | def interrupts(self) -> InterruptMap: 26 | return self._interrupts 27 | 28 | def add(self, peripheral, *, name, number=None): 29 | if number is None: 30 | raise ValueError("You need to supply a value for the IRQ number.") 31 | if number in self._interrupts.keys(): 32 | raise ValueError(f"IRQ number '{number}' has already been used.") 33 | if name in dict(self._interrupts.values()).keys(): 34 | raise ValueError(f"Peripheral name '{name}' has already been used.") 35 | if peripheral in dict(self._interrupts.values()).values(): 36 | raise ValueError(f"Peripheral '{name}' has already been added: {peripheral}") 37 | self._interrupts[number] = (name, peripheral) 38 | 39 | def elaborate(self, platform): 40 | m = Module() 41 | 42 | for number, (name, peripheral) in self._interrupts.items(): 43 | m.d.comb += self.pending[number].eq(peripheral.irq) 44 | 45 | return m 46 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/minerva.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2023-2025 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | from amaranth import * 8 | from amaranth.lib.wiring import Component, In, Out, connect, flipped 9 | 10 | try: 11 | from minerva.core import Minerva as MinervaCore 12 | except: 13 | raise ImportError("To use Minerva with luna-soc you need to install it: pip install git+https://github.com/minerva-cpu/minerva") 14 | 15 | from amaranth_soc import wishbone 16 | from amaranth_soc.periph import ConstantMap 17 | 18 | __all__ = ["Minerva"] 19 | 20 | # - Minerva ------------------------------------------------------------------- 21 | 22 | class Minerva(Component): 23 | name = "minerva" 24 | arch = "riscv" 25 | byteorder = "little" 26 | data_width = 32 27 | 28 | def __init__(self, **kwargs): 29 | super().__init__({ 30 | "ext_reset": In(unsigned(1)), 31 | "irq_external": In(unsigned(32)), 32 | "ibus": Out(wishbone.Signature( 33 | addr_width=30, 34 | data_width=32, 35 | granularity=8, 36 | features=("err", "cti", "bte") 37 | )), 38 | "dbus": Out(wishbone.Signature( 39 | addr_width=30, 40 | data_width=32, 41 | granularity=8, 42 | features=("err", "cti", "bte") 43 | )), 44 | }) 45 | 46 | self._cpu = MinervaCore(**kwargs) 47 | 48 | @property 49 | def reset_addr(self): 50 | return self._cpu._reset_address 51 | 52 | @property 53 | def muldiv(self): 54 | return "hard" if self._cpu.with_muldiv else "soft" 55 | 56 | def elaborate(self, platform): 57 | m = Module() 58 | 59 | m.submodules.minerva = self._cpu 60 | connect(m, self._cpu.ibus, flipped(self.ibus)) 61 | connect(m, self._cpu.dbus, flipped(self.dbus)) 62 | m.d.comb += [ 63 | self._cpu.external_interrupt.eq(self.irq_external), 64 | ] 65 | 66 | return m 67 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/verilog/Makefile: -------------------------------------------------------------------------------- 1 | all: cynthion cynthion+jtag imc imac+dcache 2 | 3 | cynthion: 4 | cd vexriscv/scala && sbt compile "runMain lunasoc.GenCoreCynthion" 5 | mv vexriscv/scala/vexriscv_cynthion.* vexriscv/ 6 | 7 | cynthion+jtag: 8 | cd vexriscv/scala && sbt compile "runMain lunasoc.GenCoreCynthionJtag" 9 | mv vexriscv/scala/vexriscv_cynthion+jtag.* vexriscv/ 10 | 11 | imc: 12 | cd vexriscv/scala && sbt compile "runMain lunasoc.GenCoreImc" 13 | mv vexriscv/scala/vexriscv_imc.* vexriscv/ 14 | 15 | imac+dcache: 16 | cd vexriscv/scala && sbt compile "runMain lunasoc.GenCoreImacDcache" 17 | mv vexriscv/scala/vexriscv_imac+dcache.* vexriscv/ 18 | 19 | clean: 20 | cd vexriscv/scala && sbt clean reload 21 | rm -rf vexriscv/target/ vexriscv/scala/target/ vexriscv/scala/project/target/ 22 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/verilog/README.md: -------------------------------------------------------------------------------- 1 | ## Dependencies 2 | 3 | The VexRiscv core is implemented using SpinalHDL which requires the 4 | `sbt` build tool for Scala projects to be installed. 5 | 6 | On macOS: 7 | 8 | brew install sbt 9 | 10 | 11 | ## Rebuild cores 12 | 13 | You can rebuild the verilog for all cores using: 14 | 15 | make all 16 | 17 | 18 | ## JTAG support 19 | 20 | There is a [documented issue](https://github.com/SpinalHDL/VexRiscv/issues/381) with 21 | the verilog synthesis of Vexriscv JTAG support. 22 | 23 | The `tap_fsm_state` register is not assigned a reset state resulting 24 | in the TDO line being kept in bypass mode. 25 | 26 | Until this can be tracked down you'll need to manually edit the 27 | `vexriscv_cynthion+jtag.v` after a rebuild as follows: 28 | 29 | reg [1:0] logic_jtagLogic_dmiStat_value_aheadValue; 30 | wire [3:0] tap_fsm_stateNext; 31 | reg [3:0] tap_fsm_state = 0; <-------------- 32 | wire [3:0] _zz_tap_fsm_stateNext; 33 | wire [3:0] _zz_tap_fsm_stateNext_1; 34 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/verilog/vexriscv/scala/build.sbt: -------------------------------------------------------------------------------- 1 | val spinalVersion = "1.6.0" 2 | 3 | lazy val vexRiscv = RootProject(uri("https://github.com/SpinalHDL/VexRiscv.git#master")) 4 | 5 | lazy val root = (project in file(".")). 6 | settings( 7 | inThisBuild(List( 8 | organization := "com.github.spinalhdl", 9 | scalaVersion := "2.11.12", 10 | version := "0.1.0-SNAPSHOT" 11 | )), 12 | name := "VexRiscvOnWishbone", 13 | libraryDependencies ++= Seq( 14 | compilerPlugin("com.github.spinalhdl" % "spinalhdl-idsl-plugin_2.11" % spinalVersion) 15 | ), 16 | scalacOptions += s"-Xplugin-require:idsl-plugin" 17 | ).dependsOn(vexRiscv) 18 | 19 | fork := true 20 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/verilog/vexriscv/scala/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.8.2 2 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/verilog/vexriscv/scala/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/verilog/vexriscv/scala/src/main/scala/Config.scala: -------------------------------------------------------------------------------- 1 | package lunasoc 2 | 3 | import spinal.core._ 4 | import spinal.core.internals.{ 5 | ExpressionContainer, 6 | PhaseAllocateNames, 7 | PhaseContext 8 | } 9 | 10 | object LunaSpinalConfig 11 | extends spinal.core.SpinalConfig( 12 | defaultConfigForClockDomains = ClockDomainConfig( 13 | resetKind = spinal.core.SYNC 14 | ) 15 | ) { 16 | // disable these to let the toolchain infer block memories 17 | /*phasesInserters += { (array) => 18 | array.insert( 19 | array.indexWhere(_.isInstanceOf[PhaseAllocateNames]) + 1, 20 | new ForceRamBlockPhase 21 | ) 22 | } 23 | phasesInserters += { (array) => 24 | array.insert( 25 | array.indexWhere(_.isInstanceOf[PhaseAllocateNames]) + 1, 26 | new NoRwCheckPhase 27 | ) 28 | }*/ 29 | } 30 | 31 | class ForceRamBlockPhase() extends spinal.core.internals.Phase { 32 | override def impl(pc: PhaseContext): Unit = { 33 | pc.walkBaseNodes { 34 | case mem: Mem[_] => { 35 | var asyncRead = false 36 | mem.dlcForeach[MemPortStatement] { 37 | case _: MemReadAsync => asyncRead = true 38 | case _ => 39 | } 40 | if (!asyncRead) mem.addAttribute("ram_style", "block") 41 | } 42 | case _ => 43 | } 44 | } 45 | override def hasNetlistImpact: Boolean = false 46 | } 47 | 48 | class NoRwCheckPhase() extends spinal.core.internals.Phase { 49 | override def impl(pc: PhaseContext): Unit = { 50 | pc.walkBaseNodes { 51 | case mem: Mem[_] => { 52 | var doit = false 53 | mem.dlcForeach[MemPortStatement] { 54 | case _: MemReadSync => doit = true 55 | case _ => 56 | } 57 | mem.dlcForeach[MemPortStatement] { 58 | case p: MemReadSync if p.readUnderWrite != dontCare => doit = false 59 | case _ => 60 | } 61 | if (doit) mem.addAttribute("no_rw_check") 62 | } 63 | case _ => 64 | } 65 | } 66 | override def hasNetlistImpact: Boolean = false 67 | } 68 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/verilog/vexriscv/scala/src/main/scala/GenCoreCynthion.scala: -------------------------------------------------------------------------------- 1 | package lunasoc 2 | 3 | import vexriscv.{VexRiscv, VexRiscvConfig, plugin} 4 | import vexriscv.ip.{DataCacheConfig, InstructionCacheConfig} 5 | import vexriscv.plugin._ 6 | 7 | import spinal.core._ 8 | import spinal.core.SpinalConfig 9 | import spinal.core.internals.{ 10 | ExpressionContainer, 11 | PhaseAllocateNames, 12 | PhaseContext 13 | } 14 | import spinal.lib._ 15 | import spinal.lib.sim.Phase 16 | 17 | import scala.collection.mutable.ArrayBuffer 18 | 19 | object GenCoreCynthion { 20 | def main(args: Array[String]) { 21 | val outputFile = "vexriscv_cynthion" 22 | val spinalConfig = 23 | LunaSpinalConfig.copy(netlistFileName = outputFile + ".v") 24 | 25 | spinalConfig.generateVerilog { 26 | // configure plugins 27 | val plugins = ArrayBuffer[Plugin[VexRiscv]]() 28 | plugins ++= List( 29 | new IBusCachedPlugin( 30 | resetVector = null, 31 | relaxedPcCalculation = false, 32 | prediction = STATIC, 33 | compressedGen = true, // compressed instruction support 34 | memoryTranslatorPortConfig = null, 35 | config = InstructionCacheConfig( 36 | cacheSize = 2048, 37 | bytePerLine = 32, 38 | wayCount = 1, 39 | addressWidth = 32, 40 | cpuDataWidth = 32, 41 | memDataWidth = 32, 42 | catchIllegalAccess = true, 43 | catchAccessFault = true, 44 | asyncTagMemory = false, 45 | twoCycleRam = false, 46 | twoCycleCache = false // !compressedGen 47 | ) 48 | ), 49 | new DBusCachedPlugin( 50 | dBusCmdMasterPipe = true, 51 | dBusCmdSlavePipe = true, 52 | dBusRspSlavePipe = false, 53 | relaxedMemoryTranslationRegister = false, 54 | config = new DataCacheConfig( 55 | cacheSize = 4096, 56 | bytePerLine = 32, 57 | wayCount = 1, 58 | addressWidth = 32, 59 | cpuDataWidth = 32, 60 | memDataWidth = 32, 61 | catchAccessError = true, 62 | catchIllegal = true, 63 | catchUnaligned = true, 64 | withLrSc = true, // load-reserved/store-conditional instructions (LB, LH, LW, SB, SH, SW etc.) 65 | withAmo = true, // atomic memory operation instructions (AMOSWAP, AMOADD, AMOAND etc.) 66 | earlyWaysHits = true 67 | ), 68 | memoryTranslatorPortConfig = null, 69 | csrInfo = true 70 | ), 71 | new StaticMemoryTranslatorPlugin( 72 | ioRange = _.msb // 0x8000_0000 -> 0xffff_ffff will not be cached 73 | ), 74 | new DecoderSimplePlugin( 75 | catchIllegalInstruction = true 76 | ), 77 | new RegFilePlugin( 78 | regFileReadyKind = plugin.SYNC, 79 | zeroBoot = false 80 | ), 81 | new IntAluPlugin, 82 | new SrcPlugin( 83 | separatedAddSub = false, 84 | executeInsertion = true 85 | ), 86 | new FullBarrelShifterPlugin, 87 | new HazardSimplePlugin( 88 | bypassExecute = true, 89 | bypassMemory = true, 90 | bypassWriteBack = true, 91 | bypassWriteBackBuffer = true, 92 | pessimisticUseSrc = false, 93 | pessimisticWriteRegFile = false, 94 | pessimisticAddressMatch = false 95 | ), 96 | new BranchPlugin( 97 | earlyBranch = false, 98 | catchAddressMisaligned = true 99 | ), 100 | new CsrPlugin( 101 | CsrPluginConfig.all(mtvecInit = null).copy(ebreakGen = true, xtvecModeGen = false) 102 | ), 103 | new YamlPlugin(outputFile + ".yaml"), 104 | new MulPlugin, 105 | new DivPlugin, 106 | new ExternalInterruptArrayPlugin( 107 | machineMaskCsrId = 0xbc0, 108 | machinePendingsCsrId = 0xfc0, 109 | supervisorMaskCsrId = 0x9c0, 110 | supervisorPendingsCsrId = 0xdc0 111 | ) 112 | ) 113 | 114 | // instantiate core 115 | val cpu = new VexRiscv(VexRiscvConfig(plugins.toList)) 116 | 117 | // modify CPU to use wishbone bus 118 | cpu.rework { 119 | for (plugin <- cpu.config.plugins) plugin match { 120 | case plugin: IBusSimplePlugin => { 121 | plugin.iBus.setAsDirectionLess() // clear iBus IO properties 122 | master(plugin.iBus.toWishbone()).setName("iBusWishbone") 123 | } 124 | case plugin: IBusCachedPlugin => { 125 | plugin.iBus.setAsDirectionLess() 126 | master(plugin.iBus.toWishbone()).setName("iBusWishbone") 127 | } 128 | case plugin: DBusSimplePlugin => { 129 | plugin.dBus.setAsDirectionLess() 130 | master(plugin.dBus.toWishbone()).setName("dBusWishbone") 131 | } 132 | case plugin: DBusCachedPlugin => { 133 | plugin.dBus.setAsDirectionLess() 134 | master(plugin.dBus.toWishbone()).setName("dBusWishbone") 135 | } 136 | case _ => 137 | } 138 | } 139 | cpu 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/verilog/vexriscv/scala/src/main/scala/GenCoreCynthionJtag.scala: -------------------------------------------------------------------------------- 1 | package lunasoc 2 | 3 | import vexriscv.{VexRiscv, VexRiscvConfig, plugin} 4 | import vexriscv.ip.{DataCacheConfig, InstructionCacheConfig} 5 | import vexriscv.plugin._ 6 | 7 | import spinal.core._ 8 | import spinal.core.SpinalConfig 9 | import spinal.core.internals.{ 10 | ExpressionContainer, 11 | PhaseAllocateNames, 12 | PhaseContext 13 | } 14 | import spinal.lib._ 15 | import spinal.lib.cpu.riscv.debug.DebugTransportModuleParameter 16 | import spinal.lib.sim.Phase 17 | 18 | import scala.collection.mutable.ArrayBuffer 19 | 20 | object GenCoreCynthionJtag { 21 | def main(args: Array[String]) { 22 | val outputFile = "vexriscv_cynthion+jtag" 23 | val spinalConfig = 24 | LunaSpinalConfig.copy(netlistFileName = outputFile + ".v") 25 | 26 | spinalConfig.generateVerilog { 27 | // configure plugins 28 | val plugins = ArrayBuffer[Plugin[VexRiscv]]() 29 | plugins ++= List( 30 | new IBusCachedPlugin( 31 | resetVector = null, 32 | relaxedPcCalculation = false, 33 | prediction = STATIC, 34 | compressedGen = true, // compressed instruction support 35 | memoryTranslatorPortConfig = null, 36 | config = InstructionCacheConfig( 37 | cacheSize = 2048, 38 | bytePerLine = 32, 39 | wayCount = 1, 40 | addressWidth = 32, 41 | cpuDataWidth = 32, 42 | memDataWidth = 32, 43 | catchIllegalAccess = true, 44 | catchAccessFault = true, 45 | asyncTagMemory = false, 46 | twoCycleRam = false, 47 | twoCycleCache = false // !compressedGen 48 | ) 49 | ), 50 | new DBusCachedPlugin( 51 | dBusCmdMasterPipe = true, 52 | dBusCmdSlavePipe = true, 53 | dBusRspSlavePipe = false, 54 | relaxedMemoryTranslationRegister = false, 55 | config = new DataCacheConfig( 56 | cacheSize = 4096, 57 | bytePerLine = 32, 58 | wayCount = 1, 59 | addressWidth = 32, 60 | cpuDataWidth = 32, 61 | memDataWidth = 32, 62 | catchAccessError = true, 63 | catchIllegal = true, 64 | catchUnaligned = true, 65 | withLrSc = true, // load-reserved/store-conditional instructions (LB, LH, LW, SB, SH, SW etc.) 66 | withAmo = true, // atomic memory operation instructions (AMOSWAP, AMOADD, AMOAND etc.) 67 | earlyWaysHits = true 68 | ), 69 | memoryTranslatorPortConfig = null, 70 | csrInfo = true 71 | ), 72 | new StaticMemoryTranslatorPlugin( 73 | ioRange = _.msb // 0x8000_0000 -> 0xffff_ffff will not be cached 74 | ), 75 | new DecoderSimplePlugin( 76 | catchIllegalInstruction = true 77 | ), 78 | new RegFilePlugin( 79 | regFileReadyKind = plugin.SYNC, 80 | zeroBoot = false 81 | ), 82 | new IntAluPlugin, 83 | new SrcPlugin( 84 | separatedAddSub = false, 85 | executeInsertion = true 86 | ), 87 | new FullBarrelShifterPlugin, 88 | new HazardSimplePlugin( 89 | bypassExecute = true, 90 | bypassMemory = true, 91 | bypassWriteBack = true, 92 | bypassWriteBackBuffer = true, 93 | pessimisticUseSrc = false, 94 | pessimisticWriteRegFile = false, 95 | pessimisticAddressMatch = false 96 | ), 97 | new BranchPlugin( 98 | earlyBranch = false, 99 | catchAddressMisaligned = true 100 | ), 101 | new CsrPlugin( 102 | CsrPluginConfig.all(mtvecInit = null).copy(ebreakGen = true, xtvecModeGen = false, withPrivilegedDebug = true) 103 | ), 104 | new YamlPlugin(outputFile + ".yaml"), 105 | new MulPlugin, 106 | new DivPlugin, 107 | new ExternalInterruptArrayPlugin( 108 | machineMaskCsrId = 0xbc0, 109 | machinePendingsCsrId = 0xfc0, 110 | supervisorMaskCsrId = 0x9c0, 111 | supervisorPendingsCsrId = 0xdc0 112 | ), 113 | new EmbeddedRiscvJtag( 114 | p = DebugTransportModuleParameter( 115 | addressWidth = 7, 116 | version = 1, 117 | idle = 7 118 | ), 119 | debugCd = ClockDomain.current.copy(reset = Bool().setName("debugReset")), 120 | withTunneling = false, 121 | withTap = true 122 | ) 123 | ) 124 | 125 | // instantiate core 126 | val cpu = new VexRiscv(VexRiscvConfig(plugins.toList)) 127 | 128 | // modify CPU to use wishbone bus 129 | cpu.rework { 130 | for (plugin <- cpu.config.plugins) plugin match { 131 | case plugin: IBusSimplePlugin => { 132 | plugin.iBus.setAsDirectionLess() // clear iBus IO properties 133 | master(plugin.iBus.toWishbone()).setName("iBusWishbone") 134 | } 135 | case plugin: IBusCachedPlugin => { 136 | plugin.iBus.setAsDirectionLess() 137 | master(plugin.iBus.toWishbone()).setName("iBusWishbone") 138 | } 139 | case plugin: DBusSimplePlugin => { 140 | plugin.dBus.setAsDirectionLess() 141 | master(plugin.dBus.toWishbone()).setName("dBusWishbone") 142 | } 143 | case plugin: DBusCachedPlugin => { 144 | plugin.dBus.setAsDirectionLess() 145 | master(plugin.dBus.toWishbone()).setName("dBusWishbone") 146 | } 147 | case _ => 148 | } 149 | } 150 | cpu 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/verilog/vexriscv/scala/src/main/scala/GenCoreImacDcache.scala: -------------------------------------------------------------------------------- 1 | package lunasoc 2 | 3 | import vexriscv.{VexRiscv, VexRiscvConfig, plugin} 4 | import vexriscv.ip.{DataCacheConfig, InstructionCacheConfig} 5 | import vexriscv.plugin._ 6 | 7 | import spinal.core._ 8 | import spinal.core.SpinalConfig 9 | import spinal.core.internals.{ 10 | ExpressionContainer, 11 | PhaseAllocateNames, 12 | PhaseContext 13 | } 14 | import spinal.lib._ 15 | import spinal.lib.sim.Phase 16 | 17 | import scala.collection.mutable.ArrayBuffer 18 | 19 | object GenCoreImacDcache { 20 | def main(args: Array[String]) { 21 | val outputFile = "vexriscv_imac+dcache" 22 | val spinalConfig = 23 | LunaSpinalConfig.copy(netlistFileName = outputFile + ".v") 24 | 25 | spinalConfig.generateVerilog { 26 | // configure plugins 27 | val plugins = ArrayBuffer[Plugin[VexRiscv]]() 28 | plugins ++= List( 29 | new IBusCachedPlugin( 30 | resetVector = null, 31 | relaxedPcCalculation = false, 32 | prediction = STATIC, 33 | compressedGen = true, // compressed instruction support 34 | memoryTranslatorPortConfig = null, 35 | config = InstructionCacheConfig( 36 | cacheSize = 4096, 37 | bytePerLine = 32, 38 | wayCount = 1, 39 | addressWidth = 32, 40 | cpuDataWidth = 32, 41 | memDataWidth = 32, 42 | catchIllegalAccess = true, 43 | catchAccessFault = true, 44 | asyncTagMemory = false, 45 | twoCycleRam = false, 46 | twoCycleCache = false 47 | ) 48 | ), 49 | new DBusCachedPlugin( 50 | dBusCmdMasterPipe = true, 51 | dBusCmdSlavePipe = true, 52 | dBusRspSlavePipe = false, 53 | relaxedMemoryTranslationRegister = false, 54 | config = new DataCacheConfig( 55 | cacheSize = 4096, 56 | bytePerLine = 32, 57 | wayCount = 1, 58 | addressWidth = 32, 59 | cpuDataWidth = 32, 60 | memDataWidth = 32, 61 | catchAccessError = true, 62 | catchIllegal = true, 63 | catchUnaligned = true, 64 | withLrSc = true, // atomic instruction support 65 | withAmo = true, // atomic instruction support 66 | earlyWaysHits = true 67 | ), 68 | memoryTranslatorPortConfig = null, 69 | csrInfo = true 70 | ), 71 | new StaticMemoryTranslatorPlugin( 72 | ioRange = _.msb // address => address > 0xf0000000 73 | ), 74 | new DecoderSimplePlugin( 75 | catchIllegalInstruction = true 76 | ), 77 | new RegFilePlugin( 78 | regFileReadyKind = plugin.SYNC, 79 | zeroBoot = false 80 | ), 81 | new IntAluPlugin, 82 | new SrcPlugin( 83 | separatedAddSub = false, 84 | executeInsertion = true 85 | ), 86 | new FullBarrelShifterPlugin, 87 | new HazardSimplePlugin( 88 | bypassExecute = true, 89 | bypassMemory = true, 90 | bypassWriteBack = true, 91 | bypassWriteBackBuffer = true, 92 | pessimisticUseSrc = false, 93 | pessimisticWriteRegFile = false, 94 | pessimisticAddressMatch = false 95 | ), 96 | new BranchPlugin( 97 | earlyBranch = false, 98 | catchAddressMisaligned = true 99 | ), 100 | new CsrPlugin( 101 | CsrPluginConfig.all(mtvecInit = null).copy(ebreakGen = true, xtvecModeGen = false) 102 | ), 103 | new YamlPlugin(outputFile + ".yaml"), 104 | new MulPlugin, 105 | new DivPlugin, 106 | new ExternalInterruptArrayPlugin( 107 | machineMaskCsrId = 0xbc0, 108 | machinePendingsCsrId = 0xfc0, 109 | supervisorMaskCsrId = 0x9c0, 110 | supervisorPendingsCsrId = 0xdc0 111 | )/*, 112 | // TODO make DebugPlugin optional 113 | new DebugPlugin( 114 | debugClockDomain = ClockDomain.current.clone(reset = Bool().setName("debugReset")), 115 | hardwareBreakpointCount = 0 116 | )*/ 117 | ) 118 | 119 | // instantiate core 120 | val cpu = new VexRiscv(VexRiscvConfig(plugins.toList)) 121 | 122 | // modify CPU to use wishbone bus 123 | cpu.rework { 124 | for (plugin <- cpu.config.plugins) plugin match { 125 | case plugin: IBusSimplePlugin => { 126 | plugin.iBus.setAsDirectionLess() 127 | master(plugin.iBus.toWishbone()).setName("iBusWishbone") 128 | } 129 | case plugin: IBusCachedPlugin => { 130 | plugin.iBus.setAsDirectionLess() 131 | master(plugin.iBus.toWishbone()).setName("iBusWishbone") 132 | } 133 | case plugin: DBusSimplePlugin => { 134 | plugin.dBus.setAsDirectionLess() 135 | master(plugin.dBus.toWishbone()).setName("dBusWishbone") 136 | } 137 | case plugin: DBusCachedPlugin => { 138 | plugin.dBus.setAsDirectionLess() 139 | master(plugin.dBus.toWishbone()).setName("dBusWishbone") 140 | } 141 | case _ => 142 | } 143 | } 144 | cpu 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/verilog/vexriscv/scala/src/main/scala/GenCoreImc.scala: -------------------------------------------------------------------------------- 1 | package lunasoc 2 | 3 | import vexriscv.{VexRiscv, VexRiscvConfig, plugin} 4 | import vexriscv.ip.{DataCacheConfig, InstructionCacheConfig} 5 | import vexriscv.plugin._ 6 | 7 | import spinal.core._ 8 | import spinal.core.SpinalConfig 9 | import spinal.core.internals.{ 10 | ExpressionContainer, 11 | PhaseAllocateNames, 12 | PhaseContext 13 | } 14 | import spinal.lib._ 15 | import spinal.lib.sim.Phase 16 | 17 | import scala.collection.mutable.ArrayBuffer 18 | 19 | object GenCoreImc { 20 | def main(args: Array[String]) { 21 | val outputFile = "vexriscv_imc" 22 | val spinalConfig = 23 | LunaSpinalConfig.copy(netlistFileName = outputFile + ".v") 24 | 25 | spinalConfig.generateVerilog { 26 | // configure plugins 27 | val plugins = ArrayBuffer[Plugin[VexRiscv]]() 28 | plugins ++= List( 29 | new IBusCachedPlugin( 30 | resetVector = null, 31 | relaxedPcCalculation = false, 32 | prediction = STATIC, 33 | compressedGen = true, // compressed instruction support 34 | memoryTranslatorPortConfig = null, 35 | config = InstructionCacheConfig( 36 | cacheSize = 4096, 37 | bytePerLine = 32, 38 | wayCount = 1, 39 | addressWidth = 32, 40 | cpuDataWidth = 32, 41 | memDataWidth = 32, 42 | catchIllegalAccess = true, 43 | catchAccessFault = true, 44 | asyncTagMemory = false, 45 | twoCycleRam = false, 46 | twoCycleCache = false 47 | ) 48 | ), 49 | new DBusSimplePlugin( 50 | catchAddressMisaligned = true, 51 | catchAccessFault = true, 52 | withLrSc = true, // load-reserved/store-conditional instructions (LB, LH, LW, SB, SH, SW etc.) 53 | memoryTranslatorPortConfig = null 54 | ), 55 | new StaticMemoryTranslatorPlugin( 56 | ioRange = _.msb 57 | ), 58 | new DecoderSimplePlugin( 59 | catchIllegalInstruction = true 60 | ), 61 | new RegFilePlugin( 62 | regFileReadyKind = plugin.SYNC, 63 | zeroBoot = false 64 | ), 65 | new IntAluPlugin, 66 | new SrcPlugin( 67 | separatedAddSub = false, 68 | executeInsertion = true 69 | ), 70 | new FullBarrelShifterPlugin, 71 | new HazardSimplePlugin( 72 | bypassExecute = true, 73 | bypassMemory = true, 74 | bypassWriteBack = true, 75 | bypassWriteBackBuffer = true, 76 | pessimisticUseSrc = false, 77 | pessimisticWriteRegFile = false, 78 | pessimisticAddressMatch = false 79 | ), 80 | new BranchPlugin( 81 | earlyBranch = false, 82 | catchAddressMisaligned = true 83 | ), 84 | new CsrPlugin( 85 | CsrPluginConfig.all(mtvecInit = null).copy(ebreakGen = true) 86 | ), 87 | new YamlPlugin(outputFile + ".yaml"), 88 | new MulPlugin, 89 | new DivPlugin, 90 | new ExternalInterruptArrayPlugin( 91 | machineMaskCsrId = 0xbc0, 92 | machinePendingsCsrId = 0xfc0, 93 | supervisorMaskCsrId = 0x9c0, 94 | supervisorPendingsCsrId = 0xdc0 95 | ) 96 | ) 97 | 98 | // instantiate core 99 | val cpu = new VexRiscv(VexRiscvConfig(plugins.toList)) 100 | 101 | // modify CPU to use wishbone bus 102 | cpu.rework { 103 | for (plugin <- cpu.config.plugins) plugin match { 104 | case plugin: IBusSimplePlugin => { 105 | plugin.iBus.setAsDirectionLess() 106 | master(plugin.iBus.toWishbone()).setName("iBusWishbone") 107 | } 108 | case plugin: IBusCachedPlugin => { 109 | plugin.iBus.setAsDirectionLess() 110 | master(plugin.iBus.toWishbone()).setName("iBusWishbone") 111 | } 112 | case plugin: DBusSimplePlugin => { 113 | plugin.dBus.setAsDirectionLess() 114 | master(plugin.dBus.toWishbone()).setName("dBusWishbone") 115 | } 116 | case plugin: DBusCachedPlugin => { 117 | plugin.dBus.setAsDirectionLess() 118 | master(plugin.dBus.toWishbone()).setName("dBusWishbone") 119 | } 120 | case _ => 121 | } 122 | } 123 | cpu 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/verilog/vexriscv/vexriscv_cynthion+jtag.yaml: -------------------------------------------------------------------------------- 1 | iBus: !!vexriscv.BusReport 2 | flushInstructions: [4111, 19, 19, 19] 3 | info: !!vexriscv.CacheReport {bytePerLine: 32, size: 2048} 4 | kind: cached 5 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/verilog/vexriscv/vexriscv_cynthion.yaml: -------------------------------------------------------------------------------- 1 | iBus: !!vexriscv.BusReport 2 | flushInstructions: [4111, 19, 19, 19] 3 | info: !!vexriscv.CacheReport {bytePerLine: 32, size: 2048} 4 | kind: cached 5 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/verilog/vexriscv/vexriscv_imac+dcache.yaml: -------------------------------------------------------------------------------- 1 | iBus: !!vexriscv.BusReport 2 | flushInstructions: [4111, 19, 19, 19] 3 | info: !!vexriscv.CacheReport {bytePerLine: 32, size: 4096} 4 | kind: cached 5 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/verilog/vexriscv/vexriscv_imac+litex.yaml: -------------------------------------------------------------------------------- 1 | iBus: !!vexriscv.BusReport 2 | flushInstructions: [4111, 19, 19, 19] 3 | info: !!vexriscv.CacheReport {bytePerLine: 32, size: 4096} 4 | kind: cached 5 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/verilog/vexriscv/vexriscv_imc.yaml: -------------------------------------------------------------------------------- 1 | iBus: !!vexriscv.BusReport 2 | flushInstructions: [4111, 19, 19, 19] 3 | info: !!vexriscv.CacheReport {bytePerLine: 32, size: 4096} 4 | kind: cached 5 | -------------------------------------------------------------------------------- /luna_soc/gateware/cpu/vexriscv.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2023-2025 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import os 8 | import logging 9 | 10 | from amaranth import * 11 | from amaranth.lib.wiring import Component, In, Out 12 | 13 | from amaranth_soc import wishbone 14 | from amaranth_soc.periph import ConstantMap 15 | 16 | __all__ = ["VexRiscv"] 17 | 18 | 19 | # Variants -------------------------------------------------------------------- 20 | 21 | CPU_VARIANTS = { 22 | "cynthion": "vexriscv_cynthion", 23 | "cynthion+jtag": "vexriscv_cynthion+jtag", 24 | "imac+dcache": "vexriscv_imac+dcache", 25 | "imc": "vexriscv_imc", 26 | } 27 | 28 | JTAG_VARIANTS = [ "cynthion+jtag" ] 29 | 30 | # - VexRiscv ------------------------------------------------------------------ 31 | 32 | class VexRiscv(Component): 33 | name = "vexriscv" 34 | arch = "riscv" 35 | byteorder = "little" 36 | data_width = 32 37 | 38 | def __init__(self, variant="imac+dcache", reset_addr=0x00000000): 39 | self._variant = variant 40 | self._reset_addr = reset_addr 41 | 42 | super().__init__({ 43 | "ext_reset": In(unsigned(1)), 44 | 45 | "irq_external": In(unsigned(32)), 46 | "irq_timer": In(unsigned(1)), 47 | "irq_software": In(unsigned(1)), 48 | 49 | "ibus": Out(wishbone.Signature( 50 | addr_width=30, 51 | data_width=32, 52 | granularity=8, 53 | features=("err", "cti", "bte") 54 | )), 55 | "dbus": Out(wishbone.Signature( 56 | addr_width=30, 57 | data_width=32, 58 | granularity=8, 59 | features=("err", "cti", "bte") 60 | )), 61 | 62 | "jtag_tms": In(unsigned(1)), 63 | "jtag_tdi": In(unsigned(1)), 64 | "jtag_tdo": Out(unsigned(1)), 65 | "jtag_tck": In(unsigned(1)), 66 | "dbg_reset": In(unsigned(1)), 67 | "ndm_reset": In(unsigned(1)), 68 | "stop_time": In(unsigned(1)), 69 | }) 70 | 71 | # read source verilog 72 | if not variant in CPU_VARIANTS: 73 | raise ValueError(f"unsupported variant: {variant}") 74 | self._source_file = f"{CPU_VARIANTS[variant]}.v" 75 | self._source_path = os.path.join(os.path.dirname(__file__), "verilog", "vexriscv", self._source_file) 76 | if not os.path.exists(self._source_path): 77 | FileNotFoundError(f"Verilog source file not found: {self._source_path}") 78 | with open(self._source_path, "r") as f: 79 | logging.info(f"reading verilog file: {self._source_path}") 80 | self._source_verilog = f.read() 81 | 82 | @property 83 | def reset_addr(self): 84 | return self._reset_addr 85 | 86 | def elaborate(self, platform): 87 | m = Module() 88 | 89 | # optional signals 90 | optional_signals = {} 91 | if self._variant in JTAG_VARIANTS: 92 | optional_signals = { 93 | "i_jtag_tms": self.jtag_tms, 94 | "i_jtag_tdi": self.jtag_tdi, 95 | "o_jtag_tdo": self.jtag_tdo, 96 | "i_jtag_tck": self.jtag_tck, 97 | "i_debugReset": self.dbg_reset, 98 | "o_ndmreset": self.ndm_reset, 99 | "o_stoptime": self.stop_time, 100 | } 101 | 102 | # instantiate VexRiscv 103 | platform.add_file(self._source_file, self._source_verilog) 104 | self._cpu = Instance( 105 | "VexRiscv", 106 | 107 | # clock and reset 108 | i_clk = ClockSignal("sync"), 109 | i_reset = ResetSignal("sync") | self.ext_reset, 110 | i_externalResetVector = Const(self._reset_addr, unsigned(32)), 111 | 112 | # interrupts 113 | i_externalInterruptArray = self.irq_external, 114 | i_timerInterrupt = self.irq_timer, 115 | i_softwareInterrupt = self.irq_software, 116 | 117 | # instruction bus 118 | o_iBusWishbone_ADR = self.ibus.adr, 119 | o_iBusWishbone_DAT_MOSI = self.ibus.dat_w, 120 | o_iBusWishbone_SEL = self.ibus.sel, 121 | o_iBusWishbone_CYC = self.ibus.cyc, 122 | o_iBusWishbone_STB = self.ibus.stb, 123 | o_iBusWishbone_WE = self.ibus.we, 124 | o_iBusWishbone_CTI = self.ibus.cti, 125 | o_iBusWishbone_BTE = self.ibus.bte, 126 | i_iBusWishbone_DAT_MISO = self.ibus.dat_r, 127 | i_iBusWishbone_ACK = self.ibus.ack, 128 | i_iBusWishbone_ERR = self.ibus.err, 129 | 130 | # data bus 131 | o_dBusWishbone_ADR = self.dbus.adr, 132 | o_dBusWishbone_DAT_MOSI = self.dbus.dat_w, 133 | o_dBusWishbone_SEL = self.dbus.sel, 134 | o_dBusWishbone_CYC = self.dbus.cyc, 135 | o_dBusWishbone_STB = self.dbus.stb, 136 | o_dBusWishbone_WE = self.dbus.we, 137 | o_dBusWishbone_CTI = self.dbus.cti, 138 | o_dBusWishbone_BTE = self.dbus.bte, 139 | i_dBusWishbone_DAT_MISO = self.dbus.dat_r, 140 | i_dBusWishbone_ACK = self.dbus.ack, 141 | i_dBusWishbone_ERR = self.dbus.err, 142 | 143 | # optional signals 144 | **optional_signals, 145 | ) 146 | 147 | m.submodules.vexriscv = self._cpu 148 | 149 | return m 150 | -------------------------------------------------------------------------------- /luna_soc/gateware/interface/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greatscottgadgets/luna-soc/e7e742fc93187768ed7f2a0a557a95f9a0eab8e9/luna_soc/gateware/interface/.keep -------------------------------------------------------------------------------- /luna_soc/gateware/provider/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greatscottgadgets/luna-soc/e7e742fc93187768ed7f2a0a557a95f9a0eab8e9/luna_soc/gateware/provider/.keep -------------------------------------------------------------------------------- /luna_soc/gateware/vendor/README.md: -------------------------------------------------------------------------------- 1 | ## Vendored Dependencies 2 | 3 | These are packages that have no official releases at this time and may also have implementation features that will be deprecated once official releases are made. 4 | 5 | The plan for these are to replace them as upstream development matures, official releases are made and we are able to migrate to them. 6 | 7 | The repositories and commit hashes for each of these are as follows: 8 | 9 | | Repository | Commit | 10 | | ----------------------------------------------- | ----------------------------------------------------------------------------- | 11 | | https://github.com/amaranth-lang/amaranth-soc | [`5c43cf5`](https://github.com/amaranth-lang/amaranth-soc/commit/5c43cf5) | 12 | | https://github.com/amaranth-lang/amaranth-stdio | [`618a13f`](https://github.com/amaranth-lang/amaranth-stdio/commit/618a13f) | 13 | -------------------------------------------------------------------------------- /luna_soc/gateware/vendor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greatscottgadgets/luna-soc/e7e742fc93187768ed7f2a0a557a95f9a0eab8e9/luna_soc/gateware/vendor/__init__.py -------------------------------------------------------------------------------- /luna_soc/gateware/vendor/amaranth_soc/__init__.py: -------------------------------------------------------------------------------- 1 | # Extract version for this package from the environment package metadata. This used to be a lot 2 | # more difficult in earlier Python versions, and the `__version__` field is a legacy of that time. 3 | import importlib.metadata 4 | try: 5 | __version__ = importlib.metadata.version(__package__) 6 | except importlib.metadata.PackageNotFoundError: 7 | # No importlib metadata for this package. This shouldn't normally happen, but some people 8 | # prefer not installing packages via pip at all. Although not recommended we still support it. 9 | __version__ = "unknown" # :nocov: 10 | del importlib -------------------------------------------------------------------------------- /luna_soc/gateware/vendor/amaranth_soc/csr/__init__.py: -------------------------------------------------------------------------------- 1 | from .bus import * 2 | from .event import * 3 | from .reg import * 4 | from . import action 5 | -------------------------------------------------------------------------------- /luna_soc/gateware/vendor/amaranth_soc/csr/event.py: -------------------------------------------------------------------------------- 1 | # amaranth: UnusedElaboratable=no 2 | from math import ceil 3 | 4 | from amaranth import * 5 | from amaranth.lib import wiring 6 | from amaranth.lib.wiring import In, Out, flipped, connect 7 | from amaranth.utils import ceil_log2 8 | 9 | from . import Multiplexer 10 | from .reg import Register, Field, FieldAction 11 | from .. import event 12 | from ..memory import MemoryMap 13 | 14 | 15 | __all__ = ["EventMonitor"] 16 | 17 | 18 | class _EventMaskRegister(Register, access="rw"): 19 | def __init__(self, width): 20 | super().__init__({"mask": Field(FieldAction, width, access="rw")}) 21 | 22 | 23 | class EventMonitor(wiring.Component): 24 | """Event monitor. 25 | 26 | A monitor for subordinate event sources, with a CSR bus interface. 27 | 28 | CSR registers 29 | ------------- 30 | enable : ``event_map.size``, read/write 31 | Enabled events. See :meth:`..event.EventMap.sources` for layout. 32 | pending : ``event_map.size``, read/clear 33 | Pending events. See :meth:`..event.EventMap.sources` for layout. 34 | 35 | Parameters 36 | ---------- 37 | event_map : :class:`..event.EventMap` 38 | A collection of event sources. 39 | trigger : :class:`..event.Source.Trigger` 40 | Trigger mode. See :class:`..event.Source`. 41 | data_width : int 42 | CSR bus data width. See :class:`..csr.Interface`. 43 | alignment : int, power-of-2 exponent 44 | CSR address alignment. See :class:`..memory.MemoryMap`. 45 | 46 | Attributes 47 | ---------- 48 | src : :class:`..event.Source` 49 | Event source. Its input line is asserted by the monitor when a subordinate event is enabled 50 | and pending. 51 | bus : :class:`..csr.Interface` 52 | CSR bus interface. 53 | """ 54 | def __init__(self, event_map, *, trigger="level", data_width, alignment=0, name=None): 55 | if not isinstance(data_width, int) or data_width <= 0: 56 | raise ValueError(f"Data width must be a positive integer, not {data_width!r}") 57 | if not isinstance(alignment, int) or alignment < 0: 58 | raise ValueError(f"Alignment must be a non-negative integer, not {alignment!r}") 59 | 60 | self._monitor = event.Monitor(event_map, trigger=trigger) 61 | self._enable = _EventMaskRegister(event_map.size) 62 | self._pending = _EventMaskRegister(event_map.size) 63 | 64 | reg_size = (event_map.size + data_width - 1) // data_width 65 | addr_width = 1 + max(ceil_log2(reg_size), alignment) 66 | memory_map = MemoryMap(addr_width=addr_width, data_width=data_width, alignment=alignment) 67 | memory_map.add_resource(self._enable, size=reg_size, name=("enable",)) 68 | memory_map.add_resource(self._pending, size=reg_size, name=("pending",)) 69 | 70 | self._mux = Multiplexer(memory_map) 71 | 72 | super().__init__({ 73 | "src": Out(self._monitor.src.signature), 74 | "bus": In(self._mux.bus.signature), 75 | }) 76 | self.bus.memory_map = self._mux.bus.memory_map 77 | 78 | def elaborate(self, platform): 79 | m = Module() 80 | m.submodules.monitor = self._monitor 81 | m.submodules.mux = self._mux 82 | 83 | connect(m, flipped(self.src), self._monitor.src) 84 | connect(m, self.bus, self._mux.bus) 85 | 86 | with m.If(self._enable.element.w_stb): 87 | m.d.sync += self._monitor.enable.eq(self._enable.element.w_data) 88 | m.d.comb += self._enable.element.r_data.eq(self._monitor.enable) 89 | 90 | with m.If(self._pending.element.w_stb): 91 | m.d.comb += self._monitor.clear.eq(self._pending.element.w_data) 92 | m.d.comb += self._pending.element.r_data.eq(self._monitor.pending) 93 | 94 | return m 95 | -------------------------------------------------------------------------------- /luna_soc/gateware/vendor/amaranth_soc/csr/wishbone.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import wiring 3 | from amaranth.lib.wiring import In, flipped 4 | from amaranth.utils import exact_log2 5 | 6 | from . import Interface 7 | from .. import wishbone 8 | from ..memory import MemoryMap 9 | 10 | 11 | __all__ = ["WishboneCSRBridge"] 12 | 13 | 14 | class WishboneCSRBridge(wiring.Component): 15 | """Wishbone to CSR bridge. 16 | 17 | A bus bridge for accessing CSR registers from Wishbone. This bridge supports any Wishbone 18 | data width greater or equal to CSR data width and performs appropriate address translation. 19 | 20 | Latency 21 | ------- 22 | 23 | Reads and writes always take ``self.data_width // csr_bus.data_width + 1`` cycles to complete, 24 | regardless of the select inputs. Write side effects occur simultaneously with acknowledgement. 25 | 26 | Parameters 27 | ---------- 28 | csr_bus : :class:`..csr.Interface` 29 | CSR bus driven by the bridge. 30 | data_width : int 31 | Wishbone bus data width. Optional. If ``None``, defaults to ``csr_bus.data_width``. 32 | name : :class:`..memory.MemoryMap.Name` 33 | Window name. Optional. 34 | 35 | Attributes 36 | ---------- 37 | wb_bus : :class:`..wishbone.Interface` 38 | Wishbone bus provided by the bridge. 39 | """ 40 | def __init__(self, csr_bus, *, data_width=None, name=None): 41 | if isinstance(csr_bus, wiring.FlippedInterface): 42 | csr_bus_unflipped = flipped(csr_bus) 43 | else: 44 | csr_bus_unflipped = csr_bus 45 | if not isinstance(csr_bus_unflipped, Interface): 46 | raise TypeError(f"CSR bus must be an instance of csr.Interface, not " 47 | f"{csr_bus_unflipped!r}") 48 | if csr_bus.data_width not in (8, 16, 32, 64): 49 | raise ValueError(f"CSR bus data width must be one of 8, 16, 32, 64, not " 50 | f"{csr_bus.data_width!r}") 51 | if data_width is None: 52 | data_width = csr_bus.data_width 53 | 54 | ratio = data_width // csr_bus.data_width 55 | wb_sig = wishbone.Signature(addr_width=max(0, csr_bus.addr_width - exact_log2(ratio)), 56 | data_width=data_width, 57 | granularity=csr_bus.data_width) 58 | 59 | super().__init__({"wb_bus": In(wb_sig)}) 60 | 61 | self.wb_bus.memory_map = MemoryMap(addr_width=csr_bus.addr_width, 62 | data_width=csr_bus.data_width) 63 | # Since granularity of the Wishbone interface matches the data width of the CSR bus, 64 | # no width conversion is performed, even if the Wishbone data width is greater. 65 | self.wb_bus.memory_map.add_window(csr_bus.memory_map, name=name) 66 | 67 | self._csr_bus = csr_bus 68 | 69 | @property 70 | def csr_bus(self): 71 | return self._csr_bus 72 | 73 | def elaborate(self, platform): 74 | csr_bus = self.csr_bus 75 | wb_bus = self.wb_bus 76 | 77 | m = Module() 78 | 79 | cycle = Signal(range(len(wb_bus.sel) + 1)) 80 | m.d.comb += csr_bus.addr.eq(Cat(cycle[:exact_log2(len(wb_bus.sel))], wb_bus.adr)) 81 | 82 | with m.If(wb_bus.cyc & wb_bus.stb): 83 | with m.Switch(cycle): 84 | def segment(index): 85 | return slice(index * wb_bus.granularity, (index + 1) * wb_bus.granularity) 86 | 87 | for index, sel_index in enumerate(wb_bus.sel): 88 | with m.Case(index): 89 | if index > 0: 90 | # CSR reads are registered, and we need to re-register them. 91 | m.d.sync += wb_bus.dat_r[segment(index - 1)].eq(csr_bus.r_data) 92 | m.d.comb += csr_bus.r_stb.eq(sel_index & ~wb_bus.we) 93 | m.d.comb += csr_bus.w_data.eq(wb_bus.dat_w[segment(index)]) 94 | m.d.comb += csr_bus.w_stb.eq(sel_index & wb_bus.we) 95 | m.d.sync += cycle.eq(index + 1) 96 | 97 | with m.Default(): 98 | m.d.sync += wb_bus.dat_r[segment(index)].eq(csr_bus.r_data) 99 | m.d.sync += wb_bus.ack.eq(1) 100 | 101 | with m.If(wb_bus.ack): 102 | m.d.sync += cycle.eq(0) 103 | m.d.sync += wb_bus.ack.eq(0) 104 | 105 | return m 106 | -------------------------------------------------------------------------------- /luna_soc/gateware/vendor/amaranth_soc/periph.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | from collections.abc import Mapping 3 | 4 | from amaranth.utils import bits_for 5 | 6 | from .memory import MemoryMap 7 | from . import event 8 | 9 | 10 | __all__ = ["ConstantValue", "ConstantBool", "ConstantInt", "ConstantMap", "PeripheralInfo"] 11 | 12 | 13 | class ConstantValue: 14 | pass 15 | 16 | 17 | class ConstantBool(ConstantValue): 18 | """Boolean constant. 19 | 20 | Parameters 21 | ---------- 22 | value : bool 23 | Constant value. 24 | """ 25 | def __init__(self, value): 26 | if not isinstance(value, bool): 27 | raise TypeError(f"Value must be a bool, not {value!r}") 28 | self._value = value 29 | 30 | @property 31 | def value(self): 32 | return self._value 33 | 34 | def __repr__(self): 35 | return f"ConstantBool({self.value})" 36 | 37 | 38 | class ConstantInt(ConstantValue): 39 | """Integer constant. 40 | 41 | Parameters 42 | ---------- 43 | value : int 44 | Constant value. 45 | width : int 46 | Width in bits. Optional. ``bits_for(value)`` by default. 47 | signed : bool 48 | Signedness. Optional. ``value < 0`` by default. 49 | """ 50 | def __init__(self, value, *, width=None, signed=None): 51 | if not isinstance(value, int): 52 | raise TypeError(f"Value must be an integer, not {value!r}") 53 | self._value = value 54 | 55 | if width is None: 56 | width = bits_for(value) 57 | if not isinstance(width, int): 58 | raise TypeError(f"Width must be an integer, not {width!r}") 59 | if width < bits_for(value): 60 | raise ValueError(f"Width must be greater than or equal to the number of bits needed " 61 | f"to represent {value}") 62 | self._width = width 63 | 64 | if signed is None: 65 | signed = value < 0 66 | if not isinstance(signed, bool): 67 | raise TypeError(f"Signedness must be a bool, not {signed!r}") 68 | self._signed = signed 69 | 70 | @property 71 | def value(self): 72 | return self._value 73 | 74 | @property 75 | def width(self): 76 | return self._width 77 | 78 | @property 79 | def signed(self): 80 | return self._signed 81 | 82 | def __repr__(self): 83 | return f"ConstantInt({self.value}, width={self.width}, signed={self.signed})" 84 | 85 | 86 | class ConstantMap(Mapping): 87 | """Named constant map. 88 | 89 | A read-only container for named constants. Keys are iterated in insertion order. 90 | 91 | Parameters 92 | ---------- 93 | **constants : dict(str : :class:`ConstantValue`) 94 | Named constants. 95 | 96 | Examples 97 | -------- 98 | >>> ConstantMap(RX_FIFO_DEPTH=16) 99 | ConstantMap([('RX_FIFO_DEPTH', ConstantInt(16, width=5, signed=False))]) 100 | """ 101 | def __init__(self, **constants): 102 | self._storage = OrderedDict() 103 | for key, value in constants.items(): 104 | if isinstance(value, bool): 105 | value = ConstantBool(value) 106 | if isinstance(value, int): 107 | value = ConstantInt(value) 108 | if not isinstance(value, ConstantValue): 109 | raise TypeError(f"Constant value must be an instance of ConstantValue, not " 110 | f"{value!r}") 111 | self._storage[key] = value 112 | 113 | def __getitem__(self, key): 114 | return self._storage[key] 115 | 116 | def __iter__(self): 117 | yield from self._storage 118 | 119 | def __len__(self): 120 | return len(self._storage) 121 | 122 | def __repr__(self): 123 | return f"ConstantMap({list(self._storage.items())})" 124 | 125 | 126 | class PeripheralInfo: 127 | """Peripheral metadata. 128 | 129 | A unified description of the local resources of a peripheral. It may be queried in order to 130 | recover its memory windows, CSR registers, event sources and configuration constants. 131 | 132 | Parameters 133 | ---------- 134 | memory_map : :class:`MemoryMap` 135 | Memory map of the peripheral. 136 | irq : :class:`event.Source` 137 | IRQ line of the peripheral. Optional. 138 | constant_map : :class:`ConstantMap` 139 | Constant map of the peripheral. Optional. 140 | """ 141 | def __init__(self, *, memory_map, irq=None, constant_map=None): 142 | if not isinstance(memory_map, MemoryMap): 143 | raise TypeError(f"Memory map must be an instance of MemoryMap, not {memory_map!r}") 144 | memory_map.freeze() 145 | self._memory_map = memory_map 146 | 147 | if irq is not None and not isinstance(irq, event.Source): 148 | raise TypeError(f"IRQ line must be an instance of event.Source, not {irq!r}") 149 | self._irq = irq 150 | 151 | if constant_map is None: 152 | constant_map = ConstantMap() 153 | if not isinstance(constant_map, ConstantMap): 154 | raise TypeError(f"Constant map must be an instance of ConstantMap, not " 155 | f"{constant_map!r}") 156 | self._constant_map = constant_map 157 | 158 | @property 159 | def memory_map(self): 160 | """Memory map. 161 | 162 | Return value 163 | ------------ 164 | A :class:`MemoryMap` describing the local address space of the peripheral. 165 | """ 166 | return self._memory_map 167 | 168 | @property 169 | def irq(self): 170 | """IRQ line. 171 | 172 | Return value 173 | ------------ 174 | An :class:`event.Source` used by the peripheral to request interrupts. If provided, its 175 | event map describes local events. 176 | 177 | Exceptions 178 | ---------- 179 | Raises :exn:`NotImplementedError` if the peripheral info does not have an IRQ line. 180 | """ 181 | if self._irq is None: 182 | raise NotImplementedError("Peripheral info does not have an IRQ line") 183 | return self._irq 184 | 185 | @property 186 | def constant_map(self): 187 | """Constant map. 188 | 189 | Return value 190 | ------------ 191 | A :class:`ConstantMap` containing configuration constants of the peripheral. 192 | """ 193 | return self._constant_map 194 | -------------------------------------------------------------------------------- /luna_soc/gateware/vendor/amaranth_soc/wishbone/__init__.py: -------------------------------------------------------------------------------- 1 | from .bus import * 2 | -------------------------------------------------------------------------------- /luna_soc/gateware/vendor/amaranth_soc/wishbone/sram.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import wiring 3 | from amaranth.lib.wiring import In 4 | from amaranth.lib.memory import MemoryData, Memory 5 | from amaranth.utils import exact_log2 6 | 7 | from ..memory import MemoryMap 8 | from .bus import Signature 9 | 10 | 11 | __all__ = ["WishboneSRAM"] 12 | 13 | 14 | class WishboneSRAM(wiring.Component): 15 | """Wishbone-attached SRAM. 16 | 17 | Wishbone bus accesses have a latency of one clock cycle. 18 | 19 | Arguments 20 | --------- 21 | size : :class:`int`, power of two 22 | SRAM size, in units of ``granularity`` bits. 23 | data_width : ``8``, ``16``, ``32`` or ``64`` 24 | Wishbone bus data width. 25 | granularity : ``8``, ``16``, ``32`` or ``64``, optional 26 | Wishbone bus granularity. If unspecified, it defaults to ``data_width``. 27 | writable : bool 28 | Write capability. If disabled, writes are ignored. Enabled by default. 29 | init : iterable of initial values, optional 30 | Initial values for memory rows. There are ``(size * granularity) // data_width`` rows, 31 | and each row has a shape of ``unsigned(data_width)``. 32 | 33 | Members 34 | ------- 35 | wb_bus : ``In(wishbone.Signature(...))`` 36 | Wishbone bus interface. 37 | 38 | Raises 39 | ------ 40 | :exc:`ValueError` 41 | If ``size * granularity`` is lesser than ``data_width``. 42 | """ 43 | def __init__(self, *, size, data_width, granularity=None, writable=True, init=()): 44 | if granularity is None: 45 | granularity = data_width 46 | 47 | if not isinstance(size, int) or size <= 0 or size & size-1: 48 | raise TypeError(f"Size must be an integer power of two, not {size!r}") 49 | if data_width not in (8, 16, 32, 64): 50 | raise TypeError(f"Data width must be 8, 16, 32 or 64, not {data_width!r}") 51 | if granularity not in (8, 16, 32, 64): 52 | raise TypeError(f"Granularity must be 8, 16, 32 or 64, not {granularity!r}") 53 | if size * granularity < data_width: 54 | raise ValueError(f"The product of size {size} and granularity {granularity} must be " 55 | f"greater than or equal to data width {data_width}, not " 56 | f"{size * granularity}") 57 | 58 | self._size = size 59 | self._writable = bool(writable) 60 | self._mem_data = MemoryData(depth=(size * granularity) // data_width, 61 | shape=unsigned(data_width), init=init) 62 | self._mem = Memory(self._mem_data) 63 | 64 | super().__init__({"wb_bus": In(Signature(addr_width=exact_log2(self._mem.depth), 65 | data_width=data_width, granularity=granularity))}) 66 | 67 | self.wb_bus.memory_map = MemoryMap(addr_width=exact_log2(size), data_width=granularity) 68 | self.wb_bus.memory_map.add_resource(self._mem, name=("mem",), size=size) 69 | self.wb_bus.memory_map.freeze() 70 | 71 | @property 72 | def size(self): 73 | return self._size 74 | 75 | @property 76 | def writable(self): 77 | return self._writable 78 | 79 | @property 80 | def init(self): 81 | return self._mem_data.init 82 | 83 | @init.setter 84 | def init(self, init): 85 | self._mem_data.init = init 86 | 87 | def elaborate(self, platform): 88 | m = Module() 89 | m.submodules.mem = self._mem 90 | 91 | read_port = self._mem.read_port() 92 | m.d.comb += [ 93 | read_port.addr.eq(self.wb_bus.adr), 94 | self.wb_bus.dat_r.eq(read_port.data), 95 | ] 96 | 97 | if self.writable: 98 | write_port = self._mem.write_port(granularity=self.wb_bus.granularity) 99 | m.d.comb += [ 100 | write_port.addr.eq(self.wb_bus.adr), 101 | write_port.data.eq(self.wb_bus.dat_w), 102 | ] 103 | 104 | with m.If(self.wb_bus.ack): 105 | m.d.sync += self.wb_bus.ack.eq(0) 106 | with m.Elif(self.wb_bus.cyc & self.wb_bus.stb): 107 | if self.writable: 108 | m.d.comb += write_port.en.eq(Mux(self.wb_bus.we, self.wb_bus.sel, 0)) 109 | m.d.comb += read_port.en.eq(~self.wb_bus.we) 110 | m.d.sync += self.wb_bus.ack.eq(1) 111 | 112 | return m 113 | -------------------------------------------------------------------------------- /luna_soc/gateware/vendor/amaranth_stdio/__init__.py: -------------------------------------------------------------------------------- 1 | # Extract version for this package from the environment package metadata. This used to be a lot 2 | # more difficult in earlier Python versions, and the `__version__` field is a legacy of that time. 3 | import importlib.metadata 4 | try: 5 | __version__ = importlib.metadata.version(__package__) 6 | except importlib.metadata.PackageNotFoundError: 7 | # No importlib metadata for this package. This shouldn't normally happen, but some people 8 | # prefer not installing packages via pip at all. Although not recommended we still support it. 9 | __version__ = "unknown" # :nocov: 10 | del importlib -------------------------------------------------------------------------------- /luna_soc/generate/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2023 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | -------------------------------------------------------------------------------- /luna_soc/generate/introspect.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2023 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | """Introspection tools for SoC designs.""" 8 | 9 | import logging 10 | 11 | from collections import defaultdict 12 | 13 | from amaranth.lib import wiring 14 | 15 | from amaranth_soc import csr 16 | from amaranth_soc.memory import MemoryMap, ResourceInfo 17 | 18 | from ..gateware.cpu.ic import InterruptMap 19 | 20 | 21 | # - soc attributes ------------------------------------------------------------ 22 | 23 | def soc(fragment: wiring.Component) -> wiring.Component: 24 | if hasattr(fragment, "soc"): 25 | fragment = fragment.soc 26 | if not hasattr(fragment, "wb_decoder"): 27 | logging.warning("SoC designs need to have a 'wb_decoder' attribute.") 28 | if not hasattr(fragment, "interrupt_controller"): 29 | logging.warning("SoC designs need to have an 'interrupt_controller' attribute.") 30 | return fragment 31 | 32 | def memory_map(fragment: wiring.Component) -> MemoryMap: 33 | return fragment.wb_decoder.bus.memory_map 34 | 35 | def interrupts(fragment: wiring.Component) -> InterruptMap: 36 | return fragment.interrupt_controller.interrupts() 37 | 38 | def reset_addr(fragment: wiring.Component) -> MemoryMap: 39 | return fragment.cpu.reset_addr 40 | 41 | 42 | # - soc introspections -------------------------------------------------------- 43 | 44 | def csr_base(memory_map: MemoryMap) -> int: 45 | """Scan a memory map for the starting address for csr peripheral registers.""" 46 | window: MemoryMap 47 | name: MemoryMap.Name 48 | for window, name, (start, end, ratio) in memory_map.windows(): 49 | if name[0] == "wb_to_csr": 50 | return start 51 | 52 | 53 | def csr_peripherals(memory_map: MemoryMap) -> dict[MemoryMap.Name, list[ResourceInfo]]: 54 | """Scan a memory map for csr peripheral registers.""" 55 | 56 | # group registers by peripheral 57 | csr_peripherals = defaultdict(list) 58 | 59 | # scan memory map for peripheral registers 60 | window: MemoryMap 61 | name: MemoryMap.Name 62 | for window, name, (start, end, ratio) in memory_map.windows(): 63 | resource_info: ResourceInfo 64 | for resource_info in window.all_resources(): 65 | peripheral: MemoryMap.Name = resource_info.path[0] 66 | register: csr.Register = resource_info.resource 67 | if issubclass(register.__class__, csr.Register): 68 | csr_peripherals[peripheral].append(resource_info) 69 | 70 | return csr_peripherals 71 | 72 | 73 | def wb_peripherals(memory_map: MemoryMap) -> dict[ 74 | MemoryMap.Name, 75 | list[ 76 | tuple[ 77 | wiring.Component, 78 | MemoryMap.Name, 79 | (int, int) 80 | ] 81 | ]]: 82 | """Scan a memory map for wishbone peripherals.""" 83 | 84 | # group by peripheral 85 | wb_peripherals = defaultdict(list) 86 | 87 | # scan memory map for wb peripherals 88 | window: MemoryMap 89 | for window, name, (start, end, ratio) in memory_map.windows(): 90 | # window.resources() yields a tuple: `resource, resource_name, (start, end)` 91 | # where resource is the actual peripheral e.g. `core.blockram.Peripheral` 92 | for resource, path, (start, stop) in window.resources(): 93 | wb_peripherals[name].append((resource, path, (start, stop))) 94 | 95 | return wb_peripherals 96 | -------------------------------------------------------------------------------- /luna_soc/generate/rust.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of LUNA. 3 | # 4 | # Copyright (c) 2023-2025 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | """Generate Rust support files for SoC designs.""" 8 | 9 | import datetime 10 | import logging 11 | 12 | from amaranth_soc.memory import MemoryMap 13 | 14 | class LinkerScript: 15 | def __init__(self, memory_map: MemoryMap, reset_addr: int = 0x00000000): 16 | self.memory_map = memory_map 17 | self.reset_addr = reset_addr 18 | 19 | def generate(self, file=None): 20 | """ Generate a memory.x file for the given SoC design""" 21 | 22 | def emit(content): 23 | """ Utility function that emits a string to the targeted file. """ 24 | print(content, file=file) 25 | 26 | # TODO this should be determined by introspection 27 | memories = ["ram", "rom", "blockram", "spiflash", 28 | "bootrom", "scratchpad", "mainram"] 29 | 30 | # warning header 31 | emit("/*") 32 | emit(" * Automatically generated by LUNA; edits will be discarded on rebuild.") 33 | emit(" * (Most header files phrase this 'Do not edit.'; be warned accordingly.)") 34 | emit(" *") 35 | emit(f" * Generated: {datetime.datetime.now()}.") 36 | emit(" */") 37 | emit("") 38 | 39 | # memory regions 40 | regions = set() 41 | emit("MEMORY {") 42 | window: MemoryMap 43 | name: MemoryMap.Name 44 | for window, name, (start, end, ratio) in self.memory_map.windows(): 45 | name = name[0] 46 | if name not in memories: 47 | logging.debug("Skipping non-memory resource: {}".format(name)) 48 | continue 49 | if self.reset_addr >= start and self.reset_addr < end: 50 | start = self.reset_addr 51 | emit(f" {name} : ORIGIN = 0x{start:08x}, LENGTH = 0x{end-start:08x}") 52 | regions.add(name) 53 | emit("}") 54 | emit("") 55 | 56 | # region aliases 57 | ram = "blockram" if "blockram" in regions else "scratchpad" 58 | rom = "spiflash" if "spiflash" in regions else ram 59 | aliases = { 60 | "REGION_TEXT": rom, 61 | "REGION_RODATA": rom, 62 | "REGION_DATA": ram, 63 | "REGION_BSS": ram, 64 | "REGION_HEAP": ram, 65 | "REGION_STACK": ram, 66 | } 67 | for alias, region in aliases.items(): 68 | emit(f"REGION_ALIAS(\"{alias}\", {region});") 69 | -------------------------------------------------------------------------------- /luna_soc/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greatscottgadgets/luna-soc/e7e742fc93187768ed7f2a0a557a95f9a0eab8e9/luna_soc/util/__init__.py -------------------------------------------------------------------------------- /luna_soc/util/readbin.py: -------------------------------------------------------------------------------- 1 | import logging, math, os, struct 2 | 3 | def get_mem_regions(filename_or_regions, offset): 4 | if isinstance(filename_or_regions, dict): 5 | regions = filename_or_regions 6 | else: 7 | filename = filename_or_regions 8 | if not os.path.isfile(filename): 9 | # TODO This is cursed: 10 | # 11 | # * SoC definitions occur in the SoC constructor. 12 | # * The SRAMPeripheral 'init' parameter requires a list of int rather than a filename. 13 | # * This causes instantiation of the SoC to fail even if the goal 14 | # is only to introspect the design in order to generate the 'init' file! 15 | # 16 | # A better solution will be to go back to using our own fork of SRAMPeripheral 17 | # and extends with the option to take a filename for the 'init' parameter and which 18 | # which is only resolved to the file data at elaboration time. 19 | logging.warn(f"Memory image '{filename}' has not yet been generated.") 20 | return None 21 | #raise FileNotFoundError(f"Unable to find '{filename}' memory content file.") 22 | _, ext = os.path.splitext(filename) 23 | if ext == ".json": 24 | f = open(filename, "r") 25 | _regions = json.load(f) 26 | # .json 27 | regions = dict() 28 | for k, v in _regions.items(): 29 | regions[os.path.join(os.path.dirname(filename), k)] = v 30 | f.close() 31 | else: 32 | regions = {filename: f"{offset:08x}"} 33 | return regions 34 | 35 | def get_mem_data(filename_or_regions, data_width=32, endianness="big", mem_size=None, offset=0): 36 | assert data_width % 32 == 0 37 | assert endianness in ["big", "little"] 38 | 39 | # Return empty list if no filename or regions. 40 | if filename_or_regions is None: 41 | return [] 42 | 43 | # Create memory regions. 44 | regions = get_mem_regions(filename_or_regions, offset) 45 | if regions is None: 46 | return None 47 | 48 | # Determine data_size. 49 | data_size = 0 50 | for filename, base in regions.items(): 51 | if not os.path.isfile(filename): 52 | raise OSError(f"Unable to find '{filename}' memory content file.") 53 | data_size = max(int(base, 16) + os.path.getsize(filename) - offset, data_size) 54 | assert data_size > 0 55 | if mem_size is not None: 56 | assert data_size < mem_size, ( 57 | "file is too big: {}/{} bytes".format( 58 | data_size, mem_size)) 59 | 60 | # Fill data. 61 | bytes_per_data = data_width//8 62 | data = [0]*math.ceil(data_size/bytes_per_data) 63 | for filename, base in regions.items(): 64 | base = int(base, 16) 65 | with open(filename, "rb") as f: 66 | i = 0 67 | while True: 68 | w = f.read(bytes_per_data) 69 | if not w: 70 | break 71 | if len(w) != bytes_per_data: 72 | for _ in range(len(w), bytes_per_data): 73 | w += b'\x00' 74 | unpack_order = { 75 | "little": "I" 77 | }[endianness] 78 | data[(base - offset)//bytes_per_data + i] = 0 79 | for filled_data_width in range(0, data_width, 32): 80 | cur_byte = filled_data_width//8 81 | data[(base - offset)//bytes_per_data + i] |= (struct.unpack(unpack_order, w[cur_byte:cur_byte+4])[0] << filled_data_width) 82 | i += 1 83 | return data 84 | 85 | def get_boot_address(filename_or_regions, offset=0): 86 | # Create memory regions. 87 | regions = get_mem_regions(filename_or_regions, offset) 88 | 89 | # Boot on last region. 90 | filename, base = regions.popitem() 91 | return int(base, 0) 92 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=64", "setuptools-git-versioning<2"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "luna-soc" 7 | description = "Amaranth HDL libary for building SoCs with LUNA USB peripherals." 8 | license = { text = "BSD" } 9 | readme = "README.md" 10 | requires-python = ">=3.9" 11 | authors = [ 12 | {name = "Great Scott Gadgets", email = "dev@greatscottgadgets.com"}, 13 | ] 14 | 15 | classifiers = [ 16 | "Development Status :: 4 - Beta", 17 | "Programming Language :: Python :: 3", 18 | "License :: OSI Approved :: BSD License", 19 | "Operating System :: OS Independent", 20 | "Natural Language :: English", 21 | "Environment :: Console", 22 | "Environment :: Other Environment", 23 | "Intended Audience :: Developers", 24 | "Intended Audience :: Science/Research", 25 | "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", 26 | "Topic :: System :: Hardware :: Universal Serial Bus (USB)", 27 | ] 28 | 29 | dynamic = ["version"] 30 | 31 | dependencies = [ 32 | "luna-usb~=0.2", 33 | ] 34 | 35 | [project.urls] 36 | Documentation = "https://luna-soc.readthedocs.io" 37 | Repository = "https://github.com/greatscottgadgets/luna-soc" 38 | Issues = "https://github.com/greatscottgadgets/luna-soc/issues" 39 | 40 | [tool.setuptools] 41 | include-package-data = true 42 | 43 | [tool.setuptools.package-dir] 44 | luna_soc = "luna_soc" 45 | 46 | [tool.setuptools.package-data] 47 | "luna_soc.gateware.cpu.verilog.vexriscv" = ["*.v"] 48 | 49 | [tool.setuptools-git-versioning] 50 | enabled = true 51 | starting_version = "0.2.0" --------------------------------------------------------------------------------