diff --git a/usb_host/README.md b/usb_host/README.md new file mode 100644 index 0000000..c90e593 --- /dev/null +++ b/usb_host/README.md @@ -0,0 +1,106 @@ +### USB 1.1 Host Controller + +This IP core is a cutdown USB host controller which allows communications with full-speed (12mbps) USB devices. + +The IP is accessed via a Wishbone slave interface (asynchronous read result) for control, status and data. + +Data to be sent or received is stored in some internal FIFOs (which are configurable in size). The data is accessed through the Wishbone slave port. There is no DMA engine (e.g. a bus mastering interface) associated with this IP. + +The core functions well, is very small, but is fairly inefficient in terms of CPU cycles required to perform USB transfers. +This core is not compliant with any standard USB host interface specification, e.g OHCI or EHCI. + +##### Instantiation +Instance usbh and hookup to UTMI PHY interface and a Wishbone master (e.g. from your CPU). +The core requires a 48MHz clock input. + +##### Testing + +Verified under simulation and on FPGA with various USB devices attached (hubs, mass storage, network devices). + +##### Configuration +* TX_FIFO_DEPTH - Transmit FIFO size +* TX_FIFO_ADDR_W - Transmit FIFO size (width of size field) +* RX_FIFO_DEPTH - Receive FIFO size +* RX_FIFO_ADDR_W - Receive FIFO size (width of size field) + +##### Size / Performance + +With the default configuration... + +* the design contains 214 flops, 2 RAM cells (RX and TX FIFOs) +* synthesizes to more than the required 48MHz on a Xilinx Spartan 6 LX9 (speed -3) + +##### Register Map + +| Offset | Name | Description | +| ------ | ---- | -------------------------------------------------------------- | +| 0x00 | USB_CTRL | [Write] Control of USB reset, SOF and Tx FIFO flush | +| 0x00 | USB_STATUS | [Read] line state, Rx error status and frame time | +| 0x04 | USB_IRQ_ACK | [Write] Acknowledge IRQ by setting relevant bit | +| 0x04 | USB_IRQ_STS | [Read] Interrupt status | +| 0x08 | USB_IRQ_MASK | [Read/Write] Interrupt mask | +| 0x0c | USB_XFER_DATA | [Write] Tx payload transfer length | +| 0x10 | USB_XFER_TOKEN | [Write] Transfer control info (direction, type) | +| 0x14 | USB_RX_STAT | [Read] Transfer status (Rx length, error, idle) | +| 0x18 | USB_WR_DATA | [Write] Tx FIFO address for write data | +| 0x18 | USB_RD_DATA | [Read] Tx FIFO address for read data | + + +##### Register: USB_CTRL + +| Bits | Name | Description | +| ---- | ---- | -------------------------------------------------------------- | +| 2 | USB_TX_FLUSH | Flush Tx FIFO | +| 1 | USB_ENABLE_SOF | Enable SOF (start of frame) packet generation | +| 0 | USB_RESET_ACTIVE | 1 = assert USB reset state, 0 = normal | + +##### Register: USB_STATUS + +| Bits | Name | Description | +| ----- | ---- | -------------------------------------------------------------- | +| 31:16 | USB_STAT_SOF_TIME | Current frame time (0 - 47999) | +| 2 | USB_STAT_RX_ERROR | Rx error detected (UTMI). Clear on new xfer.| +| 1:0 | USB_STAT_LINESTATE_BITS | Line state (1 = D-, 1 = D+) | + +##### Register: USB_IRQ_ACK / USB_IRQ_STS / USB_IRQ_MASK + +| Bits | Name | Description | +| ---- | ---- | --------------------------------------------------------- | +| 2 | USB_IRQ_ERR | Interrupt on error conditions. | +| 1 | USB_IRQ_DONE | Interrupt on transfer completion | +| 0 | USB_IRQ_SOF | Interrupt on start of frame | + +##### Register: USB_XFER_DATA + +| Bits | Name | Description | +| ----- | ---- | -------------------------------------------------------------- | +| 15:0 | USB_XFER_DATA_TX_LEN | Tx transfer data length | + +##### Register: USB_XFER_TOKEN + +| Bits | Name | Description | +| ----- | ---- | -------------------------------------------------------------- | +| 31 | USB_XFER_START | Transfer start request | +| 30 | USB_XFER_IN | IN transfer (1) or OUT transfer (0) | +| 29 | USB_XFER_ACK | Send ACK in response to IN data | +| 28 | USB_XFER_PID_DATAX | DATA1 (1) or DATA0 (0) | +| 23:16 | USB_XFER_PID_BITS | Token PID (SETUP=0x2d, OUT=0xE1 or IN=0x69) | +| 15:9 | USB_XFER_DEV_ADDR | Device address | +| 8:5 | USB_XFER_EP_ADDR | Endpoint address | + +##### Register: USB_RX_STAT + +| Bits | Name | Description | +| ----- | ---- | -------------------------------------------------------------- | +| 31 | USB_RX_STAT_START_PEND | Transfer start pending | +| 30 | USB_RX_STAT_CRC_ERR | CRC error detected | +| 29 | USB_RX_STAT_RESP_TIMEOUT | Response timeout detected (no response) | +| 28 | USB_RX_STAT_IDLE | SIE idle | +| 23:16 | USB_RX_STAT_RESP_BITS | Received response PID | +| 15:0 | USB_RX_STAT_COUNT_BITS | Received data count | + +##### Register: USB_WR_DATA / USB_RD_DATA + +| Bits | Name | Description | +| ----- | ---- | -------------------------------------------------------------- | +| 7:0 | DATA | Data byte | diff --git a/usb_host/rtl/usbh.v b/usb_host/rtl/usbh.v new file mode 100644 index 0000000..728163d --- /dev/null +++ b/usb_host/rtl/usbh.v @@ -0,0 +1,420 @@ +//----------------------------------------------------------------- +// USB Full Speed Host +// V0.1 +// Ultra-Embedded.com +// Copyright 2015 +// +// Email: admin@ultra-embedded.com +// +// License: GPL +// If you would like a version with a more permissive license for +// use in closed source commercial applications please contact me +// for details. +//----------------------------------------------------------------- +// +// This file is open source HDL; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This file 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public +// License along with this file; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +// USA +//----------------------------------------------------------------- + +//----------------------------------------------------------------- +// Module: Basic USB host interface +//----------------------------------------------------------------- +module usbh + +//----------------------------------------------------------------- +// Params +//----------------------------------------------------------------- +#( + parameter TX_FIFO_DEPTH = 64, + parameter TX_FIFO_ADDR_W = 6, + parameter RX_FIFO_DEPTH = 64, + parameter RX_FIFO_ADDR_W = 6 +) + +//----------------------------------------------------------------- +// Ports +//----------------------------------------------------------------- +( + // Clocking (48MHz) & Reset + input clk_i, + input rst_i, + + // Interrupt output + output intr_o, + + // Peripheral Interface (from CPU) + input [7:0] addr_i, + input [31:0] data_i, + output [31:0] data_o, + input we_i, + input stb_i, + + // UTMI Interface + output [7:0] utmi_data_o, + output utmi_txvalid_o, + input utmi_txready_i, + input [7:0] utmi_data_i, + input utmi_rxvalid_i, + input utmi_rxactive_i, + input utmi_rxerror_i, + input [1:0] utmi_linestate_i, + + output usb_rst_o +); + +//----------------------------------------------------------------- +// Registers / Wires +//----------------------------------------------------------------- +// SOF +reg [10:0] sof_value_q; +reg [15:0] sof_time_q; +reg sof_irq_q; + +reg transfer_req_ack_q; + +// Requests for transfers +wire ctrl_sof_en_w; +wire ctrl_in_transfer_w; +wire ctrl_resp_expected_w; +wire ctrl_data_idx_w; +wire [15:0] ctrl_tx_count_w; +wire [7:0] ctrl_token_pid_w; +wire [10:0] ctrl_token_data_w; + +wire [7:0] fifo_tx_data_w; +wire fifo_tx_pop_w; + +wire [7:0] fifo_rx_data_w; +wire fifo_rx_push_w; + +wire [7:0] ctrl_fifo_tx_data_w; +wire ctrl_fifo_tx_push_w; +wire ctrl_fifo_tx_flush_w; + +wire [7:0] ctrl_fifo_rx_data_w; +wire ctrl_fifo_rx_pop_w; + +wire ctrl_start_w; + +reg fifo_flush_q; + +wire [7:0] token_pid_w; +wire [6:0] token_dev_w; +wire [3:0] token_ep_w; + +reg transfer_start_q; +reg in_transfer_q; +reg sof_transfer_q; +reg resp_expected_q; +wire transfer_ack_w; + +wire status_crc_err_w; +wire status_timeout_w; +wire [7:0] status_response_w; +wire [15:0] status_rx_count_w; +wire status_sie_idle_w; +wire status_tx_done_w; +wire status_rx_done_w; + +wire send_sof_w; +wire sof_gaurd_band_w; +wire clear_to_send_w; + +//----------------------------------------------------------------- +// Definitions +//----------------------------------------------------------------- +localparam [15:0] SOF_ZERO = 0; +localparam [15:0] SOF_INC = 1; +localparam [15:0] SOF_THRESHOLD = 48000; + +localparam [15:0] EOF1_THRESHOLD = (50 * 4); // EOF1 + some margin +localparam [15:0] MAX_XFER_SIZE = (TX_FIFO_DEPTH > RX_FIFO_DEPTH) ? TX_FIFO_DEPTH : RX_FIFO_DEPTH; +localparam [15:0] MAX_XFER_PERIOD = ((MAX_XFER_SIZE + 6) * 10 * 4); // Max packet transfer time (+ margin) +localparam [15:0] SOF_GAURD_LOW = (20 * 4); +localparam [15:0] SOF_GAURD_HIGH = SOF_THRESHOLD - EOF1_THRESHOLD - MAX_XFER_PERIOD; + +localparam PID_OUT = 8'hE1; +localparam PID_IN = 8'h69; +localparam PID_SOF = 8'hA5; +localparam PID_SETUP = 8'h2D; + +localparam PID_DATA0 = 8'hC3; +localparam PID_DATA1 = 8'h4B; + +localparam PID_ACK = 8'hD2; +localparam PID_NAK = 8'h5A; +localparam PID_STALL = 8'h1E; + +//----------------------------------------------------------------- +// SIE +//----------------------------------------------------------------- +usbh_sie +u_sie +( + // Clock (48MHz) & reset + .clk_i(clk_i), + .rst_i(rst_i), + + // Control + .start_i(transfer_start_q), + .in_transfer_i(in_transfer_q), + .sof_transfer_i(sof_transfer_q), + .resp_expected_i(resp_expected_q), + .ack_o(transfer_ack_w), + + // Token packet + .token_pid_i(token_pid_w), + .token_dev_i(token_dev_w), + .token_ep_i(token_ep_w), + + // Data packet + .data_len_i(ctrl_tx_count_w), + .data_idx_i(ctrl_data_idx_w), + + // Tx Data FIFO + .tx_data_i(fifo_tx_data_w), + .tx_pop_o(fifo_tx_pop_w), + + // Rx Data FIFO + .rx_data_o(fifo_rx_data_w), + .rx_push_o(fifo_rx_push_w), + + // Status + .rx_done_o(status_rx_done_w), + .tx_done_o(status_tx_done_w), + .crc_err_o(status_crc_err_w), + .timeout_o(status_timeout_w), + .response_o(status_response_w), + .rx_count_o(status_rx_count_w), + .idle_o(status_sie_idle_w), + + // UTMI Interface + .utmi_data_o(utmi_data_o), + .utmi_txvalid_o(utmi_txvalid_o), + .utmi_txready_i(utmi_txready_i), + .utmi_data_i(utmi_data_i), + .utmi_rxvalid_i(utmi_rxvalid_i), + .utmi_rxactive_i(utmi_rxactive_i) +); + +//----------------------------------------------------------------- +// Peripheral Interface +//----------------------------------------------------------------- +usbh_periph +u_pif +( + .clk_i(clk_i), + .rst_i(rst_i), + + .intr_o(intr_o), + + // Peripheral Interface (from CPU) + .addr_i(addr_i), + .data_i(data_i), + .data_o(data_o), + .we_i(we_i), + .stb_i(stb_i), + + // UTMI interface + .utmi_linestate_i(utmi_linestate_i), + .utmi_rxerror_i(utmi_rxerror_i), + + // Control + .sie_start_o(ctrl_start_w), + .sie_sof_en_o(ctrl_sof_en_w), + .sie_rst_o(usb_rst_o), + .sie_token_pid_o(ctrl_token_pid_w), + .sie_token_data_o(ctrl_token_data_w), + .sie_tx_count_o(ctrl_tx_count_w), + .sie_data_idx_o(ctrl_data_idx_w), + .sie_in_transfer_o(ctrl_in_transfer_w), + .sie_resp_expected_o(ctrl_resp_expected_w), + + // FIFO + .sie_tx_data_o(ctrl_fifo_tx_data_w), + .sie_tx_push_o(ctrl_fifo_tx_push_w), + .sie_tx_flush_o(ctrl_fifo_tx_flush_w), + .sie_rx_pop_o(ctrl_fifo_rx_pop_w), + .sie_rx_data_i(ctrl_fifo_rx_data_w), + + // Status + .sie_rx_crc_err_i(status_crc_err_w), + .sie_rx_resp_timeout_i(status_timeout_w), + .sie_rx_resp_pid_i(status_response_w), + .sie_rx_count_i(status_rx_count_w), + .sie_rx_idle_i(status_sie_idle_w), + .sie_req_ack_i(transfer_req_ack_q), + .sie_sof_time_i(sof_time_q), + .sie_rx_done_i(status_rx_done_w), + .sie_tx_done_i(status_tx_done_w), + .sie_sof_irq_i(sof_irq_q) +); + +//----------------------------------------------------------------- +// Tx FIFO (Host -> Device) +//----------------------------------------------------------------- +usbh_fifo +#( + .DEPTH(TX_FIFO_DEPTH), + .ADDR_W(TX_FIFO_ADDR_W) +) +u_fifo_tx +( + .clk_i(clk_i), + .rst_i(rst_i), + + .data_i(ctrl_fifo_tx_data_w), + .push_i(ctrl_fifo_tx_push_w), + + .flush_i(ctrl_fifo_tx_flush_w), + + .full_o(), + .empty_o(), + + .data_o(fifo_tx_data_w), + .pop_i(fifo_tx_pop_w) +); + +//----------------------------------------------------------------- +// Rx FIFO (Device -> Host) +//----------------------------------------------------------------- +usbh_fifo +#( + .DEPTH(RX_FIFO_DEPTH), + .ADDR_W(RX_FIFO_ADDR_W) +) +u_fifo_rx +( + .clk_i(clk_i), + .rst_i(rst_i), + + // Receive from UTMI interface + .data_i(fifo_rx_data_w), + .push_i(fifo_rx_push_w), + + .flush_i(fifo_flush_q), + + .full_o(), + .empty_o(), + + .data_o(ctrl_fifo_rx_data_w), + .pop_i(ctrl_fifo_rx_pop_w) +); + +//----------------------------------------------------------------- +// Assignments +//----------------------------------------------------------------- +assign send_sof_w = (sof_time_q == SOF_THRESHOLD && ctrl_sof_en_w) & status_sie_idle_w; +assign sof_gaurd_band_w = (sof_time_q <= SOF_GAURD_LOW || sof_time_q >= SOF_GAURD_HIGH); +assign clear_to_send_w = (~sof_gaurd_band_w | ~ctrl_sof_en_w) & status_sie_idle_w; + +assign token_pid_w = sof_transfer_q ? PID_SOF : ctrl_token_pid_w; + +assign token_dev_w = sof_transfer_q ? + {sof_value_q[0], sof_value_q[1], sof_value_q[2], + sof_value_q[3], sof_value_q[4], sof_value_q[5], sof_value_q[6]} : + ctrl_token_data_w[10:4]; + +assign token_ep_w = sof_transfer_q ? + {sof_value_q[7], sof_value_q[8], sof_value_q[9], sof_value_q[10]} : + ctrl_token_data_w[3:0]; + +//----------------------------------------------------------------- +// Control logic +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +begin + if (rst_i == 1'b1) + begin + fifo_flush_q <= 1'b0; + transfer_start_q <= 1'b0; + sof_transfer_q <= 1'b0; + transfer_req_ack_q <= 1'b0; + in_transfer_q <= 1'b0; + resp_expected_q <= 1'b0; + end + else + begin + // Transfer in progress? + if (transfer_start_q) + begin + // Transfer accepted + if (transfer_ack_w) + transfer_start_q <= 1'b0; + + fifo_flush_q <= 1'b0; + transfer_req_ack_q <= 1'b0; + end + // Time to send another SOF token? + else if (send_sof_w) + begin + // Start transfer + in_transfer_q <= 1'b0; + resp_expected_q <= 1'b0; + transfer_start_q <= 1'b1; + sof_transfer_q <= 1'b1; + end + // Not in SOF gaurd band region or SOF disabled? + else if (clear_to_send_w) + begin + // Transfer request + if (ctrl_start_w) + begin + // Flush un-used previous Rx data + fifo_flush_q <= 1'b1; + + // Start transfer + in_transfer_q <= ctrl_in_transfer_w; + resp_expected_q <= ctrl_resp_expected_w; + transfer_start_q <= 1'b1; + sof_transfer_q <= 1'b0; + transfer_req_ack_q <= 1'b1; + end + end + end +end + +//----------------------------------------------------------------- +// SOF Frame Number +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +begin + if (rst_i == 1'b1) + begin + sof_value_q <= 11'd0; + sof_time_q <= SOF_ZERO; + sof_irq_q <= 1'b0; + end + // Time to send another SOF token? + else if (send_sof_w) + begin + sof_time_q <= SOF_ZERO; + sof_value_q <= sof_value_q + 11'd1; + + // Start of frame interrupt + sof_irq_q <= 1'b1; + end + else + begin + // Increment the SOF timer + if (sof_time_q != SOF_THRESHOLD) + sof_time_q <= sof_time_q + SOF_INC; + + sof_irq_q <= 1'b0; + end +end + +endmodule + diff --git a/usb_host/rtl/usbh_crc16.v b/usb_host/rtl/usbh_crc16.v new file mode 100644 index 0000000..1f39872 --- /dev/null +++ b/usb_host/rtl/usbh_crc16.v @@ -0,0 +1,69 @@ +//----------------------------------------------------------------- +// USB Full Speed Host +// V0.1 +// Ultra-Embedded.com +// Copyright 2015 +// +// Email: admin@ultra-embedded.com +// +// License: GPL +// If you would like a version with a more permissive license for +// use in closed source commercial applications please contact me +// for details. +//----------------------------------------------------------------- +// +// This file is open source HDL; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This file 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public +// License along with this file; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +// USA +//----------------------------------------------------------------- + +//----------------------------------------------------------------- +// Module: 16-bit CRC used by USB data packets +//----------------------------------------------------------------- +module usbh_crc16 +( + input [15:0] crc_i, + input [7:0] data_i, + output [15:0] crc_o +); + +//----------------------------------------------------------------- +// Implementation +//----------------------------------------------------------------- +assign crc_o[15] = data_i[0] ^ data_i[1] ^ data_i[2] ^ data_i[3] ^ data_i[4] ^ + data_i[5] ^ data_i[6] ^ data_i[7] ^ crc_i[7] ^ crc_i[6] ^ + crc_i[5] ^ crc_i[4] ^ crc_i[3] ^ crc_i[2] ^ + crc_i[1] ^ crc_i[0]; +assign crc_o[14] = data_i[0] ^ data_i[1] ^ data_i[2] ^ data_i[3] ^ data_i[4] ^ data_i[5] ^ + data_i[6] ^ crc_i[6] ^ crc_i[5] ^ crc_i[4] ^ + crc_i[3] ^ crc_i[2] ^ crc_i[1] ^ crc_i[0]; +assign crc_o[13] = data_i[6] ^ data_i[7] ^ crc_i[7] ^ crc_i[6]; +assign crc_o[12] = data_i[5] ^ data_i[6] ^ crc_i[6] ^ crc_i[5]; +assign crc_o[11] = data_i[4] ^ data_i[5] ^ crc_i[5] ^ crc_i[4]; +assign crc_o[10] = data_i[3] ^ data_i[4] ^ crc_i[4] ^ crc_i[3]; +assign crc_o[9] = data_i[2] ^ data_i[3] ^ crc_i[3] ^ crc_i[2]; +assign crc_o[8] = data_i[1] ^ data_i[2] ^ crc_i[2] ^ crc_i[1]; +assign crc_o[7] = data_i[0] ^ data_i[1] ^ crc_i[15] ^ crc_i[1] ^ crc_i[0]; +assign crc_o[6] = data_i[0] ^ crc_i[14] ^ crc_i[0]; +assign crc_o[5] = crc_i[13]; +assign crc_o[4] = crc_i[12]; +assign crc_o[3] = crc_i[11]; +assign crc_o[2] = crc_i[10]; +assign crc_o[1] = crc_i[9]; +assign crc_o[0] = data_i[0] ^ data_i[1] ^ data_i[2] ^ data_i[3] ^ data_i[4] ^ data_i[5] ^ + data_i[6] ^ data_i[7] ^ crc_i[8] ^ crc_i[7] ^ crc_i[6] ^ + crc_i[5] ^ crc_i[4] ^ crc_i[3] ^ crc_i[2] ^ + crc_i[1] ^ crc_i[0]; + +endmodule diff --git a/usb_host/rtl/usbh_crc5.v b/usb_host/rtl/usbh_crc5.v new file mode 100644 index 0000000..8cb5986 --- /dev/null +++ b/usb_host/rtl/usbh_crc5.v @@ -0,0 +1,59 @@ +//----------------------------------------------------------------- +// USB Full Speed Host +// V0.1 +// Ultra-Embedded.com +// Copyright 2015 +// +// Email: admin@ultra-embedded.com +// +// License: GPL +// If you would like a version with a more permissive license for +// use in closed source commercial applications please contact me +// for details. +//----------------------------------------------------------------- +// +// This file is open source HDL; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This file 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public +// License along with this file; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +// USA +//----------------------------------------------------------------- + +//----------------------------------------------------------------- +// Module: 5-bit CRC used by USB tokens +//----------------------------------------------------------------- +module usbh_crc5 +( + input [4:0] crc_i, + input [10:0] data_i, + output [4:0] crc_o +); + +//----------------------------------------------------------------- +// Implementation +//----------------------------------------------------------------- +assign crc_o[0] = data_i[10] ^ data_i[9] ^ data_i[6] ^ data_i[5] ^ data_i[3] ^ data_i[0] ^ + crc_i[0] ^ crc_i[3] ^ crc_i[4]; + +assign crc_o[1] = data_i[10] ^ data_i[7] ^ data_i[6] ^ data_i[4] ^ data_i[1] ^ + crc_i[0] ^ crc_i[1] ^ crc_i[4]; + +assign crc_o[2] = data_i[10] ^ data_i[9] ^ data_i[8] ^ data_i[7] ^ data_i[6] ^ data_i[3] ^ data_i[2] ^ data_i[0] ^ + crc_i[0] ^ crc_i[1] ^ crc_i[2] ^ crc_i[3] ^ crc_i[4]; + +assign crc_o[3] = data_i[10] ^ data_i[9] ^ data_i[8] ^ data_i[7] ^ data_i[4] ^ data_i[3] ^ data_i[1] ^ + crc_i[1] ^ crc_i[2] ^ crc_i[3] ^ crc_i[4]; + +assign crc_o[4] = data_i[10] ^ data_i[9] ^ data_i[8] ^ data_i[5] ^ data_i[4] ^ data_i[2] ^ + crc_i[2] ^ crc_i[3] ^ crc_i[4]; + +endmodule diff --git a/usb_host/rtl/usbh_fifo.v b/usb_host/rtl/usbh_fifo.v new file mode 100644 index 0000000..2b6ccd0 --- /dev/null +++ b/usb_host/rtl/usbh_fifo.v @@ -0,0 +1,124 @@ +//----------------------------------------------------------------- +// USB Full Speed Host +// V0.1 +// Ultra-Embedded.com +// Copyright 2015 +// +// Email: admin@ultra-embedded.com +// +// License: GPL +// If you would like a version with a more permissive license for +// use in closed source commercial applications please contact me +// for details. +//----------------------------------------------------------------- +// +// This file is open source HDL; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This file 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public +// License along with this file; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +// USA +//----------------------------------------------------------------- + +//----------------------------------------------------------------- +// Module: USB FIFO - simple FIFO +//----------------------------------------------------------------- +module usbh_fifo + +//----------------------------------------------------------------- +// Params +//----------------------------------------------------------------- +#( + parameter WIDTH = 8, + parameter DEPTH = 4, + parameter ADDR_W = 2 +) + +//----------------------------------------------------------------- +// Ports +//----------------------------------------------------------------- +( + input clk_i, + input rst_i, + + input [WIDTH-1:0] data_i, + input push_i, + + output full_o, + output empty_o, + + output [WIDTH-1:0] data_o, + input pop_i, + + input flush_i +); + +//----------------------------------------------------------------- +// Defs / Params +//----------------------------------------------------------------- +localparam COUNT_W = ADDR_W + 1; + +//----------------------------------------------------------------- +// Registers +//----------------------------------------------------------------- +reg [WIDTH-1:0] ram [DEPTH-1:0]; +reg [ADDR_W-1:0] rd_ptr_q; +reg [ADDR_W-1:0] wr_ptr_q; +reg [COUNT_W-1:0] count_q; + +//----------------------------------------------------------------- +// Sequential +//----------------------------------------------------------------- +always @ (posedge clk_i or posedge rst_i) +begin + if (rst_i) + begin + count_q <= {(COUNT_W) {1'b0}}; + rd_ptr_q <= {(ADDR_W) {1'b0}}; + wr_ptr_q <= {(ADDR_W) {1'b0}}; + end + else if (flush_i) + begin + count_q <= {(COUNT_W) {1'b0}}; + rd_ptr_q <= {(ADDR_W) {1'b0}}; + wr_ptr_q <= {(ADDR_W) {1'b0}}; + end + else + begin + // Push + if (push_i && !full_o) + begin + ram[wr_ptr_q] <= data_i; + wr_ptr_q <= wr_ptr_q + 1; + end + + // Pop + if (pop_i && !empty_o) + rd_ptr_q <= rd_ptr_q + 1; + + // Count up + if ((push_i && !full_o) && !(pop_i && !empty_o)) + count_q <= count_q + 1; + // Count down + else if (!(push_i && !full_o) && (pop_i && !empty_o)) + count_q <= count_q - 1; + end +end + +//------------------------------------------------------------------- +// Assignments +//------------------------------------------------------------------- +assign full_o = (count_q == DEPTH); +assign empty_o = (count_q == 0); + +assign data_o = ram[rd_ptr_q]; + +endmodule diff --git a/usb_host/rtl/usbh_periph.v b/usb_host/rtl/usbh_periph.v new file mode 100644 index 0000000..15e5e12 --- /dev/null +++ b/usb_host/rtl/usbh_periph.v @@ -0,0 +1,376 @@ +//----------------------------------------------------------------- +// USB Full Speed Host +// V0.1 +// Ultra-Embedded.com +// Copyright 2015 +// +// Email: admin@ultra-embedded.com +// +// License: GPL +// If you would like a version with a more permissive license for +// use in closed source commercial applications please contact me +// for details. +//----------------------------------------------------------------- +// +// This file is open source HDL; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This file 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public +// License along with this file; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +// USA +//----------------------------------------------------------------- + +//----------------------------------------------------------------- +// Module: Peripheral interface for USB host +//----------------------------------------------------------------- +module usbh_periph +( + // Clocking (48MHz) & Reset + input clk_i, + input rst_i, + + output intr_o, + + // Peripheral Interface (from CPU) + input [7:0] addr_i, + input [31:0] data_i, + output [31:0] data_o, + input we_i, + input stb_i, + + // UTMI interface + input [1:0] utmi_linestate_i, + input utmi_rxerror_i, + + // Control + output sie_start_o, + output sie_sof_en_o, + output sie_rst_o, + output [7:0] sie_token_pid_o, + output [10:0] sie_token_data_o, + output [15:0] sie_tx_count_o, + output sie_data_idx_o, + output sie_in_transfer_o, + output sie_resp_expected_o, + + // FIFO + output [7:0] sie_tx_data_o, + output sie_tx_push_o, + output sie_tx_flush_o, + output sie_rx_pop_o, + input [7:0] sie_rx_data_i, + + // Status + input sie_rx_crc_err_i, + input [7:0] sie_rx_resp_pid_i, + input sie_rx_resp_timeout_i, + input [15:0] sie_rx_count_i, + input sie_rx_idle_i, + input sie_req_ack_i, + input [15:0] sie_sof_time_i, + input sie_rx_done_i, + input sie_tx_done_i, + input sie_sof_irq_i +); + +//----------------------------------------------------------------- +// Peripheral Memory Map +//----------------------------------------------------------------- +`define USB_CTRL 8'h00 + `define USB_TX_FLUSH 2 + `define USB_ENABLE_SOF 1 + `define USB_RESET_ACTIVE 0 +`define USB_STATUS 8'h00 + `define USB_STAT_SOF_TIME 31:16 + `define USB_STAT_RX_ERROR 2 + `define USB_STAT_LINESTATE_BITS 1:0 +`define USB_IRQ 8'h04 +`define USB_IRQ_MASK 8'h08 + `define USB_IRQ_SOF 0 + `define USB_IRQ_DONE 1 + `define USB_IRQ_ERR 2 +`define USB_XFER_DATA 8'h0c + `define USB_XFER_DATA_TX_LEN 15:0 +`define USB_XFER_TOKEN 8'h10 + `define USB_XFER_START 31 + `define USB_XFER_IN 30 + `define USB_XFER_ACK 29 + `define USB_XFER_PID_DATAX 28 + `define USB_XFER_PID_BITS 23:16 +`define USB_RX_STAT 8'h14 + `define USB_RX_STAT_START_PEND 31 + `define USB_RX_STAT_CRC_ERR 30 + `define USB_RX_STAT_RESP_TIMEOUT 29 + `define USB_RX_STAT_IDLE 28 + `define USB_RX_STAT_RESP_BITS 23:16 + `define USB_RX_STAT_COUNT_BITS 15:0 +`define USB_WR_DATA 8'h18 +`define USB_RD_DATA 8'h18 + +//----------------------------------------------------------------- +// Registers / Wires +//----------------------------------------------------------------- +reg usb_err_q; + +reg intr_done_q; +reg intr_sof_q; +reg intr_err_q; + +// Interrupt Mask +reg intr_mask_done_q; +reg intr_mask_sof_q; +reg intr_mask_err_q; + +// Control +reg sie_start_q; +reg sie_sof_en_q; +reg sie_rst_q; +reg [7:0] sie_token_pid_q; +reg [10:0] sie_token_data_q; +reg [15:0] sie_tx_count_q; +reg sie_data_idx_q; +reg sie_in_transfer_q; +reg sie_resp_expected_q; + +// FIFO +reg [7:0] sie_tx_data_q; +reg sie_tx_push_q; +reg sie_tx_flush_q; +reg sie_rx_pop_q; + +//----------------------------------------------------------------- +// Control +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +if (rst_i == 1'b1) +begin + sie_sof_en_q <= 1'b0; + sie_rst_q <= 1'b0; + sie_tx_flush_q <= 1'b0; +end +// IO Write Cycle +else if (we_i && stb_i && (addr_i == `USB_CTRL)) +begin + sie_rst_q <= data_i[`USB_RESET_ACTIVE]; + sie_sof_en_q <= data_i[`USB_ENABLE_SOF]; + sie_tx_flush_q <= data_i[`USB_TX_FLUSH]; +end +else + sie_tx_flush_q <= 1'b0; + +//----------------------------------------------------------------- +// Data FIFO Write +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +if (rst_i == 1'b1) +begin + sie_tx_data_q <= 8'h00; + sie_tx_push_q <= 1'b0; +end +// IO Write Cycle +else if (we_i && stb_i && (addr_i == `USB_WR_DATA)) +begin + sie_tx_data_q <= data_i[7:0]; + sie_tx_push_q <= 1'b1; +end +else + sie_tx_push_q <= 1'b0; + +//----------------------------------------------------------------- +// Tx Length +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +if (rst_i == 1'b1) + sie_tx_count_q <= 16'h0000; +// IO Write Cycle +else if (we_i && stb_i && (addr_i == `USB_XFER_DATA)) + sie_tx_count_q <= data_i[`USB_XFER_DATA_TX_LEN]; + +//----------------------------------------------------------------- +// Tx Token +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +if (rst_i == 1'b1) +begin + sie_start_q <= 1'b0; + sie_in_transfer_q <= 1'b0; + sie_resp_expected_q<= 1'b0; + sie_token_pid_q <= 8'h00; + sie_token_data_q <= 11'h000; + sie_data_idx_q <= 1'b0; + +end +// IO Write Cycle +else if (we_i && stb_i && (addr_i == `USB_XFER_TOKEN)) +begin + sie_start_q <= data_i[`USB_XFER_START]; + sie_in_transfer_q <= data_i[`USB_XFER_IN]; + sie_resp_expected_q<= data_i[`USB_XFER_ACK]; + sie_data_idx_q <= data_i[`USB_XFER_PID_DATAX]; + sie_token_pid_q <= data_i[`USB_XFER_PID_BITS]; + sie_token_data_q <= { data_i[9], data_i[10], data_i[11], data_i[12], data_i[13], data_i[14], data_i[15], + data_i[5], data_i[6], data_i[7], data_i[8] }; +end +else if (sie_req_ack_i) + sie_start_q <= 1'b0; + +//----------------------------------------------------------------- +// Interrupt Masks +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +if (rst_i == 1'b1) +begin + intr_mask_done_q <= 1'b0; + intr_mask_sof_q <= 1'b0; + intr_mask_err_q <= 1'b0; +end +// IO Write Cycle +else if (we_i && stb_i && (addr_i == `USB_IRQ_MASK)) +begin + intr_mask_done_q <= data_i[`USB_IRQ_DONE]; + intr_mask_sof_q <= data_i[`USB_IRQ_SOF]; + intr_mask_err_q <= data_i[`USB_IRQ_ERR]; +end + +//----------------------------------------------------------------- +// Record Errors +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +if (rst_i == 1'b1) + usb_err_q <= 1'b0; +// Clear error +else if (we_i && stb_i && (addr_i == `USB_XFER_TOKEN || addr_i == `USB_CTRL)) + usb_err_q <= 1'b0; +// Record bus errors +else if (utmi_rxerror_i) + usb_err_q <= 1'b1; + +//----------------------------------------------------------------- +// Data FIFO Read +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +if (rst_i == 1'b1) + sie_rx_pop_q <= 1'b0; +// IO Read Cycle +else if (~we_i && stb_i && (addr_i == `USB_RD_DATA)) + sie_rx_pop_q <= 1'b1; +else + sie_rx_pop_q <= 1'b0; + +assign sie_start_o = sie_start_q; +assign sie_sof_en_o = sie_sof_en_q; +assign sie_rst_o = sie_rst_q; +assign sie_token_pid_o = sie_token_pid_q; +assign sie_token_data_o = sie_token_data_q; +assign sie_tx_count_o = sie_tx_count_q; +assign sie_data_idx_o = sie_data_idx_q; +assign sie_in_transfer_o = sie_in_transfer_q; +assign sie_resp_expected_o = sie_resp_expected_q; +assign sie_tx_data_o = sie_tx_data_q; +assign sie_tx_push_o = sie_tx_push_q; +assign sie_tx_flush_o = sie_tx_flush_q; +assign sie_rx_pop_o = sie_rx_pop_q; + +//----------------------------------------------------------------- +// Peripheral Registers (Read) +//----------------------------------------------------------------- +reg [31:0] data_r; + +always @ * +begin + data_r = 32'h00000000; + + case (addr_i) + + `USB_STATUS : + begin + data_r[`USB_STAT_SOF_TIME] = sie_sof_time_i; + data_r[`USB_STAT_RX_ERROR] = usb_err_q; + data_r[`USB_STAT_LINESTATE_BITS] = utmi_linestate_i; + end + + `USB_RX_STAT : + begin + data_r[`USB_RX_STAT_START_PEND] = sie_start_o; + data_r[`USB_RX_STAT_CRC_ERR] = sie_rx_crc_err_i; + data_r[`USB_RX_STAT_RESP_TIMEOUT] = sie_rx_resp_timeout_i; + data_r[`USB_RX_STAT_IDLE] = sie_rx_idle_i; + data_r[`USB_RX_STAT_RESP_BITS] = sie_rx_resp_pid_i; + data_r[`USB_RX_STAT_COUNT_BITS] = sie_rx_count_i; + end + + `USB_RD_DATA : + data_r = {24'h000000, sie_rx_data_i}; + + `USB_IRQ : + begin + data_r[`USB_IRQ_DONE] = intr_done_q; + data_r[`USB_IRQ_SOF] = intr_sof_q; + data_r[`USB_IRQ_ERR] = intr_err_q; + end + + `USB_IRQ_MASK : + begin + data_r[`USB_IRQ_DONE] = intr_mask_done_q; + data_r[`USB_IRQ_SOF] = intr_mask_sof_q; + data_r[`USB_IRQ_ERR] = intr_mask_err_q; + end + + default : + data_r = 32'h00000000; + endcase +end + +assign data_o = data_r; + +//----------------------------------------------------------------- +// Interrupts +//----------------------------------------------------------------- +reg err_cond_q; +reg intr_q; + +always @ (posedge rst_i or posedge clk_i ) +if (rst_i == 1'b1) +begin + intr_done_q <= 1'b0; + intr_sof_q <= 1'b0; + intr_err_q <= 1'b0; + err_cond_q <= 1'b0; + + intr_q <= 1'b0; +end +else +begin + if (sie_rx_done_i || sie_tx_done_i) + intr_done_q <= 1'b1; + else if (we_i && stb_i && (addr_i == `USB_IRQ) && data_i[`USB_IRQ_DONE]) + intr_done_q <= 1'b0; + + if (sie_sof_irq_i) + intr_sof_q <= 1'b1; + else if (we_i && stb_i && (addr_i == `USB_IRQ) && data_i[`USB_IRQ_SOF]) + intr_sof_q <= 1'b0; + + if ((sie_rx_crc_err_i || sie_rx_resp_timeout_i) && (!err_cond_q)) + intr_err_q <= 1'b1; + else if (we_i && stb_i && (addr_i == `USB_IRQ) && data_i[`USB_IRQ_ERR]) + intr_err_q <= 1'b0; + + err_cond_q <= (sie_rx_crc_err_i | sie_rx_resp_timeout_i); + + intr_q <= (intr_done_q & intr_mask_done_q) | + (intr_err_q & intr_mask_err_q) | + (intr_sof_q & intr_mask_sof_q); +end + +assign intr_o = intr_q; + +endmodule diff --git a/usb_host/rtl/usbh_sie.v b/usb_host/rtl/usbh_sie.v new file mode 100644 index 0000000..8de1e0f --- /dev/null +++ b/usb_host/rtl/usbh_sie.v @@ -0,0 +1,775 @@ +//----------------------------------------------------------------- +// USB Full Speed Host +// V0.1 +// Ultra-Embedded.com +// Copyright 2015 +// +// Email: admin@ultra-embedded.com +// +// License: GPL +// If you would like a version with a more permissive license for +// use in closed source commercial applications please contact me +// for details. +//----------------------------------------------------------------- +// +// This file is open source HDL; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This file 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public +// License along with this file; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +// USA +//----------------------------------------------------------------- + +//----------------------------------------------------------------- +// Module: USB host serial interface engine +//----------------------------------------------------------------- +module usbh_sie +( + // Clocking (48MHz) & Reset + input clk_i, + input rst_i, + + // Control + input start_i, + input in_transfer_i, + input sof_transfer_i, + input resp_expected_i, + output ack_o, + + // Token packet + input [7:0] token_pid_i, + input [6:0] token_dev_i, + input [3:0] token_ep_i, + + // Data packet + input [15:0] data_len_i, + input data_idx_i, + + // Tx Data FIFO + input [7:0] tx_data_i, + output tx_pop_o, + + // Rx Data FIFO + output [7:0] rx_data_o, + output rx_push_o, + + // Status + output tx_done_o, + output rx_done_o, + output crc_err_o, + output timeout_o, + output [7:0] response_o, + output [15:0] rx_count_o, + output idle_o, + + // UTMI Interface + output [7:0] utmi_data_o, + output utmi_txvalid_o, + input utmi_txready_i, + input [7:0] utmi_data_i, + input utmi_rxvalid_i, + input utmi_rxactive_i +); + +//----------------------------------------------------------------- +// Registers / Wires +//----------------------------------------------------------------- +reg start_ack_q; +reg tx_pop_q; + +// Status +reg status_tx_done_q; +reg status_rx_done_q; +reg status_crc_err_q; +reg status_timeout_q; +reg [7:0] status_response_q; + +reg utmi_txvalid_q; + +reg pid_byte_q; +reg [15:0] byte_count_q; +reg in_transfer_q; + +reg [2:0] rx_time_q; +reg rx_time_en_q; +reg [7:0] last_tx_time_q; + +reg send_data1_q; +reg send_sof_q; +reg send_ack_q; + +// CRC16 +reg [15:0] crc_sum_q; +wire [15:0] crc_out_w; +wire [7:0] crc_data_in_w; + +// CRC5 +wire [4:0] crc5_out_w; +wire [4:0] crc5_next_w = crc5_out_w ^ 5'h1F; + +reg [15:0] token_q; + +reg wait_resp_q; + +//----------------------------------------------------------------- +// Definitions +//----------------------------------------------------------------- +localparam RX_TIMEOUT = 8'd255; // ~5uS + +localparam PID_OUT = 8'hE1; +localparam PID_IN = 8'h69; +localparam PID_SOF = 8'hA5; +localparam PID_SETUP = 8'h2D; + +localparam PID_DATA0 = 8'hC3; +localparam PID_DATA1 = 8'h4B; + +localparam PID_ACK = 8'hD2; +localparam PID_NAK = 8'h5A; +localparam PID_STALL = 8'h1E; + +// States +localparam STATE_IDLE = 4'd0; +localparam STATE_RX_DATA = 4'd1; +localparam STATE_TX_DATA = 4'd2; +localparam STATE_TX_CRC = 4'd3; +localparam STATE_TX_CRC1 = 4'd4; +localparam STATE_TX_CRC2 = 4'd5; +localparam STATE_TX_TOKEN1 = 4'd6; +localparam STATE_TX_TOKEN2 = 4'd7; +localparam STATE_TX_TOKEN3 = 4'd8; +localparam STATE_TX_ACKNAK = 4'd9; +localparam STATE_TX_WAIT = 4'd10; +localparam STATE_RX_WAIT = 4'd11; + +localparam RX_TIME_ZERO = 3'd0; +localparam RX_TIME_INC = 3'd1; +localparam RX_TIME_READY = 3'd7; // 2-bit times + +//----------------------------------------------------------------- +// Wires +//----------------------------------------------------------------- + +// New byte received +wire data_ready_w = (utmi_rxvalid_i & utmi_rxactive_i); + +// 2-bit times after last RX (inter-packet delay)? +wire autoresp_thresh_w = send_ack_q & rx_time_en_q & (rx_time_q == RX_TIME_READY); + +// Response timeout (no response after 500uS from transmit) +wire rx_resp_timeout_w = (last_tx_time_q >= RX_TIMEOUT) & wait_resp_q; + +//----------------------------------------------------------------- +// State Machine +//----------------------------------------------------------------- + +// Current state +reg [3:0] state_q; +reg [3:0] next_state_r; + +always @ * +begin + next_state_r = state_q; + + //----------------------------------------- + // Tx State Machine + //----------------------------------------- + case (state_q) + + //----------------------------------------- + // TX_TOKEN1 (byte 1 of token) + //----------------------------------------- + STATE_TX_TOKEN1 : + begin + // Data sent? + if (utmi_txready_i) + next_state_r = STATE_TX_TOKEN2; + end + //----------------------------------------- + // TX_TOKEN2 (byte 2 of token) + //----------------------------------------- + STATE_TX_TOKEN2 : + begin + // Data sent? + if (utmi_txready_i) + next_state_r = STATE_TX_TOKEN3; + end + //----------------------------------------- + // TX_TOKEN3 (byte 3 of token) + //----------------------------------------- + STATE_TX_TOKEN3 : + begin + // Data sent? + if (utmi_txready_i) + begin + // SOF - no data packet + if (send_sof_q) + next_state_r = STATE_IDLE; + // IN - wait for data + else if (in_transfer_q) + next_state_r = STATE_RX_WAIT; + // OUT/SETUP - Send data or ZLP + else + next_state_r = STATE_TX_DATA; + end + end + //----------------------------------------- + // TX_DATA + //----------------------------------------- + STATE_TX_DATA : + begin + // Last data byte sent? + if (utmi_txready_i && (byte_count_q == 16'b0)) + next_state_r = STATE_TX_CRC; + end + //----------------------------------------- + // TX_CRC (generate) + //----------------------------------------- + STATE_TX_CRC : + begin + next_state_r = STATE_TX_CRC1; + end + //----------------------------------------- + // TX_CRC1 (first byte) + //----------------------------------------- + STATE_TX_CRC1 : + begin + // Data sent? + if (utmi_txready_i) + next_state_r = STATE_TX_CRC2; + end + //----------------------------------------- + // TX_CRC (second byte) + //----------------------------------------- + STATE_TX_CRC2 : + begin + // Data sent? + if (utmi_txready_i) + begin + // If a response is expected + if (wait_resp_q) + next_state_r = STATE_RX_WAIT; + // No response expected (e.g ISO transfer) + else + next_state_r = STATE_IDLE; + end + end + //----------------------------------------- + // STATE_TX_WAIT + //----------------------------------------- + STATE_TX_WAIT : + begin + // Waited long enough? + if (autoresp_thresh_w) + next_state_r = STATE_TX_ACKNAK; + end + //----------------------------------------- + // STATE_TX_ACKNAK + //----------------------------------------- + STATE_TX_ACKNAK : + begin + // Data sent? + if (utmi_txready_i) + next_state_r = STATE_IDLE; + end + //----------------------------------------- + // STATE_RX_WAIT + //----------------------------------------- + STATE_RX_WAIT : + begin + // Data received? + if (data_ready_w) + next_state_r = STATE_RX_DATA; + // Waited long enough? + else if (rx_resp_timeout_w) + next_state_r = STATE_IDLE; + end + //----------------------------------------- + // RX_DATA + //----------------------------------------- + STATE_RX_DATA : + begin + // Receive complete + if (~utmi_rxvalid_i & ~utmi_rxactive_i) + begin + // Send an ACK/NAK response without CPU interaction? + if (send_ack_q && (status_response_q == PID_DATA0 || status_response_q == PID_DATA1)) + next_state_r = STATE_TX_WAIT; + else + next_state_r = STATE_IDLE; + end + end + //----------------------------------------- + // IDLE / RECEIVE BEGIN + //----------------------------------------- + STATE_IDLE : + begin + // Token transfer request + if (start_i) + next_state_r = STATE_TX_TOKEN1; + end + default : + ; + endcase +end + +// Update state +always @ (posedge rst_i or posedge clk_i) +if (rst_i == 1'b1) + state_q <= STATE_IDLE; +else + state_q <= next_state_r; + +//----------------------------------------------------------------- +// Tx Enable +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +begin + if (rst_i == 1'b1) + begin + utmi_txvalid_q <= 1'b0; + end + else + begin + case (state_q) + + //----------------------------------------- + // TX_TOKEN1 (byte 1 of token) + //----------------------------------------- + STATE_TX_TOKEN1 : + begin + utmi_txvalid_q <= 1'b1; + end + //----------------------------------------- + // TX_TOKEN3 (byte 3 of token) + //----------------------------------------- + STATE_TX_TOKEN3 : + begin + // Data sent? + if (utmi_txready_i) + utmi_txvalid_q <= 1'b0; + end + //----------------------------------------- + // TX_DATA + //----------------------------------------- + STATE_TX_DATA : + begin + // Tx active + utmi_txvalid_q <= 1'b1; + end + //----------------------------------------- + // TX_CRC (second byte) + //----------------------------------------- + STATE_TX_CRC2 : + begin + // Data sent? + if (utmi_txready_i) + utmi_txvalid_q <= 1'b0; + end + //----------------------------------------- + // STATE_TX_ACKNAK + //----------------------------------------- + STATE_TX_ACKNAK : + begin + // Data sent? + if (utmi_txready_i) + utmi_txvalid_q <= 1'b0; + else + utmi_txvalid_q <= 1'b1; + end + + default : + ; + endcase + end +end + +//----------------------------------------------------------------- +// Tx Token +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +if (rst_i == 1'b1) + token_q <= 16'h0000; +else if (state_q == STATE_IDLE) + token_q <= {token_dev_i, token_ep_i, 5'b0}; +// PID of token sent, capture calculated CRC for token packet +else if (state_q == STATE_TX_TOKEN1 && utmi_txready_i) + token_q[4:0] <= crc5_next_w; + +//----------------------------------------------------------------- +// Tx Timer +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +if (rst_i == 1'b1) + last_tx_time_q <= 8'd0; +// Start counting from last Tx +else if (state_q == STATE_IDLE || utmi_txvalid_q) + last_tx_time_q <= 8'd0; +// Increment the Tx timeout +else if (last_tx_time_q != RX_TIMEOUT) + last_tx_time_q <= last_tx_time_q + 8'd1; + +//----------------------------------------------------------------- +// Transmit / Receive counter +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +if (rst_i == 1'b1) + byte_count_q <= 16'h0000; +// New transfer request (not automatic SOF request) +else if (state_q == STATE_IDLE && start_i && !sof_transfer_i) + byte_count_q <= data_len_i; +// Transmit byte +else if (state_q == STATE_TX_DATA && utmi_txready_i) +begin + // Count down data left to send + if (byte_count_q != 16'd0) + byte_count_q <= byte_count_q - 16'd1; +end +// Received byte +else if ((state_q == STATE_RX_WAIT || state_q == STATE_RX_DATA) && data_ready_w) + byte_count_q <= byte_count_q + 16'd1; + +// Recognise first byte in the Tx data packet +always @ (posedge rst_i or posedge clk_i ) +if (rst_i == 1'b1) + pid_byte_q <= 1'b0; +// New transfer request, first byte sent in TX_DATA state is the PID +else if (state_q == STATE_IDLE && start_i) + pid_byte_q <= 1'b1; +// Transmit byte +else if (state_q == STATE_TX_DATA && utmi_txready_i) + pid_byte_q <= 1'b0; + +//----------------------------------------------------------------- +// Transfer start ack +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +if (rst_i == 1'b1) + start_ack_q <= 1'b0; +// First byte of PID sent, ack transfer request +else if (state_q == STATE_TX_TOKEN1 && utmi_txready_i) + start_ack_q <= 1'b1; +else + start_ack_q <= 1'b0; + +//----------------------------------------------------------------- +// Record request details +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +if (rst_i == 1'b1) +begin + in_transfer_q <= 1'b0; + send_ack_q <= 1'b0; + send_data1_q <= 1'b0; + send_sof_q <= 1'b0; +end +// Start of new request +else if (state_q == STATE_IDLE && start_i) +begin + // Transfer request + // e.g. (H)SOF [sof_transfer_i] + // (H)OUT + (H)DATA + (F)ACK/NACK/STALL [data_len_i >= 0 && !in_transfer_i] + // (H)IN + (F)DATA + (H)ACK [in_transfer_i] + // (H)IN + (F)NAK/STALL [in_transfer_i] + in_transfer_q <= in_transfer_i; + + // Send ACK in response to IN DATA + send_ack_q <= in_transfer_i && resp_expected_i; + + // DATA0/1 + send_data1_q <= data_idx_i; + + send_sof_q <= sof_transfer_i; +end + +//----------------------------------------------------------------- +// Response delay timer +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +if (rst_i == 1'b1) +begin + rx_time_q <= RX_TIME_ZERO; + rx_time_en_q <= 1'b0; +end +else if (state_q == STATE_IDLE) +begin + rx_time_q <= RX_TIME_ZERO; + rx_time_en_q <= 1'b0; +end +// Receive complete +else if (state_q == STATE_RX_DATA && !utmi_rxactive_i) +begin + // Reset time since end of last data byte + rx_time_q <= RX_TIME_ZERO; + rx_time_en_q <= 1'b1; +end +// Increment timer if enabled (and less than the threshold) +else if (rx_time_en_q && rx_time_q != RX_TIME_READY) + rx_time_q <= rx_time_q + RX_TIME_INC; + +// Response expected +always @ (posedge rst_i or posedge clk_i ) +if (rst_i == 1'b1) + wait_resp_q <= 1'b0; +// Incoming data +else if (state_q == STATE_RX_WAIT && data_ready_w) + wait_resp_q <= 1'b0; +else if (state_q == STATE_IDLE && start_i) + wait_resp_q <= resp_expected_i; + +//----------------------------------------------------------------- +// Status +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +begin + if (rst_i == 1'b1) + begin + status_response_q <= 8'h00; + status_timeout_q <= 1'b0; + status_rx_done_q <= 1'b0; + status_tx_done_q <= 1'b0; + end + else + begin + case (state_q) + + //----------------------------------------- + // RX_WAIT + //----------------------------------------- + STATE_RX_WAIT : + begin + // Store response PID + if (data_ready_w) + status_response_q <= utmi_data_i; + + // Waited long enough? + if (rx_resp_timeout_w) + status_timeout_q <= 1'b1; + + status_tx_done_q <= 1'b0; + end + //----------------------------------------- + // RX_DATA + //----------------------------------------- + STATE_RX_DATA : + begin + // Receive complete + if (!utmi_rxactive_i) + status_rx_done_q <= 1'b1; + else + status_rx_done_q <= 1'b0; + end + //----------------------------------------- + // TX_CRC (second byte) + //----------------------------------------- + STATE_TX_CRC2 : + begin + // Data sent? + if (utmi_txready_i && !wait_resp_q) + begin + // Transfer now complete + status_tx_done_q <= 1'b1; + end + end + //----------------------------------------- + // IDLE / RECEIVE BEGIN + //----------------------------------------- + STATE_IDLE : + begin + // Transfer request + // e.g. (H)SOF [sof_transfer_i] + // (H)OUT + (H)DATA + (F)ACK/NACK/STALL [data_len_i >= 0 && !in_transfer_i] + // (H)IN + (F)DATA + (H)ACK [in_transfer_i] + // (H)IN + (F)NAK/STALL [in_transfer_i] + if (start_i && !sof_transfer_i) // (not automatic SOF request) + begin + // Clear status + status_response_q <= 8'h00; + status_timeout_q <= 1'b0; + end + + status_rx_done_q <= 1'b0; + status_tx_done_q <= 1'b0; + end + //----------------------------------------- + // DEFAULT + //----------------------------------------- + default : + begin + status_rx_done_q <= 1'b0; + status_tx_done_q <= 1'b0; + end + endcase + end +end + +//----------------------------------------------------------------- +// FIFO access +//----------------------------------------------------------------- +always @ (posedge rst_i or posedge clk_i ) +if (rst_i == 1'b1) + tx_pop_q <= 1'b0; +// Data byte unload (not PID) +else if (state_q == STATE_TX_DATA && utmi_txready_i && !pid_byte_q) + tx_pop_q <= 1'b1; +else + tx_pop_q <= 1'b0; + +//----------------------------------------------------------------- +// CRC +//----------------------------------------------------------------- + +// CRC16 (Data) +usbh_crc16 +u_crc16 +( + .crc_i(crc_sum_q), + .data_i(crc_data_in_w), + .crc_o(crc_out_w) +); + +// CRC5 (Token) +usbh_crc5 +u_crc5 +( + .crc_i(5'h1F), + .data_i(token_q[15:5]), + .crc_o(crc5_out_w) +); + +// CRC control / check +always @ (posedge rst_i or posedge clk_i ) +begin + if (rst_i == 1'b1) + begin + crc_sum_q <= 16'hFFFF; + status_crc_err_q <= 1'b0; + end + else + begin + case (state_q) + //----------------------------------------- + // TX_DATA + //----------------------------------------- + STATE_TX_DATA : + begin + // Data sent? + if (utmi_txready_i) + begin + // First byte is PID (not CRC'd), reset CRC16 + if (pid_byte_q) + crc_sum_q <= 16'hFFFF; + // Next CRC start value + else + crc_sum_q <= crc_out_w; + end + end + //----------------------------------------- + // TX_CRC (generate) + //----------------------------------------- + STATE_TX_CRC : + begin + // Next CRC start value + crc_sum_q <= crc_sum_q ^ 16'hFFFF; + end + //----------------------------------------- + // RX_WAIT + //----------------------------------------- + STATE_RX_WAIT : + begin + // Reset CRC16 + crc_sum_q <= 16'hFFFF; + end + //----------------------------------------- + // RX_DATA + //----------------------------------------- + STATE_RX_DATA : + begin + // Data received? + if (data_ready_w) + begin + // Next CRC start value + crc_sum_q <= crc_out_w; + end + // Receive complete + else if (utmi_rxactive_i == 1'b0) + begin + // If some data received, check CRC + if (crc_sum_q != 16'hB001 && in_transfer_q && byte_count_q != 16'd1) + status_crc_err_q <= 1'b1; + else + status_crc_err_q <= 1'b0; + end + end + + //----------------------------------------- + // IDLE / RECEIVE BEGIN + //----------------------------------------- + STATE_IDLE : + begin + // Start transfer request + if (start_i && !sof_transfer_i) + begin + // Clear error flag! + status_crc_err_q <= 1'b0; + end + end + default : + ; + endcase + end +end + +//----------------------------------------------------------------- +// Assignments +//----------------------------------------------------------------- +wire [15:0] token_rev_w; + +genvar i; +generate +for (i=0; i < 16; i=i+1) +begin : LOOP + assign token_rev_w[i] = token_q[15-i]; +end +endgenerate + +// Transmit data +assign utmi_data_o = (state_q == STATE_TX_CRC1) ? crc_sum_q[7:0] : + (state_q == STATE_TX_CRC2) ? crc_sum_q[15:8] : + (state_q == STATE_TX_TOKEN1) ? token_pid_i : + (state_q == STATE_TX_TOKEN2) ? token_rev_w[7:0] : + (state_q == STATE_TX_TOKEN3) ? token_rev_w[15:8] : + (state_q == STATE_TX_DATA && pid_byte_q && send_data1_q) ? PID_DATA1 : + (state_q == STATE_TX_DATA && pid_byte_q && ~send_data1_q) ? PID_DATA0 : + (state_q == STATE_TX_ACKNAK) ? PID_ACK : + tx_data_i; +assign utmi_txvalid_o = utmi_txvalid_q; + +// Push incoming data into FIFO (not PID or CRC) +assign rx_data_o = utmi_data_i; +assign rx_push_o = (state_q != STATE_IDLE && state_q != STATE_RX_WAIT) & data_ready_w; + +assign crc_data_in_w = (state_q == STATE_RX_DATA || state_q == STATE_RX_WAIT) ? utmi_data_i : tx_data_i; + +assign rx_count_o = byte_count_q; +assign idle_o = (state_q == STATE_IDLE); + +assign ack_o = start_ack_q; + +assign tx_pop_o = tx_pop_q; + +assign tx_done_o = status_tx_done_q; +assign rx_done_o = status_rx_done_q; +assign crc_err_o = status_crc_err_q; +assign timeout_o = status_timeout_q; +assign response_o = status_response_q; + +endmodule