Browse Source

cores/usb: Import current state of the USB core

Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
Sylvain Munaut 6 years ago
parent
commit
aff3b98e7a

+ 3 - 0
cores/usb/Makefile

@@ -0,0 +1,3 @@
+CORE := usb
+
+include ../../build/core-rules.mk

+ 34 - 0
cores/usb/core.mk

@@ -0,0 +1,34 @@
+CORE := usb
+
+DEPS_usb := misc
+
+RTL_SRCS_usb := $(addprefix rtl/, \
+	usb.v \
+	usb_crc.v \
+	usb_ep_buf.v \
+	usb_ep_status.v \
+	usb_phy.v \
+	usb_rx_ll.v \
+	usb_rx_pkt.v \
+	usb_trans.v \
+	usb_tx_ll.v \
+	usb_tx_pkt.v \
+)
+
+PREREQ_usb := \
+	$(ROOT)/cores/usb/rtl/usb_defs.vh \
+	$(BUILD_TMP)/usb_trans_mc.hex \
+	$(BUILD_TMP)/usb_ep_status.hex
+
+TESTBENCHES_usb := \
+	usb_ep_buf_tb \
+	usb_tb \
+	usb_tx_tb
+
+$(BUILD_TMP)/usb_trans_mc.hex: $(ROOT)/cores/usb/utils/microcode.py
+	$(ROOT)/cores/usb/utils/microcode.py > $@
+
+$(BUILD_TMP)/usb_ep_status.hex: $(ROOT)/cores/usb/data/usb_ep_status.hex
+	cp -a $< $@
+
+include $(ROOT)/build/core-magic.mk

File diff suppressed because it is too large
+ 1 - 0
cores/usb/data/capture_usb_raw_short.bin


+ 256 - 0
cores/usb/data/usb_ep_status.hex

@@ -0,0 +1,256 @@
+0006
+0000
+0000
+0000
+4040
+0000
+0000
+0000
+0006
+0000
+0000
+0000
+4012
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000
+0000

+ 122 - 0
cores/usb/doc/mem-map.md

@@ -0,0 +1,122 @@
+iCE40 USB Core Memory Map
+=========================
+
+Global CSR
+----------
+
+### Control (Write addr `0x000`)
+
+```
+,--------------------------------------------------------------,
+| f | e | d | c | b | a | 9 | 8| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+|--------------------------------------------------------------|
+| p | / |       addr           |      (rsvd)               | a |
+'--------------------------------------------------------------'
+```
+
+  * `p`: Enables DP pull-up
+  * `a`: Ack interrupt
+
+
+### Status (Read addr `0x000`)
+
+```
+,--------------------------------------------------------------,
+| f | e | d | c | b | a | 9 | 8| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+|--------------------------------------------------------------|
+|      cnt      |     ucnc     |      endp     | d | s | b | i |
+'--------------------------------------------------------------'
+```
+
+This contains info about the last generated notification from
+the transaction microcode
+
+  * `cnt`: Counter (incremented by 1 at each notify to detect misses)
+  * `ucnc`: Notification code
+  * `endp`: Endpoint #
+  * `d`: Direction (1=IN, 0=OUT/SETUP)
+  * `s`: Is SETUP ?
+  * `b`: Buffer Descriptor index
+  * `i`: Interrupt flag
+
+
+EP Status
+---------
+
+### Address:
+
+```
+,-----------------------------------------------,
+| b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+|-----------------------------------------------|
+| 1   0   0   0 |     ep_num    |dir| 0   0   0 |
+'-----------------------------------------------'
+```
+
+
+### Data:
+
+```
+,--------------------------------------------------------------,
+| f | e | d | c | b | a | 9 | 8| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+|--------------------------------------------------------------|
+|                                      | d | b | m |  EP type  |
+'--------------------------------------------------------------'
+```
+
+  * `d`: Data Toggle (if relevant for EP type)
+  * `b`: Buffer Descriptor index
+  * `m`: Dual buffer endpoint
+  * EP Type: (`h` indicates if this EP is halted)
+    - `000`: Non-existant
+    - `001`: Isochronous
+    - `01s`: Interrupt
+    - `10s`: Bulk
+    - `11s`: Control
+
+
+Buffer Descriptor
+-----------------
+
+### Address:
+
+```
+,-----------------------------------------------,
+| b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+|-----------------------------------------------|
+| 1   0   0   0 |     ep_num    |dir| 1 | i | w |
+'-----------------------------------------------'
+```
+
+  * `i`: BD Index (0/1)
+  * `w`: Word select
+
+
+### Word 0:
+
+```
+,--------------------------------------------------------------,
+| f | e | d | c | b | a | 9 | 8| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+|--------------------------------------------------------------|
+|   state   | s |  rsvd |          Buffer Length               |
+'--------------------------------------------------------------'
+```
+
+  * 's': Transactions was setup
+  * BD State:
+    - `000`: Empty / Unused
+    - `010`: Valid, ready for Tx/RX data
+    - `011`: Valid, issue STALL (and drop data)
+    - `100`: Used - Success
+    - `1xx`: Used - Error with xx=01/10/11 error code
+
+
+### Word 1:
+
+```
+,--------------------------------------------------------------,
+| f | e | d | c | b | a | 9 | 8| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+|--------------------------------------------------------------|
+|       (rsvd)      |             Buffer Pointer               |
+'--------------------------------------------------------------'
+```

+ 510 - 0
cores/usb/rtl/usb.v

@@ -0,0 +1,510 @@
+/*
+ * usb.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * 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.
+ */
+
+`default_nettype none
+
+module usb #(
+	parameter         TARGET = "ICE40",
+	parameter [3:0]   ADDR_MSB = 4'h3,
+	parameter integer EPDW = 16,
+
+	/* Auto-set */
+	parameter integer EPAW = 11 - $clog2(EPDW / 8)
+)(
+	// Pads
+	inout  wire pad_dp,
+	inout  wire pad_dn,
+	output reg  pad_pu,
+
+	// EP buffer interface
+	input  wire [EPAW-1:0] ep_tx_addr_0,
+	input  wire [EPDW-1:0] ep_tx_data_0,
+	input  wire ep_tx_we_0,
+
+	input  wire [EPAW-1:0] ep_rx_addr_0,
+	output wire [EPDW-1:0] ep_rx_data_1,
+	input  wire ep_rx_re_0,
+
+	input  wire ep_clk,
+
+	// Bus interface
+	input  wire [15:0] bus_addr,
+	input  wire [15:0] bus_din,
+	output wire [15:0] bus_dout,
+	input  wire bus_cyc,
+	input  wire bus_we,
+	output wire bus_ack,
+
+	// Debug
+	output wire [3:0] debug,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	// Signals
+	// -------
+
+	// PHY
+	wire phy_rx_dp;
+	wire phy_rx_dn;
+	wire phy_rx_chg;
+
+	wire phy_tx_dp;
+	wire phy_tx_dn;
+	wire phy_tx_en;
+
+	// TX Low-Level
+	wire txll_start;
+	wire txll_bit;
+	wire txll_last;
+	wire txll_ack;
+
+	// TX Packet
+	wire txpkt_start;
+	wire txpkt_done;
+	wire [3:0] txpkt_pid;
+	wire [9:0] txpkt_len;
+	wire [7:0] txpkt_data;
+	wire txpkt_data_ack;
+
+	// RX Low-Level
+	wire [1:0] rxll_sym;
+	wire rxll_bit;
+	wire rxll_valid;
+	wire rxll_eop;
+	wire rxll_sync;
+	wire rxll_bs_skip;
+	wire rxll_bs_err;
+
+	// RX Packet
+	wire rxpkt_start;
+	wire rxpkt_done_ok;
+	wire rxpkt_done_err;
+
+	wire [ 3:0] rxpkt_pid;
+	wire rxpkt_is_sof;
+	wire rxpkt_is_token;
+	wire rxpkt_is_data;
+	wire rxpkt_is_handshake;
+
+	wire [10:0] rxpkt_frameno;
+	wire [ 6:0] rxpkt_addr;
+	wire [ 3:0] rxpkt_endp;
+
+	wire [ 7:0] rxpkt_data;
+	wire rxpkt_data_stb;
+
+	// EP Buffers
+	wire [10:0] buf_tx_addr_0;
+	wire [ 7:0] buf_tx_data_1;
+	wire buf_tx_rden_0;
+
+	wire [10:0] buf_rx_addr_0;
+	wire [ 7:0] buf_rx_data_0;
+	wire buf_rx_wren_0;
+
+	// EP Status
+	wire eps_read_0;
+	wire eps_zero_0;
+	wire eps_write_0;
+	wire [ 7:0] eps_addr_0;
+	wire [15:0] eps_wrdata_0;
+	wire [15:0] eps_rddata_3;
+
+	wire eps_bus_ready;
+	reg  eps_bus_read;
+	reg  eps_bus_zero;
+	reg  eps_bus_write;
+	wire [15:0] eps_bus_dout;
+
+	// Config / Status registers
+	reg         cr_pu_ena;
+	reg  [ 6:0] cr_addr;
+	wire [15:0] sr_notify;
+	wire irq_stb;
+	wire irq_state;
+	reg  irq_ack;
+
+	// Bus interface
+	reg  eps_bus_req;
+	wire eps_bus_clear;
+
+	reg  bus_ack_wait;
+	wire bus_req_ok;
+	reg  [2:0] bus_req_ok_dly;
+
+	// Out-of-band conditions
+	wire oob_se0;
+	wire oob_sop;
+
+	reg  [19:0] timeout_suspend;	//  3 ms with no activity
+	reg  [19:0] timeout_reset;		// 10 ms SE0
+
+	reg  rst_usb_l;
+	reg  suspend;
+
+	// USB core logic reset
+	wire rst_usb;
+
+
+	// PHY
+	// ---
+
+	usb_phy #(
+		.TARGET(TARGET)
+	) phy_I (
+		.pad_dp(pad_dp),
+		.pad_dn(pad_dn),
+		.rx_dp(phy_rx_dp),
+		.rx_dn(phy_rx_dn),
+		.rx_chg(phy_rx_chg),
+		.tx_dp(phy_tx_dp),
+		.tx_dn(phy_tx_dn),
+`ifdef SIM
+		.tx_en(1'b0),
+`else
+		.tx_en(phy_tx_en),
+`endif
+		.clk(clk),
+		.rst(rst)
+	);
+
+
+	// TX
+	// --
+
+	usb_tx_ll tx_ll_I (
+		.phy_tx_dp(phy_tx_dp),
+		.phy_tx_dn(phy_tx_dn),
+		.phy_tx_en(phy_tx_en),
+		.ll_start(txll_start),
+		.ll_bit(txll_bit),
+		.ll_last(txll_last),
+		.ll_ack(txll_ack),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	usb_tx_pkt tx_pkt_I (
+		.ll_start(txll_start),
+		.ll_bit(txll_bit),
+		.ll_last(txll_last),
+		.ll_ack(txll_ack),
+		.pkt_start(txpkt_start),
+		.pkt_done(txpkt_done),
+		.pkt_pid(txpkt_pid),
+		.pkt_len(txpkt_len),
+		.pkt_data(txpkt_data),
+		.pkt_data_ack(txpkt_data_ack),
+		.clk(clk),
+		.rst(rst)
+	);
+
+
+	// RX
+	// --
+
+	usb_rx_ll rx_ll_I (
+		.phy_rx_dp(phy_rx_dp),
+		.phy_rx_dn(phy_rx_dn),
+		.phy_rx_chg(phy_rx_chg),
+		.ll_sym(rxll_sym),
+		.ll_bit(rxll_bit),
+		.ll_valid(rxll_valid),
+		.ll_eop(rxll_eop),
+		.ll_sync(rxll_sync),
+		.ll_bs_skip(rxll_bs_skip),
+		.ll_bs_err(rxll_bs_err),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	usb_rx_pkt rx_pkt_I (
+		.ll_sym(rxll_sym),
+		.ll_bit(rxll_bit),
+		.ll_valid(rxll_valid),
+		.ll_eop(rxll_eop),
+		.ll_sync(rxll_sync),
+		.ll_bs_skip(rxll_bs_skip),
+		.ll_bs_err(rxll_bs_err),
+		.pkt_start(rxpkt_start),
+		.pkt_done_ok(rxpkt_done_ok),
+		.pkt_done_err(rxpkt_done_err),
+		.pkt_pid(rxpkt_pid),
+		.pkt_is_sof(rxpkt_is_sof),
+		.pkt_is_token(rxpkt_is_token),
+		.pkt_is_data(rxpkt_is_data),
+		.pkt_is_handshake(rxpkt_is_handshake),
+		.pkt_frameno(rxpkt_frameno),
+		.pkt_addr(rxpkt_addr),
+		.pkt_endp(rxpkt_endp),
+		.pkt_data(rxpkt_data),
+		.pkt_data_stb(rxpkt_data_stb),
+		.inhibit(phy_tx_en),
+		.clk(clk),
+		.rst(rst)
+	);
+
+
+	// Transaction control
+	// -------------------
+
+	usb_trans trans_I (
+		.txpkt_start(txpkt_start),
+		.txpkt_done(txpkt_done),
+		.txpkt_pid(txpkt_pid),
+		.txpkt_len(txpkt_len),
+		.txpkt_data(txpkt_data),
+		.txpkt_data_ack(txpkt_data_ack),
+		.rxpkt_start(rxpkt_start),
+		.rxpkt_done_ok(rxpkt_done_ok),
+		.rxpkt_done_err(rxpkt_done_err),
+		.rxpkt_pid(rxpkt_pid),
+		.rxpkt_is_sof(rxpkt_is_sof),
+		.rxpkt_is_token(rxpkt_is_token),
+		.rxpkt_is_data(rxpkt_is_data),
+		.rxpkt_is_handshake(rxpkt_is_handshake),
+		.rxpkt_frameno(rxpkt_frameno),
+		.rxpkt_addr(rxpkt_addr),
+		.rxpkt_endp(rxpkt_endp),
+		.rxpkt_data(rxpkt_data),
+		.rxpkt_data_stb(rxpkt_data_stb),
+		.buf_tx_addr_0(buf_tx_addr_0),
+		.buf_tx_data_1(buf_tx_data_1),
+		.buf_tx_rden_0(buf_tx_rden_0),
+		.buf_rx_addr_0(buf_rx_addr_0),
+		.buf_rx_data_0(buf_rx_data_0),
+		.buf_rx_wren_0(buf_rx_wren_0),
+		.eps_read_0(eps_read_0),
+		.eps_zero_0(eps_zero_0),
+		.eps_write_0(eps_write_0),
+		.eps_addr_0(eps_addr_0),
+		.eps_wrdata_0(eps_wrdata_0),
+		.eps_rddata_3(eps_rddata_3),
+		.cr_addr(cr_addr),
+		.sr_notify(sr_notify),
+		.irq_stb(irq_stb),
+		.irq_state(irq_state),
+		.irq_ack(irq_ack),
+		.debug(debug),
+		.clk(clk),
+		.rst(rst)
+	);
+
+
+	// EP buffers
+	// ----------
+
+	usb_ep_buf #(
+		.TARGET(TARGET),
+		.RWIDTH(8),
+		.WWIDTH(EPDW)
+	) tx_buf_I (
+		.rd_addr_0(buf_tx_addr_0),
+		.rd_data_1(buf_tx_data_1),
+		.rd_en_0(buf_tx_rden_0),
+		.rd_clk(clk),
+		.wr_addr_0(ep_tx_addr_0),
+		.wr_data_0(ep_tx_data_0),
+		.wr_en_0(ep_tx_we_0),
+		.wr_clk(ep_clk)
+	);
+
+	usb_ep_buf #(
+		.TARGET(TARGET),
+		.RWIDTH(EPDW),
+		.WWIDTH(8)
+	) rx_buf_I (
+		.rd_addr_0(ep_rx_addr_0),
+		.rd_data_1(ep_rx_data_1),
+		.rd_en_0(ep_rx_re_0),
+		.rd_clk(ep_clk),
+		.wr_addr_0(buf_rx_addr_0),
+		.wr_data_0(buf_rx_data_0),
+		.wr_en_0(buf_rx_wren_0),
+		.wr_clk(clk)
+	);
+
+
+	// EP Status / Buffer Descriptors
+	// ------------------------------
+
+	usb_ep_status ep_status_I (
+		.p_addr_0(eps_addr_0),
+		.p_read_0(eps_read_0),
+		.p_zero_0(eps_zero_0),
+		.p_write_0(eps_write_0),
+		.p_din_0(eps_wrdata_0),
+		.p_dout_3(eps_rddata_3),
+		.s_addr_0(bus_addr[7:0]),
+		.s_read_0(eps_bus_ready),
+		.s_zero_0(eps_bus_zero),
+		.s_write_0(eps_bus_write),
+		.s_din_0(bus_din),
+		.s_dout_3(eps_bus_dout),
+		.s_ready_0(eps_bus_ready),
+		.clk(clk),
+		.rst(rst)
+	);
+
+
+	// Bus Interface
+	// -------------
+
+	(* keep="true" *) wire bus_msb_match;
+
+	wire [15:0] csr_dout;
+	wire csr_bus_clear;
+	reg  csr_req;
+	reg  cr_bus_we;
+	reg  sr_bus_re;
+
+	// Match the MSB
+	assign bus_msb_match = bus_addr[15:12] == ADDR_MSB;
+
+	// Request lines for registers
+	always @(posedge clk)
+		if (csr_bus_clear) begin
+			csr_req   <= 1'b0;
+			cr_bus_we <= 1'b0;
+			sr_bus_re <= 1'b0;
+		end else begin
+			csr_req   <= bus_msb_match & ~bus_addr[11];
+			cr_bus_we <= bus_msb_match & ~bus_addr[11] &  bus_we;
+			sr_bus_re <= bus_msb_match & ~bus_addr[11] & ~bus_we;
+		end
+
+	// Request lines for EP Status access
+	always @(posedge clk)
+		if (eps_bus_clear) begin
+			eps_bus_read  <= 1'b0;
+			eps_bus_zero  <= 1'b1;
+			eps_bus_write <= 1'b0;
+			eps_bus_req   <= 1'b0;
+		end else begin
+			eps_bus_read  <=  bus_msb_match &  bus_addr[11] & ~bus_we;
+			eps_bus_zero  <= ~bus_msb_match | ~bus_addr[11];
+			eps_bus_write <=  bus_msb_match &  bus_addr[11] &  bus_we;
+			eps_bus_req   <=  bus_msb_match &  bus_addr[11];
+		end
+
+	// Condition to force the requests to zero :
+	//  no access needed, ack pending or this cycle went through
+	assign csr_bus_clear = ~bus_cyc | csr_req;
+	assign eps_bus_clear = ~bus_cyc | bus_ack_wait | (eps_bus_req & eps_bus_ready);
+
+	// Track when request are accepted by the RAM
+	assign bus_req_ok = (eps_bus_req & eps_bus_ready);
+
+	always @(posedge clk)
+		bus_req_ok_dly <= { bus_req_ok_dly[1:0], bus_req_ok & ~bus_we };
+
+	// ACK wait state tracking
+	always @(posedge clk or posedge rst)
+		if (rst)
+			bus_ack_wait <= 1'b0;
+		else
+			bus_ack_wait <= ((bus_ack_wait & ~bus_we) | bus_req_ok) & ~bus_req_ok_dly[2];
+
+	// Bus Ack
+	assign bus_ack = csr_req | (bus_ack_wait & (bus_we | bus_req_ok_dly[2]));
+
+	// Output is simply the OR of all local units since we force them to zero if
+	// they're not accessed
+	assign bus_dout = eps_bus_dout | csr_dout;
+
+
+	// Config registers
+	// ----------------
+
+	// Write regs
+	always @(posedge clk)
+		if (cr_bus_we) begin
+			cr_pu_ena <= bus_din[15];
+			cr_addr   <= bus_din[13:8];
+		end
+
+	// Write strobe
+	always @(posedge clk)
+		irq_ack <= cr_bus_we & bus_din[0];
+
+	// Read mux
+	assign csr_dout = sr_bus_re ? sr_notify : 16'h0000;
+
+
+	// USB reset/suspend
+	// -----------------
+
+	// Detect some conditions for triggers
+	assign oob_se0 = !phy_rx_dp && !phy_rx_dn;
+	assign oob_sop = rxpkt_start & rxpkt_is_sof;
+
+	// Suspend timeout counter
+	always @(posedge clk)
+		if (rst_usb)
+			timeout_suspend <= 20'ha3280;
+		else
+			timeout_suspend <= oob_sop ? 20'ha3280 : (timeout_suspend - timeout_suspend[19]);
+
+	always @(posedge clk)
+		if (rst_usb)
+			suspend <= 1'b0;
+		else
+			suspend <= ~timeout_suspend[19];
+
+	// Reset timeout counter
+	always @(posedge clk)
+		if (rst)
+			timeout_reset <= 20'hf5300;
+		else
+			timeout_reset <= oob_se0 ? (timeout_reset - timeout_reset[19]) : 20'hf5300;
+
+	always @(posedge clk)
+		if (rst)
+			rst_usb_l <= 1'b1;
+		else
+			rst_usb_l <= ~timeout_reset[19];
+
+	// Global reset driver
+	generate
+		if (TARGET == "GENERIC")
+			assign rst_usb = rst_usb_l;
+		else if (TARGET == "ICE40")
+			SB_GB usb_rst_gb_I (
+				.USER_SIGNAL_TO_GLOBAL_BUFFER(rst_usb_l),
+				.GLOBAL_BUFFER_OUTPUT(rst_usb)
+			);
+	endgenerate
+
+	// Detection pin
+	always @(posedge clk)
+		if (rst)
+			pad_pu <= 1'b0;
+		else
+			pad_pu <= cr_pu_ena;
+
+endmodule // usb

+ 79 - 0
cores/usb/rtl/usb_crc.v

@@ -0,0 +1,79 @@
+/*
+ * usb_crc.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * 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.
+ */
+
+`default_nettype none
+
+module usb_crc #(
+	parameter integer WIDTH = 5,
+	parameter POLY  = 5'b00011,
+	parameter MATCH = 5'b00000
+)(
+	// Input
+	input  wire in_bit,
+	input  wire in_first,
+	input  wire in_valid,
+
+	// Output (updated 1 cycle after input)
+	output wire [WIDTH-1:0] crc,
+	output wire crc_match,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	wire [WIDTH-1:0] state;
+	wire [WIDTH-1:0] state_fb_mux;
+	wire [WIDTH-1:0] state_upd_mux;
+	wire [WIDTH-1:0] state_nxt;
+
+	assign state_fb_mux  = in_first ? { WIDTH{1'b1} } : state;
+	assign state_upd_mux = (state_fb_mux[WIDTH-1] != in_bit) ? POLY : 0;
+	assign state_nxt = { state_fb_mux[WIDTH-2:0], 1'b0 } ^ state_upd_mux;
+
+/*
+	always @(posedge clk)
+		if (in_valid)
+			state <= state_nxt;
+*/
+
+	dffe_n #(
+		.WIDTH(WIDTH)
+	) state_reg_I (
+		.d(state_nxt),
+		.q(state),
+		.ce(in_valid),
+		.clk(clk)
+	);
+
+	assign crc_match = (state == MATCH);
+
+	genvar i;
+	generate
+		for (i=0; i<WIDTH; i=i+1)
+			assign crc[i] = ~state[WIDTH-1-i];
+	endgenerate
+
+endmodule // usb_crc

+ 42 - 0
cores/usb/rtl/usb_defs.vh

@@ -0,0 +1,42 @@
+/*
+ * usb_defs.vh
+ *
+ * vim: ts=4 sw=4 syntax=verilog
+ *
+ * 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.
+ */
+
+localparam SYM_SE0		= 2'b00;
+localparam SYM_SE1		= 2'b11;
+localparam SYM_J		= 2'b10;
+localparam SYM_K		= 2'b01;
+
+
+localparam PID_OUT		= 4'b0001;
+localparam PID_IN		= 4'b1001;
+localparam PID_SOF		= 4'b0101;
+localparam PID_SETUP	= 4'b1101;
+
+localparam PID_DATA0	= 4'b0011;
+localparam PID_DATA1	= 4'b1011;
+
+localparam PID_ACK		= 4'b0010;
+localparam PID_NAK		= 4'b1010;
+localparam PID_STALL	= 4'b1110;

+ 287 - 0
cores/usb/rtl/usb_ep_buf.v

@@ -0,0 +1,287 @@
+/*
+ * usb_ep_buf.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * 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.
+ */
+
+`default_nettype none
+
+module usb_ep_buf #(
+	parameter TARGET = "ICE40",
+	parameter integer RWIDTH = 8,	// 8/16/32/64
+	parameter integer WWIDTH = 8,	// 8/16/32/64
+	parameter integer AWIDTH = 11,	// Assuming 'byte' access
+
+	parameter integer ARW = AWIDTH - $clog2(RWIDTH / 8),
+	parameter integer AWW = AWIDTH - $clog2(WWIDTH / 8)
+)(
+	// Read port
+	input  wire [ARW-1:0] rd_addr_0,
+	output wire [RWIDTH-1:0] rd_data_1,
+	input  wire rd_en_0,
+	input  wire rd_clk,
+
+	// Write port
+	input  wire [AWW-1:0] wr_addr_0,
+	input  wire [WWIDTH-1:0] wr_data_0,
+	input  wire wr_en_0,
+	input  wire wr_clk
+);
+	// MODE 0:  256 x 16
+	// MODE 1:  512 x 8
+	// MODE 2: 1024 x 4
+	// MODE 3: 2048 x 2
+
+	localparam WRITE_MODE = 3 - $clog2(WWIDTH / 8);
+	localparam READ_MODE  = 3 - $clog2(RWIDTH / 8);
+
+
+	// Helpers to map to the right bits of SB_RAM40_4K
+	// -----------------------------------------------
+
+	function [7:0] ram_rd_map8 (input [15:0] rdata);
+		ram_rd_map8 = {
+			rdata[14],
+			rdata[12],
+			rdata[10],
+			rdata[ 8],
+			rdata[ 6],
+			rdata[ 4],
+			rdata[ 2],
+			rdata[ 0]
+		};
+	endfunction
+
+	function [15:0] ram_wr_map8 (input [7:0] wdata);
+		ram_wr_map8 = {
+			1'b0, wdata[7],	// 14
+			1'b0, wdata[6],	// 12
+			1'b0, wdata[5], // 10
+			1'b0, wdata[4], //  8
+			1'b0, wdata[3], //  6
+			1'b0, wdata[2], //  4
+			1'b0, wdata[1], //  2
+			1'b0, wdata[0]  //  0
+		};
+	endfunction
+
+	function [3:0] ram_rd_map4 (input [15:0] rdata);
+		ram_rd_map4 = {
+			rdata[13],
+			rdata[ 9],
+			rdata[ 5],
+			rdata[ 1]
+		};
+	endfunction
+
+	function [15:0] ram_wr_map4 (input [3:0] wdata);
+		ram_wr_map4 = {
+			2'h0, wdata[3], // 13
+			3'h0, wdata[2], //  9
+			3'h0, wdata[1], //  5
+			3'h0, wdata[0], //  1
+			1'b0
+		};
+	endfunction
+
+	function [1:0] ram_rd_map2 (input [15:0] rdata);
+		ram_rd_map2 = {
+			rdata[11],
+			rdata[ 3]
+		};
+	endfunction
+
+	function [15:0] ram_wr_map2 (input [1:0] wdata);
+		ram_wr_map2 = {
+			4'h0, wdata[1], // 11
+			7'h0, wdata[0], //  3
+			3'h0
+		};
+	endfunction
+
+
+	// Helpers to shuffle bits across blocks
+	// -------------------------------------
+
+	function [63:0] ram_rd_shuffle_64(input [63:0] src);
+		ram_rd_shuffle_64 = {
+			src[63], src[55], src[47], src[39], src[31], src[23], src[15], src[ 7],
+			src[59], src[51], src[43], src[35], src[27], src[19], src[11], src[ 3],
+			src[61], src[53], src[45], src[37], src[29], src[21], src[13], src[ 5],
+			src[57], src[49], src[41], src[33], src[25], src[17], src[ 9], src[ 1],
+			src[62], src[54], src[46], src[38], src[30], src[22], src[14], src[ 6],
+			src[58], src[50], src[42], src[34], src[26], src[18], src[10], src[ 2],
+			src[60], src[52], src[44], src[36], src[28], src[20], src[12], src[ 4],
+			src[56], src[48], src[40], src[32], src[24], src[16], src[ 8], src[ 0]
+		};
+	endfunction
+
+	function [31:0] ram_rd_shuffle_32(input [31:0] src);
+		ram_rd_shuffle_32 = {
+			src[31], src[27], src[23], src[19], src[15], src[11], src[ 7], src[ 3],
+			src[29], src[25], src[21], src[17], src[13], src[ 9], src[ 5], src[ 1],
+			src[30], src[26], src[22], src[18], src[14], src[10], src[ 6], src[ 2],
+			src[28], src[24], src[20], src[16], src[12], src[ 8], src[ 4], src[ 0]
+		};
+	endfunction
+
+	function [15:0] ram_rd_shuffle_16(input [15:0] src);
+		ram_rd_shuffle_16 = {
+			src[15], src[13], src[11], src[ 9], src[ 7], src[ 5], src[ 3], src[ 1],
+			src[14], src[12], src[10], src[ 8], src[ 6], src[ 4], src[ 2], src[ 0]
+		};
+	endfunction
+
+
+	function [63:0] ram_wr_shuffle_64(input [63:0] src);
+		ram_wr_shuffle_64 = {
+			src[63], src[31], src[47], src[15], src[55], src[23], src[39], src[ 7],
+			src[62], src[30], src[46], src[14], src[54], src[22], src[38], src[ 6],
+			src[61], src[29], src[45], src[13], src[53], src[21], src[37], src[ 5],
+			src[60], src[28], src[44], src[12], src[52], src[20], src[36], src[ 4],
+			src[59], src[27], src[43], src[11], src[51], src[19], src[35], src[ 3],
+			src[58], src[26], src[42], src[10], src[50], src[18], src[34], src[ 2],
+			src[57], src[25], src[41], src[ 9], src[49], src[17], src[33], src[ 1],
+			src[56], src[24], src[40], src[ 8], src[48], src[16], src[32], src[ 0]
+		};
+	endfunction
+
+	function [31:0] ram_wr_shuffle_32(input [31:0] src);
+		ram_wr_shuffle_32 = {
+			src[31], src[15], src[23], src[ 7], src[30], src[14], src[22], src[ 6],
+			src[29], src[13], src[21], src[ 5], src[28], src[12], src[20], src[ 4],
+			src[27], src[11], src[19], src[ 3], src[26], src[10], src[18], src[ 2],
+			src[25], src[ 9], src[17], src[ 1], src[24], src[ 8], src[16], src[ 0]
+		};
+	endfunction
+
+	function [15:0] ram_wr_shuffle_16(input [15:0] src);
+		ram_wr_shuffle_16 = {
+			src[15], src[ 7], src[14], src[ 6], src[13], src[ 5], src[12], src[ 4],
+			src[11], src[ 3], src[10], src[ 2], src[ 9], src[ 1], src[ 8], src[ 0]
+		};
+	endfunction
+
+
+	// Storage array
+	// -------------
+
+	initial begin
+		$display("READ_MODE  : %d", READ_MODE);
+		$display("WRITE_MODE : %d", WRITE_MODE);
+	end
+
+	wire [10:0] ram_raddr;
+	wire [10:0] ram_waddr;
+
+	wire [RWIDTH-1:0] rd_data_1_ram;
+	wire [WWIDTH-1:0] wr_data_0_ram;
+
+	genvar i;
+	generate
+		// Map address lines for various modes
+		assign ram_raddr[7:0] = rd_addr_0[ARW-1:ARW-8];
+		assign ram_waddr[7:0] = wr_addr_0[AWW-1:AWW-8];
+
+		if (READ_MODE == 3)
+			assign ram_raddr[10:8] = { rd_addr_0[0], rd_addr_0[1], rd_addr_0[2] };
+		else if (READ_MODE == 2)
+			assign ram_raddr[10:8] = { 1'b0, rd_addr_0[0], rd_addr_0[1] };
+		else if (READ_MODE == 1)
+			assign ram_raddr[10:8] = { 2'b00, rd_addr_0[0] };
+		else
+			assign ram_raddr[10:8] = { 3'b000 };
+
+		if (WRITE_MODE == 3)
+			assign ram_waddr[10:8] = { wr_addr_0[0], wr_addr_0[1], wr_addr_0[2] };
+		else if (WRITE_MODE == 2)
+			assign ram_waddr[10:8] = { 1'b0, wr_addr_0[0], wr_addr_0[1] };
+		else if (WRITE_MODE == 1)
+			assign ram_waddr[10:8] = { 2'b00, wr_addr_0[0] };
+		else
+			assign ram_waddr[10:8] = { 3'b000 };
+
+		// Shuffle the bits
+		if (READ_MODE == 0)
+			assign rd_data_1 = ram_rd_shuffle_64(rd_data_1_ram);
+		else if (READ_MODE == 1)
+			assign rd_data_1 = ram_rd_shuffle_32(rd_data_1_ram);
+		else if (READ_MODE == 2)
+			assign rd_data_1 = ram_rd_shuffle_16(rd_data_1_ram);
+		else
+			assign rd_data_1 = rd_data_1_ram;
+
+		if (WRITE_MODE == 0)
+			assign wr_data_0_ram = ram_wr_shuffle_64(wr_data_0);
+		else if (WRITE_MODE == 1)
+			assign wr_data_0_ram = ram_wr_shuffle_32(wr_data_0);
+		else if (WRITE_MODE == 2)
+			assign wr_data_0_ram = ram_wr_shuffle_16(wr_data_0);
+		else
+			assign wr_data_0_ram = wr_data_0;
+
+		// 4 blocks
+		for (i=0; i<4; i=i+1)
+		begin : block
+			wire [15:0] ram_rdata;
+			wire [15:0] ram_wdata;
+
+			// Block
+			SB_RAM40_4K #(
+				.WRITE_MODE(WRITE_MODE),
+				.READ_MODE(READ_MODE)
+			) ram_I (
+				.RDATA(ram_rdata),
+				.RCLK(rd_clk),
+				.RCLKE(rd_en_0),
+				.RE(1'b1),
+				.RADDR(ram_raddr),
+				.WCLK(wr_clk),
+				.WCLKE(wr_en_0),
+				.WE(1'b1),
+				.WADDR(ram_waddr),
+				.MASK(16'h0000),
+				.WDATA(ram_wdata)
+			);
+
+			// Map the right bits
+			if (READ_MODE == 3)
+				assign rd_data_1_ram[i*2+:2] = ram_rd_map2(ram_rdata);
+			else if (READ_MODE == 2)
+				assign rd_data_1_ram[i*4+:4] = ram_rd_map4(ram_rdata);
+			else if (READ_MODE == 1)
+				assign rd_data_1_ram[i*8+:8] = ram_rd_map8(ram_rdata);
+			else
+				assign rd_data_1_ram[i*16+:16] = ram_rdata;
+
+			if (WRITE_MODE == 3)
+				assign ram_wdata = ram_wr_map2(wr_data_0_ram[i*2+:2]);
+			else if (WRITE_MODE == 2)
+				assign ram_wdata = ram_wr_map4(wr_data_0_ram[i*4+:4]);
+			else if (WRITE_MODE == 1)
+				assign ram_wdata = ram_wr_map8(wr_data_0_ram[i*8+:8]);
+			else
+				assign ram_wdata = wr_data_0_ram[i*16+:16];
+		end
+	endgenerate
+
+endmodule // usb_ep_buf

+ 121 - 0
cores/usb/rtl/usb_ep_status.v

@@ -0,0 +1,121 @@
+/*
+ * usb_ep_status.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * 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.
+ */
+
+`default_nettype none
+
+module usb_ep_status (
+	// Priority port
+	input  wire [ 7:0] p_addr_0,
+	input  wire        p_read_0,
+	input  wire        p_zero_0,
+	input  wire        p_write_0,
+	input  wire [15:0] p_din_0,
+	output reg  [15:0] p_dout_3,
+
+	// Aux R/W port
+	input  wire [ 7:0] s_addr_0,
+	input  wire        s_read_0,
+	input  wire        s_zero_0,
+	input  wire        s_write_0,
+	input  wire [15:0] s_din_0,
+	output reg  [15:0] s_dout_3,
+	output wire        s_ready_0,
+
+	// Clock / Reset
+	input  wire clk,
+	input  wire rst
+);
+	// Signals
+	wire s_ready_0_i;
+	reg  [ 7:0] addr_1;
+	reg  [15:0] din_1;
+	reg  we_1;
+	reg  p_read_1;
+	reg  p_zero_1;
+	reg  s_read_1;
+	reg  s_zero_1;
+
+	wire [15:0] dout_2;
+	reg  p_read_2;
+	reg  p_zero_2;
+	reg  s_read_2;
+	reg  s_zero_2;
+
+	// "Arbitration"
+	assign s_ready_0_i = ~p_read_0 & ~p_write_0;
+	assign s_ready_0 = s_ready_0_i;
+
+	// Stage 1 : Address mux and Write delay
+	always @(posedge clk)
+	begin
+		addr_1   <= (p_read_0 | p_write_0) ? p_addr_0 : s_addr_0;
+		we_1     <= p_write_0 | (s_write_0 & s_ready_0_i);
+		din_1    <= p_write_0 ? p_din_0 : s_din_0;
+		p_read_1 <= p_read_0;
+		p_zero_1 <= p_zero_0;
+		s_read_1 <= s_read_0 & s_ready_0_i;
+		s_zero_1 <= s_zero_0 & s_ready_0_i;
+	end
+
+	// Stage 2 : Delays
+	always @(posedge clk)
+	begin
+		p_read_2 <= p_read_1 | p_zero_1;
+		p_zero_2 <= p_zero_1;
+		s_read_2 <= s_read_1 | s_zero_1;
+		s_zero_2 <= s_zero_1;
+	end
+
+	// Stage 3 : Output registers
+	always @(posedge clk)
+		if (p_read_2)
+			p_dout_3 <= p_zero_2 ? 16'h0000 : dout_2;
+
+	always @(posedge clk)
+		if (s_read_2)
+			s_dout_3 <= s_zero_2 ? 16'h0000 : dout_2;
+
+	// RAM element
+	SB_RAM40_4K #(
+`ifdef SIM
+		.INIT_FILE("usb_ep_status.hex"),
+`endif
+		.WRITE_MODE(0),
+		.READ_MODE(0)
+	) ebr_I (
+		.RDATA(dout_2),
+		.RADDR({3'b000, addr_1}),
+		.RCLK(clk),
+		.RCLKE(1'b1),
+		.RE(1'b1),
+		.WDATA(din_1),
+		.WADDR({3'b000, addr_1}),
+		.MASK(16'h0000),
+		.WCLK(clk),
+		.WCLKE(we_1),
+		.WE(1'b1)
+	);
+
+endmodule // usb_ep_status

+ 151 - 0
cores/usb/rtl/usb_phy.v

@@ -0,0 +1,151 @@
+/*
+ * usb_phy.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * 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.
+ */
+
+`default_nettype none
+
+module usb_phy #(
+	parameter TARGET = "ICE40"
+)(
+	// Pads
+	inout  wire pad_dp,
+	inout  wire pad_dn,
+
+	// RX
+	output wire rx_dp,
+	output wire rx_dn,
+	output wire rx_chg,
+
+	// TX
+	input  wire tx_dp,
+	input  wire tx_dn,
+	input  wire tx_en,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	wire [1:0] rx_dp_i;
+	wire [1:0] rx_dn_i;
+	reg  [2:0] dp_state;
+	reg  [2:0] dn_state;
+
+	// IO buffers
+	generate
+		if (TARGET == "ICE40") begin
+
+			SB_IO #(
+				.PIN_TYPE(6'b110100),
+				.PULLUP(1'b0),
+				.NEG_TRIGGER(1'b0),
+				.IO_STANDARD("SB_LVCMOS")
+			) io_dp_I (
+				.PACKAGE_PIN(pad_dp),
+				.LATCH_INPUT_VALUE(1'b0),
+				.CLOCK_ENABLE(1'b1),
+				.INPUT_CLK(clk),
+				.OUTPUT_CLK(clk),
+				.OUTPUT_ENABLE(tx_en),
+				.D_OUT_0(tx_dp),
+				.D_OUT_1(1'b0),
+				.D_IN_0(rx_dp_i[0]),
+				.D_IN_1(rx_dp_i[1])
+			);
+
+			SB_IO #(
+				.PIN_TYPE(6'b110100),
+				.PULLUP(1'b0),
+				.NEG_TRIGGER(1'b0),
+				.IO_STANDARD("SB_LVCMOS")
+			) io_dn_I (
+				.PACKAGE_PIN(pad_dn),
+				.LATCH_INPUT_VALUE(1'b0),
+				.CLOCK_ENABLE(1'b1),
+				.INPUT_CLK(clk),
+				.OUTPUT_CLK(clk),
+				.OUTPUT_ENABLE(tx_en),
+				.D_OUT_0(tx_dn),
+				.D_OUT_1(1'b0),
+				.D_IN_0(rx_dn_i[0]),
+				.D_IN_1(rx_dn_i[1])
+			);
+
+		end
+	endgenerate
+
+	// Input sync, filter and change detect
+	always @(posedge clk or posedge rst)
+	begin
+		if (rst) begin
+			dp_state <= 3'b000;
+			dn_state <= 3'b000;
+		end else begin
+			case ({dp_state[1:0], rx_dp_i})
+				4'b0000: dp_state <= 3'b000;
+				4'b0001: dp_state <= 3'b001;
+				4'b0010: dp_state <= 3'b001;
+				4'b0011: dp_state <= 3'b001;
+				4'b0100: dp_state <= 3'b000;
+				4'b0101: dp_state <= 3'b001;
+				4'b0110: dp_state <= 3'b001;
+				4'b0111: dp_state <= 3'b111;
+				4'b1000: dp_state <= 3'b100;
+				4'b1001: dp_state <= 3'b010;
+				4'b1010: dp_state <= 3'b010;
+				4'b1011: dp_state <= 3'b011;
+				4'b1100: dp_state <= 3'b010;
+				4'b1101: dp_state <= 3'b010;
+				4'b1110: dp_state <= 3'b010;
+				4'b1111: dp_state <= 3'b011;
+				default: dp_state <= 3'bxxx;
+			endcase
+
+			case ({dn_state[1:0], rx_dn_i})
+				4'b0000: dn_state <= 3'b000;
+				4'b0001: dn_state <= 3'b001;
+				4'b0010: dn_state <= 3'b001;
+				4'b0011: dn_state <= 3'b001;
+				4'b0100: dn_state <= 3'b000;
+				4'b0101: dn_state <= 3'b001;
+				4'b0110: dn_state <= 3'b001;
+				4'b0111: dn_state <= 3'b111;
+				4'b1000: dn_state <= 3'b100;
+				4'b1001: dn_state <= 3'b010;
+				4'b1010: dn_state <= 3'b010;
+				4'b1011: dn_state <= 3'b011;
+				4'b1100: dn_state <= 3'b010;
+				4'b1101: dn_state <= 3'b010;
+				4'b1110: dn_state <= 3'b010;
+				4'b1111: dn_state <= 3'b011;
+				default: dn_state <= 3'bxxx;
+			endcase
+		end
+	end
+
+	assign rx_dp  = dp_state[1];
+	assign rx_dn  = dn_state[1];
+	assign rx_chg = dp_state[2] | dn_state[2];
+
+endmodule // usb_phy

+ 210 - 0
cores/usb/rtl/usb_rx_ll.v

@@ -0,0 +1,210 @@
+/*
+ * usb_rx_ll.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * 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.
+ */
+
+`default_nettype none
+
+module usb_rx_ll (
+	// PHY
+	input  wire phy_rx_dp,
+	input  wire phy_rx_dn,
+	input  wire phy_rx_chg,
+
+	// Low-Level
+	output wire [1:0] ll_sym,
+	output wire ll_bit,
+	output wire ll_valid,
+	output wire ll_eop,
+	output wire ll_sync,
+	output wire ll_bs_skip,
+	output wire ll_bs_err,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	// Signals
+	// -------
+
+	// Sampling
+	reg        samp_active;
+	(* keep="true" *) wire samp_sync;
+	reg  [2:0] samp_cnt;
+	wire [1:0] samp_sym_0;
+	reg        samp_valid_0;
+
+	// Decoding
+	(* keep="true" *) wire dec_sym_same_0;
+	(* keep="true" *) wire dec_sym_se_0;	/* Symbol is SE0 or SE1 */
+	reg  [2:0] dec_eop_state_1;
+	reg  [3:0] dec_sync_state_1;
+	reg  [4:0] dec_rep_state_1;
+
+	reg  [1:0] dec_sym_1;
+	reg        dec_bit_1;
+	reg        dec_valid_1;
+	wire       dec_eop_1;
+	wire       dec_sync_1;
+	wire [2:0] dec_rep_1;
+	wire       dec_bs_skip_1;
+	wire       dec_bs_err_1;
+
+
+	// Sampling
+	// --------
+
+	// Active
+		// The EOP and Error signals are from the next stage, but the pipeline
+		// violation doesn't matter, we just want to stop before the next
+		// packet so that the resync works well at the beginning of the next
+		// packet.
+	always @(posedge clk or posedge rst)
+		if (rst)
+			samp_active <= 1'b0;
+		else
+			samp_active <= (samp_active | phy_rx_chg) & ~(dec_valid_1 & (dec_eop_1 | dec_bs_err_1));
+
+	// When to resync
+	assign samp_sync = ~samp_active | (~samp_cnt[2] && phy_rx_chg);
+
+	// Sampling phase tracking
+	always @(posedge clk)
+		if (samp_sync)
+			samp_cnt <= 3'b101;
+		else
+			/* The following case implements :
+			 * samp_cnt <= (samp_cnt - 1) & { samp_cnt[2], 2'b11 };
+			 * but in a way that synthesis understands well */
+			case (samp_cnt)
+				3'b000:  samp_cnt <= 3'b011;
+				3'b001:  samp_cnt <= 3'b000;
+				3'b010:  samp_cnt <= 3'b001;
+				3'b011:  samp_cnt <= 3'b010;
+				3'b100:  samp_cnt <= 3'b011;
+				3'b101:  samp_cnt <= 3'b100;
+				3'b110:  samp_cnt <= 3'b101;
+				3'b111:  samp_cnt <= 3'b110;
+				default: samp_cnt <= 3'bxxx;
+			endcase
+
+	// Output to next stage
+	always @(posedge clk)
+		samp_valid_0 <= samp_active & (samp_cnt[1:0] == 2'b01) & ~samp_valid_0;
+
+	assign samp_sym_0 = { phy_rx_dp, phy_rx_dn };
+
+
+	// Bit de-stuffing & NRZI
+	// ----------------------
+
+	// Compare with previous
+	assign dec_sym_same_0 = (samp_sym_0 == dec_sym_1);
+	assign dec_sym_se_0 = ~^samp_sym_0;
+
+	// Symbol and Bit-value
+	always @(posedge clk)
+		if (samp_valid_0)
+		begin
+			dec_sym_1 <= samp_sym_0;
+			dec_bit_1 <= (samp_sym_0[0]  ^ samp_sym_0[1]) &	// Symbol is J or K
+			             (dec_sym_1[0]   ^ dec_sym_1[1]) &	// Previous symbol is J or K
+			             ~(samp_sym_0[1] ^ dec_sym_1[1]);	// Same symbol
+		end
+
+	always @(posedge clk)
+		dec_valid_1 <= samp_valid_0;
+
+	// EOP detect
+	always @(posedge clk)
+		if (samp_valid_0)
+			case ({dec_eop_state_1[1:0], samp_sym_0})
+				4'b0000: dec_eop_state_1 <= 3'b001;	// SE0
+				4'b0100: dec_eop_state_1 <= 3'b010;	// SE0
+				4'b1000: dec_eop_state_1 <= 3'b010;	// We should get J but maybe we tolerate >2 SE0 ?
+				4'b1010: dec_eop_state_1 <= 3'b111; // J
+				default: dec_eop_state_1 <= 3'b000;
+			endcase
+
+	assign dec_eop_1 = dec_eop_state_1[2];
+
+	// Sync tracking
+	always @(posedge clk)
+		if (samp_valid_0)
+		begin
+			if (dec_sym_se_0)
+				dec_sync_state_1 <= 4'b0000;
+			else
+				casez ({dec_sync_state_1[2:0], samp_sym_0[1]})
+					4'b0000: dec_sync_state_1 <= 4'b0001;
+					4'b0011: dec_sync_state_1 <= 4'b0010;
+					4'b0100: dec_sync_state_1 <= 4'b0011;
+					4'b0111: dec_sync_state_1 <= 4'b0100;
+					4'b1000: dec_sync_state_1 <= 4'b0101;
+					4'b1011: dec_sync_state_1 <= 4'b0110;
+					4'b1100: dec_sync_state_1 <= 4'b0111;
+					4'b1110: dec_sync_state_1 <= 4'b1001;
+					4'b???0: dec_sync_state_1 <= 4'b0001;
+					default: dec_sync_state_1 <= 4'b0000;
+				endcase
+		end
+
+	assign dec_sync_1 = dec_sync_state_1[3];
+
+	// Repeat tracking
+	always @(posedge clk)
+		if (samp_valid_0)
+			if (dec_sym_same_0 == 1'b0)
+				dec_rep_state_1 <= 5'b00000;
+			else
+				// This is basically a saturated increment with flags for (==5) & (>=6)
+				case (dec_rep_state_1[2:0])
+					3'b000:  dec_rep_state_1 <= 5'b00001;
+					3'b001:  dec_rep_state_1 <= 5'b00010;
+					3'b010:  dec_rep_state_1 <= 5'b00011;
+					3'b011:  dec_rep_state_1 <= 5'b00100;
+					3'b100:  dec_rep_state_1 <= 5'b00101;
+					3'b101:  dec_rep_state_1 <= 5'b01110;
+					3'b110:  dec_rep_state_1 <= 5'b10111;
+					3'b111:  dec_rep_state_1 <= 5'b10111;
+					default: dec_rep_state_1 <= 5'bxxxxx;
+				endcase
+
+	assign dec_bs_err_1  = dec_rep_state_1[4];
+	assign dec_bs_skip_1 = dec_rep_state_1[3];
+	assign dec_rep_1     = dec_rep_state_1[2:0];
+
+
+	// Output
+	// ------
+
+	assign ll_sym     = dec_sym_1;
+	assign ll_bit     = dec_bit_1;
+	assign ll_valid   = dec_valid_1;
+	assign ll_eop     = dec_eop_1;
+	assign ll_sync    = dec_sync_1;
+	assign ll_bs_skip = dec_bs_skip_1;
+	assign ll_bs_err  = dec_bs_err_1;
+
+endmodule // usb_rx_ll

+ 393 - 0
cores/usb/rtl/usb_rx_pkt.v

@@ -0,0 +1,393 @@
+/*
+ * usb_rx_pkt.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * 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.
+ */
+
+`default_nettype none
+
+module usb_rx_pkt (
+	// Low-Level
+	input  wire [1:0] ll_sym,
+	input  wire ll_bit,
+	input  wire ll_valid,
+	input  wire ll_eop,
+	input  wire ll_sync,
+	input  wire ll_bs_skip,
+	input  wire ll_bs_err,
+
+	// Packet interface
+	output reg  pkt_start,
+	output reg  pkt_done_ok,
+	output reg  pkt_done_err,
+
+	output wire [ 3:0] pkt_pid,
+	output wire pkt_is_sof,
+	output wire pkt_is_token,
+	output wire pkt_is_data,
+	output wire pkt_is_handshake,
+
+	output wire [10:0] pkt_frameno,
+	output wire [ 6:0] pkt_addr,
+	output wire [ 3:0] pkt_endp,
+
+	output wire [ 7:0] pkt_data,
+	output reg  pkt_data_stb,
+
+	// Control
+	input  wire inhibit,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	`include "usb_defs.vh"
+
+
+	// FSM
+	// ---
+
+	localparam
+		ST_IDLE      = 0,
+		ST_PID       = 1,
+		ST_PID_CHECK = 2,
+		ST_ERROR     = 3,
+		ST_TOKEN_1   = 4,
+		ST_TOKEN_2   = 5,
+		ST_WAIT_EOP  = 6,
+		ST_DATA      = 7;
+
+
+	// Signals
+	// -------
+
+	// FSM
+	reg  [3:0] state_nxt;
+	reg  [3:0] state;
+
+	reg state_prev_idle;
+	reg state_prev_error;
+
+	// Utils
+	wire llu_bit_stb;
+	wire llu_byte_stb;
+
+	// Data shift reg & bit counting
+	wire [7:0] data_nxt;
+	reg  [7:0] data;
+	reg  [3:0] bit_cnt;
+	reg  bit_eop_ok;
+	wire bit_last;
+
+	// CRC checking
+	wire crc_in_bit;
+	wire crc_in_valid;
+	reg  crc_in_first;
+
+	reg  crc_cap;
+
+	wire crc5_match;
+	reg  crc5_ok;
+
+	wire crc16_match;
+	reg  crc16_ok;
+
+	// PID capture and decoding
+	wire pid_cap;
+	reg  pid_cap_r;
+	reg  pid_valid;
+	reg  [3:0] pid;
+	reg  pid_is_sof;
+	reg  pid_is_token;
+	reg  pid_is_data;
+	reg  pid_is_handshake;
+
+	// TOKEN data capture
+	reg [10:0] token_data;
+
+
+	// Main FSM
+	// --------
+
+	// Next state logic
+	always @(*)
+	begin
+		// Default is to stay put
+		state_nxt = state;
+
+		// Main case
+		case (state)
+			ST_IDLE:
+				// Wait for SYNC to be detected
+				if (ll_valid && ll_sync && ~inhibit)
+					state_nxt = ST_PID;
+
+			ST_PID:
+				// Wait for PID capture
+				if (llu_byte_stb)
+					state_nxt = ST_PID_CHECK;
+
+			ST_PID_CHECK: begin
+				// Default is to error if no match
+				state_nxt = ST_ERROR;
+
+				// Select state depending on packet type
+				if (pid_valid) begin
+					if (pid_is_sof)
+						state_nxt = ST_TOKEN_1;
+					else if (pid_is_token)
+						state_nxt = ST_TOKEN_1;
+					else if (pid_is_data)
+						state_nxt = ST_DATA;
+					else if (pid_is_handshake)
+						state_nxt = ST_WAIT_EOP;
+				end
+			end
+
+			ST_ERROR:
+				// Error, wait for a possible IDLE state to resume
+				if (ll_valid && (ll_eop || (ll_bs_err && (ll_sym == SYM_J))))
+					state_nxt = ST_IDLE;
+
+			ST_TOKEN_1:
+				// First data byte
+				if (ll_valid && ll_eop)
+					state_nxt = ST_ERROR;
+				else if (llu_byte_stb)
+					state_nxt = ST_TOKEN_2;
+
+			ST_TOKEN_2:
+				// Second data byte
+				if (ll_valid && ll_eop)
+					state_nxt = ST_ERROR;
+				else if (llu_byte_stb)
+					state_nxt = ST_WAIT_EOP;
+
+			ST_WAIT_EOP:
+				// Need EOP at the right place
+				if (ll_valid && ll_eop)
+					state_nxt = (bit_eop_ok & (crc5_ok | pid_is_handshake)) ? ST_IDLE : ST_ERROR;
+				else if (llu_byte_stb)
+					state_nxt = ST_ERROR;
+
+			ST_DATA:
+				if (ll_valid) begin
+					if (ll_eop)
+						state_nxt = (bit_eop_ok & crc16_ok) ? ST_IDLE : ST_ERROR;
+					else if (ll_bs_err)
+						state_nxt = ST_ERROR;
+				end
+		endcase
+	end
+
+	// State register
+	always @(posedge clk or posedge rst)
+		if (rst)
+			state <= ST_IDLE;
+		else
+			state <= state_nxt;
+
+
+	// Utility signals
+	// ---------------
+
+	always @(posedge clk)
+	begin
+		state_prev_idle  <= (state == ST_IDLE);
+		state_prev_error <= (state == ST_ERROR);
+	end
+
+	assign llu_bit_stb  = ll_valid & ~ll_bs_skip;
+	assign llu_byte_stb = ll_valid & ~ll_bs_skip & bit_last;
+
+
+	// Data shift register and bit counter
+	// -----------------------------------
+
+	// Next word
+	assign data_nxt = { ll_bit, data[7:1] };
+
+	// Shift reg
+	always @(posedge clk)
+		if (llu_bit_stb)
+			data <= data_nxt;
+
+	// Bit counter
+	always @(posedge clk)
+		if (state == ST_IDLE)
+			bit_cnt <= 4'b0110;
+		else if (llu_bit_stb)
+			bit_cnt <= { 1'b0, bit_cnt[2:0] } - 1;
+
+	// Last bit ?
+	assign bit_last = bit_cnt[3];
+
+	// EOP OK at this position ?
+	always @(posedge clk)
+		if (state == ST_IDLE)
+			bit_eop_ok <= 1'b0;
+		else if (llu_bit_stb)
+			bit_eop_ok <= (bit_cnt[2:1] == 2'b10);
+
+
+	// CRC checks
+	// ----------
+
+	// CRC input data
+	assign crc_in_bit   = ll_bit;
+	assign crc_in_valid = llu_bit_stb;
+
+	always @(posedge clk)
+		if (state == ST_PID)
+			crc_in_first <= 1'b1;
+		else if (crc_in_valid)
+			crc_in_first <= 1'b0;
+
+	// CRC5 core
+	usb_crc #(
+		.WIDTH(5),
+		.POLY(5'b00101),
+		.MATCH(5'b01100)
+	) crc_5_I (
+		.in_bit(crc_in_bit),
+		.in_first(crc_in_first),
+		.in_valid(crc_in_valid),
+		.crc(),
+		.crc_match(crc5_match),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	// CRC16 core
+	usb_crc #(
+		.WIDTH(16),
+		.POLY(16'h8005),
+		.MATCH(16'h800D)
+	) crc_16_I (
+		.in_bit(crc_in_bit),
+		.in_first(crc_in_first),
+		.in_valid(crc_in_valid),
+		.crc(),
+		.crc_match(crc16_match),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	// Capture CRC status at end of each byte
+		// This will be a bit 'late' (i.e. a couple cycles after the last
+		// bit was input), but it's only used when EOP happens, which is
+		// many cycles after that, so this delay is fine
+	always @(posedge clk)
+		crc_cap <= llu_byte_stb;
+
+	always @(posedge clk)
+		if (state == ST_IDLE) begin
+			crc5_ok <= 1'b0;
+			crc16_ok <= 1'b0;
+		end else if (crc_cap) begin
+			crc5_ok  <= crc5_match;
+			crc16_ok <= crc16_match;
+		end
+
+
+	// PID capture and decoding
+	// ------------------------
+
+	// When to capture
+	assign pid_cap = (state == ST_PID) & llu_byte_stb;
+
+	// Check PID before capture
+	always @(posedge clk)
+		if (pid_cap)
+			pid_valid <= (data_nxt[3:0] == ~data_nxt[7:4]) && (
+				(data_nxt[3:0] == PID_SOF)   ||
+				(data_nxt[3:0] == PID_OUT)   ||
+				(data_nxt[3:0] == PID_IN)    ||
+				(data_nxt[3:0] == PID_SETUP) ||
+				(data_nxt[3:0] == PID_DATA0) ||
+				(data_nxt[3:0] == PID_DATA1) ||
+				(data_nxt[3:0] == PID_ACK)   ||
+				(data_nxt[3:0] == PID_NAK)   ||
+				(data_nxt[3:0] == PID_STALL)
+			);
+
+	always @(posedge clk)
+		pid_cap_r <= pid_cap;
+
+	// Capture and decode
+	always @(posedge clk)
+		if ((state == ST_PID) && llu_byte_stb)
+		begin
+			pid              <=  data_nxt;
+			pid_is_sof       <= (data_nxt[3:0] == PID_SOF);
+			pid_is_token     <= (data_nxt[3:0] == PID_OUT) || (data_nxt[3:0] == PID_IN) || (data_nxt[3:0] == PID_SETUP);
+			pid_is_data      <= (data_nxt[3:0] == PID_DATA0) || (data_nxt[3:0] == PID_DATA1);
+			pid_is_handshake <= (data_nxt[3:0] == PID_ACK) || (data_nxt[3:0] == PID_NAK) || (data_nxt[3:0] == PID_STALL);
+		end
+
+
+	// TOKEN data capture
+	// ------------------
+
+	always @(posedge clk)
+		if ((state == ST_TOKEN_1) && llu_byte_stb)
+			token_data[7:0] <= data_nxt[7:0];
+
+	always @(posedge clk)
+		if ((state == ST_TOKEN_2) && llu_byte_stb)
+			token_data[10:8] <= data_nxt[2:0];
+
+
+	// Output
+	// ------
+
+	// Generate pkt_start on PID capture
+	always @(posedge clk)
+		pkt_start <= pid_cap_r & pid_valid;
+
+	// Generate packet done signals
+	always @(posedge clk)
+	begin
+		pkt_done_ok  <= (state == ST_IDLE)  && !state_prev_idle && !state_prev_error;
+		pkt_done_err <= (state == ST_ERROR) && !state_prev_error;
+	end
+
+	// Output PID and decoded
+	assign pkt_pid          = pid;
+	assign pkt_is_sof       = pid_is_sof;
+	assign pkt_is_token     = pid_is_token;
+	assign pkt_is_data      = pid_is_data;
+	assign pkt_is_handshake = pid_is_handshake;
+
+	// Output token data
+	assign pkt_frameno = token_data;
+	assign pkt_addr    = token_data[ 6:0];
+	assign pkt_endp    = token_data[10:7];
+
+	// Data byte and associated strobe
+	assign pkt_data = data;
+
+	always @(posedge clk)
+		pkt_data_stb <= (state == ST_DATA) && llu_byte_stb;
+
+endmodule // usb_rx_pkt

+ 459 - 0
cores/usb/rtl/usb_trans.v

@@ -0,0 +1,459 @@
+/*
+ * usb_trans.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * 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.
+ */
+
+`default_nettype none
+
+module usb_trans (
+	// TX Packet interface
+	output wire txpkt_start,
+	input  wire txpkt_done,
+
+	output reg  [3:0] txpkt_pid,
+	output wire [9:0] txpkt_len,
+
+	output wire [7:0] txpkt_data,
+	input  wire txpkt_data_ack,
+
+	// RX Packet interface
+	input  wire rxpkt_start,
+	input  wire rxpkt_done_ok,
+	input  wire rxpkt_done_err,
+
+	input  wire [ 3:0] rxpkt_pid,
+	input  wire rxpkt_is_sof,
+	input  wire rxpkt_is_token,
+	input  wire rxpkt_is_data,
+	input  wire rxpkt_is_handshake,
+
+	input  wire [10:0] rxpkt_frameno,
+	input  wire [ 6:0] rxpkt_addr,
+	input  wire [ 3:0] rxpkt_endp,
+
+	input  wire [ 7:0] rxpkt_data,
+	input  wire rxpkt_data_stb,
+
+	// EP Data Buffers
+	output wire [10:0] buf_tx_addr_0,
+	input  wire [ 7:0] buf_tx_data_1,
+	output wire buf_tx_rden_0,
+
+	output wire [10:0] buf_rx_addr_0,
+	output wire [ 7:0] buf_rx_data_0,
+	output wire buf_rx_wren_0,
+
+	// EP Status RAM
+	output wire eps_read_0,
+	output wire eps_zero_0,
+	output wire eps_write_0,
+	output wire [ 7:0] eps_addr_0,
+	output wire [15:0] eps_wrdata_0,
+	input  wire [15:0] eps_rddata_3,
+
+	// Config
+	input  wire [ 6:0] cr_addr,
+	output wire [15:0] sr_notify,
+	output reg  irq_stb,
+	output wire irq_state,
+	input  wire irq_ack,
+
+	// Debug
+	output wire [ 3:0] debug,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	`include "usb_defs.vh"
+
+	// Signals
+	// -------
+
+	// Micro-Code
+	reg  [ 3:0] mc_a_reg;
+
+	reg mc_rst_n;
+	(* keep="true" *) wire [ 3:0] mc_match_bits;
+	wire mc_match;
+	wire mc_jmp;
+
+	wire [ 7:0] mc_pc;
+	reg  [ 7:0] mc_pc_nxt;
+	wire [15:0] mc_opcode;
+
+	(* keep="true" *) wire mc_op_ld;
+	(* keep="true" *) wire mc_op_ep;
+	(* keep="true" *) wire mc_op_zlen;
+	(* keep="true" *) wire mc_op_tx;
+	(* keep="true" *) wire mc_op_notify;
+	(* keep="true" *) wire mc_op_evt_clr;
+	(* keep="true" *) wire mc_op_evt_rto;
+
+	// Events
+	wire [3:0] evt_rst;
+	wire [3:0] evt_set;
+	reg  [3:0] evt;
+
+	wire rto_now;
+	reg  [9:0] rto_cnt;
+
+	// Host notify
+	reg        irq_state_i;
+	reg  [3:0] irq_cnt;
+	reg [10:0] sr_notify_i;
+
+	// Transaction / EndPoint / Buffer infos
+	reg  [3:0] trans_pid;
+	reg        trans_is_setup;
+	reg        trans_addr_zero;
+	reg        trans_addr_match;
+	reg  [3:0] trans_endp;
+	reg        trans_dir;
+
+	reg  [2:0] ep_type;
+	reg        ep_dual_buf;
+	reg        ep_bd_idx_cur;
+	reg        ep_bd_idx_nxt;
+	reg        ep_data_toggle;
+
+	reg  [2:0] bd_state;
+
+	// EP & BD Infos fetch/writeback
+	localparam
+		EPFW_IDLE		= 4'b0000,
+		EPFW_RD_STATUS	= 4'b0100,
+		EPFW_RD_BD_W0	= 4'b0110,
+		EPFW_RD_BD_W1	= 4'b0111,
+		EPFW_WR_STATUS	= 4'b1000,
+		EPFW_WR_BD_W0	= 4'b1010;
+
+	reg  [3:0] epfw_state;
+	reg  [5:0] epfw_cap_dl;
+	reg  epfw_issue_wb;
+
+	// Packet TX
+	reg  txpkt_start_i;
+
+	// Address
+	reg  [10:0] addr;
+	wire addr_inc;
+	wire addr_ld;
+
+	// Length
+	reg [10:0] bd_length;
+	reg [ 9:0] xfer_length;
+	wire len_ld;
+	wire len_bd_dec;
+	wire len_xf_inc;
+
+	assign debug = mc_pc[3:0];
+
+	// Micro-Code execution engine
+	// ---------------------------
+
+	// Local reset to avoid being in the critical path
+	always @(posedge clk or posedge rst)
+		if (rst)
+			mc_rst_n <= 1'b0;
+		else
+			mc_rst_n <= 1'b1;
+
+	// Conditional Jump handling
+	assign mc_match_bits = (mc_a_reg[3:0] & mc_opcode[7:4]) ^ mc_opcode[3:0];
+	assign mc_match = ~|mc_match_bits;
+	assign mc_jmp = mc_opcode[15] & mc_rst_n & (mc_match ^ mc_opcode[14]);
+	assign mc_pc = mc_jmp ? {mc_opcode[13:8], 2'b00} : mc_pc_nxt;
+
+	// Program counter
+	always @(posedge clk or posedge rst)
+		if (rst)
+			mc_pc_nxt <= 8'h00;
+		else
+			mc_pc_nxt <= mc_pc + 1;
+
+	// Microcode ROM
+	SB_RAM40_4K #(
+		.INIT_FILE("usb_trans_mc.hex"),
+		.WRITE_MODE(0),
+		.READ_MODE(0)
+	) mc_rom_I (
+		.RDATA(mc_opcode),
+		.RADDR({3'b000, mc_pc}),
+		.RCLK(clk),
+		.RCLKE(1'b1),
+		.RE(1'b1),
+		.WDATA(16'h0000),
+		.WADDR(11'h000),
+		.MASK(16'h0000),
+		.WCLK(1'b0),
+		.WCLKE(1'b0),
+		.WE(1'b0)
+	);
+
+	// Decode opcodes
+	assign mc_op_ld      = mc_opcode[15:12] == 4'b0001;
+	assign mc_op_ep      = mc_opcode[15:12] == 4'b0010;
+	assign mc_op_zlen    = mc_opcode[15:12] == 4'b0011;
+	assign mc_op_tx      = mc_opcode[15:12] == 4'b0100;
+	assign mc_op_notify  = mc_opcode[15:12] == 4'b0101;
+	assign mc_op_evt_clr = mc_opcode[15:12] == 4'b0110;
+	assign mc_op_evt_rto = mc_opcode[15:12] == 4'b0111;
+
+	// A-register
+	always @(posedge clk)
+		if (mc_op_ld)
+			casez (mc_opcode[2:1])
+				2'b00:   mc_a_reg <= evt;
+				2'b01:   mc_a_reg <= rxpkt_pid ^ { ep_data_toggle & mc_opcode[0], 3'b000 };
+				2'b10:   mc_a_reg <= { 1'b0, ep_type };
+				2'b11:   mc_a_reg <= { 1'b0, bd_state };
+				default: mc_a_reg <= 4'hx;
+			endcase
+
+
+	// Events
+	// ------
+
+	// Latch events
+	always @(posedge clk or posedge rst)
+		if (rst)
+			evt <= 4'h0;
+		else
+			evt <= (evt & ~evt_rst) | evt_set;
+
+	assign evt_rst = {4{mc_op_evt_clr}} & mc_opcode[3:0];
+	assign evt_set = { rto_now, txpkt_done, rxpkt_done_err, rxpkt_done_ok };
+
+	// RX Timeout counter
+	always @(posedge clk or posedge rst)
+		if (rst)
+			rto_cnt <= 0;
+		else
+			if (mc_op_evt_rto)
+				rto_cnt <= { 2'b01, mc_opcode[7:0] };
+			else
+				rto_cnt <= {
+					rto_cnt[9] & rto_cnt[8] & ~rxpkt_start,
+					rto_cnt[8:0] - rto_cnt[9]
+				};
+
+	assign rto_now = rto_cnt[9] & ~rto_cnt[8];
+
+
+	// Host NOTIFY
+	// -----------
+
+	always @(posedge clk)
+		if (mc_op_notify)
+			sr_notify_i <= {
+				mc_opcode[3:0],				// Micro-code return value
+				trans_endp,					// Endpoint #
+				trans_dir,					// Direction
+				trans_is_setup,
+				ep_bd_idx_cur				// BD where it happenned
+			};
+
+	assign sr_notify = {
+		irq_cnt,
+		sr_notify_i,
+		irq_state
+	};
+
+	always @(posedge clk)
+	begin
+		irq_stb <= mc_op_notify;
+		irq_cnt <= irq_cnt + mc_op_notify;
+		irq_state_i <= (irq_state_i & ~irq_ack) | mc_op_notify;
+	end
+
+	assign irq_state = irq_state_i;
+
+
+	// EP infos
+	// --------
+
+	// Capture EP# and direction when we get a TOKEN packet
+	always @(posedge clk)
+		if (rxpkt_done_ok & rxpkt_is_token) begin
+			trans_pid        <= rxpkt_pid;
+			trans_is_setup   <= rxpkt_pid == PID_SETUP;
+			trans_addr_zero  <= rxpkt_addr == 6'h00;
+			trans_addr_match <= rxpkt_addr == cr_addr;
+			trans_endp       <= rxpkt_endp;
+			trans_dir        <= rxpkt_pid == PID_IN;
+		end
+
+	// EP Status Fetch/WriteBack (epfw)
+
+		// State
+	always @(posedge clk or posedge rst)
+		if (rst)
+			epfw_state <= EPFW_IDLE;
+		else
+			case (epfw_state)
+				EPFW_IDLE:
+					if (epfw_issue_wb)
+						epfw_state <= EPFW_WR_STATUS;
+					else if (rxpkt_done_ok & rxpkt_is_token)
+						epfw_state <= EPFW_RD_STATUS;
+					else if (epfw_cap_dl[1:0] == 2'b01)
+						epfw_state <= EPFW_RD_BD_W0;
+					else
+						epfw_state <= EPFW_IDLE;
+
+				EPFW_RD_STATUS:
+					epfw_state <= EPFW_IDLE;
+
+				EPFW_RD_BD_W0:
+					epfw_state <= EPFW_RD_BD_W1;
+
+				EPFW_RD_BD_W1:
+					epfw_state <= EPFW_IDLE;
+
+				EPFW_WR_STATUS:
+					epfw_state <= EPFW_WR_BD_W0;
+
+				EPFW_WR_BD_W0:
+					epfw_state <= EPFW_IDLE;
+
+				default:
+					epfw_state <= EPFW_IDLE;
+			endcase
+
+		// Issue command to RAM
+	assign eps_zero_0  = 1'b0;
+	assign eps_read_0  = epfw_state[2];
+	assign eps_write_0 = epfw_state[3];
+
+	assign eps_addr_0  = {
+		trans_endp,
+		trans_dir,
+		epfw_state[1],
+		epfw_state[1] & ep_bd_idx_cur,
+		epfw_state[0]
+	};
+
+	assign eps_wrdata_0 = epfw_state[1] ?
+		{ bd_state, trans_is_setup, 2'b00, xfer_length[9:0] } :
+		{ 10'h000, ep_data_toggle, ep_bd_idx_nxt, ep_dual_buf, ep_type };
+
+		// Delay line for what to expect on read data
+	always @(posedge clk or posedge rst)
+		if (rst)
+			epfw_cap_dl = 6'b000000;
+		else
+			epfw_cap_dl <= {
+				epfw_state[1],
+				epfw_state[2] & ~^epfw_state[1:0],
+				epfw_cap_dl[5:2]
+			};
+
+		// Capture read data
+	always @(posedge clk)
+	begin
+		// EP Status
+		if (epfw_cap_dl[1:0] == 2'b01) begin
+			ep_type        <= eps_rddata_3[2:0];
+			ep_dual_buf    <= eps_rddata_3[3];
+			ep_bd_idx_cur  <= eps_rddata_3[4];
+			ep_bd_idx_nxt  <= eps_rddata_3[4];
+			ep_data_toggle <= eps_rddata_3[5] & ~trans_is_setup; /* For SETUP, DT == 0 */
+		end else begin
+			ep_data_toggle <= ep_data_toggle ^ (mc_op_ep & mc_opcode[0]);
+			ep_bd_idx_nxt  <= ep_bd_idx_nxt  ^ (mc_op_ep & mc_opcode[1] & ep_dual_buf );
+		end
+
+		// BD Word 0
+		if (epfw_cap_dl[1:0] == 2'b10) begin
+			bd_state <= eps_rddata_3[15:13];
+		end else begin
+			bd_state <= (mc_op_ep & mc_opcode[2]) ? mc_opcode[5:3]: bd_state;
+		end
+	end
+
+		// When do to write backs
+	always @(posedge clk)
+		epfw_issue_wb <= mc_op_ep & mc_opcode[7];
+
+
+	// Packet TX
+	// ---------
+
+	always @(posedge clk)
+		if (mc_op_tx)
+			txpkt_pid <= mc_opcode[3:0] ^ { mc_opcode[4] & ep_data_toggle, 3'b000 };
+
+	always @(posedge clk)
+		txpkt_start_i <= mc_op_tx;
+
+	assign txpkt_start = txpkt_start_i;
+	assign txpkt_len = bd_length[9:0];
+
+
+	// Data Address/Length shared logic
+	// --------------------------------
+
+	// Address
+	always @(posedge clk)
+		addr <= addr_ld ? eps_rddata_3[10:0] : (addr + addr_inc);
+
+	assign addr_ld  = epfw_cap_dl[1:0] == 2'b11;
+	assign addr_inc = txpkt_data_ack | txpkt_start_i | rxpkt_data_stb;
+
+	// Buffer length (decrements)
+	always @(posedge clk)
+		if (mc_op_zlen)
+			bd_length <= 0;
+		else
+			bd_length <= len_ld ? { 1'b1, eps_rddata_3[9:0] } : (bd_length -  len_bd_dec);
+
+	// Xfer length (increments)
+	always @(posedge clk)
+		xfer_length <= len_ld ? 10'h000 : (xfer_length + len_xf_inc);
+
+	// Length control
+	assign len_ld = epfw_cap_dl[1:0] == 2'b10;
+
+	assign len_bd_dec = (rxpkt_data_stb | rxpkt_start) & bd_length[10];
+	assign len_xf_inc =  rxpkt_data_stb;
+
+
+	// Data read logic
+	// ---------------
+
+	assign buf_tx_addr_0 = addr;
+	assign buf_tx_rden_0 = txpkt_data_ack | txpkt_start_i;
+
+	assign txpkt_data = buf_tx_data_1;
+
+
+	// Data write logic
+	// ----------------
+
+	assign buf_rx_addr_0 = addr;
+	assign buf_rx_data_0 = rxpkt_data;
+	assign buf_rx_wren_0 = rxpkt_data_stb & bd_length[10];
+
+endmodule // usb_trans

+ 155 - 0
cores/usb/rtl/usb_tx_ll.v

@@ -0,0 +1,155 @@
+/*
+ * usb_tx_ll.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * 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.
+ */
+
+`default_nettype none
+
+module usb_tx_ll (
+	// PHY
+	output wire phy_tx_dp,
+	output wire phy_tx_dn,
+	output wire phy_tx_en,
+
+	// Low-Level
+	input  wire ll_start,
+	input  wire ll_bit,
+	input  wire ll_last,
+	output reg  ll_ack,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	`include "usb_defs.vh"
+
+	// Signals
+	// -------
+
+	// State
+	reg [2:0] state;
+	wire active;
+
+	reg  [2:0] br_cnt;
+	wire br_now;
+
+	// Bit stuffing
+	reg  [2:0] bs_cnt;
+	reg  bs_now;
+	wire bs_bit;
+
+	// NRZI
+	reg  lvl_prev;
+
+	// Output
+	reg  out_active;
+	reg  [1:0] out_sym;
+
+
+	// State
+	// -----
+
+	always @(posedge clk or posedge rst)
+		if (rst)
+			state <= 3'b000;
+		else begin
+			if (ll_start)
+				state <= 3'b100;
+			else if (br_now) begin
+				if (ll_last)
+					state <= 3'b101;
+				else
+					case (state[1:0])
+						2'b00:   state <= state;
+						2'b01:   state <= 3'b110;
+						2'b10:   state <= 3'b111;
+						default: state <= 3'b000;
+					endcase
+			end
+		end
+
+	assign active = state[2];
+
+	always @(posedge clk)
+		br_cnt <= { 1'b0, active ? br_cnt[1:0] : 2'b10 } + 1;
+
+	assign br_now = br_cnt[2];
+
+
+	// Bit Stuffing
+	// ------------
+
+	// Track number of 1s
+	always @(posedge clk or posedge ll_start)
+		if (ll_start) begin
+			bs_cnt <= 3'b000;
+			bs_now <= 1'b0;
+		end else if (br_now) begin
+			bs_cnt <= (ll_bit & ~bs_now) ? (bs_cnt + 1) : 3'b000;
+			bs_now <= ll_bit & (bs_cnt == 3'b101);
+		end
+
+	// Effective bit
+	assign bs_bit = ~bs_now & ll_bit;
+
+	// Track previous level
+	always @(posedge clk)
+		lvl_prev <= active ? (lvl_prev ^ (~bs_bit & br_now)) : 1'b1;
+
+
+	// Output stage
+	// ------------
+
+	// Ack input
+	always @(posedge clk)
+		ll_ack <= br_now & ~bs_now & (state[1:0] == 2'b00);
+
+	// Output symbol. Must be forced to 'J' outside of active area to
+	// be ready for the next packet start
+	always @(posedge clk or posedge rst)
+	begin
+		if (rst)
+			out_sym <= SYM_J;
+		else if (br_now) begin
+			case (state[1:0])
+				2'b00:   out_sym <= (bs_bit ^ lvl_prev) ? SYM_K : SYM_J;
+				2'b01:   out_sym <= SYM_SE0;
+				2'b10:   out_sym <= SYM_SE0;
+				2'b11:   out_sym <= SYM_J;
+				default: out_sym <= 2'bxx;
+			endcase
+		end
+	end
+
+	// The OE is a bit in advance (not aligned with br_now) on purpose
+	// so that we output a bit of 'J' at the packet beginning
+	always @(posedge clk)
+		out_active <= active;
+
+	// PHY control
+	assign phy_tx_dp = out_sym[1];
+	assign phy_tx_dn = out_sym[0];
+	assign phy_tx_en = out_active;
+
+endmodule // usb_tx_ll

+ 263 - 0
cores/usb/rtl/usb_tx_pkt.v

@@ -0,0 +1,263 @@
+/*
+ * usb_tx_pkt.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * 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.
+ */
+
+`default_nettype none
+
+module usb_tx_pkt (
+	// Low-Level
+	output reg  ll_start,
+	output wire ll_bit,
+	output wire ll_last,
+	input  wire ll_ack,
+
+	// Packet interface
+	input  wire pkt_start,
+	output reg  pkt_done,
+
+	input  wire [3:0] pkt_pid,
+	input  wire [9:0] pkt_len,
+
+	input  wire [7:0] pkt_data,
+	output reg  pkt_data_ack,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	`include "usb_defs.vh"
+
+	// FSM
+	// ---
+
+	localparam
+		ST_IDLE      = 0,
+		ST_SYNC      = 1,
+		ST_PID       = 2,
+		ST_DATA      = 3,
+		ST_CRC_LSB   = 4,
+		ST_CRC_MSB   = 5;
+
+
+	// Signals
+	// -------
+
+	// FSM
+	reg  [3:0] state_nxt;
+	reg  [3:0] state;
+
+	// Helper
+	reg  pid_is_handshake;
+	wire next;
+
+	// Shift register
+	reg  [3:0] shift_bit;
+	reg  [7:0] shift_load;
+	reg  [7:0] shift_data;
+	reg  shift_data_crc;
+	wire shift_last_bit;
+	reg  shift_last_byte;
+	wire shift_do_load;
+	wire shift_now;
+	reg  shift_new_bit;
+
+	// Packet length
+	reg [10:0] len;
+	wire len_last;
+	wire len_dec;
+
+	// CRC
+	wire crc_in_bit;
+	reg  crc_in_first;
+	wire crc_in_valid;
+	wire [15:0] crc;
+
+
+	// Main FSM
+	// --------
+
+	// Next state logic
+	always @(*)
+	begin
+		// Default is to stay put
+		state_nxt = state;
+
+		// Main case
+		case (state)
+			ST_IDLE:
+				if (pkt_start)
+					state_nxt = ST_SYNC;
+
+			ST_SYNC:
+				state_nxt = ST_PID;
+
+			ST_PID:
+				if (next)
+				begin
+					if (pid_is_handshake)
+						state_nxt = ST_IDLE;
+					else if (len_last)
+						state_nxt = ST_CRC_LSB;
+					else
+						state_nxt = ST_DATA;
+				end
+
+			ST_DATA:
+				if (next && len_last)
+					state_nxt = ST_CRC_LSB;
+
+			ST_CRC_LSB:
+				if (next)
+					state_nxt = ST_CRC_MSB;
+
+			ST_CRC_MSB:
+				if (next)
+					state_nxt = ST_IDLE;
+		endcase
+	end
+
+	// State register
+	always @(posedge clk or posedge rst)
+		if (rst)
+			state <= ST_IDLE;
+		else
+			state <= state_nxt;
+
+
+	// Helper
+	// ------
+
+	always @(posedge clk)
+		pid_is_handshake <= (pkt_pid == PID_ACK) || (pkt_pid == PID_NAK) || (pkt_pid == PID_STALL);
+
+	assign next = shift_last_bit & ll_ack;
+
+
+	// Shift register
+	// --------------
+
+	// When to load a new byte
+	assign shift_do_load = (state == ST_SYNC) | (shift_last_bit & ll_ack);
+
+	// When to shift
+	assign shift_now = (state == ST_SYNC) | ll_ack;
+
+	// Bit counter
+	always @(posedge clk)
+		if (shift_now)
+			shift_bit <= (shift_do_load ? 4'b0111 : shift_bit) - 1;
+
+	assign shift_last_bit = shift_bit[3];
+
+	// Load mux
+	always @(*)
+		case (state)
+			ST_SYNC:    shift_load <= 8'h80;
+			ST_PID:     shift_load <= { ~pkt_pid, pkt_pid };
+			ST_DATA:    shift_load <= pkt_data;
+			ST_CRC_LSB: shift_load <= crc_in_first ? 8'h00 : crc[ 7:0];
+			ST_CRC_MSB: shift_load <= crc_in_first ? 8'h00 : crc[15:8];
+			default:    shift_load <= 8'hxx;
+		endcase
+
+	// Shift data
+	always @(posedge clk)
+		if (shift_now)
+			shift_data <= shift_do_load ? shift_load : {1'b0, shift_data[7:1]};
+
+	// Some flags about the data
+	always @(posedge clk)
+		if (shift_now & shift_do_load) begin
+			shift_data_crc  <= (state == ST_DATA);
+			shift_last_byte <= (state == ST_CRC_MSB) | ((state == ST_PID) & pid_is_handshake);
+		end
+
+	// When a fresh new bit is available
+	always @(posedge clk)
+		shift_new_bit <= shift_now;
+
+
+	// Packet length
+	// -------------
+
+	assign len_dec = pkt_start || (shift_do_load && ((state == ST_DATA) || (state == ST_PID)));
+
+	always @(posedge clk)
+		if (len_dec)
+			len <= (pkt_start ? { 1'b0, pkt_len } : len) - 1;
+
+	assign len_last = len[10];
+
+
+	// CRC generation
+	// --------------
+
+	// Keep track of first bit
+	always @(posedge clk)
+		crc_in_first <= (crc_in_first & ~crc_in_valid) | (state == ST_IDLE);
+
+	// Input all bits once acked
+	assign crc_in_bit   = shift_data[0];
+	assign crc_in_valid = shift_data_crc & shift_new_bit;
+
+	// CRC16 core
+	usb_crc #(
+		.WIDTH(16),
+		.POLY(16'h8005),
+		.MATCH(16'h800D)
+	) crc_16_I (
+		.in_bit(crc_in_bit),
+		.in_first(crc_in_first),
+		.in_valid(crc_in_valid),
+		.crc(crc),
+		.crc_match(),
+		.clk(clk),
+		.rst(rst)
+	);
+
+
+	// Low-level control
+	// -----------------
+
+	// Start right after the load of SYNC
+	always @(posedge clk)
+		ll_start <= state == ST_SYNC;
+
+	// Bit
+	assign ll_bit  = shift_data[0];
+	assign ll_last = shift_last_bit & shift_last_byte;
+
+
+	// Packet interface feedback
+	// -------------------------
+
+	// We don't care about the delay, better register
+	always @(posedge clk)
+	begin
+		pkt_done <= ll_ack && ll_last;
+		pkt_data_ack <= (state == ST_DATA) && next;
+	end
+
+endmodule // usb_tx_pkt

+ 84 - 0
cores/usb/sim/usb_ep_buf_tb.v

@@ -0,0 +1,84 @@
+/*
+ * usb_ep_buf_tb.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * 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.
+ */
+
+`default_nettype none
+`timescale 1ns/100ps
+
+module usb_ep_buf_tb;
+
+	localparam integer RWIDTH = 16;	// 8/16/32
+	localparam integer WWIDTH = 64;	// 8/16/32
+
+	localparam integer ARW = 11 - $clog2(RWIDTH / 8);
+	localparam integer AWW = 11 - $clog2(WWIDTH / 8);
+
+	// Signals
+	reg rst = 1;
+	reg clk  = 0;
+
+	wire [ARW-1:0] rd_addr_0;
+	wire [RWIDTH-1:0] rd_data_1;
+	wire rd_en_0;
+	wire [AWW-1:0] wr_addr_0;
+	wire [WWIDTH-1:0] wr_data_0;
+	wire wr_en_0;
+
+	// Setup recording
+	initial begin
+		$dumpfile("usb_ep_buf_tb.vcd");
+		$dumpvars(0,usb_ep_buf_tb);
+	end
+
+	// Reset pulse
+	initial begin
+		# 200 rst = 0;
+		# 1000000 $finish;
+	end
+
+	// Clocks
+	always #10 clk  = !clk;
+
+	// DUT
+	usb_ep_buf #(
+		.RWIDTH(RWIDTH),
+		.WWIDTH(WWIDTH)
+	) dut_I (
+		.rd_addr_0(rd_addr_0),
+		.rd_data_1(rd_data_1),
+		.rd_en_0(rd_en_0),
+		.rd_clk(clk),
+		.wr_addr_0(wr_addr_0),
+		.wr_data_0(wr_data_0),
+		.wr_en_0(wr_en_0),
+		.wr_clk(clk)
+	);
+
+	assign rd_en_0 = 1'b1;
+	assign wr_en_0 = 1'b1;
+	assign rd_addr_0 = 3;
+	assign wr_addr_0 = 0;
+	assign wr_data_0 = 64'hab89127bbabecafe;
+
+endmodule // usb_ep_buf_tb

+ 147 - 0
cores/usb/sim/usb_tb.v

@@ -0,0 +1,147 @@
+/*
+ * usb_tb.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * 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.
+ */
+
+`default_nettype none
+`timescale 1ns/100ps
+
+module usb_tb;
+
+	// Signals
+	reg rst = 1;
+	reg clk_48m  = 0;	// USB clock
+	reg clk_samp = 0;	// Capture samplerate
+
+	reg  [7:0] in_file_data;
+	reg  in_file_valid;
+	reg  in_file_done;
+
+	wire usb_dp;
+	wire usb_dn;
+	wire usb_pu;
+
+	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;
+
+	wire [15:0] bus_addr;
+	wire [15:0] bus_din;
+	wire [15:0] bus_dout;
+	wire bus_cyc;
+	wire bus_we;
+	wire bus_ack;
+
+	// Setup recording
+	initial begin
+		$dumpfile("usb_tb.vcd");
+		$dumpvars(0,usb_tb);
+	end
+
+	// Reset pulse
+	initial begin
+		# 200 rst = 0;
+		# 1000000 $finish;
+	end
+
+	// Clocks
+	always #10.416 clk_48m  = !clk_48m;
+	always #3.247  clk_samp = !clk_samp;
+
+	// DUT
+	usb #(
+		.TARGET("ICE40"),
+		.EPDW(32)
+	) dut_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_48m),
+		.bus_addr(bus_addr),
+		.bus_din(bus_din),
+		.bus_dout(bus_dout),
+		.bus_cyc(bus_cyc),
+		.bus_we(bus_we),
+		.bus_ack(bus_ack),
+		.clk(clk_48m),
+		.rst(rst)
+	);
+
+	reg [7:0] cnt;
+
+	always @(posedge clk_48m)
+		if (bus_ack)
+			cnt <= 0;
+		else if (~cnt[7])
+			cnt <= cnt + 1;
+
+	assign bus_addr = 16'h3000;
+	assign bus_din = 16'h8001;
+	assign bus_cyc = cnt[7];
+	assign bus_we = 1'b1;
+
+	assign ep_rx_addr_0 = 9'h000;
+	assign ep_rx_re_0 = 1'b1;
+	assign ep_tx_addr_0 = 9'h000;
+	assign ep_tx_data_0 = 32'h02000112;
+	assign ep_tx_we_0 = 1'b1;
+
+	// Read file
+	integer fh_in, rv;
+
+	initial
+		fh_in = $fopen("../data/capture_usb_raw_short.bin", "rb");
+
+	always @(posedge clk_samp)
+	begin
+		if (rst) begin
+			in_file_data  <= 8'h00;
+			in_file_valid <= 1'b0;
+			in_file_done  <= 1'b0;
+		end else begin
+			if (!in_file_done) begin
+				rv = $fread(in_file_data, fh_in);
+				in_file_valid <= (rv == 1);
+				in_file_done  <= (rv != 1);
+			end else begin
+				in_file_data  <= 8'h00;
+				in_file_valid <= 1'b0;
+				in_file_done  <= 1'b1;
+			end
+		end
+	end
+
+	// Input
+	assign usb_dp = in_file_data[1] & in_file_valid;
+	assign usb_dn = in_file_data[0] & in_file_valid;
+
+endmodule // usb_tb

+ 152 - 0
cores/usb/sim/usb_tx_tb.v

@@ -0,0 +1,152 @@
+/*
+ * usb_tx_tb.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * 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.
+ */
+
+`default_nettype none
+`timescale 1ns/100ps
+
+module usb_tx_tb;
+
+	// Signals
+	reg rst = 1;
+	reg clk_48m  = 0;	// USB clock
+
+	wire phy_tx_dp;
+	wire phy_tx_dn;
+	wire phy_tx_en;
+
+	wire ll_start;
+	wire ll_bit;
+	wire ll_last;
+	wire ll_ack;
+
+	wire pkt_start;
+	wire pkt_done;
+	wire [3:0] pkt_pid;
+	wire [9:0] pkt_len;
+	reg  [7:0] pkt_data;
+	wire pkt_data_ack;
+
+	// Setup recording
+	initial begin
+		$dumpfile("usb_tx_tb.vcd");
+		$dumpvars(0,usb_tx_tb);
+	end
+
+	// Reset pulse
+	initial begin
+		# 200 rst = 0;
+		# 1000000 $finish;
+	end
+
+	// Clocks
+	always #10.416 clk_48m  = !clk_48m;
+
+	// DUT
+	usb_tx_ll tx_ll_I (
+		.phy_tx_dp(phy_tx_dp),
+		.phy_tx_dn(phy_tx_dn),
+		.phy_tx_en(phy_tx_en),
+		.ll_start(ll_start),
+		.ll_bit(ll_bit),
+		.ll_last(ll_last),
+		.ll_ack(ll_ack),
+		.clk(clk_48m),
+		.rst(rst)
+	);
+
+	usb_tx_pkt tx_pkt_I (
+		.ll_start(ll_start),
+		.ll_bit(ll_bit),
+		.ll_last(ll_last),
+		.ll_ack(ll_ack),
+		.pkt_start(pkt_start),
+		.pkt_done(pkt_done),
+		.pkt_pid(pkt_pid),
+		.pkt_len(pkt_len),
+		.pkt_data(pkt_data),
+		.pkt_data_ack(pkt_data_ack),
+		.clk(clk_48m),
+		.rst(rst)
+	);
+
+	// Start signal
+	reg [7:0] cnt;
+	reg ready;
+
+	always @(posedge clk_48m)
+		if (rst)
+			ready <= 1'b1;
+		else
+			if (pkt_start)
+				ready <= 1'b0;
+			else if (pkt_done)
+				ready <= 1'b1;
+
+	always @(posedge clk_48m)
+		if (rst)
+			cnt <= 0;
+		else
+			cnt <= cnt + 1;
+
+	assign pkt_start = (cnt == 8'hff) & ready;
+
+	// Packet
+	assign pkt_len = 10'h000;	// 16 bytes payload
+//	assign pkt_pid = 4'b0011;	// DATA0
+	assign pkt_pid = 4'b0010;	// ACK
+
+	// Fake data source
+	always @(posedge clk_48m)
+		if (rst)
+			pkt_data <= 8'h5a;
+		else
+			pkt_data <= pkt_data + pkt_data_ack;
+
+`ifdef NO_PKT
+	wire [31:0] bit_seq = 32'b00000001_10100101_11111111_11100000;
+	reg [7:0] cnt;
+	reg started;
+
+	always @(posedge clk_48m)
+		if (rst)
+			cnt <= 0;
+		else if (ll_ack | ~started)
+			cnt <= cnt + 1;
+
+	always @(posedge clk_48m)
+		if (rst)
+			started <= 1'b0;
+		else
+			if (ll_start)
+				started <= 1'b1;
+			else if (ll_last & ll_ack)
+				started <= 1'b0;
+
+	assign ll_start = (cnt == 8'h1f);
+	assign ll_bit   = bit_seq[31 - cnt[4:0]];
+	assign ll_last  = cnt[4:0] == 31;
+`endif
+
+endmodule // usb_tx_tb

+ 425 - 0
cores/usb/utils/microcode.py

@@ -0,0 +1,425 @@
+#!/usr/bin/env python3
+
+import types
+
+
+#
+# OpCodes
+#
+
+def NOP():
+	return 0x0000
+
+def LD(src):
+	srcs = {
+		'evt': 0,
+		'pkt_pid': 2,
+		'pkt_pid_chk': 3,
+		'ep_type': 4,
+		'bd_state': 6,
+	}
+	return 0x1000 | srcs[src]
+
+def EP(bd_state=None, bdi_flip=False, dt_flip=False, wb=False):
+	return 0x2000 | \
+		((1 << 0) if dt_flip else 0) | \
+		((1 << 1) if bdi_flip else 0) | \
+		(((bd_state << 3) | (1 << 2)) if bd_state is not None else 0) | \
+		((1 << 7) if wb else 0)
+
+def ZL():
+	return 0x3000
+
+def TX(pid, set_dt=False):
+	return 0x4000 | pid | ((1 << 4) if set_dt else 0)
+
+def NOTIFY(code):
+	return 0x5000 | code
+
+def EVT_CLR(evts):
+	return 0x6000 | evts
+
+def EVT_RTO(timeout):
+	return 0x7000 | timeout
+
+def JMP(tgt, cond_val=None, cond_mask=0xf, cond_invert=False):
+	if isinstance(tgt, str):
+		return lambda resolve: JMP(resolve(tgt), cond_val, cond_mask, cond_invert)
+	assert tgt & 3 == 0
+	return (
+		(1 << 15) |
+		(tgt << 6) |
+		(0 if (cond_val is None) else ((cond_mask << 4) | cond_val)) |
+		((1<<14) if cond_invert else 0)
+	)
+
+def JEQ(tgt, cond_val=None, cond_mask=0xf):
+	return JMP(tgt, cond_val, cond_mask)
+
+def JNE(tgt, cond_val=None, cond_mask=0xf):
+	return JMP(tgt, cond_val, cond_mask, cond_invert=True)
+
+def L(label):
+	return label
+
+
+#
+# "Assembler"
+#
+
+def assemble(code):
+	flat_code = []
+	labels    = {}
+	for elem in code:
+		if isinstance(elem, str):
+			assert elem not in labels
+			while len(flat_code) & 3:
+				flat_code.append(JMP(elem))
+			labels[elem] = len(flat_code)
+		else:
+			flat_code.append(elem)
+	for offset, elem in enumerate(flat_code):
+		if isinstance(elem, types.LambdaType):
+			flat_code[offset] = elem(lambda label: labels[label])
+	return flat_code, labels
+
+
+#
+# Constants
+#
+
+EVT_ALL     = 0xf
+EVT_RX_OK   = (1 << 0)
+EVT_RX_ERR  = (1 << 1)
+EVT_TX_DONE = (1 << 2)
+EVT_TIMEOUT = (1 << 3)
+
+PID_OUT   = 0b0001
+PID_IN    = 0b1001
+PID_SETUP = 0b1101
+PID_DATA0 = 0b0011
+PID_DATA1 = 0b1011
+PID_ACK   = 0b0010
+PID_NAK   = 0b1010
+PID_STALL = 0b1110
+
+PID_DATA_MSK = 0b0111
+PID_DATA_VAL = 0b0011
+
+EP_TYPE_NONE  = 0b000
+EP_TYPE_ISOC  = 0b001
+EP_TYPE_INT   = 0b010
+EP_TYPE_BULK  = 0b100
+EP_TYPE_CTRL  = 0b110
+
+EP_TYPE_MSK   = 0b110
+EP_TYPE_HALT  = 0b001
+
+BD_NONE      = 0b000
+BD_RDY_DATA  = 0b010
+BD_RDY_STALL = 0b011
+BD_RDY_MSK   = 0b110
+BD_RDY_VAL   = 0b010
+BD_DONE_OK   = 0b100
+BD_DONE_ERR  = 0b101
+
+NOTIFY_SUCCESS = 0x00
+NOTIFY_TX_FAIL = 0x08
+NOTIFY_RX_FAIL = 0x09
+
+TIMEOUT = 70	# Default timeout value for waiting for a packet from the host
+
+
+#
+# Microcode
+#
+
+
+mc = [
+	# Main loop
+	# ---------
+
+	L('IDLE'),
+		# Wait for an event we care about
+		LD('evt'),
+		JEQ('IDLE', 0),
+		EVT_CLR(EVT_ALL),
+		JEQ('IDLE', 0, EVT_RX_OK),
+
+		# Dispatch do handler
+		LD('pkt_pid'),
+		JEQ('DO_IN', PID_IN),
+		JEQ('DO_OUT', PID_OUT),
+		JEQ('DO_SETUP', PID_SETUP),
+		JMP('IDLE'),						# invalid PID / not token, ignore packet
+
+
+	# IN Transactions
+	# ---------------
+
+	L('DO_IN'),
+		# Check endpoint type
+		LD('ep_type'),
+		JMP('DO_IN_ISOC', EP_TYPE_ISOC),	# isochronous is special
+		JMP('IDLE', EP_TYPE_NONE),			# endpoint doesn't exist, ignore packet
+
+
+		# Bulk/Control/Interrupt
+		# - - - - - - - - - - - -
+
+		# Is EP halted ?
+		JEQ('TX_STALL_HALT', EP_TYPE_HALT, EP_TYPE_HALT),
+
+		# Anything valid in the active BD ?
+		LD('bd_state'),
+		JEQ('TX_STALL_BD', BD_RDY_STALL),
+		JNE('TX_NAK', BD_RDY_DATA),
+
+		# TX packet from BD
+		TX(PID_DATA0, set_dt=True),
+
+		# Wait for TX to complete
+	L('_DO_IN_BCI_WAIT_TX'),
+		LD('evt'),
+		JEQ('_DO_IN_BCI_WAIT_TX', 0, EVT_TX_DONE),
+		EVT_CLR(EVT_TX_DONE),
+
+		# Wait for ACK
+		EVT_RTO(TIMEOUT),
+
+	L('_DO_IN_BCI_WAIT_ACK'),
+		LD('evt'),
+		JEQ('_DO_IN_BCI_WAIT_ACK', 0, EVT_TIMEOUT | EVT_RX_ERR | EVT_RX_OK),
+
+		# If it's not a good packet and a ACK, we failed
+		JEQ('_DO_IN_BCI_WAIT_ACK', 0, EVT_RX_OK),
+		LD('pkt_pid'),
+		JNE('_DO_IN_BCI_WAIT_ACK', PID_ACK),
+
+		# Success !
+		EP(bd_state=BD_DONE_OK, bdi_flip=True, dt_flip=True, wb=True),
+		NOTIFY(NOTIFY_SUCCESS),
+		JMP('IDLE'),
+
+		# TX Fail handler, notify the host
+	L('_DO_IN_BCI_FAIL'),
+		NOTIFY(NOTIFY_TX_FAIL),
+		JMP('IDLE'),
+
+
+		# Isochronous
+		# - - - - - -
+
+	L('DO_IN_ISOC'),
+		# Anything to TX ?
+		LD('bd_state'),
+		JNE('_DO_IN_ISOC_NO_DATA', BD_RDY_DATA),
+
+		# Transmit packet (with DATA0, always)
+		TX(PID_DATA0),
+
+		# "Assume" success
+		EP(bd_state=BD_DONE_OK, bdi_flip=True, dt_flip=False, wb=True),
+		NOTIFY(NOTIFY_SUCCESS),
+		JMP('IDLE'),
+
+		# Transmit empty packet
+	L('_DO_IN_ISOC_NO_DATA'),
+		ZL(),
+		TX(PID_DATA0),
+		JMP('IDLE'),
+
+
+	# SETUP Transactions
+	# ------------------
+
+	L('DO_SETUP'),
+		# Check the endpoint is 'control'
+		LD('ep_type'),
+		JNE('IDLE', EP_TYPE_CTRL, EP_TYPE_MSK),
+
+		# For Setup, if no-space, don't NAK, just ignore
+		LD('bd_state'),
+		JNE('RX_DISCARD_NEXT', BD_RDY_DATA),
+
+		# Wait for packet
+		EVT_RTO(TIMEOUT),
+
+	L('_DO_SETUP_WAIT_DATA'),
+		LD('evt'),
+		JEQ('_DO_SETUP_WAIT_DATA', 0, EVT_TIMEOUT | EVT_RX_ERR | EVT_RX_OK),
+
+		# Did it work ?
+		JEQ('_DO_SETUP_FAIL', 0, EVT_RX_OK),
+		LD('pkt_pid'),
+		JNE('_DO_SETUP_FAIL', PID_DATA0),
+
+		# Success !
+		EP(bd_state=BD_DONE_OK, bdi_flip=True, dt_flip=True, wb=True),
+		NOTIFY(NOTIFY_SUCCESS),
+		JMP('TX_ACK'),
+
+		# Setup RX handler
+	L('_DO_SETUP_FAIL'),
+		EP(bd_state=BD_DONE_ERR, bdi_flip=True, dt_flip=False, wb=True),
+		NOTIFY(NOTIFY_RX_FAIL),
+		JMP('IDLE'),
+
+
+	# OUT Transactions
+	# ----------------
+
+	L('DO_OUT'),
+		# Check endpoint type
+		LD('ep_type'),
+		JEQ('DO_OUT_ISOC', EP_TYPE_ISOC),	# isochronous is special
+		JEQ('IDLE', EP_TYPE_NONE),			# endpoint doesn't exist, ignore packet
+
+
+		# Bulk/Control/Interrupt
+		# - - - - - - - - - - - -
+
+		# If EP is halted, we drop the packet and respond with STALL
+		JEQ('_DO_OUT_BCI_DROP_DATA', EP_TYPE_HALT, EP_TYPE_HALT),
+
+		# Check we have space, if not prevent data writes
+		LD('bd_state'),
+		JEQ('_DO_OUT_BCI_DROP_DATA', BD_RDY_DATA),
+
+		# Wait for packet
+		EVT_RTO(TIMEOUT),
+
+	L('_DO_OUT_BCI_WAIT_DATA'),
+		LD('evt'),
+		JEQ('_DO_OUT_BCI_WAIT_DATA', 0, EVT_TIMEOUT | EVT_RX_ERR | EVT_RX_OK),
+
+		# We got a packet (and possibly stored the data), now we need to respond !
+			# Not a valid packet at all, or timeout, or not DATAx -> No response
+		JEQ('_DO_OUT_BCI_FAIL', 0, EVT_RX_OK),
+		LD('pkt_pid_chk'),
+		JNE('_DO_OUT_BCI_FAIL', PID_DATA_VAL, PID_DATA_MSK),	# Accept DATA0/DATA1 only
+
+			# If EP is halted, TX STALL
+		LD('ep_type'),
+		JEQ('TX_STALL_HALT', EP_TYPE_HALT, EP_TYPE_HALT),
+
+			# Wrong Data Toggle -> Ignore new data, just re-tx a ACK
+		LD('pkt_pid_chk'),
+		JEQ('TX_ACK', PID_DATA1),								# With pid_chk, DATA1 means wrong DT
+
+			# We didn't have space -> NAK
+		LD('bd_state'),
+		JNE('TX_NAK', BD_RDY_VAL, BD_RDY_MSK),
+
+			# Explicitely asked for stall ?
+		JEQ('TX_STALL_BD', BD_RDY_STALL),
+
+		# We're all good !
+		EP(bd_state=BD_DONE_OK, bdi_flip=True, dt_flip=True, wb=True),
+		NOTIFY(NOTIFY_SUCCESS),
+		JMP('TX_ACK'),
+
+		# Fail handler: Prepare to drop data
+	L('_DO_OUT_BCI_DROP_DATA'),
+		ZL(),
+		JMP('_DO_OUT_BCI_WAIT_DATA'),
+
+		# Fail hander: Packet reception failed
+	L('_DO_OUT_BCI_FAIL'),
+			# Check we actually had a BD at all
+		LD('bd_state'),
+		JNE('IDLE', BD_RDY_VAL, BD_RDY_MSK),
+
+			# We had a BD, so report the error
+		EP(bd_state=BD_DONE_ERR, bdi_flip=True, dt_flip=False, wb=True),
+		NOTIFY(NOTIFY_RX_FAIL),
+		JMP('IDLE'),
+
+
+		# Isochronous
+		# - - - - - -
+
+	L('DO_OUT_ISOC'),
+		# Do we have space to RX ?
+		LD('bd_state'),
+		JNE('_DO_OUT_ISOC_NO_SPACE', BD_RDY_DATA),
+
+		# Wait for packet RX
+		EVT_RTO(TIMEOUT),
+
+	L('_DO_OUT_ISOC_WAIT_DATA'),
+		LD('evt'),
+		JEQ('_DO_OUT_ISOC_WAIT_DATA', 0, EVT_TIMEOUT | EVT_RX_ERR | EVT_RX_OK),
+
+		# Did it work ?
+		JEQ('_DO_OUT_ISOC_FAIL', 0, EVT_RX_OK),
+		LD('pkt_pid'),
+		JNE('_DO_OUT_ISOC_FAIL', PID_DATA_VAL, PID_DATA_MSK),	# Accept DATA0/DATA1
+
+		# Success !
+		EP(bd_state=BD_DONE_OK, bdi_flip=True, dt_flip=False, wb=True),
+		NOTIFY(NOTIFY_SUCCESS),
+		JMP('IDLE'),
+
+		# RX fail handler, mark error in the BD, notify host
+	L('_DO_OUT_ISOC_FAIL'),
+		EP(bd_state=BD_DONE_ERR, bdi_flip=True, dt_flip=False, wb=True),
+		NOTIFY(NOTIFY_RX_FAIL),
+		JMP('IDLE'),
+
+		# RX no-space handler, just discard packet :(
+	L('_DO_OUT_ISOC_NO_SPACE'),
+		# Notify host ?
+		# Discard
+		JMP('RX_DISCARD_NEXT'),
+
+
+	# Common shared utility
+	# ---------------------
+
+	# Transmit STALL as asked in a Buffer Descriptor
+	L('TX_STALL_BD'),
+		EP(bd_state=BD_DONE_OK, bdi_flip=True, dt_flip=False, wb=True),
+		NOTIFY(NOTIFY_SUCCESS),
+		# fall-thru
+
+	# Transmit STALL because of halted End Point
+	L('TX_STALL_HALT'),
+		ZL(),
+		TX(PID_STALL),
+		JMP('IDLE'),
+
+	# Transmit NAK handshake
+	L('TX_NAK'),
+		ZL(),
+		TX(PID_NAK),
+		JMP('IDLE'),
+
+	# Transmit ACK handshake
+	L('TX_ACK'),
+		ZL(),
+		TX(PID_ACK),
+		JMP('IDLE'),
+
+	# Discard the next packet (if any)
+	L('RX_DISCARD_NEXT'),
+		# Zero-length to prevent store of data
+		ZL(),
+
+		# Wait for a packet
+		EVT_RTO(TIMEOUT),
+
+	L('_RX_DISCARD_WAIT'),
+		LD('evt'),
+		JEQ('_RX_DISCARD_WAIT', 0, EVT_TIMEOUT | EVT_RX_ERR | EVT_RX_OK),
+
+		# Done
+		JMP('IDLE'),
+]
+
+
+if __name__ == '__main__':
+	code, labels = assemble(mc)
+	ilabel = dict([(v,k) for k,v in labels.items()])
+	for i, v in enumerate(code):
+		print("%04x" % (v,))