├── .gitignore ├── firmware ├── .gitignore ├── usb_txbuf.S ├── cpu_rf.S ├── usb_rxbuf.S ├── usb_ep.S ├── muacm.lds ├── Makefile ├── bin2hex.py ├── usb_hw.h ├── usb_desc.c └── main.S ├── example ├── .gitignore ├── data │ ├── top-tinyfpga-bx.pcf │ ├── top-bitsy-v0.pcf │ ├── top-bitsy-v1.pcf │ ├── top-fomu-pvt1.pcf │ └── top-fomu-hacker.pcf ├── rtl │ ├── sysmgr_hfosc.v │ ├── muacm_xclk.v │ ├── fifo_sync_shift.v │ ├── sysmgr_pll.v │ ├── dfu_helper.v │ └── top.v ├── Makefile └── README.md ├── .gitmodules ├── gateware ├── rtl │ ├── irom.v │ ├── wb_epbuf.v │ ├── extif.v │ └── muacm.v ├── Makefile └── sim │ └── muacm_tb.v ├── README-bin.md ├── utils ├── gen_metadata.py └── muacm_customize.py ├── doc ├── LICENSE-MIT.txt └── LICENSE-CERN-OHL-P-2.0.txt ├── LICENSE.md ├── dist.sh ├── .github └── workflows │ └── release.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /firmware/.gitignore: -------------------------------------------------------------------------------- 1 | *.elf 2 | *.bin 3 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | ip/ 2 | *.json 3 | *.asc 4 | *.bin 5 | *.log 6 | -------------------------------------------------------------------------------- /example/data/top-tinyfpga-bx.pcf: -------------------------------------------------------------------------------- 1 | ## pkg: cm81 2 | ## dev: lp8k 3 | 4 | # USB 5 | set_io -nowarn usb_dp B4 6 | set_io -nowarn usb_dn A4 7 | set_io -nowarn usb_pu A3 8 | 9 | # Clock 10 | set_io -nowarn clk_in B2 11 | set_frequency clk_in 16 12 | -------------------------------------------------------------------------------- /example/data/top-bitsy-v0.pcf: -------------------------------------------------------------------------------- 1 | ## pkg: sg48 2 | 3 | # USB 4 | set_io -nowarn usb_dp 43 5 | set_io -nowarn usb_dn 42 6 | set_io -nowarn usb_pu 38 7 | 8 | # Button 9 | set_io -nowarn -pullup yes -pullup_resistor 10K btn 10 10 | 11 | # Clock 12 | set_io -nowarn clk_in 35 13 | set_frequency clk_in 12 14 | -------------------------------------------------------------------------------- /example/data/top-bitsy-v1.pcf: -------------------------------------------------------------------------------- 1 | ## pkg: sg48 2 | 3 | # USB 4 | set_io -nowarn usb_dp 42 5 | set_io -nowarn usb_dn 38 6 | set_io -nowarn usb_pu 37 7 | 8 | # Button 9 | set_io -nowarn -pullup yes -pullup_resistor 10K btn 2 10 | 11 | # Clock 12 | set_io -nowarn clk_in 35 13 | set_frequency clk_in 12 14 | -------------------------------------------------------------------------------- /example/data/top-fomu-pvt1.pcf: -------------------------------------------------------------------------------- 1 | ## pkg: uwg30 2 | 3 | # USB 4 | set_io -nowarn usb_dp A1 5 | set_io -nowarn usb_dn A2 6 | set_io -nowarn usb_pu A4 7 | 8 | # Button 9 | set_io -nowarn -pullup yes -pullup_resistor 10K btn E4 # Pin 1 10 | 11 | # Clock 12 | set_io -nowarn clk_in F4 13 | set_frequency clk_in 48 14 | -------------------------------------------------------------------------------- /example/data/top-fomu-hacker.pcf: -------------------------------------------------------------------------------- 1 | ## pkg: uwg30 2 | 3 | # USB 4 | set_io -nowarn usb_dp A4 5 | set_io -nowarn usb_dn A2 6 | set_io -nowarn usb_pu D5 7 | 8 | # Button 9 | set_io -nowarn -pullup yes -pullup_resistor 10K btn F4 # Pin 1 10 | 11 | # Clock 12 | set_io -nowarn clk_in F5 13 | set_frequency clk_in 48 14 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "gateware/no2usb"] 2 | path = gateware/cores/no2usb 3 | url = https://github.com/no2fpga/no2usb.git 4 | [submodule "gateware/no2ice40"] 5 | path = gateware/cores/no2ice40 6 | url = https://github.com/no2fpga/no2ice40.git 7 | [submodule "gateware/cores/serv"] 8 | path = gateware/cores/serv 9 | url = https://github.com/no2fpga/serv.git 10 | -------------------------------------------------------------------------------- /firmware/usb_txbuf.S: -------------------------------------------------------------------------------- 1 | /* 2 | * usb_tx_buf.S 3 | * 4 | * EP RX buffer - Block RAM init 5 | * 6 | * Copyright (C) 2021 Sylvain Munaut 7 | * SPDX-License-Identifier: MIT 8 | */ 9 | 10 | 11 | // USB EP TX buffers 12 | // ----------------- 13 | 14 | .section .usb_txbuf.epbuf, "a" 15 | 16 | .org 0 // Start at 0 to ensure the ep2 buffers are 64 byte aligned 17 | 18 | .global ep2_tx_bd0 19 | ep2_tx_bd0: 20 | .space 64 21 | 22 | .global ep2_tx_bd1 23 | ep2_tx_bd1: 24 | .space 64 25 | 26 | .global ep0_tx 27 | ep0_tx: 28 | .space 8 29 | 30 | .global ep3_tx 31 | ep3_tx: 32 | .space 8 33 | -------------------------------------------------------------------------------- /firmware/cpu_rf.S: -------------------------------------------------------------------------------- 1 | /* 2 | * rf.S 3 | * 4 | * SERV Register File - Block RAM init 5 | * 6 | * Copyright (C) 2021 Sylvain Munaut 7 | * SPDX-License-Identifier: MIT 8 | */ 9 | 10 | .section .cpu_rf, "a" 11 | 12 | // Bank 0 13 | .org 0 14 | 15 | .word 0x00000000 // x0 16 | 17 | .org 120 18 | .word 0x00000800 // x30 = EP Buf base 19 | 20 | // Bank 1 21 | .org 128 22 | 23 | .space 32 // x0-x7: Unused (hard wired to bank 0) 24 | 25 | 26 | // Bank 2 27 | .org 256 28 | 29 | .space 32 // x0-x7: Unused (hard wired to bank 0) 30 | 31 | 32 | // Bank 3 33 | .org 384 34 | 35 | .space 32 // x0-x7: Unused (hard wired to bank 0) 36 | -------------------------------------------------------------------------------- /gateware/rtl/irom.v: -------------------------------------------------------------------------------- 1 | /* 2 | * irom.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Instruction ROM 256x32 7 | * 8 | * Copyright (C) 2021 Sylvain Munaut 9 | * SPDX-License-Identifier: CERN-OHL-P-2.0 10 | */ 11 | 12 | `default_nettype none 13 | 14 | module irom #( 15 | parameter integer AW = 8, 16 | parameter INIT_FILE = "" 17 | )( 18 | input wire [AW-1:0] wb_addr, 19 | output reg [ 31:0] wb_rdata, 20 | input wire wb_cyc, 21 | output reg wb_ack, 22 | input wire clk 23 | ); 24 | (* ram_style="block" *) 25 | reg [31:0] mem [0:(1< 7 | * SPDX-License-Identifier: MIT 8 | */ 9 | 10 | 11 | // USB EP RX buffers 12 | // ----------------- 13 | 14 | // Space reserved for actual USB packet RX 15 | 16 | .section .usb_rxbuf.epbuf, "a" 17 | 18 | .org 0 // Start at 0 to ensure the ep2 buffers are 64 byte aligned 19 | 20 | .global ep2_rx_bd0 21 | ep2_rx_bd0: 22 | .space 64 23 | 24 | .global ep2_rx_bd1 25 | ep2_rx_bd1: 26 | .space 64 27 | 28 | .global ep0_rx_data 29 | ep0_rx_data: 30 | .space 64 31 | 32 | .global ep0_rx_setup 33 | ep0_rx_setup: 34 | .space 64 35 | 36 | 37 | // Constants 38 | // --------- 39 | 40 | // Space used just for .rodata consants read 41 | // by the CPU 42 | 43 | .section .usb_rxbuf.const, "a" 44 | -------------------------------------------------------------------------------- /README-bin.md: -------------------------------------------------------------------------------- 1 | Nitro μACM prebuilt 2 | =================== 3 | 4 | This is a pre-built distribution of the Nitro μACM core. 5 | 6 | 7 | Nitro μACM core: 8 | ---------------- 9 | 10 | For details about the core itself and its interface, please 11 | refer to the top level `README.md`. 12 | 13 | If you're using the binary distribution package, then 14 | this should be available here as `README-core.md`. 15 | 16 | 17 | License 18 | ------- 19 | 20 | This pre-built μACM core is a mix between CERN-OHL-P-2.0 and ISC (the 21 | latter being for the included SERV core). It also contains a firmware 22 | licensed under MIT. 23 | 24 | All the details can be found in the Nitro μACM git repository 25 | ( https://github.com/no2fpga/no2muacm ) but from a practical user stand 26 | point, the only thing required when using this core is attribution to : 27 | 28 | - Nitro FPGA project ( Sylvain Munaut - https://github.com/no2fpga/ ) 29 | - SERV ( Olof Kindgren - https://github.com/olofk/serv ) 30 | -------------------------------------------------------------------------------- /utils/gen_metadata.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import json 4 | import os 5 | import re 6 | import struct 7 | import sys 8 | 9 | parse = re.compile('^([0-9a-f]{8})( [0-9a-f]{8})? [a-zA-Z] (.*)\n$') 10 | 11 | sym_ofs = {} 12 | sym_len = {} 13 | 14 | for sym_line in sys.stdin.readlines(): 15 | m = parse.match(sym_line) 16 | if not m: 17 | continue 18 | 19 | sym_ofs[m.group(3)] = int(m.group(1), 16) 20 | 21 | if m.group(2) is not None: 22 | sym_len[m.group(3)] = int(m.group(2), 16) 23 | 24 | 25 | print(json.dumps({ 26 | # Simple uint16_t 27 | 'vid': ( sym_ofs['desc_dev'] + 8, 2 ), 28 | 'pid': ( sym_ofs['desc_dev'] + 10, 2 ), 29 | 30 | # Zone to fill with \xff 31 | 'dfu_disable': ( sym_ofs['desc_conf'] + 14, 3 ), 32 | 33 | # Offset/Length of str descriptor in tx buf 34 | # + Offset in rx buf of the 'len' in the desc table 35 | 'serial': ( sym_ofs['desc_str1'], sym_len['desc_str1'], sym_ofs['desc_table'] + 8 * (3+1) + 2), 36 | 'vendor': ( sym_ofs['desc_str2'], sym_len['desc_str2'], sym_ofs['desc_table'] + 8 * (3+2) + 2), 37 | 'product': ( sym_ofs['desc_str3'], sym_len['desc_str3'], sym_ofs['desc_table'] + 8 * (3+3) + 2), 38 | })) 39 | -------------------------------------------------------------------------------- /doc/LICENSE-MIT.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019-2021, Sylvain Munaut 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/rtl/sysmgr_hfosc.v: -------------------------------------------------------------------------------- 1 | /* 2 | * sysmgr_hfosc.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * CRG generating 48 MHz from internal SB_HFOSC 7 | * 8 | * Copyright (C) 2021 Sylvain Munaut 9 | * SPDX-License-Identifier: CERN-OHL-P-2.0 10 | */ 11 | 12 | `default_nettype none 13 | 14 | module sysmgr_hfosc ( 15 | input wire rst_in, 16 | output wire clk_out, 17 | output wire rst_out 18 | ); 19 | 20 | // Signals 21 | wire clk_i; 22 | reg rst_i; 23 | reg [7:0] rst_cnt = 8'h00; 24 | 25 | // 48 MHz source 26 | SB_HFOSC #( 27 | .TRIM_EN("0b0"), 28 | .CLKHF_DIV("0b00") 29 | ) osc_I ( 30 | .CLKHFPU(1'b1), 31 | .CLKHFEN(1'b1), 32 | .CLKHF(clk_i) 33 | ); 34 | 35 | assign clk_out = clk_i; 36 | 37 | // Logic reset generation 38 | // (need a larger delay here because without pll lock delay, BRAMs aren't 39 | // ready in time ...) 40 | always @(posedge clk_i or posedge rst_in) 41 | if (rst_in) 42 | rst_cnt <= 8'h00; 43 | else if (rst_i) 44 | rst_cnt <= rst_cnt + 1; 45 | 46 | always @(posedge clk_i) 47 | rst_i <= ~&rst_cnt[7:4]; 48 | 49 | SB_GB rst_gbuf_I ( 50 | .USER_SIGNAL_TO_GLOBAL_BUFFER(rst_i), 51 | .GLOBAL_BUFFER_OUTPUT(rst_out) 52 | ); 53 | 54 | endmodule // sysmgr_hfosc 55 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | In general in this repository: 2 | 3 | - The HDL core itself is licensed under the terms of the 4 | "CERN Open Hardware Licence Version 2 - Permissive" license. 5 | 6 | - The firmware running on the soft core is licensed under the terms 7 | of the MIT license 8 | 9 | - The various small utilities / scripts are licensed under 10 | the terms of the MIT license. 11 | 12 | - Included cores / libraries (through submodules or otherwise) may 13 | be licensed under a different license. 14 | 15 | . The SERV core is under the ISC license 16 | 17 | . The various "Nitro FPGA" cores can have different licenses for 18 | different parts of the cloned submodule, however, only components 19 | using compatible licenses are effectively used by this repo and 20 | end up in the final build products. 21 | 22 | - In short, the build product of this repository can be considered to be 23 | CERN-OHL-P-2.0 (gateware) / MIT (firmware) with needing attribution to: 24 | 25 | . Nitro FPGA project ( Sylvain Munaut - https://github.com/no2fpga/ ) 26 | . SERV ( Olof Kindgren - https://github.com/olofk/serv ) 27 | 28 | 29 | Refer to the header of each file to see which license it is under. 30 | 31 | See the `doc/` subdirectory for the full text of those licenses. 32 | -------------------------------------------------------------------------------- /firmware/usb_ep.S: -------------------------------------------------------------------------------- 1 | /* 2 | * usb_ep.S 3 | * 4 | * EP Status - Block RAM init 5 | * 6 | * Copyright (C) 2021 Sylvain Munaut 7 | * SPDX-License-Identifier: MIT 8 | */ 9 | 10 | #include "usb_hw.h" 11 | 12 | #define EP(n) ( (((n) & 0xf) << 6) | (((n) & 0x80) >> 2) ) 13 | 14 | 15 | .section .usb_ep, "a" 16 | 17 | /* EP 0 OUT */ 18 | .org EP(0x00) 19 | 20 | .word USB_EP_TYPE_CTRL | USB_EP_BD_CTRL 21 | .space 12 22 | 23 | .word 0 // BD0 CSR 24 | .word ep0_rx_data // BD0 PTR 25 | .word 0 // BD1 CSR 26 | .word ep0_rx_setup // BD1 PTR 27 | 28 | /* EP 0 IN */ 29 | .org EP(0x80) 30 | 31 | .word USB_EP_TYPE_CTRL | USB_EP_DT_BIT 32 | .space 12 33 | 34 | /* EP 2 OUT */ 35 | .org EP(0x02) 36 | 37 | .word USB_EP_TYPE_BULK | USB_EP_BD_DUAL 38 | .space 12 39 | 40 | .word 0 // BD0 CSR 41 | .word ep2_rx_bd0 // BD0 PTR 42 | .word 0 // BD1 CSR 43 | .word ep2_rx_bd1 // BD1 PTR 44 | 45 | /* EP 2 IN */ 46 | .org EP(0x82) 47 | 48 | .word USB_EP_TYPE_BULK | USB_EP_BD_DUAL 49 | .space 12 50 | 51 | .word 0 // BD0 CSR 52 | .word ep2_tx_bd0 // BD0 PTR 53 | .word 0 // BD1 CSR 54 | .word ep2_tx_bd1 // BD1 PTR 55 | 56 | /* EP 3 OUT */ 57 | .org EP(0x03) 58 | 59 | .word USB_EP_TYPE_BULK 60 | .space 12 61 | 62 | .word 0 // BD0 CSR 63 | .word ep3_tx // BD0 PTR 64 | .word 0 // BD1 CSR 65 | .word 0 // BD1 PTR 66 | -------------------------------------------------------------------------------- /firmware/muacm.lds: -------------------------------------------------------------------------------- 1 | /* 2 | * muacm.S 3 | * 4 | * Linker script to generate the various Block RAM 5 | * init zones and associated cross references. 6 | * 7 | * Copyright (C) 2021 Sylvain Munaut 8 | * SPDX-License-Identifier: MIT 9 | */ 10 | 11 | OUTPUT_ARCH( "riscv" ) 12 | ENTRY(_start) 13 | 14 | MEMORY 15 | { 16 | IROM (rx) : ORIGIN = 0x00000000, LENGTH = 0x0400 17 | USB_RXBUF (r) : ORIGIN = 0x00000000, LENGTH = 0x0200 18 | USB_TXBUF (r) : ORIGIN = 0x00000000, LENGTH = 0x0200 19 | USB_EP (r) : ORIGIN = 0x00000000, LENGTH = 0x0200 20 | CPU_RF (r) : ORIGIN = 0x00000000, LENGTH = 0x0200 21 | } 22 | 23 | SECTIONS 24 | { 25 | 26 | .text : 27 | { 28 | . = ALIGN(4); 29 | KEEP( *(.text.start) ) 30 | KEEP( *(.text) ) 31 | KEEP( *(.text*) ) 32 | } > IROM 33 | 34 | .usb_rxbuf : AT ( 0x00040000 ) 35 | { 36 | . = 0; 37 | KEEP( *(.usb_rxbuf.epbuf) ) 38 | KEEP( *(.usb_rxbuf.const) ) 39 | } > USB_RXBUF 40 | 41 | .usb_txbuf : AT ( 0x00050000 ) 42 | { 43 | . = 0; 44 | KEEP( *(.usb_txbuf.epbuf) ) 45 | KEEP( *(.usb_txbuf.desc) ) 46 | } > USB_TXBUF 47 | 48 | .usb_ep : AT ( 0x00060000 ) 49 | { 50 | . = 0; 51 | KEEP( *(.usb_ep) ) 52 | } > USB_EP 53 | 54 | .cpu_rf : AT ( 0x00070000 ) 55 | { 56 | . = 0; 57 | KEEP( *(.cpu_rf) ) 58 | } > CPU_RF 59 | 60 | } 61 | -------------------------------------------------------------------------------- /example/Makefile: -------------------------------------------------------------------------------- 1 | PROJ := acm-loopback 2 | 3 | BOARD ?= bitsy-v1 4 | PCF := data/top-$(BOARD).pcf 5 | 6 | H := \# 7 | DEVICE := $(shell awk '/$(H)$(H) dev:/{print $$3; exit 1}' $(PCF) && echo up5k) 8 | PACKAGE := $(shell awk '/$(H)$(H) pkg:/{print $$3; exit 1}' $(PCF) && echo sg48) 9 | 10 | SRCS_acm-loopback = $(addprefix rtl/, \ 11 | dfu_helper.v \ 12 | fifo_sync_shift.v \ 13 | muacm_xclk.v \ 14 | sysmgr_hfosc.v \ 15 | sysmgr_pll.v \ 16 | top.v \ 17 | ) 18 | TOPMOD := top 19 | 20 | MUACM_ILANG := ip/muacm.ilang 21 | 22 | YOSYS := yosys 23 | NEXTPNR := nextpnr-ice40 24 | ICEPACK := icepack 25 | DFU_UTIL := dfu-util 26 | 27 | BOARD_DEFINE := BOARD_$(shell echo $(BOARD) | tr a-z\- A-Z_) 28 | YOSYS_READ_ARGS := -D$(BOARD_DEFINE)=1 29 | YOSYS_SYNTH_ARGS := 30 | NEXTPNR_ARGS := --no-promote-globals 31 | 32 | all: $(PROJ).bin 33 | 34 | 35 | %.json %.synth.log: $(MUACM_ILANG) $(SRCS_$(PROJ)) 36 | $(YOSYS) -L $*.synth.log -p 'read_verilog $(YOSYS_READ_ARGS) $(SRCS_$(PROJ)); read_ilang $(MUACM_ILANG); synth_ice40 $(YOSYS_SYNTH_ARGS) -top $(TOPMOD) -json $*.json' 37 | 38 | %.asc %.pnr.log: $(PCF) %.json 39 | $(NEXTPNR) $(NEXTPNR_ARGS) --$(DEVICE) --package $(PACKAGE) --json $*.json --pcf $(PCF) --log $*.pnr.log --asc $*.asc 40 | 41 | %.bin: %.asc 42 | $(ICEPACK) -s $< $@ 43 | 44 | %.ilang: %.ilang.bz2 45 | bzcat $< > $@ 46 | 47 | 48 | prog: $(PROJ).bin 49 | $(DFU_UTIL) -a 0 -R -D $< 50 | 51 | clean: 52 | rm -f *.json *.asc *.bin *.log 53 | 54 | .PHONY: all clean 55 | .PRECIOUS: %.ilang %.json %.asc %.bin 56 | -------------------------------------------------------------------------------- /firmware/Makefile: -------------------------------------------------------------------------------- 1 | CROSS ?= riscv-none-embed- 2 | CC = $(CROSS)gcc 3 | OBJCOPY = $(CROSS)objcopy 4 | NM = $(CROSS)nm 5 | CFLAGS=-Wall -Os -march=rv32i -mabi=ilp32 -ffreestanding -nostartfiles --specs=nano.specs -mno-relax -I. 6 | 7 | OUT_DIR=../build 8 | 9 | SRCS = \ 10 | main.S \ 11 | cpu_rf.S \ 12 | usb_ep.S \ 13 | usb_rxbuf.S \ 14 | usb_txbuf.S \ 15 | usb_desc.c \ 16 | $(NULL) 17 | 18 | TARGETS = $(addprefix $(OUT_DIR)/, \ 19 | text.hex \ 20 | usb_txbuf.hex \ 21 | usb_rxbuf.hex \ 22 | usb_ep.hex \ 23 | cpu_rf.hex \ 24 | meta.json \ 25 | ) 26 | 27 | 28 | all: $(OUT_DIR) $(TARGETS) 29 | 30 | $(OUT_DIR): 31 | mkdir -p $(OUT_DIR) 32 | 33 | 34 | # Main firmware build 35 | 36 | main.elf: muacm.lds $(SRCS) 37 | $(CC) $(CFLAGS) -Wl,-Bstatic,-T,muacm.lds,--strip-debug -o $@ -I../gateware/cores/no2usb/fw/v0/include $(SRCS) 38 | 39 | main.%.bin: main.elf 40 | $(OBJCOPY) -O binary -j .$* $< $@ 41 | 42 | 43 | # Format to .hex depending on the BRAM geometry 44 | 45 | $(OUT_DIR)/text.hex: main.text.bin 46 | ./bin2hex.py $< $@ 32 47 | 48 | $(OUT_DIR)/usb_txbuf.hex: main.usb_txbuf.bin 49 | ./bin2hex.py $< $@ 16s 50 | 51 | $(OUT_DIR)/usb_rxbuf.hex: main.usb_rxbuf.bin 52 | ./bin2hex.py $< $@ 16s 53 | 54 | $(OUT_DIR)/usb_ep.hex: main.usb_ep.bin 55 | ./bin2hex.py $< $@ 32:16 56 | 57 | $(OUT_DIR)/cpu_rf.hex: main.cpu_rf.bin 58 | ./bin2hex.py $< $@ rf 59 | 60 | 61 | # Meta data for customizer 62 | 63 | $(OUT_DIR)/meta.json: main.elf 64 | $(NM) -S $< | ../utils/gen_metadata.py > $@ 65 | 66 | 67 | # Cleanup 68 | 69 | clean: 70 | rm -f *.elf *.bin $(OUT_DIR)/*.hex $(OUT_DIR)/meta.json 71 | 72 | .PHONY: clean 73 | -------------------------------------------------------------------------------- /dist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | TAG=`date +%Y%m%d`-`git rev-parse --short HEAD` 6 | DEST_BIN="build/muacm-bin-${TAG}" 7 | DEST_EXAMPLE="build/muacm-example-${TAG}" 8 | 9 | # Build gateware 10 | make -C gateware 11 | bzip2 -kf build/muacm.ilang 12 | bzip2 -kf build/muacm.v 13 | 14 | # Save tag 15 | echo -n "${TAG}" > "build/tag.txt" 16 | 17 | # Build 'bin' dist directory 18 | mkdir -p "${DEST_BIN}" 19 | 20 | cp "build/muacm.ilang" "${DEST_BIN}" 21 | cp "build/muacm.v" "${DEST_BIN}" 22 | cp "example/rtl/muacm_xclk.v" "${DEST_BIN}" 23 | cp "utils/muacm_customize.py" "${DEST_BIN}" 24 | 25 | cp "README.md" "${DEST_BIN}/README-core.md" 26 | cp "README-bin.md" "${DEST_BIN}/README.md" 27 | cp "doc/LICENSE-CERN-OHL-P-2.0.txt" "${DEST_BIN}" 28 | cp "doc/LICENSE-MIT.txt" "${DEST_BIN}" 29 | cp "gateware/cores/serv/LICENSE" "${DEST_BIN}/LICENSE-ISC-SERV.txt" 30 | 31 | # Build 'example' dist directory 32 | mkdir -p "${DEST_EXAMPLE}" 33 | mkdir -p "${DEST_EXAMPLE}/ip" 34 | mkdir -p "${DEST_EXAMPLE}/utils" 35 | 36 | cp -a example/* "${DEST_EXAMPLE}" 37 | 38 | cp "build/muacm.ilang.bz2" "${DEST_EXAMPLE}/ip" 39 | cp "build/muacm.v.bz2" "${DEST_EXAMPLE}/ip" 40 | 41 | cp "README.md" "${DEST_EXAMPLE}/README-core.md" 42 | cp "doc/LICENSE-CERN-OHL-P-2.0.txt" "${DEST_EXAMPLE}" 43 | cp "doc/LICENSE-MIT.txt" "${DEST_EXAMPLE}" 44 | cp "gateware/cores/serv/LICENSE" "${DEST_EXAMPLE}/LICENSE-ISC-SERV.txt" 45 | 46 | cp "utils/muacm_customize.py" "${DEST_EXAMPLE}/utils" 47 | 48 | # Create the archives 49 | pushd build 50 | tar -cjvf "muacm-bin-${TAG}.tar.bz2" "muacm-bin-${TAG}" 51 | tar -cjvf "muacm-example-${TAG}.tar.bz2" "muacm-example-${TAG}" 52 | popd 53 | -------------------------------------------------------------------------------- /gateware/rtl/wb_epbuf.v: -------------------------------------------------------------------------------- 1 | /* 2 | * wb_epbuf.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Bridge between 32b wishbone and the 16b EP buffer 7 | * interface. 8 | * 9 | * Copyright (C) 2021 Sylvain Munaut 10 | * SPDX-License-Identifier: CERN-OHL-P-2.0 11 | */ 12 | 13 | `default_nettype none 14 | 15 | module wb_epbuf ( 16 | // Wishbone 17 | input wire [6:0] wb_addr, 18 | output wire [31:0] wb_rdata, 19 | input wire [31:0] wb_wdata, 20 | input wire [3:0] wb_wmsk, 21 | input wire wb_we, 22 | input wire wb_cyc, 23 | output wire wb_ack, 24 | 25 | // EP buffer interface 26 | output wire [ 7:0] ep_tx_addr_0, 27 | output wire [15:0] ep_tx_data_0, 28 | output wire [ 1:0] ep_tx_wmsk_0, 29 | output wire ep_tx_we_0, 30 | 31 | output wire [8:0] ep_rx_addr_0, 32 | input wire [15:0] ep_rx_data_1, 33 | output wire ep_rx_re_0, 34 | 35 | // Clock 36 | input wire clk, 37 | input wire rst 38 | ); 39 | 40 | // Signals 41 | // ------- 42 | 43 | reg b_cyd; 44 | reg b_ack; 45 | reg b_we; 46 | 47 | reg [15:0] ep_rx_data_lsb; 48 | 49 | 50 | // Control 51 | // ------- 52 | 53 | always @(posedge clk) 54 | begin 55 | b_cyd <= wb_cyc & ~b_ack; 56 | b_we <= wb_cyc & ~b_ack & wb_we; 57 | b_ack <= b_cyd & ~b_ack; 58 | end 59 | 60 | assign wb_ack = b_ack; 61 | 62 | 63 | // TX Writes 64 | // --------- 65 | 66 | assign ep_tx_addr_0 = { wb_addr, b_ack }; 67 | assign ep_tx_data_0 = b_ack ? wb_wdata[31:16] : wb_wdata[15:0]; 68 | assign ep_tx_wmsk_0 = b_ack ? wb_wmsk [ 3: 2] : wb_wmsk [ 1:0]; 69 | assign ep_tx_we_0 = b_we; 70 | 71 | 72 | // RX Reads 73 | // -------- 74 | 75 | assign ep_rx_addr_0 = { wb_addr, b_cyd }; 76 | assign ep_rx_re_0 = 1'b1; 77 | 78 | always @(posedge clk) 79 | ep_rx_data_lsb <= ep_rx_data_1; 80 | 81 | assign wb_rdata = { ep_rx_data_1, ep_rx_data_lsb }; 82 | 83 | endmodule // wb_epbuf 84 | -------------------------------------------------------------------------------- /example/rtl/muacm_xclk.v: -------------------------------------------------------------------------------- 1 | /* 2 | * muacm_xclk.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Cross clock module for the muACM data port. 7 | * ( You'll need one instance per direction ) 8 | * 9 | * Copyright (C) 2021 Sylvain Munaut 10 | * SPDX-License-Identifier: CERN-OHL-P-2.0 11 | */ 12 | 13 | `default_nettype none 14 | 15 | module muacm_xclk ( 16 | input wire [7:0] i_data, 17 | input wire i_last, 18 | input wire i_valid, 19 | output reg i_ready, 20 | input wire i_clk, 21 | 22 | output wire [7:0] o_data, 23 | output wire o_last, 24 | output reg o_valid, 25 | input wire o_ready, 26 | input wire o_clk, 27 | 28 | input wire rst 29 | ); 30 | 31 | // Signals 32 | reg send_i; 33 | reg [1:0] send_sync_o; 34 | 35 | reg ack_o; 36 | reg [1:0] ack_sync_i; 37 | 38 | // Data 39 | assign o_data = i_data; 40 | assign o_last = i_last; 41 | 42 | // Handshake 43 | always @(posedge i_clk or posedge rst) 44 | if (rst) 45 | send_i <= 1'b0; 46 | else 47 | send_i <= ( send_i | (i_valid & ~i_ready) ) & ~ack_sync_i[0]; 48 | 49 | always @(posedge o_clk or posedge rst) 50 | if (rst) 51 | send_sync_o <= 2'b00; 52 | else 53 | send_sync_o <= { send_sync_o[0], send_i }; 54 | 55 | always @(posedge o_clk or posedge rst) 56 | if (rst) 57 | o_valid <= 1'b0; 58 | else 59 | o_valid <= (o_valid & ~o_ready) | (send_sync_o[0] & ~send_sync_o[1]); 60 | 61 | always @(posedge o_clk or posedge rst) 62 | if (rst) 63 | ack_o <= 1'b0; 64 | else 65 | ack_o <= (ack_o & send_sync_o[0]) | (o_valid & o_ready); 66 | 67 | always @(posedge i_clk or posedge rst) 68 | if (rst) 69 | ack_sync_i <= 2'b00; 70 | else 71 | ack_sync_i <= { ack_sync_i[0], ack_o }; 72 | 73 | always @(posedge i_clk or posedge rst) 74 | if (rst) 75 | i_ready <= 1'b0; 76 | else 77 | i_ready <= ack_sync_i[0] & ~ack_sync_i[1]; 78 | 79 | endmodule // muacm_xclk 80 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | branches: [ $default-branch ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v2 14 | 15 | - name: Checkout submodules 16 | run: git submodule update --init --recursive 17 | 18 | - name: Install FPGA toolchain 19 | uses: YosysHQ/setup-oss-cad-suite@v1 20 | 21 | - name: Install RISC-V toolchain 22 | run: | 23 | wget https://github.com/xpack-dev-tools/riscv-none-embed-gcc-xpack/releases/download/v10.1.0-1.1/xpack-riscv-none-embed-gcc-10.1.0-1.1-linux-x64.tar.gz 24 | tar -xf xpack-riscv-none-embed-gcc-10.1.0-1.1-linux-x64.tar.gz 25 | rm xpack-riscv-none-embed-gcc-10.1.0-1.1-linux-x64.tar.gz 26 | echo "$(pwd)/xpack-riscv-none-embed-gcc-10.1.0-1.1/bin" >> $GITHUB_PATH 27 | 28 | - name: Run gateware build 29 | run: | 30 | ./dist.sh 31 | TAG=$(cat build/tag.txt) 32 | echo "TAG=$TAG" >> $GITHUB_ENV 33 | 34 | - name: Build artifact upload 35 | uses: actions/upload-artifact@v2 36 | with: 37 | name: muacm-build 38 | path: | 39 | build/muacm-bin-*.tar.bz2 40 | build/muacm-example-*.tar.bz2 41 | 42 | - name: Release (as draft) 43 | uses: softprops/action-gh-release@v1 44 | with: 45 | name: Release ${{ env.TAG }} 46 | tag_name: ${{ env.TAG }} 47 | draft: true 48 | files: | 49 | build/muacm-bin-*.tar.bz2 50 | build/muacm-example-*.tar.bz2 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | -------------------------------------------------------------------------------- /firmware/bin2hex.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # bin2hex.py 5 | # 6 | # Utility to convert a binary into a HEX init file that can be 7 | # used to init Block RAM with $readmemh. It also includes required 8 | # width conversion and bit shuffling for various modes used in this 9 | # project 10 | # 11 | # Copyright (C) 2021 Sylvain Munaut 12 | # SPDX-License-Identifier: MIT 13 | # 14 | 15 | import struct 16 | import sys 17 | 18 | 19 | def process_32(out_fh, in_fh): 20 | b = in_fh.read(4) 21 | if len(b) < 4: 22 | return False 23 | out_fh.write('%08x\n' % struct.unpack('>(15-i))&1) << j for i,j in enumerate(SEQ)]) 46 | out_fh.write('%04x\n' % b) 47 | return True 48 | 49 | 50 | def process_32_16(out_fh, in_fh): 51 | b = in_fh.read(4) 52 | if len(b) < 4: 53 | return False 54 | out_fh.write('%04x\n' % (struct.unpack('>= 2 66 | return True 67 | 68 | 69 | def main(argv0, in_name, out_name, mode="32"): 70 | PFN = { 71 | '32': process_32, 72 | '16': process_16, 73 | '16s': process_16s, 74 | '32:16': process_32_16, 75 | 'rf': process_rf, 76 | } 77 | 78 | with open(in_name, 'rb') as in_fh, open(out_name, 'w') as out_fh: 79 | while PFN[mode](out_fh, in_fh): 80 | pass 81 | 82 | 83 | if __name__ == '__main__': 84 | main(*sys.argv) 85 | -------------------------------------------------------------------------------- /firmware/usb_hw.h: -------------------------------------------------------------------------------- 1 | /* 2 | * usb_hw.h 3 | * 4 | * HW register definitions 5 | * 6 | * Copyright (C) 2019-2021 Sylvain Munaut 7 | * SPDX-License-Identifier: MIT 8 | */ 9 | 10 | #pragma once 11 | 12 | /* Base register */ 13 | #define USB_BASE zero 14 | 15 | /* CSR register */ 16 | #define USB_CSR 0(USB_BASE) 17 | 18 | #define USB_CSR_PU_ENA (1 << 15) 19 | #define USB_CSR_EVT_PENDING (1 << 14) 20 | #define USB_CSR_CEL_ACTIVE (1 << 13) 21 | #define USB_CSR_CEL_ENA (1 << 12) 22 | #define USB_CSR_BUS_SUSPEND (1 << 11) 23 | #define USB_CSR_BUS_RST (1 << 10) 24 | #define USB_CSR_BUS_RST_PENDING (1 << 9) 25 | #define USB_CSR_SOF_PENDING (1 << 8) 26 | #define USB_CSR_ADDR_MATCH (1 << 7) 27 | #define USB_CSR_ADDR(x) ((x) & 0x7f) 28 | 29 | /* Action register */ 30 | #define USB_AR 4(USB_BASE) 31 | 32 | #define USB_AR_CEL_RELEASE (1 << 13) 33 | #define USB_AR_BUS_RST_CLEAR (1 << 9) 34 | #define USB_AR_SOF_CLEAR (1 << 8) 35 | 36 | /* Event register */ 37 | #define USB_EVT 8(USB_BASE) 38 | 39 | /* Endpoint CSR */ 40 | #define EP_CSR(n) (1024 + (((n) & 0xf) << 6) + (((n) & 0x80) >> 2) + 0)(USB_BASE) 41 | 42 | #define USB_EP_TYPE_NONE 0x0000 43 | #define USB_EP_TYPE_ISOC 0x0001 44 | #define USB_EP_TYPE_INT 0x0002 45 | #define USB_EP_TYPE_BULK 0x0004 46 | #define USB_EP_TYPE_CTRL 0x0006 47 | #define USB_EP_TYPE_HALTED 0x0001 48 | #define USB_EP_TYPE_IS_BCI(x) (((x) & 6) != 0) 49 | #define USB_EP_TYPE(x) ((x) & 7) 50 | #define USB_EP_TYPE_MSK 0x0007 51 | 52 | #define USB_EP_DT_BIT 0x0080 53 | #define USB_EP_BD_IDX 0x0040 54 | #define USB_EP_BD_CTRL 0x0020 55 | #define USB_EP_BD_DUAL 0x0010 56 | 57 | /* Endpoint Buffer Descriptor */ 58 | #define EP_BD_CSR(n,i) (1024 + (((n) & 0xf) << 6) + (((n) & 0x80) >> 2) + 16 + ((i)*8) + 0)(USB_BASE) 59 | #define EP_BD_PTR(n,i) (1024 + (((n) & 0xf) << 6) + (((n) & 0x80) >> 2) + 16 + ((i)*8) + 4)(USB_BASE) 60 | 61 | #define USB_BD_STATE_MSK 0xe000 62 | #define USB_BD_STATE_NONE 0x0000 63 | #define USB_BD_STATE_RDY_DATA 0x4000 64 | #define USB_BD_STATE_RDY_STALL 0x6000 65 | #define USB_BD_STATE_DONE_OK 0x8000 66 | #define USB_BD_STATE_DONE_ERR 0xa000 67 | #define USB_BD_IS_SETUP 0x1000 68 | 69 | #define USB_BD_LEN(l) ((l) & 0x3ff) 70 | #define USB_BD_LEN_MSK 0x03ff 71 | -------------------------------------------------------------------------------- /example/rtl/fifo_sync_shift.v: -------------------------------------------------------------------------------- 1 | /* 2 | * fifo_sync_shift.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Simple synchronous fifo using registers as storage element for 7 | * very shallow FIFOs. 8 | * 9 | * - First-Word-Fall-Thru operating mode 10 | * - Safeties not included (i.e. don't read from empty or 11 | * write to full FIFO !) 12 | * 13 | * Copyright (C) 2019-2020 Sylvain Munaut 14 | * SPDX-License-Identifier: CERN-OHL-P-2.0 15 | */ 16 | 17 | `default_nettype none 18 | 19 | module fifo_sync_shift #( 20 | parameter integer DEPTH = 4, 21 | parameter integer WIDTH = 16 22 | )( 23 | input wire [WIDTH-1:0] wr_data, 24 | input wire wr_ena, 25 | output wire wr_full, 26 | 27 | output wire [WIDTH-1:0] rd_data, 28 | input wire rd_ena, 29 | output wire rd_empty, 30 | 31 | input wire clk, 32 | input wire rst 33 | ); 34 | 35 | // Signals 36 | // ------- 37 | 38 | wire [DEPTH+1:0] ce; 39 | wire [DEPTH+1:0] valid; 40 | wire [WIDTH-1:0] data [DEPTH+1:0]; 41 | 42 | 43 | // Stages 44 | // ------ 45 | 46 | // Generate loop 47 | genvar i; 48 | 49 | generate 50 | for (i=1; i<=DEPTH; i=i+1) 51 | begin : stage 52 | // Local signals 53 | reg [WIDTH-1:0] l_data; 54 | reg l_valid; 55 | 56 | // Data register 57 | always @(posedge clk or posedge rst) 58 | if (rst) 59 | l_data <= 0; 60 | else if (ce[i]) 61 | l_data <= valid[i+1] ? data[i+1] : wr_data; 62 | 63 | // Valid flag 64 | always @(posedge clk or posedge rst) 65 | if (rst) 66 | l_valid <= 1'b0; 67 | else if (ce[i]) 68 | l_valid <= ~rd_ena | valid[i+1] | (wr_ena & valid[i]); 69 | 70 | // CE for this stage 71 | assign ce[i] = rd_ena | (wr_ena & ~valid[i] & valid[i-1]); 72 | 73 | // Map 74 | assign data[i] = l_data; 75 | assign valid[i] = l_valid; 76 | end 77 | endgenerate 78 | 79 | // Boundary conditions 80 | assign data[DEPTH+1] = wr_data; 81 | assign data[0] = { WIDTH{1'bx} }; 82 | 83 | assign valid[DEPTH+1] = 1'b0; 84 | assign valid[0] = 1'b1; 85 | 86 | assign ce[DEPTH+1] = 1'bx; 87 | assign ce[0] = 1'bx; 88 | 89 | 90 | // User IF 91 | // ------- 92 | 93 | assign wr_full = valid[DEPTH]; 94 | 95 | assign rd_empty = ~valid[1]; 96 | assign rd_data = data[1]; 97 | 98 | endmodule // fifo_sync_shift 99 | -------------------------------------------------------------------------------- /gateware/Makefile: -------------------------------------------------------------------------------- 1 | # Sources 2 | SRCS_SERV = $(abspath $(addprefix cores/serv/rtl/, \ 3 | serv_alu.v \ 4 | serv_bufreg.v \ 5 | serv_csr.v \ 6 | serv_ctrl.v \ 7 | serv_decode.v \ 8 | serv_immdec.v \ 9 | serv_mem_if.v \ 10 | serv_rf_top.v \ 11 | serv_rf_ram.v \ 12 | serv_rf_ram_if.v \ 13 | serv_rf_if.v \ 14 | serv_state.v \ 15 | serv_top.v \ 16 | )) 17 | 18 | SRCS_NO2USB = $(abspath $(addprefix cores/no2usb/rtl/, \ 19 | usb_crc.v \ 20 | usb_ep_buf.v \ 21 | usb_ep_status.v \ 22 | usb_phy.v \ 23 | usb_rx_ll.v \ 24 | usb_rx_pkt.v \ 25 | usb_trans.v \ 26 | usb_tx_ll.v \ 27 | usb_tx_pkt.v \ 28 | usb.v \ 29 | )) 30 | 31 | SRCS_NO2ICE40 = $(abspath $(addprefix cores/no2ice40/rtl/, \ 32 | ice40_ebr.v \ 33 | )) 34 | 35 | SRCS_MUACM = $(abspath $(addprefix rtl/, \ 36 | extif.v \ 37 | irom.v \ 38 | wb_epbuf.v \ 39 | muacm.v \ 40 | )) 41 | 42 | AUX_DATA = $(addprefix $(OUT_DIR)/, \ 43 | cpu_rf.hex \ 44 | text.hex \ 45 | usb_ep.hex \ 46 | usb_rxbuf.hex \ 47 | usb_txbuf.hex \ 48 | usb_trans_mc.hex \ 49 | meta.json \ 50 | ) 51 | 52 | # Build config / tgt 53 | OUT_DIR := ../build 54 | 55 | all: $(OUT_DIR)/muacm.ilang $(OUT_DIR)/muacm.v 56 | sim: $(OUT_DIR)/muacm_tb 57 | 58 | # Dependencies 59 | $(OUT_DIR): 60 | mkdir -p $(OUT_DIR) 61 | 62 | $(AUX_DATA): 63 | make -C ../firmware $@ 64 | 65 | $(OUT_DIR)/usb_trans_mc.hex: ./cores/no2usb/utils/microcode.py 66 | ./cores/no2usb/utils/microcode.py mini > $@ 67 | 68 | # Pre-built core 69 | $(OUT_DIR)/muacm.ilang $(OUT_DIR)/muacm.v: $(OUT_DIR) $(AUX_DATA) $(SRCS_SERV) $(SRCS_NO2USB) $(SRCS_NO2ICE40) $(SRCS_MUACM) 70 | cd ../build && \ 71 | yosys \ 72 | -L $(OUT_DIR)/synth.log \ 73 | -p 'read_verilog -DSERV_INIT_RAM="cpu_rf.hex" -I../build $(SRCS_SERV) $(SRCS_NO2USB) $(SRCS_NO2ICE40) $(SRCS_MUACM); synth_ice40 -abc2 -top muacm; write_ilang $(OUT_DIR)/muacm.ilang; write_verilog $(OUT_DIR)/muacm.v' 74 | echo "# META: $$(cat $(OUT_DIR)/meta.json)" >> $(OUT_DIR)/muacm.ilang 75 | echo "/* META: $$(cat $(OUT_DIR)/meta.json) */" >> $(OUT_DIR)/muacm.v 76 | 77 | # Test bench 78 | $(OUT_DIR)/%_tb: $(OUT_DIR) $(AUX_DATA) $(SRCS_SERV) $(SRCS_NO2USB) $(SRCS_NO2ICE40) $(SRCS_MUACM) sim/%_tb.v 79 | iverilog \ 80 | -DNO_ICE40_DEFAULT_ASSIGNMENTS \ 81 | -DSIM -DSERV_INIT_RAM=\"cpu_rf.hex\" \ 82 | -DSIM_TRACE \ 83 | -o $@ \ 84 | -s $*_tb \ 85 | -Icores/no2usb/rtl/ \ 86 | `yosys-config --datdir/ice40/cells_sim.v` \ 87 | $(SRCS_SERV) \ 88 | $(SRCS_NO2USB) \ 89 | $(SRCS_NO2ICE40) \ 90 | $(SRCS_MUACM) \ 91 | sim/$*_tb.v 92 | 93 | clean: 94 | rm -f $(OUT_DIR)/muacm.ilang $(OUT_DIR)/muacm.v $(OUT_DIR)/muacm_tb $(OUT_DIR)/synth.log 95 | make -C ../firmware clean 96 | 97 | .PHONY: clean 98 | -------------------------------------------------------------------------------- /example/rtl/sysmgr_pll.v: -------------------------------------------------------------------------------- 1 | /* 2 | * sysmgr_pll.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * CRG generating 24 & 48 MHz from external 12/48 MHz 7 | * (depending on board) 8 | * 9 | * Copyright (C) 2021 Sylvain Munaut 10 | * SPDX-License-Identifier: CERN-OHL-P-2.0 11 | */ 12 | 13 | `default_nettype none 14 | 15 | module sysmgr_pll ( 16 | input wire clk_in, 17 | input wire rst_in, 18 | output wire clk_24m, 19 | output wire clk_48m, 20 | output wire rst_out 21 | ); 22 | 23 | // Signals 24 | wire pll_lock; 25 | wire pll_reset_n; 26 | 27 | wire clk_1x; 28 | wire clk_2x; 29 | wire rst_i; 30 | reg [3:0] rst_cnt; 31 | 32 | // Clock frequency input depends on board 33 | `ifdef BOARD_FOMU_HACKER 34 | `define CLK_IN_FABRIC 35 | `define CLK_IN_48M 36 | `elsif BOARD_FOMU_PVT1 37 | `define CLK_IN_FABRIC 38 | `define CLK_IN_48M 39 | `elsif BOARD_TINYFPGA_BX 40 | `define CLK_IN_FABRIC 41 | `define CLK_IN_16M 42 | `endif 43 | 44 | // PLL instance 45 | `ifdef SIM 46 | reg clk_div = 1'b0; 47 | 48 | always @(posedge clk_in) 49 | clk_div <= ~clk_div; 50 | 51 | assign clk_1x = clk_div; 52 | assign clk_2x = clk_in; 53 | assign pll_lock = pll_reset_n; 54 | 55 | initial 56 | rst_cnt <= 4'h8; 57 | `else 58 | `ifdef CLK_IN_FABRIC 59 | SB_PLL40_2F_CORE #( 60 | `else 61 | SB_PLL40_2F_PAD #( 62 | `endif 63 | `ifdef CLK_IN_48M 64 | // clk_in is 48 MHz 65 | .DIVR(4'b0000), 66 | .DIVF(7'b0001111), 67 | .DIVQ(3'b100), 68 | .FILTER_RANGE(3'b100), 69 | `elsif CLK_IN_16M 70 | // clk_in is 16 MHz 71 | .DIVR(4'b0000), 72 | .DIVF(7'b0101111), 73 | .DIVQ(3'b100), 74 | .FILTER_RANGE(3'b001), 75 | `else 76 | // clk_in is 12 MHz 77 | .DIVR(4'b0000), 78 | .DIVF(7'b0111111), 79 | .DIVQ(3'b100), 80 | .FILTER_RANGE(3'b001), 81 | `endif 82 | .FEEDBACK_PATH("SIMPLE"), 83 | .DELAY_ADJUSTMENT_MODE_FEEDBACK("FIXED"), 84 | .FDA_FEEDBACK(4'b0000), 85 | .SHIFTREG_DIV_MODE(2'b00), 86 | .PLLOUT_SELECT_PORTA("GENCLK"), 87 | .PLLOUT_SELECT_PORTB("GENCLK_HALF"), 88 | .ENABLE_ICEGATE_PORTA(1'b0), 89 | .ENABLE_ICEGATE_PORTB(1'b0) 90 | ) pll_I ( 91 | `ifdef CLK_IN_FABRIC 92 | .REFERENCECLK (clk_in), 93 | `else 94 | .PACKAGEPIN (clk_in), 95 | `endif 96 | .PLLOUTCOREA (), 97 | .PLLOUTGLOBALA (clk_2x), 98 | .PLLOUTCOREB (), 99 | .PLLOUTGLOBALB (clk_1x), 100 | .EXTFEEDBACK (1'b0), 101 | .DYNAMICDELAY (8'h00), 102 | .RESETB (pll_reset_n), 103 | .BYPASS (1'b0), 104 | .LATCHINPUTVALUE(1'b0), 105 | .LOCK (pll_lock), 106 | .SDI (1'b0), 107 | .SDO (), 108 | .SCLK (1'b0) 109 | ); 110 | `endif 111 | 112 | assign clk_24m = clk_1x; 113 | assign clk_48m = clk_2x; 114 | 115 | // PLL reset generation 116 | assign pll_reset_n = ~rst_in; 117 | 118 | // Logic reset generation 119 | always @(posedge clk_1x or negedge pll_lock) 120 | if (!pll_lock) 121 | rst_cnt <= 4'h8; 122 | else if (rst_cnt[3]) 123 | rst_cnt <= rst_cnt + 1; 124 | 125 | assign rst_i = rst_cnt[3]; 126 | 127 | SB_GB rst_gbuf_I ( 128 | .USER_SIGNAL_TO_GLOBAL_BUFFER(rst_i), 129 | .GLOBAL_BUFFER_OUTPUT(rst_out) 130 | ); 131 | 132 | endmodule // sysmgr_pll 133 | -------------------------------------------------------------------------------- /gateware/sim/muacm_tb.v: -------------------------------------------------------------------------------- 1 | /* 2 | * muacm.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2021 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-P-2.0 8 | */ 9 | 10 | `default_nettype none 11 | `timescale 1ns / 100ps 12 | 13 | module muacm_tb; 14 | 15 | // Signals 16 | // ------- 17 | 18 | // USB 19 | wire usb_dp; 20 | wire usb_dn; 21 | wire usb_pu; 22 | 23 | // Data interface 24 | wire [7:0] in_data; 25 | wire in_last; 26 | wire in_valid; 27 | wire in_ready; 28 | wire in_flush_now; 29 | wire in_flush_time; 30 | 31 | wire [7:0] out_data; 32 | wire out_last; 33 | wire out_valid; 34 | wire out_ready; 35 | 36 | // Misc 37 | wire bootloader; 38 | 39 | // File input 40 | reg [7:0] in_file_data; 41 | reg in_file_valid; 42 | reg in_file_done; 43 | reg in_file_hold; 44 | 45 | // Clocking 46 | reg rst = 1'b1; 47 | reg clk = 1'b0; 48 | reg clk_samp = 1'b0; 49 | 50 | 51 | // DUT 52 | // --- 53 | 54 | // Core 55 | muacm dut_I ( 56 | .usb_dp (usb_dp), 57 | .usb_dn (usb_dn), 58 | .usb_pu (usb_pu), 59 | .in_data (in_data), 60 | .in_last (in_last), 61 | .in_valid (in_valid), 62 | .in_ready (in_ready), 63 | .in_flush_now (in_flush_now), 64 | .in_flush_time (in_flush_time), 65 | .out_data (out_data), 66 | .out_last (out_last), 67 | .out_valid (out_valid), 68 | .out_ready (out_ready), 69 | .bootloader (bootloader), 70 | .clk (clk ), 71 | .rst (rst ) 72 | ); 73 | 74 | // Data loopback 75 | assign in_data = out_data; 76 | assign in_last = out_last; 77 | assign in_valid = out_valid; 78 | assign out_ready = in_ready; 79 | 80 | assign in_flush_now = 1'b0; 81 | assign in_flush_time = 1'b1; 82 | 83 | 84 | // Data feed 85 | // --------- 86 | 87 | integer fh_in, rv; 88 | 89 | initial 90 | fh_in = $fopen("../gateware/cores/no2usb/data/capture_usb_raw_short.bin", "rb"); 91 | 92 | always @(posedge clk_samp) 93 | begin 94 | if (rst) begin 95 | in_file_data <= 8'h00; 96 | in_file_valid <= 1'b0; 97 | in_file_done <= 1'b0; 98 | end else begin 99 | if (!in_file_done) begin 100 | if (!in_file_hold) begin 101 | rv = $fread(in_file_data, fh_in); 102 | in_file_valid <= (rv == 1); 103 | in_file_done <= (rv != 1); 104 | end 105 | end else begin 106 | in_file_data <= 8'h00; 107 | in_file_valid <= 1'b0; 108 | in_file_done <= 1'b1; 109 | end 110 | end 111 | end 112 | 113 | // Input 114 | assign usb_dp = in_file_data[1] & in_file_valid; 115 | assign usb_dn = in_file_data[0] & in_file_valid; 116 | 117 | // Delay some parts 118 | initial 119 | begin 120 | in_file_hold <= 1'b0; 121 | #190000 in_file_hold <= 1'b1; 122 | #100000 in_file_hold <= 1'b0; 123 | end 124 | 125 | 126 | // Sim setup 127 | // --------- 128 | 129 | // Setup recording 130 | initial begin 131 | $dumpfile("muacm_tb.vcd"); 132 | $dumpvars(0,muacm_tb); 133 | end 134 | 135 | // Reset pulse 136 | initial begin 137 | # 200 rst = 0; 138 | # 1000000 $finish; 139 | end 140 | 141 | // Clocks 142 | always #10.42 clk = !clk; 143 | always #3.247 clk_samp = !clk_samp; 144 | 145 | endmodule // muacm_tb 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Nitro μACM 2 | ========== 3 | 4 | The Nitro μACM core is a small implementation of a USB CDC ACM device 5 | entirely in FPGA fabric using only FPGA IOs (and an external 1.5kohm 6 | resistor from the `usb_pu` to the USB DP line). 7 | 8 | Features: 9 | 10 | - Entirely synchronous design, running in a single clock domain at 48 MHz 11 | - For convenience, a lightweight clock-domain crossing helper is provided 12 | in the `examples/` directory if your user logic needs to run at a 13 | different rate. 14 | 15 | - Simple data interface similar to AXI-stream (`data`/`last`/`valid`/`ready`) 16 | 17 | - Flexible packet flushing (on-demand, immediate, after-timeout) depending 18 | on your application requirements. 19 | 20 | - Exposes a DFU-Runtime interface to integrate nicely if you're using a DFU 21 | bootloader to configure your FPGA. 22 | - This allows the host to programmatically request the reset of the fpga 23 | to its bootloader mode. 24 | - Includes all the requires `WinUSB` descriptors to work out-of-the-box 25 | under Win10+ without any user intervention configuring drivers. 26 | 27 | - Customizable descriptors (PID/VID/Strings/...) 28 | 29 | - Designed for easy "drop-in" integration 30 | - It generates a single source file you can use as a black box component 31 | - You can either build it yourself from this repo, or use the 32 | `no2muacm-bin` repository or one of the tagged release tarball. 33 | 34 | 35 | Example: 36 | -------- 37 | 38 | Refer to the `example/` directory to see how to use the core on some real boards. 39 | 40 | 41 | Building: 42 | --------- 43 | 44 | Currently the core is only setup for `iCE40` builds. Other FPGA targets will be 45 | coming soon. Feel free to open an issue if you have a particular need. 46 | 47 | To build the core you will need the corresponding OSS toolchain for your target 48 | and a RISC-V compiler (default is using `riscv-none-embed-` prefix. Change with 49 | `CROSS` environment variable). 50 | 51 | ```bash 52 | $ cd gateware 53 | $ make 54 | ``` 55 | 56 | This will create a `build/` directory with both a `muacm.v` and `muacm.ilang` 57 | (select whichever you prefer) that you can integrate as a single source file 58 | in your own project. 59 | 60 | To integrate the core in your own project you can either: 61 | 62 | - Just add this pre-built source as-is in your project for minimum hassle. 63 | 64 | - Add this repository as a submodule and call the `make` step from your own 65 | build system. 66 | 67 | - Add the `no2muacm-bin` repository as a submodule which should always contain 68 | pre-built version of the latest release of this core. 69 | 70 | 71 | Customization: 72 | -------------- 73 | 74 | You can customize several aspects of this core. Some of them can even be 75 | changed after synthesis in the pre-built core directly (useful if you plan 76 | to use `no2muacm-bin` releases. 77 | 78 | To customize prior to building, the most relevant file is `firmware/usb_desc.c` 79 | which contains all the USB descriptors that will be included and should be 80 | self-explanatory. 81 | 82 | To customize the pre-built netlist, a special python tool called 83 | `muacm_customize.py` is provided in `utils/`. Refer to the `--help` to see 84 | how to use it. This will allow direct patching of VID/PID/Strings inside 85 | the netlist. Note that strings are limited to 16 chars in this case (since 86 | space is pre-reserved during build). 87 | 88 | 89 | Clocking: 90 | --------- 91 | 92 | As mentionned in the "Features" above, the core runs entirely at 48 MHz. 93 | 94 | The requirements on the clock are pretty wide, the USB specification only requires 95 | it to be within 2500 ppm. And the clock-recovery mechanism used here is capable 96 | of decoding packets with much wider clock range. For instance, this core has been 97 | used sucessfully with the _iCE40_ `SB_HFOSC` which is 48 MHz +- 10%. YMMV though. 98 | 99 | 100 | Data interface: 101 | --------------- 102 | 103 | The data interface is synchronous to the clock of the μACM module 104 | and is essentially a pair of AXI Streaming interfaces, one for 105 | RX and one for TX. 106 | 107 | - `data` is the 8 bit data to/from the host 108 | 109 | - `last` is the packet delineation marker. For `out` (i.e. from host to 110 | FPGA) it indicates the USB packets boundary as received from the host. 111 | For `in`, it can be used to force sending short packets. 112 | 113 | - `valid` and `ready` are the handshake signals. Data transfer happens 114 | when both signals are high on the same cycle. 115 | 116 | The `in` interface also has two additional control signals that are 117 | independent from the streaming interface : 118 | 119 | - `in_flush_now`: Indicates that whatever pending data is still in buffers 120 | should be sent to the host ASAP. 121 | 122 | - `in_flush_time`: Indicates that any pending data can be sent to the host 123 | after some reasonable timeout (to avoid data staying in buffer waiting to 124 | fill a full USB packet). 125 | 126 | 127 | License 128 | ------- 129 | 130 | See LICENSE.md for the licenses of the various components in this repository 131 | 132 | In short, the build product of this repository can be considered to be 133 | CERN-OHL-P-2.0 (gateware) / MIT (firmware) with needing attribution to: 134 | 135 | - Nitro FPGA project ( Sylvain Munaut - https://github.com/no2fpga/ ) 136 | - SERV ( Olof Kindgren - https://github.com/olofk/serv ) 137 | -------------------------------------------------------------------------------- /example/rtl/dfu_helper.v: -------------------------------------------------------------------------------- 1 | /* 2 | * dfu_helper.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * - Samples the button every 2^SAMP_TW 7 | * (or every btn_tick if external ticks are used). 8 | * 9 | * - Debounces / flips state when sampled identically 4 times consecutively 10 | * 11 | * - Detect long presses if held active for more than 2^LONG_TW 12 | * 13 | * - Safety against 'boot' presses: buttons need to be inactive for 14 | * 2^(LONG_TW-2) before it is "armed" 15 | * 16 | * - btn_val is the current 'debounced' value of the button 17 | * (possibly already inverted if 'active-low' is set) 18 | * 19 | * For application mode: 20 | * When released after a long press, triggers bootloader image 21 | * When released after a short press, outputs a pulse on btn_press 22 | * 23 | * For bootloader mode: 24 | * Any button presses triggers reboot to application mode image 25 | * 26 | * Copyright (C) 2021 Sylvain Munaut 27 | * SPDX-License-Identifier: CERN-OHL-P-2.0 28 | */ 29 | 30 | `default_nettype none 31 | 32 | module dfu_helper #( 33 | parameter integer SAMP_TW = 7, // Sample button every 128 cycles 34 | parameter integer LONG_TW = 19, // Consider long press after 2^19 sampling 35 | parameter integer BTN_MODE = 3, // [2] Use btn_tick, [1] Include IO buffer, [0] Invert (active-low) 36 | parameter integer BOOTLOADER_MODE = 0, // 0 = For user app, 1 = For bootloader 37 | parameter BOOT_IMAGE = 2'b01, // Bootloader image 38 | parameter USER_IMAGE = 2'b10 // User image 39 | )( 40 | // External control 41 | input wire [1:0] boot_sel, 42 | input wire boot_now, 43 | 44 | // Button 45 | input wire btn_in, 46 | input wire btn_tick, 47 | 48 | // Outputs 49 | output wire btn_val, 50 | output reg btn_press, 51 | 52 | // Clock 53 | input wire clk, 54 | input wire rst 55 | ); 56 | 57 | // Signals 58 | // ------- 59 | 60 | // Input stage 61 | wire btn_raw; 62 | reg btn_cur; 63 | 64 | // Sampling 65 | reg [SAMP_TW:0] samp_cnt = 0; // init only needed for sim 66 | wire samp_now; 67 | 68 | // Debounce 69 | reg [2:0] debounce = 0; // init only needed for sim 70 | reg btn_fall; 71 | 72 | // Long timer 73 | reg [LONG_TW:0] long_cnt; 74 | wire [LONG_TW:0] long_inc; 75 | wire [LONG_TW:0] long_msk; 76 | 77 | reg armed; 78 | 79 | // Boot logic 80 | reg [1:0] wb_sel; 81 | reg wb_req; 82 | reg wb_now; 83 | 84 | 85 | // Button 86 | // ------ 87 | 88 | // IOB 89 | generate 90 | if (BTN_MODE[1]) 91 | SB_IO #( 92 | .PIN_TYPE(6'b000000), // Reg input, no output 93 | .PULLUP(1'b1), 94 | .IO_STANDARD("SB_LVCMOS") 95 | ) btn_iob_I ( 96 | .PACKAGE_PIN(btn_in), 97 | .INPUT_CLK (clk), 98 | .D_IN_0 (btn_raw) 99 | ); 100 | else 101 | assign btn_raw = btn_in; 102 | endgenerate 103 | 104 | // Invert & Synchronize 105 | always @(posedge clk) 106 | btn_cur <= btn_raw ^ BTN_MODE[0]; 107 | 108 | // Sampling tick 109 | always @(posedge clk) 110 | samp_cnt <= ({ 1'b0, samp_cnt[SAMP_TW-1:0] } + 1) & {(SAMP_TW+1){~samp_cnt[SAMP_TW]}}; 111 | 112 | assign samp_now = BTN_MODE[3] ? btn_tick : samp_cnt[SAMP_TW]; 113 | 114 | // Debounce 115 | always @(posedge clk or posedge rst) 116 | if (rst) 117 | debounce <= 3'b000; 118 | else if (samp_now) 119 | casez ({debounce, btn_cur}) 120 | 4'b0zz0: debounce <= 3'b000; 121 | 4'b0001: debounce <= 3'b001; 122 | 4'b0011: debounce <= 3'b010; 123 | 4'b0101: debounce <= 3'b011; 124 | 4'b0111: debounce <= 3'b111; 125 | 4'b1zz1: debounce <= 3'b111; 126 | 4'b1110: debounce <= 3'b110; 127 | 4'b1100: debounce <= 3'b101; 128 | 4'b1010: debounce <= 3'b100; 129 | 4'b1000: debounce <= 3'b000; 130 | default: debounce <= 3'bxxx; 131 | endcase 132 | 133 | assign btn_val = debounce[2]; 134 | 135 | always @(posedge clk) 136 | btn_fall <= (debounce == 3'b100) & ~btn_cur & samp_now; 137 | 138 | 139 | // Long-press / Arming 140 | // ------------------- 141 | 142 | always @(posedge clk or posedge rst) 143 | if (rst) 144 | armed <= 1'b0; 145 | else 146 | armed <= armed | long_cnt[LONG_TW-2]; 147 | 148 | 149 | assign long_inc = { {LONG_TW{1'b0}}, ~long_cnt[LONG_TW] }; 150 | assign long_msk = { (LONG_TW+1){~(armed ^ btn_val)} }; 151 | 152 | always @(posedge clk or posedge rst) 153 | if (rst) 154 | long_cnt <= 0; 155 | else if (samp_now) 156 | long_cnt <= (long_cnt + long_inc) & long_msk; 157 | 158 | 159 | // Command logic 160 | // ------------- 161 | 162 | always @(posedge clk or posedge rst) 163 | if (rst) begin 164 | wb_sel <= 2'b00; 165 | wb_req <= 1'b0; 166 | btn_press <= 1'b0; 167 | end else if (~wb_req) begin 168 | if (boot_now) begin 169 | // External boot request 170 | wb_sel <= boot_sel; 171 | wb_req <= 1'b1; 172 | btn_press <= 1'b0; 173 | end else begin 174 | if (BOOTLOADER_MODE == 1) begin 175 | // We're in a DFU bootloader, any button press results in 176 | // boot to application 177 | wb_sel <= USER_IMAGE; 178 | wb_req <= (armed & btn_fall) | wb_req; 179 | btn_press <= 1'b0; 180 | end else begin 181 | // We're in user application, short press resets the 182 | // logic, long press triggers DFU reboot 183 | wb_sel <= BOOT_IMAGE; 184 | wb_req <= (armed & btn_fall & long_cnt[LONG_TW]) | wb_req; 185 | btn_press <= (armed & btn_fall & ~long_cnt[LONG_TW]); 186 | end 187 | end 188 | end 189 | 190 | 191 | // Boot 192 | // ---- 193 | 194 | // Ensure select bits are set before the boot pulse 195 | always @(posedge clk or posedge rst) 196 | if (rst) 197 | wb_now <= 1'b0; 198 | else 199 | wb_now <= wb_req; 200 | 201 | // IP core 202 | SB_WARMBOOT warmboot ( 203 | .BOOT (wb_now), 204 | .S0 (wb_sel[0]), 205 | .S1 (wb_sel[1]) 206 | ); 207 | 208 | endmodule // dfu_helper 209 | -------------------------------------------------------------------------------- /example/rtl/top.v: -------------------------------------------------------------------------------- 1 | `default_nettype none 2 | 3 | `define WITH_FIFO 4 | `define WITH_BUTTON 5 | `define HFOSC 6 | 7 | module top ( 8 | // USB 9 | inout wire usb_dp, 10 | inout wire usb_dn, 11 | output wire usb_pu, 12 | 13 | `ifdef WITH_BUTTON 14 | // Button 15 | input wire btn, 16 | `endif 17 | 18 | // Clock 19 | input wire clk_in 20 | ); 21 | 22 | // Which image to reboot to 23 | // 01 for no2bootloader, 00 for foboot/tinyfpga-bootloader 24 | localparam [1:0] BOOT_IMAGE = 2'b01; 25 | 26 | 27 | // Signals 28 | // ------- 29 | 30 | // Pipe data ( USB side ) 31 | wire [7:0] in_usb_data; 32 | wire in_usb_last; 33 | wire in_usb_valid; 34 | wire in_usb_ready; 35 | wire in_usb_flush_now; 36 | wire in_usb_flush_time; 37 | 38 | wire [7:0] out_usb_data; 39 | wire out_usb_last; 40 | wire out_usb_valid; 41 | wire out_usb_ready; 42 | 43 | // Pipe data ( System side ) 44 | wire [7:0] in_usr_data; 45 | wire in_usr_last; 46 | wire in_usr_valid; 47 | wire in_usr_ready; 48 | 49 | wire [7:0] out_usr_data; 50 | wire out_usr_last; 51 | wire out_usr_valid; 52 | wire out_usr_ready; 53 | 54 | // Bootloader request 55 | wire bootloader; 56 | 57 | // Clock / Reset 58 | wire rst_in; 59 | 60 | wire clk_usb; 61 | wire rst_usb; 62 | 63 | wire clk_usr; 64 | wire rst_usr; 65 | 66 | 67 | // uACM 68 | // ---- 69 | 70 | // Core 71 | muacm acm_I ( 72 | .usb_dp (usb_dp), 73 | .usb_dn (usb_dn), 74 | .usb_pu (usb_pu), 75 | .in_data (in_usb_data), 76 | .in_last (in_usb_last), 77 | .in_valid (in_usb_valid), 78 | .in_ready (in_usb_ready), 79 | .in_flush_now (in_usb_flush_now), 80 | .in_flush_time (in_usb_flush_time), 81 | .out_data (out_usb_data), 82 | .out_last (out_usb_last), 83 | .out_valid (out_usb_valid ), 84 | .out_ready (out_usb_ready ), 85 | .bootloader (bootloader), 86 | .clk (clk_usb), 87 | .rst (rst_usb) 88 | ); 89 | 90 | // Cross clock to 'user' domain 91 | muacm_xclk xclk_usr2usb_I ( 92 | .i_data (in_usr_data), 93 | .i_last (in_usr_last), 94 | .i_valid(in_usr_valid), 95 | .i_ready(in_usr_ready), 96 | .i_clk (clk_usr), 97 | .o_data (in_usb_data), 98 | .o_last (in_usb_last), 99 | .o_valid(in_usb_valid), 100 | .o_ready(in_usb_ready), 101 | .o_clk (clk_usb), 102 | .rst (rst_usb) 103 | ); 104 | 105 | muacm_xclk xclk_usb2usr_I ( 106 | .i_data (out_usb_data), 107 | .i_last (out_usb_last), 108 | .i_valid(out_usb_valid), 109 | .i_ready(out_usb_ready), 110 | .i_clk (clk_usb), 111 | .o_data (out_usr_data), 112 | .o_last (out_usr_last), 113 | .o_valid(out_usr_valid), 114 | .o_ready(out_usr_ready), 115 | .o_clk (clk_usr), 116 | .rst (rst_usb) 117 | ); 118 | 119 | 120 | // "User" application 121 | // ------------------ 122 | 123 | // Static options: 124 | // - Don't immediate flush 125 | // - Flush after timeout expires 126 | assign in_usb_flush_now = 1'b0; 127 | assign in_usb_flush_time = 1'b1; 128 | 129 | `ifdef WITH_FIFO 130 | 131 | // FIFO loopback 132 | wire fifo_wrena; 133 | wire fifo_rdena; 134 | wire fifo_full; 135 | wire fifo_empty; 136 | 137 | fifo_sync_shift #( 138 | .DEPTH(4), 139 | .WIDTH(8) 140 | ) fifo_I ( 141 | .wr_data (out_usr_data), 142 | .wr_ena (fifo_wrena), 143 | .wr_full (fifo_full), 144 | .rd_data (in_usr_data), 145 | .rd_ena (fifo_rdena), 146 | .rd_empty (fifo_empty), 147 | .clk (clk_usr), 148 | .rst (rst_usr) 149 | ); 150 | 151 | assign out_usr_ready = ~fifo_full; 152 | assign fifo_wrena = out_usr_valid & out_usr_ready; 153 | 154 | assign in_usr_valid = ~fifo_empty; 155 | assign fifo_rdena = in_usr_valid & in_usr_ready; 156 | 157 | `else 158 | 159 | // Simple loopback 160 | assign in_usr_data = out_usr_data; 161 | assign in_usr_last = 1'b0; 162 | assign in_usr_valid = out_usr_valid; 163 | assign out_usr_ready = in_usr_ready; 164 | 165 | `endif 166 | 167 | 168 | // DFU helper 169 | // ---------- 170 | 171 | `ifdef WITH_BUTTON 172 | dfu_helper #( 173 | .BTN_MODE(3), 174 | .BOOT_IMAGE(BOOT_IMAGE) 175 | ) dfu_helper_I ( 176 | .boot_sel (BOOT_IMAGE), 177 | .boot_now (bootloader), 178 | .btn_in (btn), 179 | .btn_tick (), 180 | .btn_val (), 181 | .btn_press(rst_in), 182 | .clk (clk_usb), 183 | .rst (rst_usb) 184 | ); 185 | `else 186 | assign rst_in = 1'b0; 187 | 188 | reg boot = 1'b0; 189 | always @(posedge clk_usb) 190 | boot <= boot | bootloader; 191 | 192 | SB_WARMBOOT warmboot ( 193 | .BOOT (boot), 194 | .S0 (BOOT_IMAGE[0]), 195 | .S1 (BOOT_IMAGE[1]) 196 | ); 197 | `endif 198 | 199 | 200 | // Clock / Reset 201 | // ------------- 202 | 203 | `ifdef HFOSC 204 | 205 | // Use HF OSC to generate USB clock 206 | sysmgr_hfosc sysmgr_I ( 207 | .rst_in (rst_in), 208 | .clk_out(clk_usb), 209 | .rst_out(rst_usb) 210 | ); 211 | 212 | // Use the clock input "as-is" for user clock 213 | assign clk_usr = clk_in; 214 | 215 | // Generate a reset signal with synchronized release in clk_usr 216 | reg rst_usr_r; 217 | 218 | always @(posedge clk_usr or posedge rst_usb) 219 | if (rst_usb) 220 | rst_usr_r <= 1'b1; 221 | else 222 | rst_usr_r <= 1'b0; 223 | 224 | SB_GB rst_gbuf_I ( 225 | .USER_SIGNAL_TO_GLOBAL_BUFFER(rst_usr_r), 226 | .GLOBAL_BUFFER_OUTPUT(rst_usr) 227 | ); 228 | 229 | `else 230 | 231 | // Generate both 48 MHz (for USB) and 24 MHz (for "user") out of the PLL 232 | sysmgr_pll sysmgr_I ( 233 | .clk_in (clk_in), 234 | .rst_in (rst_in), 235 | .clk_48m(clk_usb), 236 | .clk_24m(clk_usr), 237 | .rst_out(rst_usb) 238 | ); 239 | 240 | // They're both from PLL and sync "enough" to use the same signal 241 | assign rst_usr = rst_usb; 242 | 243 | `endif 244 | 245 | endmodule // top 246 | -------------------------------------------------------------------------------- /gateware/rtl/extif.v: -------------------------------------------------------------------------------- 1 | /* 2 | * extif.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * External data interface, shuffling data between the EP buffers 7 | * and the user application and controlled by the CPU. (Mini-DMA) 8 | * 9 | * Copyright (C) 2021 Sylvain Munaut 10 | * SPDX-License-Identifier: CERN-OHL-P-2.0 11 | */ 12 | 13 | `default_nettype none 14 | 15 | module extif ( 16 | // Data IF 17 | input wire [7:0] in_data, 18 | input wire in_last, 19 | input wire in_valid, 20 | output wire in_ready, 21 | input wire in_flush_now, 22 | input wire in_flush_time, 23 | 24 | output reg [7:0] out_data, 25 | output reg out_last, 26 | output reg out_valid, 27 | input wire out_ready, 28 | 29 | // Wishbone 30 | input wire [1:0] wb_addr, 31 | output wire [31:0] wb_rdata, 32 | input wire [31:0] wb_wdata, 33 | input wire wb_we, 34 | input wire wb_cyc, 35 | output wire wb_ack, 36 | 37 | // EP buffer interface 38 | output wire [ 7:0] ep_tx_addr_0, 39 | output wire [15:0] ep_tx_data_0, 40 | output wire [ 1:0] ep_tx_wmsk_0, 41 | output wire ep_tx_we_0, 42 | 43 | output wire [8:0] ep_rx_addr_0, 44 | input wire [15:0] ep_rx_data_1, 45 | output wire ep_rx_re_0, 46 | 47 | // Misc 48 | input wire cpu_ibus_ack, 49 | input wire cpu_dbus_ack, 50 | output wire active, 51 | 52 | output wire bootloader, 53 | 54 | // Clock 55 | input wire clk, 56 | input wire rst 57 | ); 58 | 59 | // Signals 60 | // ------- 61 | 62 | // Status 63 | wire [31:0] csr; 64 | 65 | // Active timer 66 | (* keep *) 67 | wire trig; 68 | reg ena; 69 | reg [5:0] active_cnt; 70 | 71 | // Bus interface 72 | reg b_ack; 73 | 74 | (* keep *) 75 | wire b_we_pre; 76 | reg b_we_boot; 77 | reg b_we_csr; 78 | reg b_we_in; 79 | reg b_we_out; 80 | 81 | // IN 82 | reg [2:0] in_msb; 83 | reg [6:0] in_bcnt; 84 | wire [6:0] in_inc; 85 | wire in_end; 86 | wire in_we; 87 | 88 | // OUT 89 | reg [2:0] out_msb; 90 | reg [5:0] out_lsb; 91 | reg [6:0] out_cnt; 92 | wire out_filled; 93 | 94 | (* keep *) 95 | wire out_load; 96 | reg out_did_read; 97 | 98 | 99 | // Active timer 100 | // ------------ 101 | 102 | // Activate for 32 cycles after each dbus or ibus ack 103 | // (due to SERV arch, we _know_ there won't be any CPU access and 104 | // we can freely access the EPs buffer without conflict) 105 | 106 | assign trig = (cpu_dbus_ack | cpu_ibus_ack) & ena; 107 | 108 | always @(posedge clk or posedge rst) 109 | begin 110 | if (rst) 111 | active_cnt <= 0; 112 | else 113 | active_cnt <= (active_cnt + {5'd0, active}) | { trig, 5'd0 }; 114 | end 115 | 116 | assign active = active_cnt[5]; 117 | 118 | // Bus interface 119 | 120 | // Ack 121 | always @(posedge clk) 122 | b_ack <= wb_cyc & ~b_ack; 123 | 124 | assign wb_ack = b_ack; 125 | 126 | // Write Strobes 127 | assign b_we_pre = wb_cyc & wb_we; 128 | 129 | always @(posedge clk) 130 | begin 131 | b_we_boot <= b_we_pre & (wb_addr == 2'b00) & ~b_ack; 132 | b_we_csr <= b_we_pre & (wb_addr == 2'b01) & ~b_ack; 133 | b_we_in <= b_we_pre & (wb_addr == 2'b10) & ~b_ack; 134 | b_we_out <= b_we_pre & (wb_addr == 2'b11) & ~b_ack; 135 | end 136 | 137 | // Read data 138 | assign wb_rdata = csr; 139 | 140 | // Bootloader request 141 | assign bootloader = b_we_boot; 142 | 143 | // Global CSR 144 | always @(posedge clk or posedge rst) 145 | if (rst) 146 | ena <= 1'b0; 147 | else if (b_we_csr) 148 | ena <= wb_wdata[0]; 149 | 150 | assign csr = { 151 | // [31:16] 152 | 16'd0, 153 | 154 | // [15:8] 155 | 1'b0, 156 | in_bcnt, 157 | 158 | // [7:4] 159 | out_filled, 160 | 1'b0, 161 | in_flush_now, 162 | in_flush_time, 163 | 164 | // [3:0] 165 | 3'd0, 166 | ena 167 | }; 168 | 169 | 170 | // IN 171 | // -- 172 | 173 | // Addressing: Set on CPU write, increment on write 174 | // (with special mask if in_last to end packet) 175 | always @(posedge clk or posedge rst) 176 | begin 177 | if (rst) begin 178 | in_msb <= 3'b000; 179 | in_bcnt <= 7'h00; 180 | end else begin 181 | in_msb <= b_we_in ? wb_wdata[8:6] : in_msb; 182 | in_bcnt <= b_we_in ? { 7'd64 } : (in_inc & { ~in_end, 6'd63 }); 183 | end 184 | end 185 | 186 | assign in_end = in_last & in_valid & active; 187 | assign in_inc = in_bcnt + {6'd0, in_we}; 188 | 189 | // Write when valid & ready 190 | assign in_we = in_ready & in_valid; 191 | 192 | // Ready when EIF is active (guaranteed no overlap with bus accesses) 193 | // and when we have a non-full buffer 194 | assign in_ready = active & in_bcnt[6]; 195 | 196 | // EP TX for ExtIF IN 197 | assign ep_tx_addr_0 = { in_msb, in_bcnt[5:1] }; 198 | assign ep_tx_data_0 = { in_data, in_data }; 199 | assign ep_tx_wmsk_0 = { ~in_bcnt[0], in_bcnt[0] }; 200 | assign ep_tx_we_0 = in_we; 201 | 202 | 203 | // OUT 204 | // --- 205 | 206 | // Addressing+Len: Set on CPU write, adjust on load 207 | always @(posedge clk or posedge rst) 208 | begin 209 | if (rst) begin 210 | out_msb <= 3'b000; 211 | out_lsb <= 6'h00; 212 | out_cnt <= 7'h00; 213 | end else begin 214 | out_msb <= b_we_out ? wb_wdata[8:6] : out_msb; 215 | out_lsb <= b_we_out ? 6'h00 : (out_lsb + { 5'd0, out_load }); 216 | out_cnt <= b_we_out ? { 1'b1, wb_wdata[5:0] } : (out_cnt + {7{out_load}}); 217 | end 218 | end 219 | 220 | assign out_filled = out_cnt[6]; 221 | 222 | // Load when EIF was active last cycle (so we read a byte) 223 | // and the current is not valid 224 | always @(posedge clk) 225 | out_did_read <= active & out_filled; 226 | 227 | assign out_load = out_did_read & ~out_valid; 228 | 229 | // Data+valid register 230 | always @(posedge clk or posedge rst) 231 | if (rst) begin 232 | out_data <= 8'h00; 233 | out_last <= 1'b0; 234 | end else if (out_load) begin 235 | out_data <= out_lsb[0] ? ep_rx_data_1[15:8] : ep_rx_data_1[7:0]; 236 | out_last <= (out_cnt[6:0] == 0); 237 | end 238 | 239 | always @(posedge clk or posedge rst) 240 | if (rst) 241 | out_valid <= 1'b0; 242 | else 243 | out_valid <= (out_valid & ~out_ready) | out_load; 244 | 245 | // EP RX For ExtIF OUT 246 | assign ep_rx_addr_0 = { out_msb, out_lsb[5:1] }; 247 | assign ep_rx_re_0 = 1'b1; 248 | 249 | endmodule // extif 250 | -------------------------------------------------------------------------------- /utils/muacm_customize.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import json 5 | import re 6 | import struct 7 | import sys 8 | 9 | 10 | class MuAcmPatcher: 11 | 12 | def __init__(self): 13 | self.fmt = None 14 | self.lines = [] 15 | self.meta = {} 16 | self.data_tx = None 17 | self.lidx_tx = None 18 | self.data_rx = None 19 | self.lidx_rx = None 20 | 21 | def _unmix(self, v): 22 | s = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15] 23 | return sum([(1 << i) if v & (1 << s[i]) else 0 for i in range(16)]) 24 | 25 | def _mix(self, v): 26 | s = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15] 27 | return sum([(1 << s[i]) if v & (1 << i) else 0 for i in range(16)]) 28 | 29 | def _dat_from_bin(self, bin_val): 30 | return b''.join([struct.pack(' max_str_len: 143 | raise RuntimeError('New string length is too long. Max is %d' % (max_str_len,)) 144 | 145 | self.data_rx[dtl_ofs] = 2 + len(new_str) * 2 146 | self.data_tx[desc_ofs] = 2 + len(new_str) * 2 147 | 148 | for i, b in enumerate(new_str.encode('utf-16le')): 149 | self.data_tx[desc_ofs + 2 + i] = b 150 | 151 | def set_vendor(self, vendor): 152 | self._patch_string_desc('vendor', vendor) 153 | 154 | def set_product(self, product): 155 | self._patch_string_desc('product', product) 156 | 157 | def set_serial(self, serial): 158 | self._patch_string_desc('serial', serial) 159 | 160 | 161 | def main(argv0, *args): 162 | 163 | parser = argparse.ArgumentParser() 164 | parser.add_argument("-i", "--input", help="Input file (.ilang or .v)") 165 | parser.add_argument("-o", "--output", help="Outpu file") 166 | parser.add_argument("--vid", help="New Vendor ID (as hex)") 167 | parser.add_argument("--pid", help="New Product ID (as hex)") 168 | parser.add_argument("--vendor", help="New Vendor String") 169 | parser.add_argument("--product", help="New Product String") 170 | parser.add_argument("--serial", help="New Serial String") 171 | parser.add_argument("--no-dfu-rt", help="Disable the DFU runtime support", default=False, action="store_true") 172 | 173 | args = parser.parse_args() 174 | 175 | sf = MuAcmPatcher() 176 | sf.load(args.input) 177 | 178 | if args.vid: 179 | sf.set_vid(int(args.vid, 16)) 180 | 181 | if args.pid: 182 | sf.set_pid(int(args.pid, 16)) 183 | 184 | if args.vendor: 185 | sf.set_vendor(args.vendor) 186 | 187 | if args.product: 188 | sf.set_product(args.product) 189 | 190 | if args.serial: 191 | sf.set_serial(args.serial) 192 | 193 | if args.no_dfu_rt: 194 | sf.disable_dfu_rt() 195 | 196 | sf.save(args.output) 197 | 198 | 199 | if __name__ == '__main__': 200 | main(*sys.argv) 201 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | Nitro μACM Example skeleton project 2 | =================================== 3 | 4 | This project is a small skeleton example of using the Nitro μACM 5 | as a pre-built module. You can use it as a base skeleton for your 6 | own project. 7 | 8 | 9 | Project infos: 10 | -------------- 11 | 12 | This project tries to be fairly simple while still illustrating 13 | the points that a user of the μACM module are likely to encounter. 14 | 15 | It implements a simple data loopback through a FIFO that lives in 16 | a clock domain distinct from the USB clock domain. 17 | 18 | It just instanciates the `muacm` pre-built module, feeds the 19 | required 48 MHz clock in and associated reset signal and wires 20 | up the USB signals to the right pads. 21 | 22 | The data interface is then "crossed" over using a pair of special 23 | companion blocks `muacm_xclk` that can be used to cross the clock 24 | domain boundary from the USB domain into any other clock domain 25 | as required by the user application. 26 | 27 | 28 | Nitro μACM core: 29 | ---------------- 30 | 31 | For details about the core itself and its interface, please 32 | refer to the top level `README.md`. 33 | 34 | If you're using the example binary distribution package, then 35 | this should be available here as `README-core.md`. 36 | 37 | 38 | Clocking Infos: 39 | --------------- 40 | 41 | The μACM core itself is required to run at 48 MHz to operate 42 | properly (USB is 12 MBits and we use 4x oversampling for clock 43 | recovery). 44 | 45 | In this project the 48 MHz clock can come from 2 sources depending 46 | on the `HFOSC` define at the top of `top.v`: 47 | 48 | - Either it comes from the PLL where its generated appropriately 49 | depending on whatever crystal/oscillator is attached to the 50 | board (e.g. 48 MHz for the `fomu` or 12 MHz for the `bitsy`). 51 | The advantage of this approach is a high clock quality and 52 | precision, but it also uses the PLL which is a precious 53 | resource that might be needed to generate some other frequency 54 | in the design. 55 | In this particular design we generate both 48 MHz ("USB clock") 56 | and 24 MHz ("User clock") from the PLL but that might not be 57 | an option in your design. 58 | 59 | - The other option (only in `UP5k`) is to use the `SB_HFOSC` 60 | block which provides a 48 MHz internal oscillator. 61 | The advantage is that the PLL remains free and can be used 62 | to generate any frequency that the user needs. 63 | The downside is that technically the oscillator is not precise 64 | enough for the USB spec. I haven't seen any instances where this 65 | has causes any issue but YMMV. 66 | 67 | The data interface out of the blocck is synchronous to the USB 68 | clock. If you need to pass data back/forth a custom user clock 69 | domain, it's recommended to make use of the companion blocks 70 | `muacm_xclk` provided in this repo, or to use asynchronous FIFOs. 71 | 72 | The `muacm_xclk` is not very performant but is still good enough 73 | to not cause much penatly if a minimal amount of buffering is 74 | provided on the user side. 75 | 76 | 77 | Options: 78 | -------- 79 | 80 | There are a few options that can be enabled / disabled with defines 81 | at the beginning of `top.v` or by changing some `localparam` : 82 | 83 | - `WITH_FIFO`: Add a tiny FIFO in the loopback path, running in the 84 | user clock domain. This helps efficiency. 85 | 86 | - `WITH_BUTTON`: This makes uses of a small `dfu_helper.v` block 87 | that connects to the button on the board (if available) and 88 | it allows to manually reboot into the bootloader when executing 89 | a long press on the button. 90 | 91 | - `HFOSC`: See 'Clocking' section above. 92 | 93 | - `BOOT_IMAGE`: By default when receiving a `DFU_DETACH` request, 94 | this will reboot to `WARMBOOT` image `0b01` which is correct 95 | if you're using the `no2bootloader`. However if using `foboot`, 96 | it lives in image `0b00` and so that's what should be selected 97 | to trigger a reboot to `foboot`. 98 | 99 | 100 | Build: 101 | ------ 102 | 103 | To build: 104 | 105 | ``` 106 | make BOARD=bitsy-v1 107 | ``` 108 | 109 | 110 | To program via DFU (assuming a DFU bootloader is already loaded): 111 | 112 | ``` 113 | make BOARD=bitsy-v1 prog 114 | ``` 115 | 116 | 117 | Supported boards: 118 | 119 | - `bitsy-v0`: iCEbreaker bitsy rev 0.x 120 | - `bitsy-v1`: iCEbreaker bitsy rev 1.x 121 | - `fomu-hacker`: FOMU hacker board 122 | - `fomu-pvt1`: FOMU production board 123 | - `tinyfpga-bx`: TinyFPGA-BX board 124 | 125 | Make sure to run `make clean` between changing board configuration 126 | since simply changing the `BOARD` variable will not trigger a rebuild. 127 | 128 | 129 | Board specific notes 130 | -------------------- 131 | 132 | ### 1BitSquared iCEBreaker Bitsy 133 | 134 | Those boards are probably the best supported and since they ship with 135 | `no2bootloader` as the main way of loading firmware onto them will 136 | integrate nicely with this project. 137 | 138 | 139 | ### FOMU using `fooboot` 140 | 141 | `foboot` is a DFU bootloader so it integrates pretty well with the 142 | DFU runtime provided by `muacm` if you set the `BOOT_IMAGE` to `0b00` 143 | (which is the slot where `foboot` resides). Note that on power up 144 | `foboot` always starts by default and you need to explicitely start 145 | the user bitstream using `dfu-util -e`. 146 | 147 | For the `fomu`, since there is no physical button, what is used is 148 | the IO pad 1 and can be 'pressed' by shorting it to ground (for instance 149 | by using tweezers). 150 | 151 | 152 | ### FOMU using `no2bootloader` 153 | 154 | If you have replaced the bootloader on your FOMU with `no2bootloader` 155 | instead, then the notes above regarding the `BOOT_IMAGE` settings don't 156 | apply. 157 | 158 | 159 | ### TinyFPGA-BX 160 | 161 | This board is using the `iCE40 LP8k` and as such doesn't have an internal 162 | 48 MHz oscillator. You must make sure the `HFOSC` option is not selected. 163 | 164 | Also, the only button present on this board is directly wired to the 165 | fpga reset pin and is not accessible to the user, so the `WITH_BUTTON` 166 | must also be disabled. 167 | 168 | The default bootloader that ships from the vendor with this board is 169 | the _TinyFPGA-Bootloader_ which is not a DFU bootloader. `muacm` will 170 | still expose a DFU runtime interface and you can use `dfu-util -e` to 171 | programmatically reset the board back to the bootloader if you 172 | properly set `BOOT_IMAGE` to `0b00` but you will need to use `tinyprog` 173 | to actually flash a bitstream. 174 | 175 | Finally, the _TinyFPGA-Bootloader_ always boots first when plugging 176 | the board into a USB port and will only boot the user bitstream if 177 | either a new bitstream was just programmed, or by using `tinyprog -b`. 178 | Refer to the board manual for more information about its bootloader 179 | operations. 180 | 181 | 182 | License 183 | ------- 184 | 185 | Everything included here is under permissive licenses. 186 | 187 | - The example verilog is under CERN-OHL-P-2.0 188 | 189 | - The bundled pre-built μACM core is a mix between CERN-OHL-P-2.0 and 190 | ISC (the latter being for the included SERV core). It also contains 191 | a firmware licensed under MIT. 192 | 193 | All the details can be found in the Nitro μACM git repository 194 | ( https://github.com/no2fpga/no2muacm ) but from a practical user stand 195 | point, the only thing required when using this core is attribution to : 196 | 197 | - Nitro FPGA project ( Sylvain Munaut - https://github.com/no2fpga/ ) 198 | - SERV ( Olof Kindgren - https://github.com/olofk/serv ) 199 | -------------------------------------------------------------------------------- /gateware/rtl/muacm.v: -------------------------------------------------------------------------------- 1 | /* 2 | * muacm.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Main top level gluing everything together 7 | * 8 | * Copyright (C) 2021 Sylvain Munaut 9 | * SPDX-License-Identifier: CERN-OHL-P-2.0 10 | */ 11 | 12 | `default_nettype none 13 | 14 | module muacm ( 15 | // USB 16 | inout wire usb_dp, 17 | inout wire usb_dn, 18 | output wire usb_pu, 19 | 20 | // Data IF 21 | input wire [7:0] in_data, 22 | input wire in_last, 23 | input wire in_valid, 24 | output wire in_ready, 25 | input wire in_flush_now, 26 | input wire in_flush_time, 27 | 28 | output wire [7:0] out_data, 29 | output wire out_last, 30 | output wire out_valid, 31 | input wire out_ready, 32 | 33 | // Bootloader request 34 | output wire bootloader, 35 | 36 | // Clock 37 | input wire clk, 38 | input wire rst 39 | ); 40 | 41 | // Signals 42 | // ------- 43 | 44 | // CPU IBus 45 | wire [31:0] wb_ibus_addr; 46 | wire [31:0] wb_ibus_rdata; 47 | wire wb_ibus_cyc; 48 | wire wb_ibus_ack; 49 | 50 | // CPU DBus 51 | wire [31:0] wb_dbus_addr; 52 | reg [31:0] wb_dbus_rdata; 53 | wire [31:0] wb_dbus_wdata; 54 | wire [ 3:0] wb_dbus_sel; 55 | wire wb_dbus_we; 56 | wire wb_dbus_cyc; 57 | wire wb_dbus_ack; 58 | 59 | // USB 60 | // EP buffer interface 61 | reg [ 7:0] ep_tx_addr_0; 62 | reg [15:0] ep_tx_data_0; 63 | reg [ 1:0] ep_tx_wmsk_0; 64 | reg ep_tx_we_0; 65 | 66 | wire [ 7:0] ep_rx_addr_0; 67 | wire [15:0] ep_rx_data_1; 68 | wire ep_rx_re_0; 69 | 70 | // Bus interface 71 | wire [11:0] wb_usb_addr; 72 | wire [15:0] wb_usb_rdata; 73 | wire [15:0] wb_usb_wdata; 74 | wire wb_usb_we; 75 | wire wb_usb_cyc; 76 | wire wb_usb_ack; 77 | 78 | // EP buffer wishbone IF 79 | // EP buffer interface 80 | wire [ 7:0] ep_tx_addr_wb; 81 | wire [15:0] ep_tx_data_wb; 82 | wire [ 1:0] ep_tx_wmsk_wb; 83 | wire ep_tx_we_wb; 84 | 85 | wire [8:0] ep_rx_addr_wb; 86 | wire ep_rx_re_wb; 87 | 88 | // Bus interface 89 | wire [6:0] wb_ep_addr; 90 | wire [31:0] wb_ep_rdata; 91 | wire [31:0] wb_ep_wdata; 92 | wire [3:0] wb_ep_wmsk; 93 | wire wb_ep_we; 94 | wire wb_ep_cyc; 95 | wire wb_ep_ack; 96 | 97 | // ExtIF 98 | // Misc 99 | wire eif_active; 100 | 101 | // EP buffer interface 102 | wire [ 7:0] ep_tx_addr_eif; 103 | wire [15:0] ep_tx_data_eif; 104 | wire [ 1:0] ep_tx_wmsk_eif; 105 | wire ep_tx_we_eif; 106 | 107 | wire [8:0] ep_rx_addr_eif; 108 | wire ep_rx_re_eif; 109 | 110 | // Bus interface 111 | wire [1:0] wb_eif_addr; 112 | wire [31:0] wb_eif_rdata; 113 | wire [31:0] wb_eif_wdata; 114 | wire wb_eif_we; 115 | wire wb_eif_cyc; 116 | wire wb_eif_ack; 117 | 118 | 119 | // CPU 120 | // --- 121 | 122 | serv_rf_top #( 123 | .RESET_PC (32'h0000_0000), 124 | .RESET_STRATEGY ("MINI"), 125 | .WITH_CSR (0) 126 | ) cpu_I ( 127 | .clk (clk), 128 | .i_rst (rst), 129 | .i_timer_irq (1'b0), 130 | .o_ibus_adr (wb_ibus_addr), 131 | .o_ibus_cyc (wb_ibus_cyc), 132 | .i_ibus_rdt (wb_ibus_rdata), 133 | .i_ibus_ack (wb_ibus_ack), 134 | .o_dbus_adr (wb_dbus_addr), 135 | .o_dbus_dat (wb_dbus_wdata), 136 | .o_dbus_sel (wb_dbus_sel), 137 | .o_dbus_we (wb_dbus_we), 138 | .o_dbus_cyc (wb_dbus_cyc), 139 | .i_dbus_rdt (wb_dbus_rdata), 140 | .i_dbus_ack (wb_dbus_ack) 141 | ); 142 | 143 | `ifdef SIM_TRACE 144 | always @(posedge clk) 145 | begin 146 | if (wb_ibus_ack) 147 | $display("IBUS [%d] %03x : %08x", wb_ibus_addr[31:30], wb_ibus_addr[11:0], wb_ibus_rdata); 148 | if (wb_dbus_ack) begin 149 | if (wb_dbus_we) 150 | $display("DBUS W %04x : %08x", wb_dbus_addr[15:0], wb_dbus_wdata); 151 | else 152 | $display("DBUS R %04x : %08x", wb_dbus_addr[15:0], wb_dbus_rdata); 153 | end 154 | end 155 | `endif 156 | 157 | 158 | // Instruction ROM 159 | // --------------- 160 | 161 | irom #( 162 | .AW(8), 163 | .INIT_FILE("text.hex") 164 | ) irom_I ( 165 | .wb_addr (wb_ibus_addr[9:2]), 166 | .wb_rdata (wb_ibus_rdata), 167 | .wb_cyc (wb_ibus_cyc), 168 | .wb_ack (wb_ibus_ack), 169 | .clk (clk) 170 | ); 171 | 172 | 173 | // USB core 174 | // -------- 175 | 176 | usb #( 177 | .EP_BUF_SIZE(9), 178 | .EP_BUF_WIDTH(16), 179 | .EVT_DEPTH(0) 180 | ) usb_I ( 181 | .pad_dp (usb_dp), 182 | .pad_dn (usb_dn), 183 | .pad_pu (usb_pu), 184 | .ep_tx_addr_0 (ep_tx_addr_0), 185 | .ep_tx_data_0 (ep_tx_data_0), 186 | .ep_tx_wmsk_0 (ep_tx_wmsk_0), 187 | .ep_tx_we_0 (ep_tx_we_0), 188 | .ep_rx_addr_0 (ep_rx_addr_0), 189 | .ep_rx_data_1 (ep_rx_data_1), 190 | .ep_rx_re_0 (ep_rx_re_0), 191 | .ep_clk (clk), 192 | .wb_addr (wb_usb_addr), 193 | .wb_rdata (wb_usb_rdata), 194 | .wb_wdata (wb_usb_wdata), 195 | .wb_we (wb_usb_we ), 196 | .wb_cyc (wb_usb_cyc), 197 | .wb_ack (wb_usb_ack), 198 | .irq (), 199 | .sof (), 200 | .clk (clk), 201 | .rst (rst) 202 | ); 203 | 204 | 205 | // EP buffer wishbone IF 206 | // --------------------- 207 | 208 | wb_epbuf epbuf_I ( 209 | .wb_addr (wb_ep_addr), 210 | .wb_rdata (wb_ep_rdata), 211 | .wb_wdata (wb_ep_wdata), 212 | .wb_wmsk (wb_ep_wmsk), 213 | .wb_we (wb_ep_we), 214 | .wb_cyc (wb_ep_cyc), 215 | .wb_ack (wb_ep_ack), 216 | .ep_tx_addr_0 (ep_tx_addr_wb), 217 | .ep_tx_data_0 (ep_tx_data_wb), 218 | .ep_tx_wmsk_0 (ep_tx_wmsk_wb), 219 | .ep_tx_we_0 (ep_tx_we_wb), 220 | .ep_rx_addr_0 (ep_rx_addr_wb), 221 | .ep_rx_data_1 (ep_rx_data_1), 222 | .ep_rx_re_0 (ep_rx_re_wb), 223 | .clk (clk), 224 | .rst (rst) 225 | ); 226 | 227 | 228 | // ExtIF 229 | // ----- 230 | 231 | extif extif_I ( 232 | .in_data (in_data), 233 | .in_last (in_last), 234 | .in_valid (in_valid), 235 | .in_ready (in_ready), 236 | .in_flush_now (in_flush_now), 237 | .in_flush_time (in_flush_time), 238 | .out_data (out_data), 239 | .out_last (out_last), 240 | .out_valid (out_valid), 241 | .out_ready (out_ready), 242 | .wb_addr (wb_eif_addr), 243 | .wb_rdata (wb_eif_rdata), 244 | .wb_wdata (wb_eif_wdata), 245 | .wb_we (wb_eif_we), 246 | .wb_cyc (wb_eif_cyc), 247 | .wb_ack (wb_eif_ack), 248 | .ep_tx_addr_0 (ep_tx_addr_eif), 249 | .ep_tx_data_0 (ep_tx_data_eif), 250 | .ep_tx_wmsk_0 (ep_tx_wmsk_eif), 251 | .ep_tx_we_0 (ep_tx_we_eif), 252 | .ep_rx_addr_0 (ep_rx_addr_eif), 253 | .ep_rx_data_1 (ep_rx_data_1), 254 | .ep_rx_re_0 (ep_rx_re_eif), 255 | .cpu_ibus_ack (wb_ibus_ack), 256 | .cpu_dbus_ack (wb_dbus_ack), 257 | .active (eif_active), 258 | .bootloader (bootloader), 259 | .clk (clk), 260 | .rst (rst) 261 | ); 262 | 263 | 264 | // EP buffer IF mux 265 | // ---------------- 266 | 267 | // TX buffer writes 268 | // (we need LUTs for mux here anyway so we might as well 269 | // register and break critical path) 270 | always @(posedge clk) 271 | begin 272 | ep_tx_addr_0 <= eif_active ? ep_tx_addr_eif : ep_tx_addr_wb; 273 | ep_tx_data_0 <= eif_active ? ep_tx_data_eif : ep_tx_data_wb; 274 | ep_tx_wmsk_0 <= eif_active ? ep_tx_wmsk_eif : ep_tx_wmsk_wb; 275 | ep_tx_we_0 <= eif_active ? ep_tx_we_eif : ep_tx_we_wb; 276 | end 277 | 278 | // RX buffer reads 279 | assign ep_rx_addr_0 = eif_active ? ep_rx_addr_eif : ep_rx_addr_wb; 280 | assign ep_rx_re_0 = eif_active ? ep_rx_re_eif : ep_rx_re_wb; 281 | 282 | 283 | // Data bus mux 284 | // ------------ 285 | 286 | // Ack 287 | assign wb_dbus_ack = wb_usb_ack | wb_ep_ack | wb_eif_ack; 288 | 289 | // USB CSR access 290 | assign wb_usb_addr = {wb_dbus_addr[10], 3'b000, wb_dbus_addr[9:2] }; 291 | assign wb_usb_wdata = wb_dbus_wdata[15:0]; 292 | assign wb_usb_we = wb_dbus_we; 293 | assign wb_usb_cyc = wb_dbus_cyc & (wb_dbus_addr[12:11] == 2'b00); 294 | 295 | // EP buffer access 296 | assign wb_ep_addr = wb_dbus_addr[8:2]; 297 | assign wb_ep_wdata = wb_dbus_wdata; 298 | assign wb_ep_wmsk = ~wb_dbus_sel; 299 | assign wb_ep_we = wb_dbus_we; 300 | assign wb_ep_cyc = wb_dbus_cyc & (wb_dbus_addr[12:11] == 2'b01); 301 | 302 | // ExtIF access 303 | assign wb_eif_addr = wb_dbus_addr[3:2]; 304 | assign wb_eif_wdata = wb_dbus_wdata; 305 | assign wb_eif_we = wb_dbus_we; 306 | assign wb_eif_cyc = wb_dbus_cyc & wb_dbus_addr[12]; 307 | 308 | // Read Data muxing 309 | always @(*) 310 | casez (wb_dbus_addr[12:11]) 311 | 2'b00: wb_dbus_rdata = wb_usb_rdata; 312 | 2'b01: wb_dbus_rdata = wb_ep_rdata; 313 | 2'b1z: wb_dbus_rdata = wb_eif_rdata; 314 | default: wb_dbus_rdata = 32'hxxxxxxxx; 315 | endcase 316 | 317 | endmodule // muacm 318 | -------------------------------------------------------------------------------- /firmware/usb_desc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * usb_desc.c 3 | * 4 | * USB descriptors 5 | * 6 | * Copyright (C) 2021 Sylvain Munaut 7 | * SPDX-License-Identifier: MIT 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | /* Device Descriptor */ 18 | 19 | static const struct usb_dev_desc desc_dev __attribute__((section(".usb_txbuf.desc"))) = { 20 | .bLength = sizeof(struct usb_dev_desc), 21 | .bDescriptorType = USB_DT_DEV, 22 | .bcdUSB = 0x0200, 23 | .bDeviceClass = 0, 24 | .bDeviceSubClass = 0, 25 | .bDeviceProtocol = 0, 26 | .bMaxPacketSize0 = 64, 27 | .idVendor = 0x1d50, 28 | .idProduct = 0x6159, 29 | .bcdDevice = 0x0001, /* v0.1 */ 30 | .iManufacturer = 2, 31 | .iProduct = 3, 32 | .iSerialNumber = 1, 33 | .bNumConfigurations = 1 34 | }; 35 | 36 | 37 | /* Configuration Descriptor */ 38 | 39 | usb_cdc_union_desc_def(1); 40 | 41 | static const struct { 42 | /* Configuration */ 43 | struct usb_conf_desc conf; 44 | 45 | /* DFU Runtime */ 46 | struct { 47 | struct usb_intf_desc intf; 48 | struct usb_dfu_func_desc func; 49 | } __attribute__ ((packed)) dfu; 50 | 51 | /* CDC */ 52 | struct { 53 | struct usb_intf_desc intf_ctl; 54 | struct usb_cdc_hdr_desc cdc_hdr; 55 | struct usb_cdc_acm_desc cdc_acm; 56 | struct usb_cdc_union_desc__1 cdc_union; 57 | struct usb_ep_desc ep_ctl; 58 | struct usb_intf_desc intf_data; 59 | struct usb_ep_desc ep_data_out; 60 | struct usb_ep_desc ep_data_in; 61 | } __attribute__ ((packed)) cdc; 62 | } __attribute__ ((packed)) desc_conf __attribute__((section(".usb_txbuf.desc"))) = { 63 | .conf = { 64 | .bLength = sizeof(struct usb_conf_desc), 65 | .bDescriptorType = USB_DT_CONF, 66 | .wTotalLength = sizeof(desc_conf), 67 | .bNumInterfaces = 3, 68 | .bConfigurationValue = 1, 69 | .iConfiguration = 0, 70 | .bmAttributes = 0x80, 71 | .bMaxPower = 0x32, /* 100 mA */ 72 | }, 73 | .dfu = { 74 | .intf = { 75 | .bLength = sizeof(struct usb_intf_desc), 76 | .bDescriptorType = USB_DT_INTF, 77 | .bInterfaceNumber = 0, 78 | .bAlternateSetting = 0, 79 | .bNumEndpoints = 0, 80 | .bInterfaceClass = 0xfe, 81 | .bInterfaceSubClass = 0x01, 82 | .bInterfaceProtocol = 0x01, 83 | .iInterface = 4, 84 | }, 85 | .func = { 86 | .bLength = sizeof(struct usb_dfu_func_desc), 87 | .bDescriptorType = USB_DFU_DT_FUNC, 88 | .bmAttributes = 0x0d, 89 | .wDetachTimeOut = 1000, 90 | .wTransferSize = 4096, 91 | .bcdDFUVersion = 0x0101, 92 | }, 93 | }, 94 | .cdc = { 95 | .intf_ctl = { 96 | .bLength = sizeof(struct usb_intf_desc), 97 | .bDescriptorType = USB_DT_INTF, 98 | .bInterfaceNumber = 1, 99 | .bAlternateSetting = 0, 100 | .bNumEndpoints = 1, 101 | .bInterfaceClass = USB_CLS_CDC_CONTROL, 102 | .bInterfaceSubClass = USB_CDC_SCLS_ACM, 103 | .bInterfaceProtocol = 0x00, 104 | .iInterface = 0, 105 | }, 106 | .cdc_hdr = { 107 | .bLength = sizeof(struct usb_cdc_hdr_desc), 108 | .bDescriptorType = USB_CS_DT_INTF, 109 | .bDescriptorsubtype = USB_CDC_DST_HEADER, 110 | .bcdCDC = 0x0110, 111 | }, 112 | .cdc_acm = { 113 | .bLength = sizeof(struct usb_cdc_acm_desc), 114 | .bDescriptorType = USB_CS_DT_INTF, 115 | .bDescriptorsubtype = USB_CDC_DST_ACM, 116 | .bmCapabilities = 0x00, /* Pure pipe, no control ... */ 117 | }, 118 | .cdc_union = { 119 | .bLength = sizeof(struct usb_cdc_union_desc) + 1, 120 | .bDescriptorType = USB_CS_DT_INTF, 121 | .bDescriptorsubtype = USB_CDC_DST_UNION, 122 | .bMasterInterface = 1, 123 | .bSlaveInterface = { 2 }, 124 | }, 125 | .ep_ctl = { 126 | .bLength = sizeof(struct usb_ep_desc), 127 | .bDescriptorType = USB_DT_EP, 128 | .bEndpointAddress = 0x83, 129 | .bmAttributes = 0x03, 130 | .wMaxPacketSize = 8, 131 | .bInterval = 0x40, 132 | }, 133 | .intf_data = { 134 | .bLength = sizeof(struct usb_intf_desc), 135 | .bDescriptorType = USB_DT_INTF, 136 | .bInterfaceNumber = 2, 137 | .bAlternateSetting = 0, 138 | .bNumEndpoints = 2, 139 | .bInterfaceClass = USB_CLS_CDC_DATA, 140 | .bInterfaceSubClass = 0x00, 141 | .bInterfaceProtocol = 0x00, 142 | .iInterface = 0, 143 | }, 144 | .ep_data_out = { 145 | .bLength = sizeof(struct usb_ep_desc), 146 | .bDescriptorType = USB_DT_EP, 147 | .bEndpointAddress = 0x02, 148 | .bmAttributes = 0x02, 149 | .wMaxPacketSize = 64, 150 | .bInterval = 0x00, 151 | }, 152 | .ep_data_in = { 153 | .bLength = sizeof(struct usb_ep_desc), 154 | .bDescriptorType = USB_DT_EP, 155 | .bEndpointAddress = 0x82, 156 | .bmAttributes = 0x02, 157 | .wMaxPacketSize = 64, 158 | .bInterval = 0x00, 159 | }, 160 | }, 161 | }; 162 | 163 | 164 | /* MSOS2.0 descriptor */ 165 | 166 | const struct { 167 | struct msos20_desc_set_hdr hdr; 168 | struct msos20_feat_compat_id_desc feat; 169 | } __attribute__ ((packed)) desc_msos20 __attribute__((section(".usb_txbuf.desc"))) = { 170 | .hdr = { 171 | .wLength = sizeof(struct msos20_desc_set_hdr), 172 | .wDescriptorType = MSOS20_SET_HEADER_DESCRIPTOR, 173 | .dwWindowsVersion = MSOS20_WIN_VER_8_1, 174 | .wTotalLength = sizeof(desc_msos20), 175 | }, 176 | .feat = { 177 | .wLength = sizeof(struct msos20_feat_compat_id_desc), 178 | .wDescriptorType = MSOS20_FEATURE_COMPATBLE_ID, 179 | .CompatibleID = { 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00 }, 180 | .SubCompatibleID = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 181 | }, 182 | }; 183 | 184 | 185 | /* BOS descriptor */ 186 | 187 | static const struct { 188 | struct usb_bos_desc bos; 189 | struct usb_bos_plat_cap_hdr cap_hdr; 190 | struct usb_bos_msos20_desc_set cap_data; 191 | } __attribute__ ((packed)) desc_bos __attribute__((section(".usb_txbuf.desc"))) = { 192 | .bos = { 193 | .bLength = sizeof(struct usb_bos_desc), 194 | .bDescriptorType = USB_DT_BOS, 195 | .wTotalLength = sizeof(desc_bos), 196 | .bNumDeviceCaps = 1, 197 | }, 198 | .cap_hdr = { 199 | .bLength = sizeof(struct usb_bos_plat_cap_hdr) + 8, 200 | .bDescriptorType = USB_DT_DEV_CAP, 201 | .bDevCapabilityType = 5, /* PLATFORM */ 202 | .bReserved = 0, 203 | .PlatformCapabilityUUID = { 204 | 0xdf, 0x60, 0xdd, 0xd8, 0x89, 0x45, 0xc7, 0x4c, 205 | 0x9c, 0xd2, 0x65, 0x9d, 0x9e, 0x64, 0x8a, 0x9f, 206 | }, 207 | }, 208 | .cap_data = { 209 | .dwWindowsVersion = MSOS20_WIN_VER_8_1, 210 | .wMSOSDescriptorSetTotalLength = sizeof(desc_msos20), 211 | .bMS_VendorCode = MSOS20_MS_VENDOR_CODE, 212 | .bAltEnumCode = 0x00, 213 | }, 214 | }; 215 | 216 | 217 | /* String descriptors */ 218 | 219 | #define STR0_LEN (sizeof(struct usb_str_desc) + 2 * 1) 220 | static const struct usb_str_desc desc_str0 __attribute__((section(".usb_txbuf.desc"))) = { 221 | .bLength = STR0_LEN, 222 | .bDescriptorType = USB_DT_STR, 223 | .wString = { 0x0409 }, 224 | }; 225 | 226 | #define STR1_LEN (sizeof(struct usb_str_desc) + 2 * 16) 227 | static const struct usb_str_desc desc_str1 __attribute__((section(".usb_txbuf.desc"))) = { 228 | .bLength = STR1_LEN, 229 | .bDescriptorType = USB_DT_STR, 230 | .wString = { 231 | '0', '0', '0', '0', '0', '0', '0', '0', 232 | '0', '0', '0', '0', '0', '0', '0', '0', 233 | }, 234 | }; 235 | 236 | #define STR2_LEN (sizeof(struct usb_str_desc) + 2 * 10) 237 | static const struct usb_str_desc desc_str2 __attribute__((section(".usb_txbuf.desc"))) = { 238 | .bLength = STR2_LEN, 239 | .bDescriptorType = USB_DT_STR, 240 | .wString = { 241 | 'N', 'i', 't', 'r', 'o', ' ', 'F', 'P', 'G', 'A', 0, 0, 0, 0, 0, 0, 242 | }, 243 | }; 244 | 245 | #define STR3_LEN (sizeof(struct usb_str_desc) + 2 * 4) 246 | static const struct usb_str_desc desc_str3 __attribute__((section(".usb_txbuf.desc"))) = { 247 | .bLength = STR3_LEN, 248 | .bDescriptorType = USB_DT_STR, 249 | .wString = { 250 | 0x03bc, 'a', 'c', 'm', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 251 | }, 252 | }; 253 | 254 | #define STR4_LEN (sizeof(struct usb_str_desc) + 2 * 6) 255 | static const struct usb_str_desc desc_str4 __attribute__((section(".usb_txbuf.desc"))) = { 256 | .bLength = STR4_LEN, 257 | .bDescriptorType = USB_DT_STR, 258 | .wString = { 259 | 'D', 'F', 'U', ' ', 'r', 't', 260 | }, 261 | }; 262 | 263 | 264 | /* Descriptor table */ 265 | /* Could be more compact (6 bytes per entry) but 266 | .usb_rxbuf isn't nearly full so don't bother for now */ 267 | const struct { 268 | uint8_t idx; 269 | uint8_t type; 270 | uint16_t len; 271 | const void *ptr; 272 | } desc_table[] __attribute__((section(".usb_rxbuf.const"))) = { 273 | { 0, USB_DT_DEV, sizeof(desc_dev), &desc_dev }, 274 | { 0, USB_DT_CONF, sizeof(desc_conf), &desc_conf }, 275 | { 0, USB_DT_BOS, sizeof(desc_bos), &desc_bos }, 276 | { 0, USB_DT_STR, STR0_LEN, &desc_str0 }, 277 | { 1, USB_DT_STR, STR1_LEN, &desc_str1 }, 278 | { 2, USB_DT_STR, STR2_LEN, &desc_str2 }, 279 | { 3, USB_DT_STR, STR3_LEN, &desc_str3 }, 280 | { 4, USB_DT_STR, STR4_LEN, &desc_str4 }, 281 | { 0, 0, 0, 0 }, 282 | }; 283 | -------------------------------------------------------------------------------- /doc/LICENSE-CERN-OHL-P-2.0.txt: -------------------------------------------------------------------------------- 1 | CERN Open Hardware Licence Version 2 - Permissive 2 | 3 | 4 | Preamble 5 | 6 | CERN has developed this licence to promote collaboration among 7 | hardware designers and to provide a legal tool which supports the 8 | freedom to use, study, modify, share and distribute hardware designs 9 | and products based on those designs. Version 2 of the CERN Open 10 | Hardware Licence comes in three variants: this licence, CERN-OHL-P 11 | (permissive); and two reciprocal licences: CERN-OHL-W (weakly 12 | reciprocal) and CERN-OHL-S (strongly reciprocal). 13 | 14 | The CERN-OHL-P is copyright CERN 2020. Anyone is welcome to use it, in 15 | unmodified form only. 16 | 17 | Use of this Licence does not imply any endorsement by CERN of any 18 | Licensor or their designs nor does it imply any involvement by CERN in 19 | their development. 20 | 21 | 22 | 1 Definitions 23 | 24 | 1.1 'Licence' means this CERN-OHL-P. 25 | 26 | 1.2 'Source' means information such as design materials or digital 27 | code which can be applied to Make or test a Product or to 28 | prepare a Product for use, Conveyance or sale, regardless of its 29 | medium or how it is expressed. It may include Notices. 30 | 31 | 1.3 'Covered Source' means Source that is explicitly made available 32 | under this Licence. 33 | 34 | 1.4 'Product' means any device, component, work or physical object, 35 | whether in finished or intermediate form, arising from the use, 36 | application or processing of Covered Source. 37 | 38 | 1.5 'Make' means to create or configure something, whether by 39 | manufacture, assembly, compiling, loading or applying Covered 40 | Source or another Product or otherwise. 41 | 42 | 1.6 'Notice' means copyright, acknowledgement and trademark notices, 43 | references to the location of any Notices, modification notices 44 | (subsection 3.3(b)) and all notices that refer to this Licence 45 | and to the disclaimer of warranties that are included in the 46 | Covered Source. 47 | 48 | 1.7 'Licensee' or 'You' means any person exercising rights under 49 | this Licence. 50 | 51 | 1.8 'Licensor' means a person who creates Source or modifies Covered 52 | Source and subsequently Conveys the resulting Covered Source 53 | under the terms and conditions of this Licence. A person may be 54 | a Licensee and a Licensor at the same time. 55 | 56 | 1.9 'Convey' means to communicate to the public or distribute. 57 | 58 | 59 | 2 Applicability 60 | 61 | 2.1 This Licence governs the use, copying, modification, Conveying 62 | of Covered Source and Products, and the Making of Products. By 63 | exercising any right granted under this Licence, You irrevocably 64 | accept these terms and conditions. 65 | 66 | 2.2 This Licence is granted by the Licensor directly to You, and 67 | shall apply worldwide and without limitation in time. 68 | 69 | 2.3 You shall not attempt to restrict by contract or otherwise the 70 | rights granted under this Licence to other Licensees. 71 | 72 | 2.4 This Licence is not intended to restrict fair use, fair dealing, 73 | or any other similar right. 74 | 75 | 76 | 3 Copying, Modifying and Conveying Covered Source 77 | 78 | 3.1 You may copy and Convey verbatim copies of Covered Source, in 79 | any medium, provided You retain all Notices. 80 | 81 | 3.2 You may modify Covered Source, other than Notices. 82 | 83 | You may only delete Notices if they are no longer applicable to 84 | the corresponding Covered Source as modified by You and You may 85 | add additional Notices applicable to Your modifications. 86 | 87 | 3.3 You may Convey modified Covered Source (with the effect that You 88 | shall also become a Licensor) provided that You: 89 | 90 | a) retain Notices as required in subsection 3.2; and 91 | 92 | b) add a Notice to the modified Covered Source stating that You 93 | have modified it, with the date and brief description of how 94 | You have modified it. 95 | 96 | 3.4 You may Convey Covered Source or modified Covered Source under 97 | licence terms which differ from the terms of this Licence 98 | provided that: 99 | 100 | a) You comply at all times with subsection 3.3; and 101 | 102 | b) You provide a copy of this Licence to anyone to whom You 103 | Convey Covered Source or modified Covered Source. 104 | 105 | 106 | 4 Making and Conveying Products 107 | 108 | You may Make Products, and/or Convey them, provided that You ensure 109 | that the recipient of the Product has access to any Notices applicable 110 | to the Product. 111 | 112 | 113 | 5 DISCLAIMER AND LIABILITY 114 | 115 | 5.1 DISCLAIMER OF WARRANTY -- The Covered Source and any Products 116 | are provided 'as is' and any express or implied warranties, 117 | including, but not limited to, implied warranties of 118 | merchantability, of satisfactory quality, non-infringement of 119 | third party rights, and fitness for a particular purpose or use 120 | are disclaimed in respect of any Source or Product to the 121 | maximum extent permitted by law. The Licensor makes no 122 | representation that any Source or Product does not or will not 123 | infringe any patent, copyright, trade secret or other 124 | proprietary right. The entire risk as to the use, quality, and 125 | performance of any Source or Product shall be with You and not 126 | the Licensor. This disclaimer of warranty is an essential part 127 | of this Licence and a condition for the grant of any rights 128 | granted under this Licence. 129 | 130 | 5.2 EXCLUSION AND LIMITATION OF LIABILITY -- The Licensor shall, to 131 | the maximum extent permitted by law, have no liability for 132 | direct, indirect, special, incidental, consequential, exemplary, 133 | punitive or other damages of any character including, without 134 | limitation, procurement of substitute goods or services, loss of 135 | use, data or profits, or business interruption, however caused 136 | and on any theory of contract, warranty, tort (including 137 | negligence), product liability or otherwise, arising in any way 138 | in relation to the Covered Source, modified Covered Source 139 | and/or the Making or Conveyance of a Product, even if advised of 140 | the possibility of such damages, and You shall hold the 141 | Licensor(s) free and harmless from any liability, costs, 142 | damages, fees and expenses, including claims by third parties, 143 | in relation to such use. 144 | 145 | 146 | 6 Patents 147 | 148 | 6.1 Subject to the terms and conditions of this Licence, each 149 | Licensor hereby grants to You a perpetual, worldwide, 150 | non-exclusive, no-charge, royalty-free, irrevocable (except as 151 | stated in this section 6, or where terminated by the Licensor 152 | for cause) patent license to Make, have Made, use, offer to 153 | sell, sell, import, and otherwise transfer the Covered Source 154 | and Products, where such licence applies only to those patent 155 | claims licensable by such Licensor that are necessarily 156 | infringed by exercising rights under the Covered Source as 157 | Conveyed by that Licensor. 158 | 159 | 6.2 If You institute patent litigation against any entity (including 160 | a cross-claim or counterclaim in a lawsuit) alleging that the 161 | Covered Source or a Product constitutes direct or contributory 162 | patent infringement, or You seek any declaration that a patent 163 | licensed to You under this Licence is invalid or unenforceable 164 | then any rights granted to You under this Licence shall 165 | terminate as of the date such process is initiated. 166 | 167 | 168 | 7 General 169 | 170 | 7.1 If any provisions of this Licence are or subsequently become 171 | invalid or unenforceable for any reason, the remaining 172 | provisions shall remain effective. 173 | 174 | 7.2 You shall not use any of the name (including acronyms and 175 | abbreviations), image, or logo by which the Licensor or CERN is 176 | known, except where needed to comply with section 3, or where 177 | the use is otherwise allowed by law. Any such permitted use 178 | shall be factual and shall not be made so as to suggest any kind 179 | of endorsement or implication of involvement by the Licensor or 180 | its personnel. 181 | 182 | 7.3 CERN may publish updated versions and variants of this Licence 183 | which it considers to be in the spirit of this version, but may 184 | differ in detail to address new problems or concerns. New 185 | versions will be published with a unique version number and a 186 | variant identifier specifying the variant. If the Licensor has 187 | specified that a given variant applies to the Covered Source 188 | without specifying a version, You may treat that Covered Source 189 | as being released under any version of the CERN-OHL with that 190 | variant. If no variant is specified, the Covered Source shall be 191 | treated as being released under CERN-OHL-S. The Licensor may 192 | also specify that the Covered Source is subject to a specific 193 | version of the CERN-OHL or any later version in which case You 194 | may apply this or any later version of CERN-OHL with the same 195 | variant identifier published by CERN. 196 | 197 | 7.4 This Licence shall not be enforceable except by a Licensor 198 | acting as such, and third party beneficiary rights are 199 | specifically excluded. 200 | -------------------------------------------------------------------------------- /firmware/main.S: -------------------------------------------------------------------------------- 1 | /* 2 | * main.S 3 | * 4 | * Main firmware source code 5 | * 6 | * Copyright (C) 2021 Sylvain Munaut 7 | * SPDX-License-Identifier: MIT 8 | */ 9 | 10 | #include "usb_hw.h" 11 | 12 | 13 | /* 14 | * Common Register usage: 15 | * 16 | * x0 - zero 17 | * x1 - Task 0 PC 18 | * x2 - Task 1 PC 19 | * x3 - Task 2 PC 20 | * x4 - USB frame tick timeout counter 21 | * x5 - ? 22 | * x6 - ? 23 | * x7 - ? 24 | */ 25 | 26 | .section .text 27 | .global _start 28 | 29 | _start: 30 | // Global hardware init 31 | 32 | // Reset EPs buffer descriptors CSRs 33 | #if 0 34 | sw zero, EP_BD_CSR(0x00, 0) // EP 0 OUT DATA 35 | sw zero, EP_BD_CSR(0x00, 1) // EP 0 OUT SETUP 36 | sw zero, EP_BD_CSR(0x80, 0) // EP 0 IN 37 | sw zero, EP_BD_CSR(0x02, 0) // EP 2 OUT 0 38 | sw zero, EP_BD_CSR(0x02, 1) // EP 2 OUT 1 39 | sw zero, EP_BD_CSR(0x82, 0) // EP 2 IN 0 40 | sw zero, EP_BD_CSR(0x82, 1) // EP 2 IN 1 41 | #else 42 | addi x8, USB_BASE, 0x0400 // EP_CSR base 43 | addi x9, x8, 4*2*32 // Total size (Only do 4 EPs) 44 | 1: 45 | sw zero, 16(x8) // BD[0].csr 46 | sw zero, 24(x8) // BD[1].csr 47 | addi x8, x8, 32 48 | bne x8, x9, 1b 49 | #endif 50 | 51 | // Start core 52 | li x8, USB_CSR_PU_ENA | USB_CSR_CEL_ENA 53 | sw x8, USB_CSR 54 | 55 | // Wait for reset to not be active 56 | 1: 57 | lw x8, USB_CSR 58 | andi x8, x8, USB_CSR_BUS_RST 59 | bne x8, zero, 1b 60 | 61 | // Clear any pending status 62 | li x8, USB_AR_CEL_RELEASE | USB_AR_BUS_RST_CLEAR | USB_AR_SOF_CLEAR 63 | sw x8, USB_AR 64 | 65 | // Init tasks 66 | li x1, %lo(_task0_start) 67 | 68 | lui x2, %hi(1 << 30) 69 | addi x2, x2, %lo(_task1_start) 70 | 71 | lui x3, %hi(2 << 30) 72 | addi x3, x3, %lo(_task2_start) 73 | 74 | 75 | /* ------------------------------------------------------------------------ 76 | Task 0: USB Control (Events + EP0) 77 | ------------------------------------------------------------------------ */ 78 | 79 | /* Task 0 Register usage: 80 | * 81 | * x8 - General purpose temp register 82 | * x9 - General purpose temp register 83 | * 84 | * x12 - SETUP wRequestAndType 85 | * x13 - SETUP wValue 86 | * x14 - SETUP wIndex 87 | * x15 - SETUP wLength 88 | * 89 | * x16 - Response: Current pointer to data (in txbuf) 90 | * x17 - Response: Remaining length 91 | * x18 - Response: Chunk length 92 | * x19 - Response: Completion callback 93 | * 94 | * x30 - Constant: EP buffer base address 95 | */ 96 | 97 | #define yield jalr x1, x2 98 | 99 | _task0_start: 100 | 101 | // Setup EP0 BD CSR to RX of SETUP 102 | li x8, USB_BD_STATE_RDY_DATA | USB_BD_LEN(64) 103 | sw x8, EP_BD_CSR(0x00, 1) // EP 0 OUT SETUP 104 | 105 | // Wait for something 106 | _task0_loop: 107 | jal x1, 1f // Set x1 108 | 1: 109 | //j _task0_common_loop // (No need for jump, it's right below ...) 110 | 111 | 112 | /* 113 | * Common part of the task0 wait loops 114 | * Must be called using: 115 | * jal x1, _task0_common_loop 116 | * So that the task PC is set to properly 117 | */ 118 | _task0_common_loop: 119 | // Load CSR 120 | lw x8, USB_CSR 121 | 122 | // Check for pending reset 123 | andi x9, x8, USB_CSR_BUS_RST 124 | bne x9, zero, _start 125 | 126 | // Check for pending SOF 127 | andi x9, x8, USB_CSR_SOF_PENDING 128 | beq x9, zero, 1f 129 | 130 | // If so, increment frame tick and clear event 131 | addi x4, x4, 1 132 | li x8, USB_AR_SOF_CLEAR 133 | sw x8, USB_AR 134 | 135 | 1: 136 | // Check SETUP 137 | // (uses sign extension of 'lh' to check MSB of CSR to know if buffer is done) 138 | lh x8, EP_BD_CSR(0x00, 1) 139 | blt x8, zero, _task0_do_setup 140 | 141 | // Hand-over to next task 142 | // (don't use 'yield' macro since caller already set x1) 143 | jr x2 144 | 145 | 146 | /* 147 | * SETUP received handler 148 | */ 149 | _task0_do_setup: 150 | // Load the entire SETUP packet in x12-x15 151 | lhu x12, %lo(ep0_rx_setup + 0)(x30) // offsetof(struct usb_ctrl_req, wRequestAndType) 152 | lhu x13, %lo(ep0_rx_setup + 2)(x30) // offsetof(struct usb_ctrl_req, wValue) 153 | lhu x14, %lo(ep0_rx_setup + 4)(x30) // offsetof(struct usb_ctrl_req, wIndex) 154 | lhu x15, %lo(ep0_rx_setup + 6)(x30) // offsetof(struct usb_ctrl_req, wLength) 155 | 156 | // Reset BDs 157 | // - EP0 OUT SETUP : Re-arm for 64 byte RX 158 | // - EP0 OUT DATA : BD disabled 159 | // - EP0 IN : BD disabled 160 | li x8, USB_BD_STATE_RDY_DATA | USB_BD_LEN(64) 161 | sw x8, EP_BD_CSR(0x00, 1) // EP 0 OUT SETUP 162 | sw zero, EP_BD_CSR(0x00, 0) // EP 0 OUT DATA 163 | sw zero, EP_BD_CSR(0x80, 0) // EP 0 IN 164 | 165 | // Release CEL 166 | li x8, USB_AR_CEL_RELEASE 167 | sw x8, USB_AR 168 | 169 | // Default is no completion handler 170 | li x19, %lo(_task0_loop) 171 | 172 | // Find handler 173 | addi x8, x30, %lo(_task0_handlers) 174 | 1: 175 | lhu x9, 2(x8) // Request/Type 176 | addi x8, x8, 4 177 | beq x9, zero, _task0_resp_stall // 0x0000 = end marker (not a valid reqtype) 178 | bne x9, x12, 1b 179 | 180 | // Call handler 181 | lhu x8, -4(x8) // Function pointer 182 | jr x8 183 | 184 | 185 | /* 186 | * Data has been prepared and must be sent to the host, possibly 187 | * using multiple packets and possibly using a ZLP to notify the 188 | * end 189 | */ 190 | _task0_resp_data: 191 | // Sets CSR to ensure DT=1 192 | li x8, USB_EP_TYPE_CTRL | USB_EP_DT_BIT 193 | sw x8, EP_CSR(0x80) 194 | 195 | // Limit size to wLength 196 | bge x15, x17, _task0_resp_data_nxt_pkt 197 | mv x17, x15 198 | 199 | _task0_resp_data_nxt_pkt: 200 | // Size of this chunk 201 | li x18, 64 202 | bge x17, x18, 1f 203 | mv x18, x17 204 | 1: 205 | 206 | // Setup BD (setup pointer first !) 207 | sw x16, EP_BD_PTR(0x80, 0) // EP 0 IN 208 | 209 | li x8, USB_BD_STATE_RDY_DATA 210 | or x8, x8, x18 211 | sw x8, EP_BD_CSR(0x80, 0) // EP 0 IN 212 | 213 | // Wait for TX success (or abort through incoming SETUP) 214 | 1: 215 | // Common wait loop part 216 | jal x1, _task0_common_loop 217 | 218 | // Check BD 219 | // (uses sign extension of 'lh' to check MSB of CSR to know if buffer is done) 220 | lh x8, EP_BD_CSR(0x80, 0) 221 | bge x8, zero, 1b 222 | 223 | // Next chunk 224 | add x16, x16, x18 225 | addi x17, x17, -64 226 | bge x17, zero, _task0_resp_data_nxt_pkt // If >= 0, work left to do 227 | 228 | // Status stage, accept one OUT ZLP 229 | li x8, USB_BD_STATE_RDY_DATA | USB_BD_LEN(0) 230 | sw x8, EP_BD_CSR(0x00, 0) // EP 0 OUT 231 | 232 | // We're done 233 | // (either we get rx success and we don't really need to do 234 | // anything, or we get a setup packet, so might as well return 235 | // to the main loop) 236 | j _task0_loop 237 | 238 | 239 | /* 240 | * Sends a ZLP through IN endpoint with DT=1 241 | */ 242 | _task0_resp_nodata: 243 | // Sets CSR to ensure DT=1 244 | li x8, USB_EP_TYPE_CTRL | USB_EP_DT_BIT 245 | sw x8, EP_CSR(0x80) 246 | 247 | // Set BD for ZLP 248 | li x8, USB_BD_STATE_RDY_DATA | USB_BD_LEN(0) 249 | sw x8, EP_BD_CSR(0x80, 0) // EP 0 IN 250 | 251 | // Wait for TX success (or abort through incoming SETUP) 252 | // (for most requests, we could just go to _task0_loop directly, 253 | // but for SET_ADDRESS we need to wait and setup address 254 | // filtering _after_ the status stage ...) 255 | 1: 256 | jal x1, _task0_common_loop 257 | 258 | // Check BD 259 | // (uses sign extension of 'lh' to check MSB of CSR to know if buffer is done) 260 | lh x8, EP_BD_CSR(0x80, 0) 261 | bge x8, zero, 1b 262 | 263 | // Done, call completion handler 264 | jr x19 265 | 266 | 267 | /* 268 | * Continuously sends STALL to all IN/OUT requests until the next 269 | * setup packet 270 | */ 271 | _task0_resp_stall: 272 | // Common wait loop part 273 | jal x1, _task0_common_loop 274 | 275 | // Update BDs 276 | // (faster to rewrite them in all cases ...) 277 | li x8, USB_BD_STATE_RDY_STALL 278 | sw x8, EP_BD_CSR(0x00, 0) // EP 0 OUT DATA 279 | sw x8, EP_BD_CSR(0x80, 0) // EP 0 IN 280 | 281 | // And again 282 | j _task0_resp_stall 283 | 284 | 285 | /* 286 | * Control request handlers 287 | */ 288 | 289 | /* GET_STATUS: Always return { 0x00, 0x00 } */ 290 | _task0_h_get_status: 291 | // Setup response 292 | sw zero, %lo(ep0_tx)(x30) 293 | 294 | li x16, %lo(ep0_tx) 295 | li x17, 2 296 | 297 | // Return 298 | j _task0_resp_data 299 | 300 | /* SET_ADDRESS: Need to enable address filtering _after_ the status stage ! */ 301 | _task0_h_set_address: 302 | // No data, but we have a completion handler 303 | jal x19, _task0_resp_nodata 304 | 305 | // Setup address filtering 306 | li x8, USB_CSR_PU_ENA | USB_CSR_CEL_ENA | USB_CSR_ADDR_MATCH 307 | andi x13, x13, 0x7f // Safety 308 | add x8, x8, x13 309 | sw x8, USB_CSR 310 | 311 | // Done 312 | j _task0_loop 313 | 314 | /* GET_DESCRIPTOR: Lookup in 'desc_table' */ 315 | _task0_h_get_descriptor: 316 | // Find matching descriptor 317 | addi x8, x30, %lo(desc_table) 318 | 1: 319 | lhu x9, 0(x8) // [ type:index ] 320 | addi x8, x8, 8 321 | beq x9, zero, _task0_resp_stall // 0x0000 = end marker (not a valid desc type) 322 | bne x9, x13, 1b 323 | 324 | // Setup response 325 | lhu x16, -4(x8) 326 | lhu x17, -6(x8) 327 | 328 | // Return 329 | j _task0_resp_data 330 | 331 | /* GET_CONFIGURATION: FIXME */ 332 | _task0_h_get_configuration: 333 | // Return 334 | //j _task0_resp_data 335 | j _task0_resp_stall 336 | 337 | /* SET_CONFIGURATION: FIXME */ 338 | _task0_h_set_configuration: 339 | // Return 340 | j _task0_resp_nodata 341 | 342 | /* GET_INTERFACE: Always return { 0x00 } since we have no alt interface */ 343 | _task0_h_get_interface: 344 | // Setup response 345 | sw zero, %lo(ep0_tx)(x30) 346 | 347 | li x16, %lo(ep0_tx) 348 | li x17, 1 349 | 350 | // Return 351 | j _task0_resp_data 352 | 353 | /* SET_INTERFACE: Since we have no alt interface, we can STALL */ 354 | _task0_h_set_interface: 355 | // Return 356 | j _task0_resp_stall 357 | 358 | /* Microsoft OS 2.0 Descriptors support */ 359 | _task0_h_msos20: 360 | // Check wIndex == MSOS20_DESCRIPTOR_INDEX 361 | li x8, 0x07 362 | bne x8, x14, _task0_resp_stall 363 | 364 | // Setup response 365 | li x16, %lo(desc_msos20) 366 | lhu x17, (%lo(desc_msos20)+8)(x30) 367 | 368 | // Return 369 | j _task0_resp_data 370 | 371 | /* DFU_DETACH (only required request for runtime mode) */ 372 | _task0_h_dfu_detach: 373 | // No data, but we have a completion handler 374 | jal x19, _task0_resp_nodata 375 | 376 | // Trigger reboots 377 | li x8, 0x1000 378 | sw zero, 0(x8) 379 | 380 | // Done 381 | j _task0_loop 382 | 383 | 384 | /* 385 | * Control request handler table 386 | * [31:16] is the wRequestType 16 bits to match against 387 | * [15: 0] is the function pointer to handler 388 | */ 389 | .section .usb_rxbuf.const, "a" 390 | _task0_handlers: 391 | .word _task0_h_get_status + 0x00800000 // Dev 392 | .word _task0_h_get_status + 0x00810000 // Intf 393 | .word _task0_h_get_status + 0x00820000 // EP 394 | .word _task0_h_set_address + 0x05000000 395 | .word _task0_h_get_descriptor + 0x06800000 396 | .word _task0_h_get_configuration + 0x08800000 397 | .word _task0_h_set_configuration + 0x09000000 398 | .word _task0_h_get_interface + 0x0a810000 399 | .word _task0_h_set_interface + 0x0b010000 400 | .word _task0_h_msos20 + 0xc04d0000 401 | .word _task0_h_dfu_detach + 0x00210000 402 | .section .text 403 | 404 | #undef yield 405 | 406 | 407 | /* ------------------------------------------------------------------------ 408 | Task 1: USB CDC Data EP2 OUT 409 | ------------------------------------------------------------------------ */ 410 | 411 | /* Task 1 Register usage: 412 | * 413 | * x8 - General purpose temp register 414 | * x9 - General purpose temp register 415 | * 416 | * x16 - Pointer to current buffer descriptor CSR 417 | * 418 | * x29 - Constant: ExtIF base address 419 | */ 420 | 421 | #define yield jalr x2, x3 422 | 423 | _task1_start: 424 | // Init vars 425 | li x29, 0x1000 // ExtIF base 426 | li x16, 0x490 // EP_BD_CSR(0x02,0) = Current BD 427 | 428 | // Submit two BD as part of init 429 | li x8, USB_BD_STATE_RDY_DATA | USB_BD_LEN(64) 430 | sw x8, EP_BD_CSR(0x02, 0) 431 | sw x8, EP_BD_CSR(0x02, 1) 432 | 433 | _task1_loop: 434 | // Wait for BD to be filled 435 | 1: 436 | yield 437 | 438 | lh x8, 0(x16) 439 | bge x8, zero, 1b 440 | 441 | // Submit this to ExtIF OUT 442 | andi x8, x8, 0x3ff // Keep length only 443 | addi x8, x8, -2 // Remove CRC len 444 | bge zero, x8, 2f // Check it's not ZLP (or err if negative) 445 | addi x8, x8, -1 // Remove 1 since ExtIF.OUT is (len-1) 446 | lh x9, 4(x16) // Grab pointer 447 | or x8, x8, x9 // Combine length with pointer 448 | sw x8, 12(x29) // Send to ExtIF.OUT 449 | 450 | // Wait for ExtIF OUT to be done 451 | 1: 452 | yield 453 | 454 | lb x8, 4(x29) 455 | blt x8, zero, 1b 456 | 457 | // Resubmit this buffer 458 | 2: 459 | li x8, USB_BD_STATE_RDY_DATA | USB_BD_LEN(64) 460 | sw x8, 0(x16) 461 | 462 | // Next BD 463 | xori x16, x16, 0x0008 464 | 465 | // Loop 466 | j _task1_loop 467 | 468 | #undef yield 469 | 470 | 471 | /* ------------------------------------------------------------------------ 472 | Task 2: USB CDC Data EP2 IN 473 | ------------------------------------------------------------------------ */ 474 | 475 | /* Task 2 Register usage: 476 | * 477 | * x8 - General purpose temp register 478 | * x9 - General purpose temp register 479 | * x10 - General purpose temp register 480 | * 481 | * x16 - Pointer to current buffer descriptor CSR 482 | * x17 - Size of last sent packet (to evaluate ZLP need) 483 | * 484 | * x28 - Constant: 64 485 | * x29 - Constant: ExtIF base address 486 | */ 487 | 488 | #define yield jalr x3, x1 489 | 490 | _task2_start: 491 | // Init vars 492 | li x28, 64 493 | li x29, 0x1000 // ExtIF base 494 | 495 | li x16, 0x4b0 // EP_BD_CSR(0x82,0) = Current BD 496 | li x17, 0 497 | 498 | _task2_loop: 499 | // Wait for BD to be NONE or DONE 500 | 1: 501 | yield 502 | 503 | lh x8, 0(x16) 504 | andi x8, x8, -2048 // Mask out 'len' 505 | blt zero, x8, 1b 506 | 507 | // Submit this buffer to ExtIF IN 508 | lh x8, 4(x16) // Grab pointer 509 | sw x8, 8(x29) // Send to ExtIF.IN 510 | 511 | // Re-enable ExtIF in case we disabled it 512 | li x8, 1 513 | sw x8, 4(x29) 514 | 515 | // Wait for either : 516 | // - Timeout if "Flush by timeout" is enabled 517 | // - "Flush Now" signal 518 | // - IN byte count to be full 519 | 1: 520 | yield 521 | 522 | lbu x8, 4(x29) // Grab CSR[7:0] == Flags 523 | lbu x9, 5(x29) // Grab CSR[15:8] == IN byte count 524 | 525 | beq x17, x28, 2f // Check if last packet was MPS 526 | bne x9, x28, 2f // Check if empty 527 | li x4, -5 // Reset timeout counter if empty and 528 | j 1b // last packet was not MPS 529 | 2: 530 | 531 | blt x9, x28, 4f // Check if full or early ended 532 | 533 | andi x10, x8, 1<<5 // Check "Flush Now" 534 | bne x10, zero, 3f 535 | 536 | andi x10, x8, 1<<4 // Check "Flush Timeout" 537 | beq x10, zero, 1b 538 | 539 | blt x4, zero, 1b // If timeout counter is still < 0, don't flush yet 540 | 541 | // This is an early flush, disable ExtIF to avoid race conditions 542 | // and reload the current length 543 | 3: 544 | sw zero, 4(x29) 545 | lbu x9, 5(x29) // Grab CSR[15:8] == IN byte count 546 | 547 | // Submit BD to USB core 548 | 4: 549 | xori x17, x9, 64 // Compute actual size (0 - 64) 550 | bge x28, x17, 5f // from CSR[15:8] 551 | addi x17, x17, -64 552 | 5: 553 | 554 | li x8, USB_BD_STATE_RDY_DATA 555 | or x8, x8, x17 556 | sw x8, 0(x16) 557 | 558 | // Next BD 559 | xori x16, x16, 0x0008 560 | 561 | // Reset timeout since we just sent something 562 | li x4, -5 563 | 564 | // Loop 565 | j _task2_loop 566 | 567 | #undef yield 568 | --------------------------------------------------------------------------------