blob: 439ecd8721a056dba170a8af3c688819cc01e9b7 [file] [log] [blame]
// 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 a inter-packed 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_d_flipped, usb_se0_flipped, usb_dp_flipped, usb_dn_flipped;
always_comb begin
if (link_reset_i) begin
usb_d_flipped = 1'b0 ^ cfg_pinflip_i;
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_d_flipped = usb_d_d ^ cfg_pinflip_i;
usb_se0_flipped = usb_se0_d;
usb_dp_flipped = (usb_d_d & ~usb_se0_d) ^ cfg_pinflip_i;
usb_dn_flipped = (~usb_d_d & ~usb_se0_d) ^ cfg_pinflip_i;
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_d_flipped),
.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