diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7f9d705 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +CORE := usb + +include ../../build/core-rules.mk diff --git a/core.mk b/core.mk new file mode 100644 index 0000000..1782326 --- /dev/null +++ b/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 diff --git a/data/capture_usb_raw_short.bin b/data/capture_usb_raw_short.bin new file mode 100644 index 0000000..f188272 --- /dev/null +++ b/data/capture_usb_raw_short.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/usb_ep_status.hex b/data/usb_ep_status.hex new file mode 100644 index 0000000..a36e9e4 --- /dev/null +++ b/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 diff --git a/doc/mem-map.md b/doc/mem-map.md new file mode 100644 index 0000000..2b9b2dd --- /dev/null +++ b/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 | +'--------------------------------------------------------------' +``` diff --git a/rtl/usb.v b/rtl/usb.v new file mode 100644 index 0000000..02f6c2b --- /dev/null +++ b/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 diff --git a/rtl/usb_crc.v b/rtl/usb_crc.v new file mode 100644 index 0000000..9df5f5e --- /dev/null +++ b/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; i2 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 diff --git a/rtl/usb_rx_pkt.v b/rtl/usb_rx_pkt.v new file mode 100644 index 0000000..ba49a6d --- /dev/null +++ b/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 diff --git a/rtl/usb_trans.v b/rtl/usb_trans.v new file mode 100644 index 0000000..83a2d70 --- /dev/null +++ b/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 diff --git a/rtl/usb_tx_ll.v b/rtl/usb_tx_ll.v new file mode 100644 index 0000000..6232bf6 --- /dev/null +++ b/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 diff --git a/rtl/usb_tx_pkt.v b/rtl/usb_tx_pkt.v new file mode 100644 index 0000000..497ea72 --- /dev/null +++ b/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 diff --git a/sim/usb_ep_buf_tb.v b/sim/usb_ep_buf_tb.v new file mode 100644 index 0000000..46972e0 --- /dev/null +++ b/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 diff --git a/sim/usb_tb.v b/sim/usb_tb.v new file mode 100644 index 0000000..1dc1390 --- /dev/null +++ b/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 diff --git a/sim/usb_tx_tb.v b/sim/usb_tx_tb.v new file mode 100644 index 0000000..a3d3916 --- /dev/null +++ b/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 diff --git a/utils/microcode.py b/utils/microcode.py new file mode 100755 index 0000000..cbf6912 --- /dev/null +++ b/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,))