Bladeren bron

projects/riscv_usb: Import RISCV + USB prototype

Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
Sylvain Munaut 6 jaren geleden
bovenliggende
commit
9cf400b9ad

+ 38 - 0
projects/riscv_usb/Makefile

@@ -0,0 +1,38 @@
+# Project config
+PROJ = riscv_usb
+
+PROJ_DEPS := usb misc
+PROJ_RTL_SRCS := $(addprefix rtl/, \
+	bridge.v \
+	picorv32.v \
+	soc_bram.v \
+	soc_spram.v \
+	sysmgr.v \
+)
+PROJ_SIM_SRCS := $(addprefix sim/, \
+	spiflash.v \
+)
+PROJ_SIM_SRCS += rtl/top.v
+PROJ_TESTBENCHES := \
+        top_tb
+PROJ_PREREQ = \
+	$(BUILD_TMP)/boot.hex
+PROJ_TOP_SRC := rtl/top.v
+PROJ_TOP_MOD := top
+
+# Target config
+BOARD ?= icebreaker
+DEVICE = up5k
+PACKAGE = sg48
+
+NEXTPNR_ARGS = --pre-pack data/clocks.py --seed 2
+
+# Include default rules
+include ../../build/project-rules.mk
+
+# Custom rules
+fw/boot.hex:
+	make -C fw boot.hex
+
+$(BUILD_TMP)/boot.hex: fw/boot.hex
+	cp $< $@

+ 18 - 0
projects/riscv_usb/README.md

@@ -0,0 +1,18 @@
+RISC-V + USB core demo
+======================
+
+For the icebreaker, the hardware connections are :
+  * `P1B4`: USB DP
+  * `P1B3`: USB DN
+  * `P1B2`: Pull up. Resistor of 1.5 kOhm to USB DP 
+
+To run :
+  * Build and flash the bitstream
+      * This will build `fw/boot.hex` and include it as the BRAM initial data
+
+  * Flash the main application code in SPI at offset 1M
+      * `make -C fw prog_fw`
+
+  * Connect to the iCEBreaker uart console (`ttyUSB1`) with a 1M baudrate
+      * and then at the `Command>` prompt, press `r` for 'run'. This will
+        start the USB detection and device should enumerate

+ 2 - 0
projects/riscv_usb/data/clocks.py

@@ -0,0 +1,2 @@
+ctx.addClock("clk_24m", 25)
+ctx.addClock("clk_48m", 49)

+ 38 - 0
projects/riscv_usb/data/top-icebreaker.pcf

@@ -0,0 +1,38 @@
+# SPI
+set_io -nowarn spi_mosi 14
+set_io -nowarn spi_miso 17
+set_io -nowarn spi_clk 15
+set_io -nowarn spi_flash_cs_n 16
+set_io -nowarn spi_ram_cs_n 37
+
+# USB
+set_io -nowarn usb_dp 31
+set_io -nowarn usb_dn 34
+set_io -nowarn usb_pu 38
+
+# UART
+set_io -nowarn uart_rx 6
+set_io -nowarn uart_tx 9
+
+# Clock
+set_io -nowarn clk_in 35
+
+# Leds
+set_io -nowarn rgb[0] 39
+set_io -nowarn rgb[1] 40
+set_io -nowarn rgb[2] 41
+
+# Debug
+set_io -nowarn debug[0] 45
+set_io -nowarn debug[1] 47
+set_io -nowarn debug[2]  2
+set_io -nowarn debug[3]  4
+set_io -nowarn debug[4] 44
+set_io -nowarn debug[5] 46
+set_io -nowarn debug[6] 48
+set_io -nowarn debug[7]  3
+set_io -nowarn debug[8] 28
+set_io -nowarn debug[9] 32
+set_io -nowarn debug[10] 36
+set_io -nowarn debug[11] 42
+

+ 3 - 0
projects/riscv_usb/fw/.gitignore

@@ -0,0 +1,3 @@
+*.bin
+*.elf
+*.hex

+ 37 - 0
projects/riscv_usb/fw/Makefile

@@ -0,0 +1,37 @@
+CROSS = riscv-none-embed-
+CC = $(CROSS)gcc
+OBJCOPY = $(CROSS)objcopy
+ICEPROG = iceprog
+
+CFLAGS=-Wall -Os -march=rv32i -mabi=ilp32 -ffreestanding -nostartfiles
+
+HEADERS=\
+	mini-printf.h \
+	io.h \
+	usb_desc.h \
+	usb_desc_data.h
+
+SOURCES=\
+	start.S \
+	firmware.c \
+	io.c \
+	mini-printf.c  \
+	usb_desc.c
+
+firmware.elf: lnk-app.lds $(HEADERS) $(SOURCES)
+	$(CC) $(CFLAGS) -Wl,-Bstatic,-T,lnk-app.lds,--strip-debug -o $@ $(SOURCES)
+
+boot.elf: lnk-boot.lds boot.S
+	$(CC) $(CFLAGS) -Wl,-Bstatic,-T,lnk-boot.lds,--strip-debug -o $@ boot.S
+
+%.hex: %.bin
+	./bin2hex.py $< $@
+
+%.bin: %.elf
+	$(OBJCOPY) -O binary $< $@
+
+prog_fw: firmware.bin
+	$(ICEPROG) -o 1M $<
+
+clean:
+	rm -f *.bin *.hex *.elf

+ 16 - 0
projects/riscv_usb/fw/bin2hex.py

@@ -0,0 +1,16 @@
+#!/usr/bin/env python3
+
+import struct
+import sys
+
+
+def main(argv0, in_name, out_name):
+	with open(in_name, 'rb') as in_fh, open(out_name, 'w') as out_fh:
+		while True:
+			b = in_fh.read(4)
+			if len(b) < 4:
+				break
+			out_fh.write('%08x\n' % struct.unpack('<I', b))
+
+if __name__ == '__main__':
+	main(*sys.argv)

+ 173 - 0
projects/riscv_usb/fw/boot.S

@@ -0,0 +1,173 @@
+/*
+ * boot.S
+ *
+ * SPI boot code
+ *
+ * Copyright (C) 2019 Sylvain Munaut <tnt@246tNt.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef FLASH_APP_ADDR
+#define FLASH_APP_ADDR 0x00100000
+#endif
+
+	.section .text
+
+start:
+
+#ifdef BOOT_DEBUG
+	// Set UART divisor
+	li	a0, 0x81000004
+	li	a1, 23
+	sw	a1, 0(a0)
+
+	// Output 'a'
+	li	a0, 0x81000000
+	li	a1, 97
+	sw	a1, 0(a0)
+#endif
+
+	// SPI init
+	jal	spi_init
+
+#ifdef BOOT_DEBUG
+	// Output 'b'
+	li	a0, 0x81000000
+	li	a1, 98
+	sw	a1, 0(a0)
+#endif
+
+	li	a0, 0x00010000
+	li	a1, 0x00010000
+	li	a2, FLASH_APP_ADDR
+	jal	spi_flash_read
+
+#ifdef BOOT_DEBUG
+	// Output 'c'
+	li	a0, 0x81000000
+	li	a1, 99
+	sw	a1, 0(a0)
+#endif
+
+	// Setup reboot code
+	li	t0, 0x0001006f
+	sw	t0, 0(zero)
+
+	// Jump to main code
+	j	0x00010000
+
+
+	.equ    SPI_BASE, 0x82000000
+	.equ    SPICR0,  4 * 0x08
+	.equ    SPICR1,  4 * 0x09
+	.equ    SPICR2,  4 * 0x0a
+	.equ    SPIBR,   4 * 0x0b
+	.equ    SPISR,   4 * 0x0c
+	.equ    SPITXDR, 4 * 0x0d
+	.equ    SPIRXDR, 4 * 0x0e
+	.equ    SPICSR,  4 * 0x0f
+
+
+spi_init:
+	li	a0, SPI_BASE
+
+	li	a1, 0xff
+	sw	a1, SPICR0(a0)
+
+	li	a1, 0x80
+	sw	a1, SPICR1(a0)
+
+	li	a1, 0xc0
+	sw	a1, SPICR2(a0)
+
+	li	a1, 0x03
+	sw	a1, SPIBR(a0)
+
+	li	a1, 0x0f
+	sw	a1, SPICSR(a0)
+
+	ret
+
+
+// Params:
+//  a0 - destination pointer
+//  a1 - length (bytes)
+//  a2 - flash offset
+//       
+
+spi_flash_read:
+	// Save params
+	mv	s0, a0
+	mv	s1, a1
+	mv	s2, ra
+
+	// Setup CS
+	li	t0, SPI_BASE
+	li	t1, 0x0e
+	sw	t1, SPICSR(t0)
+
+	// Send command
+	li	a0, 0x03
+	jal	_spi_do_one
+
+	srli	a0, a2, 16
+	and	a0, a0, 0xff
+	jal	_spi_do_one
+
+	srli	a0, a2, 8
+	and	a0, a0, 0xff
+	jal	_spi_do_one
+
+	and	a0, a2, 0xff
+	jal	_spi_do_one
+
+	// Read loop
+_spi_loop:
+	li	a0, 0x00
+	jal	_spi_do_one
+	sb	a0, 0(s0)	
+	addi	s0, s0,  1
+	addi	s1, s1, -1
+	bne	s1, zero, _spi_loop
+
+	// Release CS
+	li	t0, SPI_BASE
+	li	t1, 0x0f
+	sw	t1, SPICSR(t0)
+
+	// Done
+	jr	s2
+
+
+// Params:  a0 - Data to TX
+// Returns: a0 - RX data
+// Clobbers t0, t1
+_spi_do_one:
+	li	t0, SPI_BASE
+	li	t1, 0x08
+
+	// Write TX data
+	sw	a0, SPITXDR(t0)
+
+	// Wait for RXRDY
+1:
+	lw	a0, SPISR(t0)
+	and	a0, a0, t1
+	bne	a0, t1, 1b
+
+	// Read RX data
+	lw	a0, SPIRXDR(t0)
+
+	// Done
+	ret

+ 583 - 0
projects/riscv_usb/fw/firmware.c

@@ -0,0 +1,583 @@
+/*
+ * firmware.c
+ *
+ * Copyright (C) 2019 Sylvain Munaut
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "io.h"
+#include "usb_desc.h"
+
+
+
+/* USB specs structures / values */
+
+struct usb_ctrl_req_hdr {
+	uint8_t  bmRequestType;
+	uint8_t  bRequest;
+	uint16_t wValue;
+	uint16_t wIndex;
+	uint16_t wLength;
+}  __attribute__((packed));
+
+#define USB_REQ_IS_READ(req)		(  req->bmRequestType & 0x80 )
+#define USB_REQ_IS_WRITE(req)		(!(req->bmRequestType & 0x80))
+
+#define USB_REQ_GET_STATUS		0
+#define USB_REQ_CLEAR_FEATURE		1
+#define USB_REQ_SET_FEATURE		3
+#define USB_REQ_SET_ADDRESS		5
+#define USB_REQ_GET_DESCRIPTOR		6
+#define USB_REQ_SET_DESCRIPTOR		7
+#define USB_REQ_GET_CONFIGURATION	8
+#define USB_REQ_SET_CONFIGURATION	9
+#define USB_REQ_GET_INTERFACE		10
+#define USB_REQ_SET_INTERFACE		11
+#define USB_REQ_SYNCHFRAME		12
+
+
+
+/* Registers / Control addresses */
+
+#define usb_csr			(*(volatile uint32_t*)(0x84000000))
+#define usb_ep_status(ep,dir)	(*(volatile uint32_t*)(0x84002000 + ((ep)<<6) + ((dir) << 5)))
+#define usb_ep_bd(ep,dir,i,w)	(*(volatile uint32_t*)(0x84002010 + ((ep)<<6) + ((dir) << 5) + ((i) << 3) + ((w) << 2)))
+#define usb_data(o)		(*(volatile uint32_t*)(0x85000000 + ((o) << 2)))
+
+#define USB_SR_IS_SETUP		(1 <<  2)
+#define USB_SR_IRQ_PENDING	(1 <<  0)
+
+#define USB_CR_PU_ENA		(1 << 15)
+#define USB_CR_CEL_ENA		(1 << 14)
+#define USB_CR_CEL_RELEASE	(1 <<  1)
+#define USB_CR_IRQ_ACK		(1 <<  0)
+
+#define USB_EP_TYPE_NONE	0x0000
+#define USB_EP_TYPE_ISOC	0x0001
+#define USB_EP_TYPE_INT		0x0002
+#define USB_EP_TYPE_BULK	0x0004
+#define USB_EP_TYPE_CTRL	0x0006
+#define USB_EP_TYPE_HALTED	0x0001
+
+#define USB_EP_DT_BIT		0x0080
+#define USB_EP_BD_IDX		0x0040
+#define USB_EP_BD_CTRL		0x0020
+#define USB_EP_BD_DUAL		0x0010
+
+#define USB_BD_STATE_MSK	0xe000
+#define USB_BD_STATE_NONE	0x0000
+#define USB_BD_STATE_RDY_DATA	0x4000
+#define USB_BD_STATE_RDY_STALL	0x6000
+#define USB_BD_STATE_DONE_OK	0x8000
+#define USB_BD_STATE_DONE_ERR	0xa000
+#define USB_BD_IS_SETUP		0x1000
+
+
+/* Helpers to copy data to/from EP memory */
+
+static void
+usb_data_write(volatile uint32_t *dst_u32, const void *src, int len)
+{
+	uint32_t *src_u32 = (uint32_t *)src;
+	int i, j;
+
+	for (i=0, j=0; i<len; i+=4, j++)
+		dst_u32[j] = src_u32[j];
+}
+
+static void
+usb_data_read(void *dst, const volatile uint32_t *src_u32, int len)
+{
+	uint32_t *dst_u32 = (uint32_t *)dst;
+	uint8_t  *dst_u8  = (uint8_t  *)dst;
+	uint32_t x;
+	int i, j;
+
+	for (i=0, j=0; i<(len-3); i+=4, j++)
+		dst_u32[j] = src_u32[j];
+
+	if (len & 3) {
+		x = src_u32[j];
+		for (;i<len; i++) {
+			dst_u8[i] = (x & 0xff);
+			x >>= 8;
+		}
+	}
+}
+
+
+/* Main USB functions */
+static void
+usb_short_debug_print(void)
+{
+	printf("BD0.0  %04x\n", usb_ep_bd(0,0,0,0));
+	printf("BD1.0  %04x\n", usb_ep_bd(0,0,1,0));
+}
+
+static void
+usb_debug_print(void)
+{
+	puts("\nCSR\n");
+	printf("SR    %04x\n", usb_csr);
+
+	puts("\nEP0 OUT\n");
+	printf("S      %04x\n", usb_ep_status(0,0));
+	printf("BD0.0  %04x\n", usb_ep_bd(0,0,0,0));
+	printf("BD0.1  %04x\n", usb_ep_bd(0,0,0,1));
+	printf("BD1.0  %04x\n", usb_ep_bd(0,0,1,0));
+	printf("BD1.1  %04x\n", usb_ep_bd(0,0,1,1));
+
+	puts("\nEP0 IN\n");
+	printf("S      %04x\n", usb_ep_status(0,1));
+	printf("BD0.0  %04x\n", usb_ep_bd(0,1,0,0));
+	printf("BD0.1  %04x\n", usb_ep_bd(0,1,0,1));
+	printf("BD1.0  %04x\n", usb_ep_bd(0,1,1,0));
+	printf("BD1.1  %04x\n", usb_ep_bd(0,1,1,1));
+
+	puts("\nEP1 OUT\n");
+	printf("S      %04x\n", usb_ep_status(1,0));
+	printf("BD0.0  %04x\n", usb_ep_bd(1,0,0,0));
+	printf("BD0.1  %04x\n", usb_ep_bd(1,0,0,1));
+	printf("BD1.0  %04x\n", usb_ep_bd(1,0,1,0));
+	printf("BD1.1  %04x\n", usb_ep_bd(1,0,1,1));
+
+	puts("\nData\n");
+	printf("%08x\n", usb_data(0));
+	printf("%08x\n", usb_data(1));
+	printf("%08x\n", usb_data(2));
+	printf("%08x\n", usb_data(3));
+}
+
+
+struct {
+	uint32_t csr;
+
+	struct {
+		enum {
+			IDLE,
+			DATA_IN,		/* Data stage via 'IN'  */
+			DATA_OUT,		/* Data stage via 'OUT' */
+			STATUS_DONE_OUT,	/* Status sent via 'OUT' EP */
+			STATUS_DONE_IN,		/* Status sent via 'IN' EP */
+		} state;
+
+		struct usb_ctrl_req_hdr req;
+
+		union {
+			const uint8_t *out;
+			uint8_t *in;
+		} data;
+
+		int len;
+		int ofs;
+	} ctrl;
+} g_usb;
+
+
+static inline void
+usb_ep0_out_queue_bd(bool setup, int ofs, int len, bool stall)
+{
+	int bdi = setup ? 1 : 0;
+	usb_ep_bd(0,0,bdi,1) = ofs;
+	usb_ep_bd(0,0,bdi,0) = stall ? 0x6000 : (0x4000 | len);
+}
+
+static inline void
+usb_ep0_in_queue_bd(int ofs, int len, bool stall)
+{
+	usb_ep_bd(0,1,0,1) = ofs;
+	usb_ep_bd(0,1,0,0) = stall ? 0x6000 : (0x4000 | len);
+}
+
+static inline uint32_t
+usb_ep0_out_peek_bd(bool setup)
+{
+	int bdi = setup ? 1 : 0;
+	return usb_ep_bd(0,0,bdi,0);
+}
+
+static inline uint32_t
+usb_ep0_in_peek_bd(void)
+{
+	return usb_ep_bd(0,1,0,0);
+}
+
+static inline void
+usb_ep0_out_done_bd(bool setup)
+{
+	int bdi = setup ? 1 : 0;
+	usb_ep_bd(0,0,bdi,0) = 0;
+}
+
+static inline void
+usb_ep0_in_done_bd(void)
+{
+	usb_ep_bd(0,1,0,0) = 0;
+}
+
+
+static void
+usb_init(void)
+{
+	memset(&g_usb, 0x00, sizeof(g_usb));
+
+	g_usb.csr = USB_CR_PU_ENA | USB_CR_CEL_ENA;
+
+	/* Configure EP0 */
+	usb_ep_status(0,0) = 0x0026;	/* Type=Control, control mode buffered */
+	usb_ep_status(0,1) = 0x0086;	/* Type=Control, single buffered, DT=1 */
+
+	/* Queue one buffer for SETUP */
+	usb_ep0_out_queue_bd(true, 0, 64, false);
+
+	/* Configure EP1 IN/OUT */
+	usb_ep_status(1,0) = 0x0011;	/* Type=Isochronous, dual buffered */
+	usb_ep_status(1,1) = 0x0011;	/* Type=Isochronous, dual buffered */
+
+	usb_ep_bd(1,0,0,1) = 1184;
+	usb_ep_bd(1,0,0,0) = 0x4000 | 432;
+
+	usb_ep_bd(1,0,1,1) = 1616;
+	usb_ep_bd(1,0,1,0) = 0x4000 | 432;
+
+	g_usb.ctrl.state = IDLE;
+}
+
+
+
+static void
+usb_handle_control_data()
+{
+	/* Handle read requests */
+	if (g_usb.ctrl.state == DATA_IN) {
+		/* How much left to do ? */
+		int xflen = g_usb.ctrl.len - g_usb.ctrl.ofs;
+		if (xflen > 64)
+			xflen = 64;
+
+		/* Setup descriptor for output */
+		if (xflen)
+			usb_data_write(&usb_data(0), &g_usb.ctrl.data.in[g_usb.ctrl.ofs], xflen);
+		usb_ep0_in_queue_bd(0, xflen, false);
+
+		/* Move on */
+		g_usb.ctrl.ofs += xflen;
+
+		/* If we're done, setup the OUT ack */
+		if (xflen < 64) {
+			usb_ep0_out_queue_bd(false, 0, 0, false);
+			g_usb.ctrl.state = STATUS_DONE_OUT;
+		}
+	}
+
+	/* Handle write requests */
+	if (g_usb.ctrl.state == DATA_OUT) {
+		if (g_usb.ctrl.ofs == g_usb.ctrl.len)
+		{
+			/* Done, ACK with a ZLP */
+			usb_ep0_in_queue_bd(0, 0, false);
+			g_usb.ctrl.state = STATUS_DONE_IN;
+		}
+		else
+		{
+			/* Fill a BD with as much as we can */
+
+		}
+	}
+}
+
+static void
+usb_handle_control_request(struct usb_ctrl_req_hdr *req)
+{
+	bool handled = false;
+
+	/* Defaults */
+	g_usb.ctrl.data.in  = NULL;
+	g_usb.ctrl.data.out = NULL;
+	g_usb.ctrl.len  = req->wLength;
+	g_usb.ctrl.ofs  = 0;
+
+	/* Process request */
+	switch (req->bRequest)
+	{
+	case USB_REQ_GET_STATUS:
+	case USB_REQ_CLEAR_FEATURE:
+	case USB_REQ_SET_FEATURE:
+		break;
+
+	case USB_REQ_SET_ADDRESS:
+		handled = true;
+		break;
+
+	case USB_REQ_GET_DESCRIPTOR:
+	{
+		int idx = req->wValue & 0xff;
+
+		switch (req->wValue & 0xff00)
+		{
+		case 0x0100:	/* Device */
+			g_usb.ctrl.data.out = usb_get_device_desc(&g_usb.ctrl.len);
+			break;
+
+		case 0x0200:	/* Configuration */
+			g_usb.ctrl.data.out = usb_get_config_desc(&g_usb.ctrl.len, idx);
+			break;
+
+		case 0x0300:	/* String */
+			g_usb.ctrl.data.out = usb_get_string_desc(&g_usb.ctrl.len, idx);
+			break;
+		}
+
+		handled = g_usb.ctrl.data.out != NULL;
+		break;
+	}
+
+	case USB_REQ_SET_DESCRIPTOR:
+	case USB_REQ_GET_CONFIGURATION:
+		break;
+
+	case USB_REQ_SET_CONFIGURATION:
+		handled = true;
+		break;
+
+	case USB_REQ_GET_INTERFACE:
+	case USB_REQ_SET_INTERFACE:
+	case USB_REQ_SYNCHFRAME:
+	default:
+		break;
+	}
+
+	/* If the request isn't handled, answer wil STALL */
+	if (!handled) {
+		if (USB_REQ_IS_READ(req)) {
+			/* Read request, send a STALL for the DATA IN stage */
+			g_usb.ctrl.state = STATUS_DONE_IN;
+			usb_ep0_in_queue_bd(0, 0, true);
+		} else if (req->wLength) {
+			/* Write request with some incoming data, send a STALL to next OUT */
+			g_usb.ctrl.state = STATUS_DONE_OUT;
+			usb_ep0_out_queue_bd(false, 0,  0, true);
+		} else {
+			/* Write request with no dat, send a STALL in the STATUS IN stage */
+			g_usb.ctrl.state = STATUS_DONE_IN;
+			usb_ep0_in_queue_bd(0, 0, true);
+		}
+
+		return;
+	}
+
+	/* Handle the 'data' stage now */
+	g_usb.ctrl.state = USB_REQ_IS_READ(req) ? DATA_IN : DATA_OUT;
+
+	if (g_usb.ctrl.len > req->wLength)
+		g_usb.ctrl.len = req->wLength;
+
+	usb_handle_control_data();
+}
+
+static void
+usb_run_control(void)
+{
+	uint32_t bds_setup, bds_out, bds_in;
+	bool acted;
+
+	do {
+		/* Not done anything yet */
+		acted = false;
+
+		/* Grab current EP status */
+		bds_out   = usb_ep0_out_peek_bd(false);
+		bds_setup = usb_ep0_out_peek_bd(true);
+		bds_in    = usb_ep0_in_peek_bd();
+
+		/* Check for status IN stage finishing */
+		if (g_usb.ctrl.state == STATUS_DONE_IN) {
+			if ((bds_in & USB_BD_STATE_MSK) == USB_BD_STATE_DONE_OK)
+			{
+				g_usb.ctrl.state = IDLE;
+				usb_ep0_in_done_bd();
+				acted = true;
+				continue;
+			}
+		}
+
+		/* Check for status OUT stage finishing */
+		if (g_usb.ctrl.state == STATUS_DONE_OUT) {
+			if ((bds_out & USB_BD_STATE_MSK) == USB_BD_STATE_DONE_OK)
+			{
+				if ((bds_out & 0x0fff) == 2) {
+					g_usb.ctrl.state = IDLE;
+					usb_ep0_out_done_bd(false);
+					acted = true;
+					continue;
+				} else {
+					puts("[!] Got a non ZLP as a status stage packet ?!?\n");
+				}
+			}
+		}
+
+		/* Retry any RX error on both setup and data buffers */
+		if ((bds_setup & USB_BD_STATE_MSK) == USB_BD_STATE_DONE_ERR)
+		{
+			usb_ep0_out_queue_bd(true, 0, 64, false);
+			acted = true;
+			continue;
+		}
+
+		if ((bds_out & USB_BD_STATE_MSK) == USB_BD_STATE_DONE_ERR)
+		{
+			usb_ep0_out_queue_bd(false, 64, 64, false);
+			acted = true;
+			continue;
+		}
+
+		/* Check for SETUP */
+		if ((bds_setup & USB_BD_STATE_MSK) == USB_BD_STATE_DONE_OK)
+		{
+			/* Really setup ? */
+			if (!(bds_setup & USB_BD_IS_SETUP)) {
+				puts("[!] Got non-SETUP in the SETUP BD !?!\n");
+			}
+
+			/* Were we waiting for this ? */
+			if (g_usb.ctrl.state != IDLE) {
+				puts("[!] Got SETUP while busy !??\n");
+			}
+
+			/* Clear descriptors */
+			usb_ep_bd(0,0,0,0) = 0x0000;
+			usb_ep_bd(0,1,0,0) = 0x0000;
+
+			/* Make sure DT=1 for IN endpoint after a SETUP */
+			usb_ep_status(0,1) = 0x0086;	/* Type=Control, single buffered, DT=1 */
+
+			/* We acked it, need to handle it */
+			usb_data_read(&g_usb.ctrl.req, &usb_data(0), sizeof(struct usb_ctrl_req_hdr));
+			usb_handle_control_request(&g_usb.ctrl.req);
+
+			/* Release the lockout and allow new SETUP */
+			usb_csr = g_usb.csr | USB_CR_CEL_RELEASE;
+			usb_ep0_out_queue_bd(true, 0, 64, false);
+
+			return;
+		}
+
+		/* Process data stage */
+		if (((bds_out & USB_BD_STATE_MSK) == USB_BD_STATE_DONE_OK)) {
+			usb_ep0_out_done_bd(false);
+			if (g_usb.ctrl.state != DATA_OUT) {
+				puts("[!] Got unexpected DATA !?!\n");
+				continue;
+			}
+			usb_handle_control_data();
+			acted = true;
+		}
+
+		if ((bds_in & USB_BD_STATE_MSK) == USB_BD_STATE_DONE_OK) {
+			usb_ep0_in_done_bd();
+			if (g_usb.ctrl.state == DATA_IN) {
+				usb_handle_control_data();
+				acted = true;
+			}
+		}
+
+	} while (acted);
+}
+
+static void
+usb_run(void)
+{
+	uint32_t status;
+	int isoc_bdi = 0;
+
+	/* Enable pull-up for detection */
+	usb_csr = g_usb.csr;
+
+	/* Main polling loop */
+	while (1) {
+		if (getchar_nowait() == 'd')
+			usb_debug_print();
+
+		/* Poll for activity */
+		status = usb_csr;
+		if (!(status & USB_SR_IRQ_PENDING))
+			continue;
+
+		/* Ack interrupt */
+		usb_csr = g_usb.csr | USB_CR_IRQ_ACK;
+
+		/* Check control transfers */
+		usb_run_control();
+
+		/* Check ISOC */
+		{
+			uint32_t bds = usb_ep_bd(1,0,isoc_bdi,0);
+
+			if ((bds & USB_BD_STATE_MSK) == USB_BD_STATE_DONE_OK)
+			{
+				printf("%d\n", bds & 0xfff);
+
+				/* Re-arm */
+				usb_ep_bd(1,0,isoc_bdi,0) = 0x4000 | 432;
+				isoc_bdi ^= 1;
+			}
+		}
+	}
+}
+
+
+void main()
+{
+	/* Init debug IO */
+	io_init();
+	puts("Booting..\n");
+
+	/* Init USB */
+	usb_init();
+
+	while (1)
+	{
+		for (int rep = 10; rep > 0; rep--)
+		{
+			puts("Command> ");
+			char cmd = getchar();
+			if (cmd > 32 && cmd < 127)
+				putchar(cmd);
+			puts("\n");
+
+			switch (cmd)
+			{
+			case 'd':
+				usb_debug_print();
+				break;
+			case 'r':
+				usb_run();
+				break;
+			default:
+				continue;
+			}
+
+			break;
+		}
+	}
+}

+ 81 - 0
projects/riscv_usb/fw/io.c

@@ -0,0 +1,81 @@
+/*
+ * io.c
+ *
+ * Copyright (C) 2019 Sylvain Munaut
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <stdint.h>
+
+#include "mini-printf.h"
+
+#define NULL ((void*)0)
+
+#define reg_uart_clkdiv (*(volatile uint32_t*)0x81000004)
+#define reg_uart_data   (*(volatile uint32_t*)0x81000000)
+
+static char _printf_buf[128];
+
+void io_init(void)
+{
+	reg_uart_clkdiv = 23;	/* 1 Mbaud with clk=24MHz */
+}
+
+char getchar(void)
+{
+	int32_t c;
+	do {
+		c = reg_uart_data;
+	} while (c & 0x80000000);
+	return c;
+}
+
+int getchar_nowait(void)
+{
+	int32_t c;
+	c = reg_uart_data;
+	return c & 0x80000000 ? -1 : (c & 0xff);
+}
+
+void putchar(char c)
+{
+	if (c == '\n')
+		putchar('\r');
+	reg_uart_data = c;
+}
+
+void puts(const char *p)
+{
+	while (*p)
+		putchar(*(p++));
+}
+
+int printf(const char *fmt, ...)
+{
+        va_list va;
+        int l;
+
+        va_start(va, fmt);
+        l = mini_vsnprintf(_printf_buf, 128, fmt, va);
+        va_end(va);
+
+	puts(_printf_buf);
+
+	return l;
+}

+ 31 - 0
projects/riscv_usb/fw/io.h

@@ -0,0 +1,31 @@
+/*
+ * io.h
+ *
+ * Copyright (C) 2019 Sylvain Munaut
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+void io_init(void);
+char getchar(void);
+int  getchar_nowait(void);
+void putchar(char c);
+void puts(const char *p);
+int  printf(const char *fmt, ...);

+ 51 - 0
projects/riscv_usb/fw/lnk-app.lds

@@ -0,0 +1,51 @@
+MEMORY
+{
+    ROM (rx)    : ORIGIN = 0x00010000, LENGTH = 0xc000
+    SPRAM (xrw) : ORIGIN = 0x0001c000, LENGTH = 0x4000
+    BRAM  (xrw) : ORIGIN = 0x00000010, LENGTH = 0x03f0
+}
+SECTIONS {
+    .text :
+    {
+        . = ALIGN(4);
+        *(.text)
+        *(.text*)
+        *(.rodata)
+        *(.rodata*)
+        *(.srodata)
+        *(.srodata*)
+        . = ALIGN(4);
+        _etext = .;
+        _sidata = _etext;
+    } >ROM
+    .data : AT ( _sidata )
+    {
+        . = ALIGN(4);
+        _sdata = .;
+        _ram_start = .;
+        . = ALIGN(4);
+        *(.data)
+        *(.data*)
+        *(.sdata)
+        *(.sdata*)
+        . = ALIGN(4);
+        _edata = .;
+    } >SPRAM
+    .bss :
+    {
+        . = ALIGN(4);
+        _sbss = .;
+        *(.bss)
+        *(.bss*)
+        *(.sbss)
+        *(.sbss*)
+        *(COMMON)
+        . = ALIGN(4);
+        _ebss = .;
+    } >SPRAM
+    .heap :
+    {
+        . = ALIGN(4);
+        _heap_start = .;
+    } >SPRAM
+}

+ 12 - 0
projects/riscv_usb/fw/lnk-boot.lds

@@ -0,0 +1,12 @@
+MEMORY
+{
+    ROM (rx)    : ORIGIN = 0x00000000, LENGTH = 0x0400
+}
+SECTIONS {
+    .text :
+    {
+        . = ALIGN(4);
+        *(.text)
+        *(.text*)
+    } >ROM
+}

+ 208 - 0
projects/riscv_usb/fw/mini-printf.c

@@ -0,0 +1,208 @@
+/*
+ * The Minimal snprintf() implementation
+ *
+ * Copyright (c) 2013,2014 Michal Ludvig <michal@logix.cz>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the auhor nor the names of its contributors
+ *       may be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ----
+ *
+ * This is a minimal snprintf() implementation optimised
+ * for embedded systems with a very limited program memory.
+ * mini_snprintf() doesn't support _all_ the formatting
+ * the glibc does but on the other hand is a lot smaller.
+ * Here are some numbers from my STM32 project (.bin file size):
+ *      no snprintf():      10768 bytes
+ *      mini snprintf():    11420 bytes     (+  652 bytes)
+ *      glibc snprintf():   34860 bytes     (+24092 bytes)
+ * Wasting nearly 24kB of memory just for snprintf() on
+ * a chip with 32kB flash is crazy. Use mini_snprintf() instead.
+ *
+ */
+
+#include "mini-printf.h"
+
+static unsigned int
+mini_strlen(const char *s)
+{
+	unsigned int len = 0;
+	while (s[len] != '\0') len++;
+	return len;
+}
+
+static unsigned int
+mini_itoa(int value, unsigned int radix, unsigned int uppercase, unsigned int unsig,
+	 char *buffer, unsigned int zero_pad)
+{
+	char	*pbuffer = buffer;
+	int	negative = 0;
+	unsigned int	i, len;
+
+	/* No support for unusual radixes. */
+	if (radix > 16)
+		return 0;
+
+	if (value < 0 && !unsig) {
+		negative = 1;
+		value = -value;
+	}
+
+	/* This builds the string back to front ... */
+	do {
+		int digit = value % radix;
+		*(pbuffer++) = (digit < 10 ? '0' + digit : (uppercase ? 'A' : 'a') + digit - 10);
+		value /= radix;
+	} while (value > 0);
+
+	for (i = (pbuffer - buffer); i < zero_pad; i++)
+		*(pbuffer++) = '0';
+
+	if (negative)
+		*(pbuffer++) = '-';
+
+	*(pbuffer) = '\0';
+
+	/* ... now we reverse it (could do it recursively but will
+	 * conserve the stack space) */
+	len = (pbuffer - buffer);
+	for (i = 0; i < len / 2; i++) {
+		char j = buffer[i];
+		buffer[i] = buffer[len-i-1];
+		buffer[len-i-1] = j;
+	}
+
+	return len;
+}
+
+struct mini_buff {
+	char *buffer, *pbuffer;
+	unsigned int buffer_len;
+};
+
+static int
+_putc(int ch, struct mini_buff *b)
+{
+	if ((unsigned int)((b->pbuffer - b->buffer) + 1) >= b->buffer_len)
+		return 0;
+	*(b->pbuffer++) = ch;
+	*(b->pbuffer) = '\0';
+	return 1;
+}
+
+static int
+_puts(char *s, unsigned int len, struct mini_buff *b)
+{
+	unsigned int i;
+
+	if (b->buffer_len - (b->pbuffer - b->buffer) - 1 < len)
+		len = b->buffer_len - (b->pbuffer - b->buffer) - 1;
+
+	/* Copy to buffer */
+	for (i = 0; i < len; i++)
+		*(b->pbuffer++) = s[i];
+	*(b->pbuffer) = '\0';
+
+	return len;
+}
+
+int
+mini_vsnprintf(char *buffer, unsigned int buffer_len, const char *fmt, va_list va)
+{
+	struct mini_buff b;
+	char bf[24];
+	char ch;
+
+	b.buffer = buffer;
+	b.pbuffer = buffer;
+	b.buffer_len = buffer_len;
+
+	while ((ch=*(fmt++))) {
+		if ((unsigned int)((b.pbuffer - b.buffer) + 1) >= b.buffer_len)
+			break;
+		if (ch!='%')
+			_putc(ch, &b);
+		else {
+			char zero_pad = 0;
+			char *ptr;
+			unsigned int len;
+
+			ch=*(fmt++);
+
+			/* Zero padding requested */
+			if (ch=='0') {
+				ch=*(fmt++);
+				if (ch == '\0')
+					goto end;
+				if (ch >= '0' && ch <= '9')
+					zero_pad = ch - '0';
+				ch=*(fmt++);
+			}
+
+			switch (ch) {
+				case 0:
+					goto end;
+
+				case 'u':
+				case 'd':
+					len = mini_itoa(va_arg(va, unsigned int), 10, 0, (ch=='u'), bf, zero_pad);
+					_puts(bf, len, &b);
+					break;
+
+				case 'x':
+				case 'X':
+					len = mini_itoa(va_arg(va, unsigned int), 16, (ch=='X'), 1, bf, zero_pad);
+					_puts(bf, len, &b);
+					break;
+
+				case 'c' :
+					_putc((char)(va_arg(va, int)), &b);
+					break;
+
+				case 's' :
+					ptr = va_arg(va, char*);
+					_puts(ptr, mini_strlen(ptr), &b);
+					break;
+
+				default:
+					_putc(ch, &b);
+					break;
+			}
+		}
+	}
+end:
+	return b.pbuffer - b.buffer;
+}
+
+
+int
+mini_snprintf(char* buffer, unsigned int buffer_len, const char *fmt, ...)
+{
+	int ret;
+	va_list va;
+	va_start(va, fmt);
+	ret = mini_vsnprintf(buffer, buffer_len, fmt, va);
+	va_end(va);
+
+	return ret;
+}

+ 50 - 0
projects/riscv_usb/fw/mini-printf.h

@@ -0,0 +1,50 @@
+/*
+ * The Minimal snprintf() implementation
+ *
+ * Copyright (c) 2013 Michal Ludvig <michal@logix.cz>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the auhor nor the names of its contributors
+ *       may be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#ifndef __MINI_PRINTF__
+#define __MINI_PRINTF__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+
+int mini_vsnprintf(char* buffer, unsigned int buffer_len, const char *fmt, va_list va);
+int mini_snprintf(char* buffer, unsigned int buffer_len, const char *fmt, ...);
+
+#ifdef __cplusplus
+}
+#endif
+
+#define vsnprintf mini_vsnprintf
+#define snprintf mini_snprintf
+
+#endif

+ 125 - 0
projects/riscv_usb/fw/start.S

@@ -0,0 +1,125 @@
+/*
+ * start.S
+ *
+ * Startup code taken from picosoc/picorv32 and adapted for use here
+ *
+ * Copyright (C) 2017 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2019 Sylvain Munaut <tnt@246tNt.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+	.section .text
+
+start:
+
+	// zero-initialize register file
+	addi x1, zero, 0
+	// x2 (sp) is initialized by reset
+	addi x3, zero, 0
+	addi x4, zero, 0
+	addi x5, zero, 0
+	addi x6, zero, 0
+	addi x7, zero, 0
+	addi x8, zero, 0
+	addi x9, zero, 0
+	addi x10, zero, 0
+	addi x11, zero, 0
+	addi x12, zero, 0
+	addi x13, zero, 0
+	addi x14, zero, 0
+	addi x15, zero, 0
+	addi x16, zero, 0
+	addi x17, zero, 0
+	addi x18, zero, 0
+	addi x19, zero, 0
+	addi x20, zero, 0
+	addi x21, zero, 0
+	addi x22, zero, 0
+	addi x23, zero, 0
+	addi x24, zero, 0
+	addi x25, zero, 0
+	addi x26, zero, 0
+	addi x27, zero, 0
+	addi x28, zero, 0
+	addi x29, zero, 0
+	addi x30, zero, 0
+	addi x31, zero, 0
+
+#ifdef BOOT_DEBUG
+	// Set UART divisor
+	li a0, 0x81000000
+	li a1, 23
+	sw a1, 4(a0)
+
+	// Output '1'
+	li a1, 49
+	sw a1, 0(a0)
+#endif
+
+	// zero initialize entire scratchpad memory
+	li a0, 0x0001c000
+	li a1, 0
+setmemloop:
+	sw a1, 0(a0)
+	addi a0, a0, 4
+	blt a0, sp, setmemloop
+
+#ifdef BOOT_DEBUG
+	// Output '2'
+	li a0, 0x81000000
+	li a1, 50
+	sw a1, 0(a0)
+#endif
+
+	// copy data section
+	la a0, _sidata
+	la a1, _sdata
+	la a2, _edata
+	bge a1, a2, end_init_data
+loop_init_data:
+	lw a3, 0(a0)
+	sw a3, 0(a1)
+	addi a0, a0, 4
+	addi a1, a1, 4
+	blt a1, a2, loop_init_data
+end_init_data:
+
+#ifdef BOOT_DEBUG
+	// Output '3'
+	li a0, 0x81000000
+	li a1, 51
+	sw a1, 0(a0)
+#endif
+
+	// zero-init bss section
+	la a0, _sbss
+	la a1, _ebss
+	bge a0, a1, end_init_bss
+loop_init_bss:
+	sw zero, 0(a0)
+	addi a0, a0, 4
+	blt a0, a1, loop_init_bss
+end_init_bss:
+
+#ifdef BOOT_DEBUG
+	// Output '4'
+	li a0, 0x81000000
+	li a1, 52
+	sw a1, 0(a0)
+#endif
+
+	// call main
+	call main
+loop:
+	j loop

+ 63 - 0
projects/riscv_usb/fw/usb_desc.c

@@ -0,0 +1,63 @@
+/*
+ * usb_desc.c
+ *
+ * Copyright (C) 2019 Sylvain Munaut
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <stdint.h>
+
+#include "usb_desc_data.h"
+
+#define NULL ((void*)0)
+#define num_elem(a) (sizeof(a) / sizeof(a[0]))
+
+const void *
+usb_get_device_desc(int *len)
+{
+	*len = Devices[0][0];
+	return Devices[0];
+}
+
+const void *
+usb_get_config_desc(int *len, int idx)
+{
+	if (idx < num_elem(Configurations)) {
+		*len = Configurations[idx][2] + (Configurations[idx][3] << 8);
+		return Configurations[idx];
+	} else {
+		*len = 0;
+		return NULL;
+	}
+}
+
+const void *
+usb_get_string_desc(int *len, int idx)
+{
+	if (idx <= 0) {
+		*len = StringZeros[0][0];
+		return StringZeros[0];
+	} else if ((idx-1) < num_elem(Strings)) {
+		*len = Strings[idx-1][0];
+		return Strings[idx-1];
+	} else {
+		*len = 0;
+		return NULL;
+	}
+}

+ 28 - 0
projects/riscv_usb/fw/usb_desc.h

@@ -0,0 +1,28 @@
+/*
+ * usb_desc.h
+ *
+ * Copyright (C) 2019 Sylvain Munaut
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+const void *usb_get_device_desc(int *len);
+const void *usb_get_config_desc(int *len, int idx);
+const void *usb_get_string_desc(int *len, int idx);

+ 126 - 0
projects/riscv_usb/fw/usb_desc_data.h

@@ -0,0 +1,126 @@
+
+/***************************************************************************
+   AUTOMATICALLY GENERATED by USBDescriptorKitchen (0.3)
+   When editing, make sure you keep the indentation right
+   and use an editor that doesn't mess around with the linefeeds.
+
+   See http://github.com/zonque/USBDescriptorKitchen/
+   for more information about the tool that was used to generate this file.
+ ***************************************************************************/
+static const char Device_1[] = {
+  /* Device*/
+  0x12,                       /* bLength (Size of this descriptor in bytes) */
+  0x01,                       /* bDescriptorType (DEVICE Descriptor Type) */
+  0x00, 0x02,                 /* bcdUSB (USB Specification Release Number in Binary-Coded Decimal) ("2.0") (512) */
+  0x00,                       /* bDeviceClass (Class code (assigned by the USB-IF).) ("Defined at interface level") */
+  0x00,                       /* bDeviceSubClass (Subclass code (assigned by the USB-IF).) */
+  0x00,                       /* bDeviceProtocol (Protocol code (assigned by the USB-IF).) */
+  0x40,                       /* bMaxPacketSize0 (Maximum packet size for endpoint zero) ("64") */
+  0x50, 0x1d,                 /* idVendor (Vendor ID (assigned by the USB-IF)) (7504) */
+  0xe1, 0xe1,                 /* idProduct (Product ID (assigned by the manufacturer)) (57825) */
+  0x00, 0x01,                 /* bcdDevice (Device release number in binary-coded decimal) (256) */
+  0x01,                       /* iManufacturer */
+  0x02,                       /* iProduct */
+  0x03,                       /* iSerialNumber */
+  0x01,                       /* bNumConfigurations (Number of possible configurations) */
+};
+
+static const char Configuration_1[] = {
+  /* Configuration*/
+  0x09,                       /* bLength (Size of this descriptor in bytes) */
+  0x02,                       /* bDescriptorType (CONFIGURATION Descriptor Type) */
+  0x27, 0x00,                 /* wTotalLength (Total length of data returned for this configuration) (39) */
+  0x01,                       /* bNumInterfaces (Number of interfaces supported by this configuration) */
+  0x01,                       /* bConfigurationValue (Value to use as an argument to the SetConfiguration() request to select this configuration) */
+  0x04,                       /* iConfiguration (Index of string descriptor describing this configuration) */
+  0x80,                       /* bmAttributes (Configuration characteristics) ('Remote Wakeup' = 0, 'Self-powered' = 0, 'Reserved (set to one)' = 1) */
+  0x32,                       /* bMaxPower (Maximum power consumption of the USB, device. Expressed in 2 mA units) */
+    /* Interface*/
+    0x09,                     /* bLength (Size of this descriptor in bytes) */
+    0x04,                     /* bDescriptorType (INTERFACE Descriptor Type) */
+    0x00,                     /* bInterfaceNumber (Number of this interface. Zero-based value.) */
+    0x00,                     /* bAlternateSetting (Value used to select this alternate setting) */
+    0x03,                     /* bNumEndpoints (Number of endpoints used by this interface) */
+    0xff,                     /* bInterfaceClass (Class code (assigned by the USB-IF).) ("Vendor specific") */
+    0x00,                     /* bInterfaceSubClass (Subclass code (assigned by the USB-IF).) ("0") */
+    0x00,                     /* bInterfaceProtocol (Protocol code (assigned by the USB).) */
+    0x02,                     /* iInterface (Index of string descriptor describing this interface) */
+      /* Endpoint*/
+      0x07,                   /* bLength (Size of this descriptor in bytes) */
+      0x05,                   /* bDescriptorType (ENDPOINT descriptor) */
+      0x01,                   /* bEndpointAddress ('Endpoint Number' = 1, 'Direction' = 0) */
+      0x05,                   /* bEndpointAttributes ('Transfer Type' = 1, 'Synchronization Type' = 1, 'Usage Type' = 0) */
+      0xb0, 0x01,             /* wMaxPacketSize (Maximum packet size this endpoint is capable of sending or receiving when this configuration is selected.) (432) */
+      0x01,                   /* bInterval (Interval for polling endpoint for data transfers. Expressed in frames or microframes depending on the device operating speed (i.e., either 1 millisecond or 125 us units)) */
+      /* Endpoint*/
+      0x07,                   /* bLength (Size of this descriptor in bytes) */
+      0x05,                   /* bDescriptorType (ENDPOINT descriptor) */
+      0x81,                   /* bEndpointAddress ('Endpoint Number' = 1, 'Direction' = 1) */
+      0x11,                   /* bEndpointAttributes ('Transfer Type' = 1, 'Synchronization Type' = 0, 'Usage Type' = 1) */
+      0x08, 0x00,             /* wMaxPacketSize (Maximum packet size this endpoint is capable of sending or receiving when this configuration is selected.) (8) */
+      0x03,                   /* bInterval (Interval for polling endpoint for data transfers. Expressed in frames or microframes depending on the device operating speed (i.e., either 1 millisecond or 125 us units)) */
+      /* Endpoint*/
+      0x07,                   /* bLength (Size of this descriptor in bytes) */
+      0x05,                   /* bDescriptorType (ENDPOINT descriptor) */
+      0x82,                   /* bEndpointAddress ('Endpoint Number' = 2, 'Direction' = 1) */
+      0x05,                   /* bEndpointAttributes ('Transfer Type' = 1, 'Synchronization Type' = 1, 'Usage Type' = 0) */
+      0xb0, 0x01,             /* wMaxPacketSize (Maximum packet size this endpoint is capable of sending or receiving when this configuration is selected.) (432) */
+      0x01,                   /* bInterval (Interval for polling endpoint for data transfers. Expressed in frames or microframes depending on the device operating speed (i.e., either 1 millisecond or 125 us units)) */
+};
+
+static const char StringZero_1[] = {
+  /* StringZero*/
+  0x04,                       /* bLength (Size of this descriptor in bytes) */
+  0x03,                       /* bDescriptorType (STRING Descriptor Type) */
+  0x09, 0x04,                 /* wLANGID(0) ("English (United States)") (1033) */
+};
+
+static const char String_1[] = {
+  /* String*/
+  0x10,                       /* bLength (Size of this descriptor in bytes) */
+  0x03,                       /* bDescriptorType (STRING Descriptor Type) */
+  0x6f, 0x00, 0x73, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, /* bString (0) */
+};
+
+static const char String_2[] = {
+  /* String*/
+  0x22,                       /* bLength (Size of this descriptor in bytes) */
+  0x03,                       /* bDescriptorType (STRING Descriptor Type) */
+  0x55, 0x00, 0x53, 0x00, 0x42, 0x00, 0x20, 0x00, 0x45, 0x00, 0x31, 0x00, 0x20, 0x00, 0x69, 0x00, 
+  0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x66, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 
+   /* bString (0) */
+};
+
+static const char String_3[] = {
+  /* String*/
+  0x12,                       /* bLength (Size of this descriptor in bytes) */
+  0x03,                       /* bDescriptorType (STRING Descriptor Type) */
+  0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 
+   /* bString (0) */
+};
+
+static const char String_4[] = {
+  /* String*/
+  0x0a,                       /* bLength (Size of this descriptor in bytes) */
+  0x03,                       /* bDescriptorType (STRING Descriptor Type) */
+  0x4d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, /* bString ("Main") (0) */
+};
+
+static const char *Devices[] = {
+	Device_1,
+};
+
+static const char *Configurations[] = {
+	Configuration_1,
+};
+
+static const char *Strings[] = {
+	String_1,
+	String_2,
+	String_3,
+	String_4,
+};
+
+static const char *StringZeros[] = {
+	StringZero_1,
+};

+ 143 - 0
projects/riscv_usb/rtl/bridge.v

@@ -0,0 +1,143 @@
+/*
+ * bridge.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+`default_nettype none
+
+module bridge #(
+	parameter integer WB_N  =  8,
+	parameter integer WB_DW = 32,
+	parameter integer WB_AW = 16,
+	parameter integer WB_AI =  2
+)(
+	/* PicoRV32 bus */
+	input  wire [31:0] pb_addr,
+	output wire [31:0] pb_rdata,
+	input  wire [31:0] pb_wdata,
+	input  wire [ 3:0] pb_wstrb,
+	input  wire pb_valid,
+	output wire pb_ready,
+
+	/* BRAM */
+	output wire [ 7:0] bram_addr,
+	input  wire [31:0] bram_rdata,
+	output wire [31:0] bram_wdata,
+	output wire [ 3:0] bram_wmsk,
+	output wire        bram_we,
+
+	/* SPRAM */
+	output wire [13:0] spram_addr,
+	input  wire [31:0] spram_rdata,
+	output wire [31:0] spram_wdata,
+	output wire [ 3:0] spram_wmsk,
+	output wire        spram_we,
+
+	/* Wishbone buses */
+	output wire [WB_AW-1:0] wb_addr,
+	output wire [WB_DW-1:0] wb_wdata,
+	input  wire [(WB_DW*WB_N)-1:0] wb_rdata,
+	output wire [WB_N-1:0] wb_cyc,
+	output wire wb_we,
+	input  wire [WB_N-1:0] wb_ack,
+
+	/* Clock / Reset */
+	input  wire clk,
+	input  wire rst
+);
+
+	// Signals
+	// -------
+
+	wire ram_sel;
+	reg  ram_rdy;
+	wire [31:0] ram_rdata;
+
+	reg  [31:0] wb_rdata_or;
+	wire wb_rdy;
+
+
+	// RAM access
+	// ----------
+	// BRAM  : 0x00000000 -> 0x000003ff
+	// SPRAM : 0x00010000 -> 0x0001ffff
+
+	assign bram_addr  = pb_addr[ 9:2];
+	assign spram_addr = pb_addr[15:2];
+
+	assign bram_wdata  = pb_wdata;
+	assign spram_wdata = pb_wdata;
+
+	assign bram_wmsk  = pb_wstrb;
+	assign spram_wmsk = pb_wstrb;
+
+	assign bram_we  = pb_valid & ~pb_addr[31] & |pb_wstrb & ~pb_addr[16];
+	assign spram_we = pb_valid & ~pb_addr[31] & |pb_wstrb &  pb_addr[16];
+
+	assign ram_rdata = ~pb_addr[31] ? (pb_addr[16] ? spram_rdata : bram_rdata) : 32'h00000000;
+
+	assign ram_sel = pb_valid & ~pb_addr[31];
+
+	always @(posedge clk)
+		ram_rdy <= ram_sel && ~ram_rdy;
+
+
+	// Wishbone
+	// --------
+	// wb[x] = 0x8x000000 - 0x8xffffff
+
+	genvar i;
+
+	assign wb_addr  = pb_addr[WB_AW+WB_AI-1:WB_AI];
+	assign wb_wdata = pb_wdata[WB_DW-1:0];
+	assign wb_we    = |pb_wstrb;
+
+	for (i=0; i<WB_N; i=i+1)
+		assign wb_cyc[i] = pb_valid & pb_addr[31] & (pb_addr[27:24] == i);
+
+	assign wb_rdy = |wb_ack;
+
+	always @(*)
+	begin : wb_or
+		integer i;
+		wb_rdata_or = 0;
+		for (i=0; i<WB_N; i=i+1)
+			wb_rdata_or[WB_DW-1:0] = wb_rdata_or[WB_DW-1:0] | wb_rdata[WB_DW*i+:WB_DW];
+	end
+
+
+	// Final data combining
+	// --------------------
+
+	assign pb_rdata = ram_rdata | wb_rdata_or;
+	assign pb_ready = ram_rdy | wb_rdy;
+
+endmodule // bridge

File diff suppressed because it is too large
+ 2979 - 0
projects/riscv_usb/rtl/picorv32.v


+ 62 - 0
projects/riscv_usb/rtl/soc_bram.v

@@ -0,0 +1,62 @@
+/*
+ * soc_bram.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+`default_nettype none
+
+module soc_bram #(
+	parameter integer AW = 8,
+	parameter INIT_FILE = ""
+)(
+	input  wire [AW-1:0] addr,
+	output reg  [31:0] rdata,
+	input  wire [31:0] wdata,
+	input  wire [ 3:0] wmsk,
+	input  wire we,
+	input  wire clk
+);
+
+	reg [31:0] mem [0:(1<<AW)-1];
+
+	initial
+		if (INIT_FILE != "")
+			$readmemh(INIT_FILE, mem);
+
+	always @(posedge clk) begin
+		rdata <= mem[addr];
+		if (we & wmsk[0]) mem[addr][ 7: 0] <= wdata[ 7: 0];
+		if (we & wmsk[1]) mem[addr][15: 8] <= wdata[15: 8];
+		if (we & wmsk[2]) mem[addr][23:16] <= wdata[23:16];
+		if (we & wmsk[3]) mem[addr][31:24] <= wdata[31:24];
+	end
+
+endmodule // soc_bram

+ 74 - 0
projects/riscv_usb/rtl/soc_spram.v

@@ -0,0 +1,74 @@
+/*
+ * soc_spram.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+`default_nettype none
+
+module soc_spram (
+	input  wire [13:0] addr,
+	output wire [31:0] rdata,
+	input  wire [31:0] wdata,
+	input  wire [ 3:0] wmsk,
+	input  wire we,
+	input  wire clk
+);
+
+	wire [3:0] msk_msb = { wmsk[3], wmsk[3], wmsk[2], wmsk[2] };
+	wire [3:0] msk_lsb = { wmsk[1], wmsk[1], wmsk[0], wmsk[0] };
+
+	SB_SPRAM256KA spram_msb_I (
+		.DATAIN(wdata[31:16]),
+		.ADDRESS(addr[13:0]),
+		.MASKWREN(msk_msb),
+		.WREN(we),
+		.CHIPSELECT(1'b1),
+		.CLOCK(clk),
+		.STANDBY(1'b0),
+		.SLEEP(1'b0),
+		.POWEROFF(1'b1),
+		.DATAOUT(rdata[31:16])
+	);
+
+	SB_SPRAM256KA spram_lsb_I (
+		.DATAIN(wdata[15:0]),
+		.ADDRESS(addr[13:0]),
+		.MASKWREN(msk_lsb),
+		.WREN(we),
+		.CHIPSELECT(1'b1),
+		.CLOCK(clk),
+		.STANDBY(1'b0),
+		.SLEEP(1'b0),
+		.POWEROFF(1'b1),
+		.DATAOUT(rdata[15:0])
+	);
+
+endmodule // soc_spram

+ 104 - 0
projects/riscv_usb/rtl/sysmgr.v

@@ -0,0 +1,104 @@
+/*
+ * sysmgr.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+`default_nettype none
+
+module sysmgr (
+	input  wire clk_in,
+	input  wire rst_in,
+	output wire clk_24m,
+	output wire clk_48m,
+	output wire rst_out
+);
+
+	// Signals
+	wire pll_lock;
+	wire pll_reset_n;
+
+	wire clk_24m_i;
+	wire clk_48m_i;
+	wire rst_i;
+	reg [3:0] rst_cnt;
+
+	// PLL instance
+	SB_PLL40_2F_PAD #(
+		.DIVR(4'b0000),
+		.DIVF(7'b0111111),
+		.DIVQ(3'b100),
+		.FILTER_RANGE(3'b001),
+		.FEEDBACK_PATH("SIMPLE"),
+		.DELAY_ADJUSTMENT_MODE_FEEDBACK("FIXED"),
+		.FDA_FEEDBACK(4'b0000),
+		.SHIFTREG_DIV_MODE(2'b00),
+		.PLLOUT_SELECT_PORTA("GENCLK"),
+		.PLLOUT_SELECT_PORTB("GENCLK_HALF"),
+		.ENABLE_ICEGATE_PORTA(1'b0),
+		.ENABLE_ICEGATE_PORTB(1'b0)
+	) pll_I (
+		.PACKAGEPIN(clk_in),
+		.PLLOUTCOREA(),
+		.PLLOUTGLOBALA(clk_48m_i),
+		.PLLOUTCOREB(),
+		.PLLOUTGLOBALB(clk_24m_i),
+		.EXTFEEDBACK(1'b0),
+		.DYNAMICDELAY(8'h00),
+		.RESETB(pll_reset_n),
+		.BYPASS(1'b0),
+		.LATCHINPUTVALUE(1'b0),
+		.LOCK(pll_lock),
+		.SDI(1'b0),
+		.SDO(),
+		.SCLK(1'b0)
+	);
+
+	assign clk_24m = clk_24m_i;
+	assign clk_48m = clk_48m_i;
+
+	// PLL reset generation
+	assign pll_reset_n = ~rst_in;
+
+	// Logic reset generation
+	always @(posedge clk_24m_i or negedge pll_lock)
+		if (!pll_lock)
+			rst_cnt <= 4'h0;
+		else if (~rst_cnt[3])
+			rst_cnt <= rst_cnt + 1;
+
+	assign rst_i = ~rst_cnt[3];
+
+	SB_GB rst_gbuf_I (
+		.USER_SIGNAL_TO_GLOBAL_BUFFER(rst_i),
+		.GLOBAL_BUFFER_OUTPUT(rst_out)
+	);
+
+endmodule // sysmgr

+ 503 - 0
projects/riscv_usb/rtl/top.v

@@ -0,0 +1,503 @@
+/*
+ * top.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+`default_nettype none
+
+module top (
+	// SPI
+	inout  wire spi_mosi,
+	inout  wire spi_miso,
+	inout  wire spi_clk,
+	inout  wire spi_flash_cs_n,
+	inout  wire spi_ram_cs_n,
+
+	// USB
+	inout  wire usb_dp,
+	inout  wire usb_dn,
+	output wire usb_pu,
+
+	// Debug UART
+	input  wire uart_rx,
+	output wire uart_tx,
+
+	// LED
+	output wire [2:0] rgb,
+
+	// Clock
+	input  wire clk_in
+);
+
+	localparam WB_N  =  6;
+	localparam WB_DW = 32;
+	localparam WB_AW = 16;
+	localparam WB_AI =  2;
+
+	genvar i;
+
+
+	// Signals
+	// -------
+
+	// Memory bus
+	wire        mem_valid;
+	wire        mem_instr;
+	wire        mem_ready;
+	wire [31:0] mem_addr;
+	wire [31:0] mem_rdata;
+	wire [31:0] mem_wdata;
+	wire [ 3:0] mem_wstrb;
+
+	// RAM
+		// BRAM
+	wire [ 7:0] bram_addr;
+	wire [31:0] bram_rdata;
+	wire [31:0] bram_wdata;
+	wire [ 3:0] bram_wmsk;
+	wire        bram_we;
+
+		// SPRAM
+	wire [13:0] spram_addr;
+	wire [31:0] spram_rdata;
+	wire [31:0] spram_wdata;
+	wire [ 3:0] spram_wmsk;
+	wire        spram_we;
+
+	// Wishbone
+	wire [WB_AW-1:0] wb_addr;
+	wire [WB_DW-1:0] wb_wdata;
+	wire [WB_DW-1:0] wb_rdata [0:WB_N-1];
+	wire [(WB_DW*WB_N)-1:0] wb_rdata_flat;
+	wire [WB_N-1:0] wb_cyc;
+	wire wb_we;
+	wire [WB_N-1:0] wb_ack;
+
+	// UART
+
+	// USB Core
+		// EP Buffer
+	wire [ 8:0] ep_tx_addr_0;
+	wire [31:0] ep_tx_data_0;
+	wire ep_tx_we_0;
+
+	wire [ 8:0] ep_rx_addr_0;
+	wire [31:0] ep_rx_data_1;
+	wire ep_rx_re_0;
+
+		// Bus interface
+	wire [11:0] ub_addr;
+	wire [15:0] ub_wdata;
+	wire [15:0] ub_rdata;
+	wire ub_cyc;
+	wire ub_we;
+	wire ub_ack;
+
+	wire        usb_ready;
+	wire [31:0] usb_rdata;
+
+	// SPI
+	wire [7:0] sb_addr;
+	wire [7:0] sb_di;
+	wire [7:0] sb_do;
+	wire sb_rw;
+	wire sb_stb;
+	wire sb_ack;
+	wire sb_irq;
+	wire sb_wkup;
+
+	wire sio_miso_o, sio_miso_oe, sio_miso_i;
+	wire sio_mosi_o, sio_mosi_oe, sio_mosi_i;
+	wire sio_clk_o,  sio_clk_oe,  sio_clk_i;
+	wire [3:0] sio_csn_o, sio_csn_oe;
+
+	// LEDs
+	reg  [4:0] led_ctrl;
+	wire [2:0] rgb_pwm;
+
+	// Clock / Reset logic
+	wire clk_24m;
+	wire clk_48m;
+	wire rst;
+
+
+	// SoC
+	// ---
+
+	// CPU
+	picorv32 #(
+		.PROGADDR_RESET(32'h 0000_0000),
+		.STACKADDR(32'h 0000_0400),
+		.BARREL_SHIFTER(0),
+		.COMPRESSED_ISA(0),
+		.ENABLE_COUNTERS(0),
+		.ENABLE_MUL(0),
+		.ENABLE_DIV(0),
+		.ENABLE_IRQ(0),
+		.ENABLE_IRQ_QREGS(0),
+		.CATCH_MISALIGN(0),
+		.CATCH_ILLINSN(0)
+	) cpu_I (
+		.clk       (clk_24m),
+		.resetn    (~rst),
+		.mem_valid (mem_valid),
+		.mem_instr (mem_instr),
+		.mem_ready (mem_ready),
+		.mem_addr  (mem_addr),
+		.mem_wdata (mem_wdata),
+		.mem_wstrb (mem_wstrb),
+		.mem_rdata (mem_rdata)
+	);
+
+	// Bus interface
+	bridge #(
+		.WB_N(WB_N),
+		.WB_DW(WB_DW),
+		.WB_AW(WB_AW),
+		.WB_AI(WB_AI)
+	) pb_I (
+		.pb_addr(mem_addr),
+		.pb_rdata(mem_rdata),
+		.pb_wdata(mem_wdata),
+		.pb_wstrb(mem_wstrb),
+		.pb_valid(mem_valid),
+		.pb_ready(mem_ready),
+		.bram_addr(bram_addr),
+		.bram_rdata(bram_rdata),
+		.bram_wdata(bram_wdata),
+		.bram_wmsk(bram_wmsk),
+		.bram_we(bram_we),
+		.spram_addr(spram_addr),
+		.spram_rdata(spram_rdata),
+		.spram_wdata(spram_wdata),
+		.spram_wmsk(spram_wmsk),
+		.spram_we(spram_we),
+		.wb_addr(wb_addr),
+		.wb_wdata(wb_wdata),
+		.wb_rdata(wb_rdata_flat),
+		.wb_cyc(wb_cyc),
+		.wb_we(wb_we),
+		.wb_ack(wb_ack),
+		.clk(clk_24m),
+		.rst(rst)
+	);
+
+	for (i=0; i<WB_N; i=i+1)
+		assign wb_rdata_flat[i*WB_DW+:WB_DW] = wb_rdata[i];
+
+	assign wb_rdata[0] = 0;
+	assign wb_ack[0] = wb_cyc[0];
+
+	// Boot memory
+	soc_bram #(
+		.INIT_FILE("boot.hex")
+	) bram_I (
+		.addr(bram_addr),
+		.rdata(bram_rdata),
+		.wdata(bram_wdata),
+		.wmsk(bram_wmsk),
+		.we(bram_we),
+		.clk(clk_24m)
+	);
+
+	// Main memory
+	soc_spram spram_I (
+		.addr(spram_addr),
+		.rdata(spram_rdata),
+		.wdata(spram_wdata),
+		.wmsk(spram_wmsk),
+		.we(spram_we),
+		.clk(clk_24m)
+	);
+
+
+	// UART
+	// ----
+
+	uart_wb #(
+		.DIV_WIDTH(12),
+		.DW(WB_DW)
+	) uart_I (
+		.uart_tx(uart_tx),
+		.uart_rx(uart_rx),
+		.bus_addr(wb_addr[1:0]),
+		.bus_wdata(wb_wdata),
+		.bus_rdata(wb_rdata[1]),
+		.bus_cyc(wb_cyc[1]),
+		.bus_ack(wb_ack[1]),
+		.bus_we(wb_we),
+		.clk(clk_24m),
+		.rst(rst)
+	);
+
+
+	// SPI
+	// ---
+
+	// Hard-IP
+`ifndef SIM
+	SB_SPI #(
+		.BUS_ADDR74("0b0000")
+	) spi_I (
+		.SBCLKI(clk_24m),
+		.SBRWI(sb_rw),
+		.SBSTBI(sb_stb),
+		.SBADRI7(sb_addr[7]),
+		.SBADRI6(sb_addr[6]),
+		.SBADRI5(sb_addr[5]),
+		.SBADRI4(sb_addr[4]),
+		.SBADRI3(sb_addr[3]),
+		.SBADRI2(sb_addr[2]),
+		.SBADRI1(sb_addr[1]),
+		.SBADRI0(sb_addr[0]),
+		.SBDATI7(sb_di[7]),
+		.SBDATI6(sb_di[6]),
+		.SBDATI5(sb_di[5]),
+		.SBDATI4(sb_di[4]),
+		.SBDATI3(sb_di[3]),
+		.SBDATI2(sb_di[2]),
+		.SBDATI1(sb_di[1]),
+		.SBDATI0(sb_di[0]),
+		.MI(sio_miso_i),
+		.SI(sio_mosi_i),
+		.SCKI(sio_clk_i),
+		.SCSNI(1'b1),
+		.SBDATO7(sb_do[7]),
+		.SBDATO6(sb_do[6]),
+		.SBDATO5(sb_do[5]),
+		.SBDATO4(sb_do[4]),
+		.SBDATO3(sb_do[3]),
+		.SBDATO2(sb_do[2]),
+		.SBDATO1(sb_do[1]),
+		.SBDATO0(sb_do[0]),
+		.SBACKO(sb_ack),
+		.SPIIRQ(sb_irq),
+		.SPIWKUP(sb_wkup),
+		.SO(sio_miso_o),
+		.SOE(sio_miso_oe),
+		.MO(sio_mosi_o),
+		.MOE(sio_mosi_oe),
+		.SCKO(sio_clk_o),
+		.SCKOE(sio_clk_oe),
+		.MCSNO3(sio_csn_o[3]),
+		.MCSNO2(sio_csn_o[2]),
+		.MCSNO1(sio_csn_o[1]),
+		.MCSNO0(sio_csn_o[0]),
+		.MCSNOE3(sio_csn_oe[3]),
+		.MCSNOE2(sio_csn_oe[2]),
+		.MCSNOE1(sio_csn_oe[1]),
+		.MCSNOE0(sio_csn_oe[0])
+	);
+`else
+	reg [3:0] sim;
+
+	assign sb_ack = sb_stb;
+	assign sb_do = { sim, 4'h8 };
+
+	always @(posedge clk_24m)
+		if (rst)
+			sim <= 0;
+		else if (sb_ack & sb_rw)
+			sim <= sim + 1;
+`endif
+
+	// IO pads
+	SB_IO #(
+		.PIN_TYPE(6'b101001),
+		.PULLUP(1'b1)
+	) spi_io_I[2:0] (
+		.PACKAGE_PIN  ({spi_mosi,    spi_miso,    spi_clk   }),
+		.OUTPUT_ENABLE({sio_mosi_oe, sio_miso_oe, sio_clk_oe}),
+		.D_OUT_0      ({sio_mosi_o,  sio_miso_o,  sio_clk_o }),
+		.D_IN_0       ({sio_mosi_i,  sio_miso_i,  sio_clk_i })
+	);
+
+		// Bypass OE for CS_n lines
+	assign spi_flash_cs_n = sio_csn_o[0];
+	assign spi_ram_cs_n   = sio_csn_o[1];
+
+	// Bus interface
+	assign sb_addr = { 4'h0, wb_addr[3:0] };
+	assign sb_di   = wb_wdata[7:0];
+	assign sb_rw   = wb_we;
+	assign sb_stb  = wb_cyc[2];
+
+	assign wb_rdata[2] = { {(WB_DW-8){1'b0}}, wb_cyc[2] ? sb_do : 8'h00 };
+	assign wb_ack[2] = sb_ack;
+
+
+	// LEDs
+	// ----
+
+	SB_LEDDA_IP led_I (
+		.LEDDCS(wb_addr[4] & wb_we),
+		.LEDDCLK(clk_24m),
+		.LEDDDAT7(wb_wdata[7]),
+		.LEDDDAT6(wb_wdata[6]),
+		.LEDDDAT5(wb_wdata[5]),
+		.LEDDDAT4(wb_wdata[4]),
+		.LEDDDAT3(wb_wdata[3]),
+		.LEDDDAT2(wb_wdata[2]),
+		.LEDDDAT1(wb_wdata[1]),
+		.LEDDDAT0(wb_wdata[0]),
+		.LEDDADDR3(wb_addr[3]),
+		.LEDDADDR2(wb_addr[2]),
+		.LEDDADDR1(wb_addr[1]),
+		.LEDDADDR0(wb_addr[0]),
+		.LEDDDEN(wb_cyc[3]),
+		.LEDDEXE(led_ctrl[1]),
+		.PWMOUT0(rgb_pwm[0]),
+		.PWMOUT1(rgb_pwm[1]),
+		.PWMOUT2(rgb_pwm[2]),
+		.LEDDON()
+	);
+
+	SB_RGBA_DRV #(
+		.CURRENT_MODE("0b1"),
+		.RGB0_CURRENT("0b000001"),
+		.RGB1_CURRENT("0b000001"),
+		.RGB2_CURRENT("0b000001")
+	) rgb_drv_I (
+		.RGBLEDEN(led_ctrl[2]),
+		.RGB0PWM(rgb_pwm[0]),
+		.RGB1PWM(rgb_pwm[1]),
+		.RGB2PWM(rgb_pwm[2]),
+		.CURREN(led_ctrl[3]),
+		.RGB0(rgb[0]),
+		.RGB1(rgb[1]),
+		.RGB2(rgb[2])
+	);
+
+	always @(posedge clk_24m or posedge rst)
+		if (rst)
+			led_ctrl <= 0;
+		else if (wb_cyc[3] & ~wb_addr[4] & wb_we)
+			led_ctrl <= wb_wdata[4:0];
+
+	assign wb_rdata[3] = { WB_DW{1'b0} };
+	assign wb_ack[3] = wb_cyc[3];
+
+
+	// USB Core
+	// --------
+
+	// Core
+	usb #(
+		.EPDW(32),
+		.ADDR_MSB(4'h0)
+	) usb_I (
+		.pad_dp(usb_dp),
+		.pad_dn(usb_dn),
+		.pad_pu(usb_pu),
+		.ep_tx_addr_0(ep_tx_addr_0),
+		.ep_tx_data_0(ep_tx_data_0),
+		.ep_tx_we_0(ep_tx_we_0),
+		.ep_rx_addr_0(ep_rx_addr_0),
+		.ep_rx_data_1(ep_rx_data_1),
+		.ep_rx_re_0(ep_rx_re_0),
+		.ep_clk(clk_24m),
+		.bus_addr({4'h0, ub_addr}),
+		.bus_din(ub_wdata),
+		.bus_dout(ub_rdata),
+		.bus_cyc(ub_cyc),
+		.bus_we(ub_we),
+		.bus_ack(ub_ack),
+		.clk(clk_48m),
+		.rst(rst)
+	);
+
+	// Cross clock bridge
+	xclk_wb #(
+		.DW(16),
+		.AW(12)
+	)  wb_48m_xclk_I (
+		.s_addr(wb_addr[11:0]),
+		.s_wdata(wb_wdata),
+		.s_rdata(wb_rdata[4][15:0]),
+		.s_cyc(wb_cyc[4]),
+		.s_ack(wb_ack[4]),
+		.s_we(wb_we),
+		.s_clk(clk_24m),
+		.m_addr(ub_addr),
+		.m_wdata(ub_wdata),
+		.m_rdata(ub_rdata),
+		.m_cyc(ub_cyc),
+		.m_ack(ub_ack),
+		.m_we(ub_we),
+		.m_clk(clk_48m),
+		.rst(rst)
+	);
+
+	assign wb_rdata[4][31:16] = 16'h0000;
+
+	// EP buffer interface
+	always @(posedge clk_24m)
+		wb_ack[5] <= wb_cyc[5] & ~wb_ack[5];
+
+	assign ep_tx_addr_0 = wb_addr[8:0];
+	assign ep_tx_data_0 = wb_wdata;
+	assign ep_tx_we_0   = wb_cyc[5] & ~wb_ack[5] & wb_we;
+
+	assign ep_rx_addr_0 = wb_addr[8:0];
+	assign ep_rx_re_0   = 1'b1;
+
+	assign wb_rdata[5] = wb_cyc[5] ? ep_rx_data_1 : 32'h00000000;
+
+
+	// Clock / Reset
+	// -------------
+
+`ifdef SIM
+	reg clk_48m_s = 1'b0;
+	reg clk_24m_s = 1'b0;
+	reg rst_s = 1'b1;
+
+	always #10.42 clk_48m_s <= !clk_48m_s;
+	always #20.84 clk_24m_s <= !clk_24m_s;
+
+	initial begin
+		#200 rst_s = 0;
+	end
+
+	assign clk_48m = clk_48m_s;
+	assign clk_24m = clk_24m_s;
+	assign rst = rst_s;
+`else
+	sysmgr sys_mgr_I (
+		.clk_in(clk_in),
+		.rst_in(1'b0),
+		.clk_48m(clk_48m),
+		.clk_24m(clk_24m),
+		.rst_out(rst)
+	);
+`endif
+
+endmodule // top

+ 425 - 0
projects/riscv_usb/sim/spiflash.v

@@ -0,0 +1,425 @@
+/*
+ *  PicoSoC - A simple example SoC using PicoRV32
+ *
+ *  Copyright (C) 2017  Clifford Wolf <clifford@clifford.at>
+ *
+ *  Permission to use, copy, modify, and/or distribute this software for any
+ *  purpose with or without fee is hereby granted, provided that the above
+ *  copyright notice and this permission notice appear in all copies.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+`timescale 1 ns / 1 ps
+
+//
+// Simple SPI flash simulation model
+//
+// This model samples io input signals 1ns before the SPI clock edge and
+// updates output signals 1ns after the SPI clock edge.
+//
+// Supported commands:
+//    AB, B9, FF, 03, BB, EB, ED
+//
+// Well written SPI flash data sheets:
+//    Cypress S25FL064L http://www.cypress.com/file/316661/download
+//    Cypress S25FL128L http://www.cypress.com/file/316171/download
+//
+// SPI flash used on iCEBreaker board:
+//    https://www.winbond.com/resource-files/w25q128jv%20dtr%20revb%2011042016.pdf
+//
+
+module spiflash (
+	input csb,
+	input clk,
+	inout io0, // MOSI
+	inout io1, // MISO
+	inout io2,
+	inout io3
+);
+	localparam verbose = 1;
+	localparam integer latency = 8;
+
+	reg [7:0] buffer;
+	integer bitcount = 0;
+	integer bytecount = 0;
+	integer dummycount = 0;
+
+	reg [7:0] spi_cmd;
+	reg [7:0] xip_cmd = 0;
+	reg [23:0] spi_addr;
+
+	reg [7:0] spi_in;
+	reg [7:0] spi_out;
+	reg spi_io_vld;
+
+	reg powered_up = 1;
+
+	localparam [3:0] mode_spi         = 1;
+	localparam [3:0] mode_dspi_rd     = 2;
+	localparam [3:0] mode_dspi_wr     = 3;
+	localparam [3:0] mode_qspi_rd     = 4;
+	localparam [3:0] mode_qspi_wr     = 5;
+	localparam [3:0] mode_qspi_ddr_rd = 6;
+	localparam [3:0] mode_qspi_ddr_wr = 7;
+
+	reg [3:0] mode = 0;
+	reg [3:0] next_mode = 0;
+
+	reg io0_oe = 0;
+	reg io1_oe = 0;
+	reg io2_oe = 0;
+	reg io3_oe = 0;
+
+	reg io0_dout = 0;
+	reg io1_dout = 0;
+	reg io2_dout = 0;
+	reg io3_dout = 0;
+
+	assign #1 io0 = io0_oe ? io0_dout : 1'bz;
+	assign #1 io1 = io1_oe ? io1_dout : 1'bz;
+	assign #1 io2 = io2_oe ? io2_dout : 1'bz;
+	assign #1 io3 = io3_oe ? io3_dout : 1'bz;
+
+	wire io0_delayed;
+	wire io1_delayed;
+	wire io2_delayed;
+	wire io3_delayed;
+
+	assign #1 io0_delayed = io0;
+	assign #1 io1_delayed = io1;
+	assign #1 io2_delayed = io2;
+	assign #1 io3_delayed = io3;
+
+	// 16 MB (128Mb) Flash
+	reg [7:0] memory [0:16*1024*1024-1];
+
+	reg [1023:0] firmware_file;
+	initial begin
+		if (!$value$plusargs("firmware=%s", firmware_file))
+			firmware_file = "firmware.hex";
+		$readmemh(firmware_file, memory);
+	end
+
+	task spi_action;
+		begin
+			spi_in = buffer;
+
+			if (bytecount == 1) begin
+				spi_cmd = buffer;
+
+				if (spi_cmd == 8'h ab)
+					powered_up = 1;
+
+				if (spi_cmd == 8'h b9)
+					powered_up = 0;
+
+				if (spi_cmd == 8'h ff)
+					xip_cmd = 0;
+			end
+
+			if (powered_up && spi_cmd == 'h 03) begin
+				if (bytecount == 2)
+					spi_addr[23:16] = buffer;
+
+				if (bytecount == 3)
+					spi_addr[15:8] = buffer;
+
+				if (bytecount == 4)
+					spi_addr[7:0] = buffer;
+
+				if (bytecount >= 4) begin
+					buffer = memory[spi_addr];
+					spi_addr = spi_addr + 1;
+				end
+			end
+
+			if (powered_up && spi_cmd == 'h 0b) begin
+				if (bytecount == 2)
+					spi_addr[23:16] = buffer;
+
+				if (bytecount == 3)
+					spi_addr[15:8] = buffer;
+
+				if (bytecount == 4)
+					spi_addr[7:0] = buffer;
+
+				if (bytecount >= 5) begin
+					buffer = memory[spi_addr];
+					spi_addr = spi_addr + 1;
+				end
+			end
+
+			if (powered_up && spi_cmd == 'h bb) begin
+				if (bytecount == 1)
+					mode = mode_dspi_rd;
+
+				if (bytecount == 2)
+					spi_addr[23:16] = buffer;
+
+				if (bytecount == 3)
+					spi_addr[15:8] = buffer;
+
+				if (bytecount == 4)
+					spi_addr[7:0] = buffer;
+
+				if (bytecount == 5) begin
+					xip_cmd = (buffer == 8'h a5) ? spi_cmd : 8'h 00;
+					mode = mode_dspi_wr;
+					dummycount = latency;
+				end
+
+				if (bytecount >= 5) begin
+					buffer = memory[spi_addr];
+					spi_addr = spi_addr + 1;
+				end
+			end
+
+			if (powered_up && spi_cmd == 'h eb) begin
+				if (bytecount == 1)
+					mode = mode_qspi_rd;
+
+				if (bytecount == 2)
+					spi_addr[23:16] = buffer;
+
+				if (bytecount == 3)
+					spi_addr[15:8] = buffer;
+
+				if (bytecount == 4)
+					spi_addr[7:0] = buffer;
+
+				if (bytecount == 5) begin
+					xip_cmd = (buffer == 8'h a5) ? spi_cmd : 8'h 00;
+					mode = mode_qspi_wr;
+					dummycount = latency;
+				end
+
+				if (bytecount >= 5) begin
+					buffer = memory[spi_addr];
+					spi_addr = spi_addr + 1;
+				end
+			end
+
+			if (powered_up && spi_cmd == 'h ed) begin
+				if (bytecount == 1)
+					next_mode = mode_qspi_ddr_rd;
+
+				if (bytecount == 2)
+					spi_addr[23:16] = buffer;
+
+				if (bytecount == 3)
+					spi_addr[15:8] = buffer;
+
+				if (bytecount == 4)
+					spi_addr[7:0] = buffer;
+
+				if (bytecount == 5) begin
+					xip_cmd = (buffer == 8'h a5) ? spi_cmd : 8'h 00;
+					mode = mode_qspi_ddr_wr;
+					dummycount = latency;
+				end
+
+				if (bytecount >= 5) begin
+					buffer = memory[spi_addr];
+					spi_addr = spi_addr + 1;
+				end
+			end
+
+			spi_out = buffer;
+			spi_io_vld = 1;
+
+			if (verbose) begin
+				if (bytecount == 1)
+					$write("<SPI-START>");
+				$write("<SPI:%02x:%02x>", spi_in, spi_out);
+			end
+
+		end
+	endtask
+
+	task ddr_rd_edge;
+		begin
+			buffer = {buffer, io3_delayed, io2_delayed, io1_delayed, io0_delayed};
+			bitcount = bitcount + 4;
+			if (bitcount == 8) begin
+				bitcount = 0;
+				bytecount = bytecount + 1;
+				spi_action;
+			end
+		end
+	endtask
+
+	task ddr_wr_edge;
+		begin
+			io0_oe = 1;
+			io1_oe = 1;
+			io2_oe = 1;
+			io3_oe = 1;
+
+			io0_dout = buffer[4];
+			io1_dout = buffer[5];
+			io2_dout = buffer[6];
+			io3_dout = buffer[7];
+
+			buffer = {buffer, 4'h 0};
+			bitcount = bitcount + 4;
+			if (bitcount == 8) begin
+				bitcount = 0;
+				bytecount = bytecount + 1;
+				spi_action;
+			end
+		end
+	endtask
+
+	always @(csb) begin
+		if (csb) begin
+			if (verbose) begin
+				$display("");
+				$fflush;
+			end
+			buffer = 0;
+			bitcount = 0;
+			bytecount = 0;
+			mode = mode_spi;
+			io0_oe = 0;
+			io1_oe = 0;
+			io2_oe = 0;
+			io3_oe = 0;
+		end else
+		if (xip_cmd) begin
+			buffer = xip_cmd;
+			bitcount = 0;
+			bytecount = 1;
+			spi_action;
+		end
+	end
+
+	always @(csb, clk) begin
+		spi_io_vld = 0;
+		if (!csb && !clk) begin
+			if (dummycount > 0) begin
+				io0_oe = 0;
+				io1_oe = 0;
+				io2_oe = 0;
+				io3_oe = 0;
+			end else
+			case (mode)
+				mode_spi: begin
+					io0_oe = 0;
+					io1_oe = 1;
+					io2_oe = 0;
+					io3_oe = 0;
+					io1_dout = buffer[7];
+				end
+				mode_dspi_rd: begin
+					io0_oe = 0;
+					io1_oe = 0;
+					io2_oe = 0;
+					io3_oe = 0;
+				end
+				mode_dspi_wr: begin
+					io0_oe = 1;
+					io1_oe = 1;
+					io2_oe = 0;
+					io3_oe = 0;
+					io0_dout = buffer[6];
+					io1_dout = buffer[7];
+				end
+				mode_qspi_rd: begin
+					io0_oe = 0;
+					io1_oe = 0;
+					io2_oe = 0;
+					io3_oe = 0;
+				end
+				mode_qspi_wr: begin
+					io0_oe = 1;
+					io1_oe = 1;
+					io2_oe = 1;
+					io3_oe = 1;
+					io0_dout = buffer[4];
+					io1_dout = buffer[5];
+					io2_dout = buffer[6];
+					io3_dout = buffer[7];
+				end
+				mode_qspi_ddr_rd: begin
+					ddr_rd_edge;
+				end
+				mode_qspi_ddr_wr: begin
+					ddr_wr_edge;
+				end
+			endcase
+			if (next_mode) begin
+				case (next_mode)
+					mode_qspi_ddr_rd: begin
+						io0_oe = 0;
+						io1_oe = 0;
+						io2_oe = 0;
+						io3_oe = 0;
+					end
+					mode_qspi_ddr_wr: begin
+						io0_oe = 1;
+						io1_oe = 1;
+						io2_oe = 1;
+						io3_oe = 1;
+						io0_dout = buffer[4];
+						io1_dout = buffer[5];
+						io2_dout = buffer[6];
+						io3_dout = buffer[7];
+					end
+				endcase
+				mode = next_mode;
+				next_mode = 0;
+			end
+		end
+	end
+
+	always @(posedge clk) begin
+		if (!csb) begin
+			if (dummycount > 0) begin
+				dummycount = dummycount - 1;
+			end else
+			case (mode)
+				mode_spi: begin
+					buffer = {buffer, io0};
+					bitcount = bitcount + 1;
+					if (bitcount == 8) begin
+						bitcount = 0;
+						bytecount = bytecount + 1;
+						spi_action;
+					end
+				end
+				mode_dspi_rd, mode_dspi_wr: begin
+					buffer = {buffer, io1, io0};
+					bitcount = bitcount + 2;
+					if (bitcount == 8) begin
+						bitcount = 0;
+						bytecount = bytecount + 1;
+						spi_action;
+					end
+				end
+				mode_qspi_rd, mode_qspi_wr: begin
+					buffer = {buffer, io3, io2, io1, io0};
+					bitcount = bitcount + 4;
+					if (bitcount == 8) begin
+						bitcount = 0;
+						bytecount = bytecount + 1;
+						spi_action;
+					end
+				end
+				mode_qspi_ddr_rd: begin
+					ddr_rd_edge;
+				end
+				mode_qspi_ddr_wr: begin
+					ddr_wr_edge;
+				end
+			endcase
+		end
+	end
+endmodule

+ 100 - 0
projects/riscv_usb/sim/top_tb.v

@@ -0,0 +1,100 @@
+/*
+ * top_tb.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+`default_nettype none
+
+module top_tb;
+
+	// Signals
+	// -------
+
+	wire spi_mosi;
+	wire spi_miso;
+	wire spi_flash_cs_n;
+	wire spi_clk;
+
+	wire usb_dp;
+	wire usb_dn;
+	wire usb_pu;
+
+	wire uart_rx;
+	wire uart_tx;
+
+
+	// Setup recording
+	// ---------------
+
+	initial begin
+		$dumpfile("top_tb.vcd");
+		$dumpvars(0,top_tb);
+		# 2000000 $finish;
+	end
+
+
+	// DUT
+	// ---
+
+	top dut_I (
+		.spi_mosi(spi_mosi),
+		.spi_miso(spi_miso),
+		.spi_flash_cs_n(spi_flash_cs_n),
+		.spi_clk(spi_clk),
+		.usb_dp(usb_dp),
+		.usb_dn(usb_dn),
+		.usb_pu(usb_pu),
+		.uart_rx(uart_rx),
+		.uart_tx(uart_tx),
+		.rgb(),
+		.clk_in(1'b0)
+	);
+
+
+	// Support
+	// -------
+
+	pullup(usb_dp);
+	pullup(usb_dn);
+
+	pullup(uart_tx);
+	pullup(uart_rx);
+
+	spiflash flash_I (
+		.csb(spi_flash_cs_n),
+		.clk(spi_clk),
+		.io0(spi_mosi),
+		.io1(spi_miso),
+		.io2(),
+		.io3()
+	);
+
+endmodule // top_tb