| // Copyright lowRISC contributors. | |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| // SPDX-License-Identifier: Apache-2.0 | |
| // Copyright Luke Valenty (TinyFPGA project, https://github.com/tinyfpga/TinyFPGA-Bootloader). | |
| module usb_fs_tx ( | |
| // A 48MHz clock is required to receive USB data at 12MHz | |
| // it's simpler to juse use 48MHz everywhere | |
| input logic clk_i, | |
| input logic rst_ni, // asyc reset | |
| input logic link_reset_i, // USB reset, sync to 48 MHz, active high | |
| input logic cfg_pinflip_i, | |
| // Oscillator test mode (constantly output JK) | |
| input logic tx_osc_test_mode_i, | |
| // bit strobe from rx to align with senders clock | |
| input logic bit_strobe_i, | |
| // output enable to take ownership of bus and data out | |
| output logic usb_oe_o, | |
| output logic usb_d_o, | |
| output logic usb_se0_o, | |
| output logic usb_dp_o, | |
| output logic usb_dn_o, | |
| // pulse to initiate new packet transmission | |
| input logic pkt_start_i, | |
| output logic pkt_end_o, | |
| // pid_i to send | |
| input logic [3:0] pid_i, | |
| // tx logic pulls data until there is nothing available | |
| input logic tx_data_avail_i, | |
| output logic tx_data_get_o, | |
| input logic [7:0] tx_data_i | |
| ); | |
| typedef enum logic [2:0] {Idle, Sync, Pid, DataOrCrc160, Crc161, Eop, OscTest} state_e; | |
| typedef enum logic [1:0] {OsIdle, OsWaitByte, OsTransmit} out_state_e; | |
| ///////////////////////// | |
| // Signal Declarations // | |
| ///////////////////////// | |
| logic [3:0] pid_q, pid_d; | |
| logic bitstuff; | |
| logic bitstuff_q; | |
| logic bitstuff_q2; | |
| logic bitstuff_q3; | |
| logic bitstuff_q4; | |
| logic [5:0] bit_history; | |
| state_e state_d, state_q; | |
| out_state_e out_state_d, out_state_q; | |
| logic [7:0] data_shift_reg_q, data_shift_reg_d; | |
| logic [7:0] oe_shift_reg_q, oe_shift_reg_d; | |
| logic [7:0] se0_shift_reg_q, se0_shift_reg_d; | |
| logic data_payload_q, data_payload_d; | |
| logic tx_data_get_q, tx_data_get_d; | |
| logic byte_strobe_q, byte_strobe_d; | |
| logic [4:0] bit_history_d, bit_history_q; | |
| logic [2:0] bit_count_d, bit_count_q; | |
| logic [15:0] crc16_d, crc16_q; | |
| logic oe_q, oe_d; | |
| logic usb_d_q, usb_d_d; | |
| logic usb_se0_q, usb_se0_d; | |
| logic [2:0] dp_eop_q, dp_eop_d; | |
| logic test_mode_start; | |
| logic serial_tx_data; | |
| logic serial_tx_oe; | |
| logic serial_tx_se0; | |
| logic crc16_invert; | |
| logic pkt_end; | |
| logic out_nrzi_en; | |
| // save packet parameters at pkt_start_i | |
| always_ff @(posedge clk_i or negedge rst_ni) begin : proc_pid | |
| if (!rst_ni) begin | |
| pid_q <= 0; | |
| end else begin | |
| if (link_reset_i) begin | |
| pid_q <= 0; | |
| end else begin | |
| pid_q <= pid_d; | |
| end | |
| end | |
| end | |
| assign pid_d = pkt_start_i ? pid_i : pid_q; | |
| assign serial_tx_data = data_shift_reg_q[0]; | |
| assign serial_tx_oe = oe_shift_reg_q[0]; | |
| assign serial_tx_se0 = se0_shift_reg_q[0]; | |
| // serialize sync, pid_i, data payload, and crc16 | |
| assign bit_history = {serial_tx_data, bit_history_q}; | |
| assign bitstuff = bit_history == 6'b111111; | |
| always_ff @(posedge clk_i or negedge rst_ni) begin : proc_bitstuff | |
| if (!rst_ni) begin | |
| bitstuff_q <= 0; | |
| bitstuff_q2 <= 0; | |
| bitstuff_q3 <= 0; | |
| bitstuff_q4 <= 0; | |
| end else begin | |
| if (link_reset_i) begin | |
| bitstuff_q <= 0; | |
| bitstuff_q2 <= 0; | |
| bitstuff_q3 <= 0; | |
| bitstuff_q4 <= 0; | |
| end else begin | |
| bitstuff_q <= bitstuff; | |
| bitstuff_q2 <= bitstuff_q; | |
| bitstuff_q3 <= bitstuff_q2; | |
| bitstuff_q4 <= bitstuff_q3; | |
| end | |
| end | |
| end | |
| assign pkt_end = bit_strobe_i && se0_shift_reg_q[1:0] == 2'b01; | |
| assign pkt_end_o = pkt_end; | |
| ///////// | |
| // FSM // | |
| ///////// | |
| always_comb begin : proc_fsm | |
| // Default assignments | |
| state_d = state_q; | |
| data_shift_reg_d = data_shift_reg_q; | |
| oe_shift_reg_d = oe_shift_reg_q; | |
| se0_shift_reg_d = se0_shift_reg_q; | |
| data_payload_d = data_payload_q; | |
| tx_data_get_d = tx_data_get_q; | |
| bit_history_d = bit_history_q; | |
| bit_count_d = bit_count_q; | |
| test_mode_start = 0; | |
| unique case (state_q) | |
| Idle : begin | |
| if (tx_osc_test_mode_i) begin | |
| state_d = OscTest; | |
| test_mode_start = 1; | |
| end else if (pkt_start_i) begin | |
| state_d = Sync; | |
| end | |
| end | |
| Sync : begin | |
| if (byte_strobe_q) begin | |
| state_d = Pid; | |
| data_shift_reg_d = 8'b10000000; | |
| oe_shift_reg_d = 8'b11111111; | |
| se0_shift_reg_d = 8'b00000000; | |
| end | |
| end | |
| Pid : begin | |
| if (byte_strobe_q) begin | |
| if (pid_q[1:0] == 2'b11) begin | |
| state_d = DataOrCrc160; | |
| end else begin | |
| state_d = Eop; | |
| end | |
| data_shift_reg_d = {~pid_q, pid_q}; | |
| oe_shift_reg_d = 8'b11111111; | |
| se0_shift_reg_d = 8'b00000000; | |
| end | |
| end | |
| DataOrCrc160 : begin | |
| if (byte_strobe_q) begin | |
| if (tx_data_avail_i) begin | |
| state_d = DataOrCrc160; | |
| data_payload_d = 1; | |
| tx_data_get_d = 1; | |
| data_shift_reg_d = tx_data_i; | |
| oe_shift_reg_d = 8'b11111111; | |
| se0_shift_reg_d = 8'b00000000; | |
| end else begin | |
| state_d = Crc161; | |
| data_payload_d = 0; | |
| tx_data_get_d = 0; | |
| data_shift_reg_d = ~{crc16_q[8], crc16_q[9], crc16_q[10], crc16_q[11], | |
| crc16_q[12], crc16_q[13], crc16_q[14], crc16_q[15]}; | |
| oe_shift_reg_d = 8'b11111111; | |
| se0_shift_reg_d = 8'b00000000; | |
| end | |
| end else begin | |
| tx_data_get_d = 0; | |
| end | |
| end | |
| Crc161 : begin | |
| if (byte_strobe_q) begin | |
| state_d = Eop; | |
| data_shift_reg_d = ~{crc16_q[0], crc16_q[1], crc16_q[2], crc16_q[3], | |
| crc16_q[4], crc16_q[5], crc16_q[6], crc16_q[7]}; | |
| oe_shift_reg_d = 8'b11111111; | |
| se0_shift_reg_d = 8'b00000000; | |
| end | |
| end | |
| Eop : begin | |
| if (byte_strobe_q) begin | |
| state_d = Idle; | |
| oe_shift_reg_d = 8'b00000111; | |
| se0_shift_reg_d = 8'b00000111; | |
| end | |
| end | |
| OscTest: begin | |
| // Oscillator test mode: toggle constantly | |
| if (!tx_osc_test_mode_i && byte_strobe_q) begin | |
| oe_shift_reg_d = 8'b00000000; | |
| state_d = Idle; | |
| end else if (byte_strobe_q) begin | |
| data_shift_reg_d = 8'b00000000; | |
| oe_shift_reg_d = 8'b11111111; | |
| se0_shift_reg_d = 8'b00000000; | |
| end | |
| end | |
| default: state_d = Idle; | |
| endcase | |
| // Logic closely coupled to the FSM | |
| if (pkt_start_i) begin | |
| // We need to have an inter-packet delay between | |
| // 2 and 6.5 bit times (see USB 2.0 spec / 7.1.18.1) | |
| // The latency in the rest of the system is approximately (measured) | |
| // 3.68 bit-times, so we only introduce 1 bit-time here | |
| bit_count_d = 7; // 8-7 = 1 | |
| bit_history_d = 0; | |
| end else if (bit_strobe_i) begin | |
| // bitstuff | |
| if (bitstuff /* && !serial_tx_se0*/) begin | |
| bit_history_d = bit_history[5:1]; | |
| data_shift_reg_d[0] = 0; | |
| // normal deserialize | |
| end else begin | |
| bit_count_d = bit_count_q + 1; | |
| data_shift_reg_d = (data_shift_reg_q >> 1); | |
| oe_shift_reg_d = (oe_shift_reg_q >> 1); | |
| se0_shift_reg_d = (se0_shift_reg_q >> 1); | |
| bit_history_d = bit_history[5:1]; | |
| end | |
| end | |
| end | |
| `ASSERT(StateValid_A, state_q inside {Idle, Sync, Pid, DataOrCrc160, Crc161, Eop, OscTest}) | |
| always_comb begin : proc_byte_str | |
| if (bit_strobe_i && !bitstuff && !pkt_start_i) begin | |
| byte_strobe_d = (bit_count_q == 3'b000); | |
| end else begin | |
| byte_strobe_d = 0; | |
| end | |
| end | |
| assign tx_data_get_o = tx_data_get_q; | |
| // calculate crc16 | |
| assign crc16_invert = serial_tx_data ^ crc16_q[15]; | |
| always_comb begin : proc_crc16 | |
| crc16_d = crc16_q; // default assignment | |
| if (pkt_start_i) begin | |
| crc16_d = 16'b1111111111111111; | |
| end | |
| if (bit_strobe_i && data_payload_q && !bitstuff_q4 && !pkt_start_i) begin | |
| crc16_d = {crc16_q[14:0], 1'b0} ^ ({16{crc16_invert}} & 16'b1000000000000101); | |
| end | |
| end | |
| /////////////////////// | |
| // Regular Registers // | |
| /////////////////////// | |
| always_ff @(posedge clk_i or negedge rst_ni) begin : proc_reg | |
| if (!rst_ni) begin | |
| state_q <= Idle; | |
| data_payload_q <= 0; | |
| data_shift_reg_q <= 0; | |
| oe_shift_reg_q <= 0; | |
| se0_shift_reg_q <= 0; | |
| tx_data_get_q <= 0; | |
| byte_strobe_q <= 0; | |
| bit_history_q <= 0; | |
| bit_count_q <= 0; | |
| crc16_q <= 0; | |
| end else begin | |
| if (link_reset_i) begin | |
| state_q <= Idle; | |
| data_payload_q <= 0; | |
| data_shift_reg_q <= 0; | |
| oe_shift_reg_q <= 0; | |
| se0_shift_reg_q <= 0; | |
| tx_data_get_q <= 0; | |
| byte_strobe_q <= 0; | |
| bit_history_q <= 0; | |
| bit_count_q <= 0; | |
| crc16_q <= 0; | |
| end else begin | |
| state_q <= state_d; | |
| data_payload_q <= data_payload_d; | |
| data_shift_reg_q <= data_shift_reg_d; | |
| oe_shift_reg_q <= oe_shift_reg_d; | |
| se0_shift_reg_q <= se0_shift_reg_d; | |
| tx_data_get_q <= tx_data_get_d; | |
| byte_strobe_q <= byte_strobe_d; | |
| bit_history_q <= bit_history_d; | |
| bit_count_q <= bit_count_d; | |
| crc16_q <= crc16_d; | |
| end | |
| end | |
| end | |
| /////////////////////////////////// | |
| // nrzi and differential driving // | |
| /////////////////////////////////// | |
| // Output FSM | |
| always_comb begin : proc_out_fsm | |
| out_state_d = out_state_q; | |
| out_nrzi_en = 1'b0; | |
| unique case (out_state_q) | |
| OsIdle: begin | |
| if (pkt_start_i || test_mode_start) begin | |
| out_state_d = OsWaitByte; | |
| end | |
| end | |
| OsWaitByte: begin | |
| if (byte_strobe_q) begin | |
| out_state_d = OsTransmit; | |
| end | |
| end | |
| OsTransmit: begin | |
| out_nrzi_en = 1'b1; | |
| if ((bit_strobe_i && !serial_tx_oe)) begin | |
| out_state_d = OsIdle; | |
| end | |
| end | |
| default : out_state_d = OsIdle; | |
| endcase | |
| end | |
| `ASSERT(OutStateValid_A, out_state_q inside {OsIdle, OsWaitByte, OsTransmit}) | |
| always_comb begin : proc_diff | |
| usb_d_d = usb_d_q; | |
| usb_se0_d = usb_se0_q; | |
| oe_d = oe_q; | |
| dp_eop_d = dp_eop_q; | |
| if (pkt_start_i) begin | |
| usb_d_d = 1; // J -> first bit will be K (start of sync) | |
| dp_eop_d = 3'b100; // Eop: {SE0, SE0, J} | |
| end else if (bit_strobe_i && out_nrzi_en) begin | |
| oe_d = serial_tx_oe; | |
| if (serial_tx_se0) begin | |
| // Eop | |
| dp_eop_d = dp_eop_q >> 1; | |
| if (dp_eop_q[0]) begin | |
| // last bit of Eop: J | |
| usb_d_d = 1; | |
| usb_se0_d = 0; | |
| end else begin | |
| // first two bits of Eop: SE0 | |
| usb_se0_d = 1; | |
| end | |
| end else if (serial_tx_data) begin | |
| // value should stay the same, do nothing | |
| end else begin | |
| usb_d_d = !usb_d_q; | |
| end | |
| // Set to J state when OE=0 to avoid | |
| // glitches | |
| if (!oe_d) begin | |
| usb_d_d = 1; | |
| end | |
| end | |
| end | |
| always_ff @(posedge clk_i or negedge rst_ni) begin : proc_diff_reg | |
| if (!rst_ni) begin | |
| dp_eop_q <= 0; | |
| out_state_q <= OsIdle; | |
| end else begin | |
| if (link_reset_i) begin | |
| dp_eop_q <= 0; | |
| out_state_q <= OsIdle; | |
| end else begin | |
| dp_eop_q <= dp_eop_d; | |
| out_state_q <= out_state_d; | |
| end | |
| end | |
| end | |
| prim_flop u_oe_flop ( | |
| .clk_i, | |
| .rst_ni, | |
| .d_i(link_reset_i ? 1'b0 : oe_d), | |
| .q_o(oe_q) | |
| ); | |
| prim_flop #( | |
| .ResetValue(1) // J state = idle state | |
| ) u_usb_d_flop ( | |
| .clk_i, | |
| .rst_ni, | |
| .d_i(link_reset_i ? 1'b0 : usb_d_d), | |
| .q_o(usb_d_q) | |
| ); | |
| prim_flop u_usb_se0_flop ( | |
| .clk_i, | |
| .rst_ni, | |
| .d_i(link_reset_i ? 1'b0 : usb_se0_d), | |
| .q_o(usb_se0_q) | |
| ); | |
| // Handle the D+ / D- pin flip on the USB side, and provide both the | |
| // dp/dn and d/se0 interfaces, for compatibility with multiple driver types. | |
| logic usb_se0_flipped, usb_dp_flipped, usb_dn_flipped; | |
| always_comb begin | |
| if (link_reset_i) begin | |
| usb_se0_flipped = 1'b0; | |
| usb_dp_flipped = 1'b0 ^ cfg_pinflip_i; | |
| usb_dn_flipped = 1'b1 ^ cfg_pinflip_i; | |
| end else begin | |
| usb_se0_flipped = usb_se0_d; | |
| usb_dp_flipped = (cfg_pinflip_i ? ~usb_d_d : usb_d_d) & ~usb_se0_d; | |
| usb_dn_flipped = (cfg_pinflip_i ? usb_d_d : ~usb_d_d) & ~usb_se0_d; | |
| end | |
| end | |
| // Use registered outputs for the I/Os | |
| prim_flop #( | |
| .ResetValue(1) // J state = idle state | |
| ) u_usb_d_o_flop ( | |
| .clk_i, | |
| .rst_ni, | |
| .d_i(usb_dp_flipped), // Note: single-ended 'D' output mirrors D+ | |
| .q_o(usb_d_o) | |
| ); | |
| prim_flop #( | |
| .ResetValue(0) // J state = idle state | |
| ) u_usb_se0_o_flop ( | |
| .clk_i, | |
| .rst_ni, | |
| .d_i(usb_se0_flipped), | |
| .q_o(usb_se0_o) | |
| ); | |
| prim_flop #( | |
| .ResetValue(1) // J state = idle state | |
| ) u_usb_dp_o_flop ( | |
| .clk_i, | |
| .rst_ni, | |
| .d_i(usb_dp_flipped), | |
| .q_o(usb_dp_o) | |
| ); | |
| prim_flop #( | |
| .ResetValue(0) // J state = idle state | |
| ) u_usb_dn_o_flop ( | |
| .clk_i, | |
| .rst_ni, | |
| .d_i(usb_dn_flipped), | |
| .q_o(usb_dn_o) | |
| ); | |
| assign usb_oe_o = oe_q; | |
| endmodule |