blob: 6fb6d0e5f8cab1ce7ce5bc0447bbd927dfce6d57 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// USB Full-Speed Device Interface core internals
//
//
// This module runs on the 48MHz USB clock
module usbdev_usbif #(
parameter int AVFifoWidth = 4,
parameter int RXFifoWidth = 4,
parameter int MaxPktSizeByte = 64,
parameter int NBuf = 4,
parameter int SramAw = 4,
parameter int NBufWidth = $clog2(NBuf), // derived parameter
parameter int PktW = $clog2(MaxPktSizeByte) // derived parameter
) (
input clk_48mhz_i, // 48MHz USB clock
input rst_ni,
// Pins
input usb_dp_i,
output logic usb_dp_o,
output logic usb_dp_en_o,
input usb_dn_i,
output logic usb_dn_o,
output logic usb_dn_en_o,
input usb_sense_i,
output logic usb_pullup_o,
output logic usb_pullup_en_o,
// receive (OUT or SETUP) side
input [11:0] rx_setup_i,
input [11:0] rx_out_i,
input [11:0] rx_stall_i,
input av_rvalid_i,
output logic av_rready_o,
input [AVFifoWidth - 1: 0] av_rdata_i,
output logic event_av_empty_o,
output logic rx_wvalid_o,
input rx_wready_i,
output logic [RXFifoWidth - 1:0] rx_wdata_o,
output logic event_rx_full_o,
output logic out_clear_rdy_o,
output [3:0] out_endpoint_o,
// transmit (IN) side
input [NBufWidth - 1:0] in_buf_i,
input [PktW:0] in_size_i,
input [11:0] in_stall_i,
input [11:0] in_rdy_i,
output logic set_sent_o,
output [3:0] in_endpoint_o,
// memory interface
output logic mem_req_o,
output logic mem_write_o,
output logic [SramAw-1:0] mem_addr_o,
output logic [31:0] mem_wdata_o,
input logic [31:0] mem_rdata_i,
// control
input enable_i,
input [6:0] devaddr_i,
output clr_devaddr_o,
// status
output logic [10:0] frame_o,
output logic [1:0] link_state_o,
output logic link_disconnect_o,
output logic link_reset_o,
output logic link_suspend_o,
output logic link_resume_o,
output logic host_lost_o
);
logic usb_tx_en;
// Enable -- This is working but should these be swapped so there is no active pull down?
assign usb_pullup_o = enable_i;
assign usb_pullup_en_o = 1'b1;
assign usb_dp_en_o = usb_tx_en;
assign usb_dn_en_o = usb_tx_en;
assign clr_devaddr_o = ~enable_i | link_reset_o;
// OUT or SETUP direction
logic [PktW:0] out_max_used_next, out_max_used;
logic [PktW-1:0] out_ep_put_addr;
logic [7:0] out_ep_data;
logic [3:0] out_ep_current;
logic out_ep_data_put, out_ep_acked, out_ep_rollback;
logic current_setup, all_out_blocked, out_ep_newpkt;
logic [11:0] out_ep_setup, out_ep_full, out_ep_stall;
logic [11:0] setup_blocked, out_blocked;
logic [31:0] wdata;
logic std_write, mem_read;
logic [SramAw-1:0] mem_waddr, mem_raddr;
assign out_endpoint_o = out_ep_current;
always_comb begin
if (out_ep_acked || out_ep_rollback) begin
out_max_used_next = 0;
end else if (out_ep_data_put) begin
// In the normal case <MaxPktSizeByte this is out_max_used <= out_ep_put_addr
// Following all ones out_max_used will get 1,00..00 and 1,00..01 to cover
// one and two bytes of the CRC overflowing, then stick at 1,00..01
out_max_used_next[0] = (out_max_used[PktW] & out_max_used[0]) ? 1'b1 : out_ep_put_addr[0];
out_max_used_next[PktW - 1: 1] = out_max_used[PktW] ? '0 : out_ep_put_addr[PktW - 1:1];
out_max_used_next[PktW] = (&out_max_used[PktW - 1:0]) | out_max_used[PktW];
end else begin
out_max_used_next = out_max_used;
end
end // always_comb
always_ff @(posedge clk_48mhz_i) begin
out_max_used <= out_max_used_next;
if (out_ep_data_put) begin
case (out_ep_put_addr[1:0])
0: begin
wdata[7:0] <= out_ep_data;
end
1: begin
wdata[15:8] <= out_ep_data;
end
2: begin
wdata[23:16] <= out_ep_data;
end
3: begin
wdata[31:24] <= out_ep_data;
end
endcase
// don't write if the address has wrapped (happens for two CRC bytes after max data)
if (!out_max_used[PktW] && (out_ep_put_addr[1:0] == 2'b11)) begin
std_write <= 1'b1;
end else begin
std_write <= 1'b0;
end
end else begin
std_write <= 1'b0;
end
end // always_ff @ (posedge clk_48mhz_i)
// need extra write at end if packet not multiple of 4 bytes
assign mem_write_o = std_write |
(~out_max_used[PktW] & (out_max_used[1:0] != 2'b11) & out_ep_acked);
assign mem_waddr = {av_rdata_i, out_max_used[PktW-1:2]};
assign mem_wdata_o = wdata;
assign mem_addr_o = mem_write_o ? mem_waddr : mem_raddr;
assign mem_req_o = mem_read | mem_write_o;
assign current_setup = out_ep_setup[out_ep_current]; // lint: out_ep_current range was checked
logic [PktW:0] out_max_minus1;
// -2 for CRC bytes but +1 for zero-based address to size
assign out_max_minus1 = out_max_used - 1;
assign rx_wdata_o = {
out_ep_current,
current_setup,
out_max_minus1,
av_rdata_i
};
assign rx_wvalid_o = out_ep_acked & ~all_out_blocked;
// Pop the available fifo after the write that used the previous value
always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
if (!rst_ni) begin
av_rready_o <= 1'b0;
end else begin
av_rready_o <= rx_wvalid_o;
end
end
// full here covers the software blocking by clearing the enable
assign setup_blocked = out_ep_setup & ~rx_setup_i;
assign out_blocked = ~out_ep_setup & ~rx_out_i;
// full also covers being blocked because the hardware can't take any transaction
assign all_out_blocked = (~rx_wready_i) | (~av_rvalid_i);
// These are used to raise appropriate interrupt
assign event_av_empty_o = out_ep_newpkt & (~av_rvalid_i);
assign event_rx_full_o = out_ep_newpkt & (~rx_wready_i);
assign out_ep_full = {12{all_out_blocked}} | setup_blocked | out_blocked;
assign out_ep_stall = rx_stall_i;
// Need to clear IN read if a SETUP is received because it may use the IN channel
assign out_clear_rdy_o = current_setup & rx_wvalid_o;
// IN (device to host) transfers
logic in_ep_acked, in_ep_data_get, in_data_done, in_ep_newpkt, pkt_start_rd;
logic [11:0] in_ep_data_done;
logic [PktW-1:0] in_ep_get_addr;
logic [7:0] in_ep_data;
// The protocol engine will automatically generate done for a full-length packet
// Note: this does the correct thing for sending zero length packets
assign in_data_done = {1'b0, in_ep_get_addr} == in_size_i;
always_comb begin
in_ep_data_done = '0;
in_ep_data_done[in_endpoint_o] = in_data_done; // lint: in_endpoint_o range was checked
end
// Need extra read at start of packet to get the first word of data
// Delay by one cycle from the in_endpoint update
always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
if (!rst_ni) begin
pkt_start_rd <= 1'b0;
end else begin
pkt_start_rd <= in_ep_newpkt;
end
end
assign mem_raddr = {in_buf_i,in_ep_get_addr[PktW-1:2]};
assign mem_read = pkt_start_rd | (in_ep_data_get & (in_ep_get_addr[1:0] == 2'b0));
assign in_ep_data = in_ep_get_addr[1] ?
(in_ep_get_addr[0] ? mem_rdata_i[31:24] : mem_rdata_i[23:16]) :
(in_ep_get_addr[0] ? mem_rdata_i[15:8] : mem_rdata_i[7:0]);
assign set_sent_o = in_ep_acked;
logic sof_valid;
logic [10:0] frame_index_raw;
usb_fs_nb_pe #(
.NumOutEps(12),
.NumInEps(12),
.MaxPktSizeByte(MaxPktSizeByte)
) u_usb_fs_nb_pe (
.clk_48mhz_i(clk_48mhz_i),
.rst_ni(rst_ni),
.link_reset_i(link_reset_o),
.usb_p_tx_o(usb_dp_o),
.usb_n_tx_o(usb_dn_o),
.usb_p_rx_i(usb_dp_i),
.usb_n_rx_i(usb_dn_i),
.usb_tx_en_o(usb_tx_en),
.dev_addr_i(devaddr_i),
// out endpoint interfaces
.out_ep_current_o(out_ep_current),
.out_ep_newpkt_o(out_ep_newpkt),
.out_ep_data_put_o(out_ep_data_put),
.out_ep_put_addr_o(out_ep_put_addr),
.out_ep_data_o(out_ep_data),
.out_ep_acked_o(out_ep_acked),
.out_ep_rollback_o(out_ep_rollback),
.out_ep_setup_o(out_ep_setup),
.out_ep_full_i(out_ep_full),
.out_ep_stall_i(out_ep_stall),
// in endpoint interfaces
.in_ep_current_o(in_endpoint_o),
.in_ep_rollback_o(),
.in_ep_acked_o(in_ep_acked),
.in_ep_get_addr_o(in_ep_get_addr),
.in_ep_data_get_o(in_ep_data_get),
.in_ep_newpkt_o(in_ep_newpkt),
.in_ep_stall_i(in_stall_i),
.in_ep_has_data_i(in_rdy_i),
.in_ep_data_i(in_ep_data),
.in_ep_data_done_i(in_ep_data_done),
// sof interface
.sof_valid_o(sof_valid),
.frame_index_o(frame_index_raw)
);
// us_tick ticks for one cycle every us
logic [5:0] ns_cnt;
logic us_tick;
assign us_tick = (ns_cnt == 6'd48);
always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
if (!rst_ni) begin
ns_cnt <= '0;
end else begin
if (us_tick) begin
ns_cnt <= '0;
end else begin
ns_cnt <= ns_cnt + 1'b1;
end
end
end
// Capture frame number (host sends evert 1ms)
always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
if (!rst_ni) begin
frame_o <= '0;
end else begin
if (sof_valid) begin
frame_o <= frame_index_raw;
end
end
end
usbdev_linkstate u_usbdev_linkstate (
.clk_48mhz_i (clk_48mhz_i),
.rst_ni (rst_ni),
.us_tick_i (us_tick),
.usb_sense_i (usb_sense_i),
.usb_rx_dp_i (usb_dp_i),
.usb_rx_dn_i (usb_dn_i),
.sof_valid_i (sof_valid),
.link_disconnect_o (link_disconnect_o),
.link_reset_o (link_reset_o),
.link_suspend_o (link_suspend_o),
.link_resume_o (link_resume_o),
.link_state_o (link_state_o),
.host_lost_o (host_lost_o)
);
endmodule