blob: 89dc8f38780b3a6dce7099d4a5d1428ca78c272c [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).
// USB Full Speed Non-Buffered Protocol Engine for IN endpoints
//
// Decode IN requests from host and respond with data if there is any
//
// Based on usb_fs_in_pe.v from the TinyFPGA-Bootloader project but
// this version contains no packet buffers
`include "prim_assert.sv"
module usb_fs_nb_in_pe #(
parameter logic [4:0] NumInEps = 12,
parameter int unsigned MaxInPktSizeByte = 32,
// Targeting 17 bit times for the timeout. The counter runs on the 48 MHz
// clock, the SOP delay to rx_pkt_start_i is 3 bit times, and the EOP delay
// to tx_pkt_end_i is essentially 0. This timeout originates from Section
// 7.1.19.1 of the USB 2.0 specification.
parameter int unsigned AckTimeoutCnt = (17 + 3) * 4,
localparam int unsigned InEpW = $clog2(NumInEps), // derived parameter
localparam int unsigned PktW = $clog2(MaxInPktSizeByte), // derived parameter
localparam int unsigned AckTimeoutCntW = $clog2(AckTimeoutCnt) // derived parameter
) (
input logic clk_48mhz_i,
input logic rst_ni,
input logic link_reset_i,
input logic link_active_i,
input logic [6:0] dev_addr_i,
////////////////////
// endpoint interface
////////////////////
output logic [3:0] in_ep_current_o, // Other signals addressed to this ep
output logic in_ep_rollback_o, // Bad termination, rollback transaction
output logic in_ep_xact_end_o, // good termination, transaction complete
output logic [PktW - 1:0] in_ep_get_addr_o, // Offset requested (0..pktlen)
output logic in_ep_data_get_o, // Accept data (get_addr advances too)
output logic in_ep_newpkt_o, // New IN packet starting (updates in_ep_current_o)
input logic [NumInEps-1:0] in_ep_enabled_i, // Endpoint is enabled and produces handshakes
input logic [NumInEps-1:0] in_ep_stall_i, // Endpoint in a stall state
input logic [NumInEps-1:0] in_ep_has_data_i, // Endpoint has data to supply
input logic [7:0] in_ep_data_i, // Data for current get_addr
input logic [NumInEps-1:0] in_ep_data_done_i, // Set when out of data
input logic [NumInEps-1:0] in_ep_iso_i, // Configure endpoint in isochronous mode
input logic [NumInEps-1:0] data_toggle_clear_i, // Clear the data toggles for an EP
////////////////////
// rx path
////////////////////
// Strobed on reception of packet.
input logic rx_pkt_start_i,
input logic rx_pkt_end_i,
input logic rx_pkt_valid_i,
// Most recent packet received.
input logic [3:0] rx_pid_i,
input logic [6:0] rx_addr_i,
input logic [3:0] rx_endp_i,
////////////////////
// tx path
////////////////////
// Strobe to send new packet.
output logic tx_pkt_start_o,
input logic tx_pkt_end_i,
// Packet type to send
output logic [3:0] tx_pid_o,
// Data payload to send if any
output logic tx_data_avail_o,
input logic tx_data_get_i,
output logic [7:0] tx_data_o
);
////////////////////////////////////////////////////////////////////////////////
// in transaction state machine
////////////////////////////////////////////////////////////////////////////////
import usb_consts_pkg::*;
typedef enum logic [2:0] {
StIdle,
StRcvdIn,
StSendData,
StWaitTxEnd,
StWaitAckStart,
StWaitAck
} state_in_e;
state_in_e in_xact_state;
state_in_e in_xact_state_next;
logic in_xact_end;
assign in_ep_xact_end_o = in_xact_end;
// data toggle state
logic [NumInEps-1:0] data_toggle_q, data_toggle_d;
// endpoint data buffer
logic token_received, setup_token_received, in_token_received, ack_received;
logic more_data_to_send;
logic ep_in_hw, ep_active;
logic [3:0] in_ep_current_d;
// More syntax so can compare with enum
usb_pid_type_e rx_pid_type;
usb_pid_e rx_pid;
assign rx_pid_type = usb_pid_type_e'(rx_pid_i[1:0]);
assign rx_pid = usb_pid_e'(rx_pid_i);
assign token_received =
rx_pkt_end_i &&
rx_pkt_valid_i &&
rx_pid_type == UsbPidTypeToken &&
rx_addr_i == dev_addr_i;
assign setup_token_received =
token_received &&
rx_pid == UsbPidSetup;
assign in_token_received =
token_received &&
rx_pid == UsbPidIn;
assign ack_received =
rx_pkt_end_i &&
rx_pkt_valid_i &&
rx_pid == UsbPidAck;
// Is the specified endpoint actually implemented in hardware?
assign ep_in_hw = {1'b0, rx_endp_i} < NumInEps;
assign in_ep_current_d = ep_in_hw ? rx_endp_i : '0;
// Make widths work - in_ep_current_d/in_ep_current_o only hold implemented endpoint IDs.
// These signals can be used to index signals of NumInEps width.
// They are only valid if ep_active is set, i.e., if the specified endpoint is implemented.
logic [InEpW-1:0] in_ep_index;
logic [InEpW-1:0] in_ep_index_d;
assign in_ep_index = in_ep_current_o[0 +: InEpW];
assign in_ep_index_d = in_ep_current_d[0 +: InEpW];
// Is the endpoint active?
assign ep_active = in_ep_enabled_i[in_ep_index_d] & ep_in_hw;
assign more_data_to_send =
in_ep_has_data_i[in_ep_index] & ~in_ep_data_done_i[in_ep_index];
assign tx_data_avail_o = logic'(in_xact_state == StSendData) & more_data_to_send;
////////////////////////////////////////////////////////////////////////////////
// in transaction state machine
////////////////////////////////////////////////////////////////////////////////
logic [AckTimeoutCntW-1:0] timeout_cntdown_d, timeout_cntdown_q;
logic rollback_in_xact;
always_comb begin
in_xact_state_next = in_xact_state;
in_xact_end = 1'b0;
tx_pkt_start_o = 1'b0;
tx_pid_o = 4'b0000;
rollback_in_xact = 1'b0;
timeout_cntdown_d = AckTimeoutCnt[AckTimeoutCntW-1:0];
unique case (in_xact_state)
StIdle: begin
if (ep_active && in_token_received) begin
in_xact_state_next = StRcvdIn;
end else begin
// Ignore tokens to inactive endpoints. Send no response.
in_xact_state_next = StIdle;
end
end
StRcvdIn: begin
tx_pkt_start_o = 1'b1; // Need to transmit NAK/STALL or DATA
if (in_ep_iso_i[in_ep_index]) begin
// ISO endpoint
// We always need to transmit. When no data is available, we send
// a zero-length packet.
// DATA0 always for full-speed isochronous endpoints
in_xact_state_next = StSendData;
tx_pid_o = {UsbPidData0};
end else if (in_ep_stall_i[in_ep_index]) begin
in_xact_state_next = StIdle;
tx_pid_o = {UsbPidStall}; // STALL
end else if (in_ep_has_data_i[in_ep_index]) begin
in_xact_state_next = StSendData;
tx_pid_o = {data_toggle_q[in_ep_index], 1'b0, {UsbPidTypeData}}; // DATA0/1
end else begin
in_xact_state_next = StIdle;
tx_pid_o = {UsbPidNak}; // NAK
end
end
StSendData: begin
// Use &in_ep_get_addr so width can vary, looking for all ones
if ((!more_data_to_send) || ((&in_ep_get_addr_o) && tx_data_get_i)) begin
if (in_ep_iso_i[in_ep_index]) begin
in_xact_state_next = StIdle; // no ACK for ISO EPs
in_xact_end = in_ep_has_data_i[in_ep_index];
end else begin
if (tx_pkt_end_i) begin
in_xact_state_next = StWaitAckStart;
end else begin
in_xact_state_next = StWaitTxEnd;
end
end
end else begin
in_xact_state_next = StSendData;
end
end
StWaitTxEnd: begin
if (tx_pkt_end_i) begin
in_xact_state_next = StWaitAckStart;
end
end
StWaitAckStart: begin
// The spec says we have up to 18 bit times to wait for the host
// response. If it doesn't arrive in time, we must invalidate the
// transaction.
timeout_cntdown_d = timeout_cntdown_q - 1'b1;
if (rx_pkt_start_i) begin
in_xact_state_next = StWaitAck;
end else if (timeout_cntdown_q == '0) begin
in_xact_state_next = StIdle;
rollback_in_xact = 1'b1;
end else begin
in_xact_state_next = StWaitAckStart;
end
end
StWaitAck: begin
if (ack_received) begin
in_xact_state_next = StIdle;
in_xact_end = 1'b1;
end else if (in_token_received) begin
in_xact_state_next = StRcvdIn;
rollback_in_xact = 1'b1;
end else if (rx_pkt_end_i) begin
in_xact_state_next = StIdle;
rollback_in_xact = 1'b1;
end else begin
in_xact_state_next = StWaitAck;
end
end
default: in_xact_state_next = StIdle;
endcase
end
`ASSERT(InXactStateValid_A,
in_xact_state inside {StIdle, StRcvdIn, StSendData, StWaitTxEnd, StWaitAckStart, StWaitAck},
clk_48mhz_i)
always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
if (!rst_ni) begin
timeout_cntdown_q <= AckTimeoutCnt[AckTimeoutCntW-1:0];
end else begin
timeout_cntdown_q <= timeout_cntdown_d;
end
end
always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
if (!rst_ni) begin
tx_data_o <= '0;
end else begin
tx_data_o <= in_ep_data_i;
end
end
always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
if (!rst_ni) begin
in_xact_state <= StIdle;
in_ep_rollback_o <= 1'b0;
end else if (link_reset_i || !link_active_i) begin
in_xact_state <= StIdle;
in_ep_rollback_o <= 1'b0;
end else begin
in_xact_state <= in_xact_state_next;
in_ep_rollback_o <= rollback_in_xact;
end
end
always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
if (!rst_ni) begin
in_ep_get_addr_o <= '0;
end else begin
if (in_xact_state == StIdle) begin
in_ep_get_addr_o <= '0;
end else if ((in_xact_state == StSendData) && tx_data_get_i) begin
in_ep_get_addr_o <= in_ep_get_addr_o + 1'b1;
end
end
end
always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
if (!rst_ni) begin
in_ep_newpkt_o <= 1'b0;
in_ep_current_o <= '0;
end else begin
if (in_token_received) begin
in_ep_current_o <= in_ep_current_d;
in_ep_newpkt_o <= 1'b1;
end else begin
in_ep_newpkt_o <= 1'b0;
end
end
end
always_comb begin : proc_data_toggle_d
data_toggle_d = data_toggle_q;
if (setup_token_received && ep_active) begin
data_toggle_d[in_ep_index_d] = 1'b1;
end else if ((in_xact_state == StWaitAck) && ack_received) begin
data_toggle_d[in_ep_index] = ~data_toggle_q[in_ep_index];
end
data_toggle_d = data_toggle_d & ~data_toggle_clear_i;
end
always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
if (!rst_ni) begin
data_toggle_q <= '0; // Clear for all endpoints
end else if (link_reset_i) begin
data_toggle_q <= '0; // Clear for all endpoints
end else begin
data_toggle_q <= data_toggle_d;
end
end
always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
if (!rst_ni) begin
in_ep_data_get_o <= 1'b0;
end else begin
if ((in_xact_state == StSendData) && tx_data_get_i) begin
in_ep_data_get_o <= 1'b1;
end else begin
in_ep_data_get_o <= 1'b0;
end
end
end
////////////////
// Assertions //
////////////////
endmodule