| // 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 | |
| // | |
| // | |
| `include "prim_assert.sv" | |
| // This module runs on the 48MHz USB clock | |
| module usbdev_usbif #( | |
| parameter int NEndpoints = 12, | |
| parameter int AVFifoWidth = 4, | |
| parameter int RXFifoWidth = 4, | |
| parameter int MaxPktSizeByte = 64, | |
| parameter int NBuf = 4, | |
| parameter int SramAw = 4, | |
| localparam int NBufWidth = $clog2(NBuf), // derived parameter | |
| localparam int PktW = $clog2(MaxPktSizeByte) // derived parameter | |
| ) ( | |
| input logic clk_48mhz_i, // 48MHz USB clock | |
| input logic rst_ni, | |
| // Pins (synchronous) | |
| input logic usb_d_i, | |
| input logic usb_dp_i, | |
| input logic usb_dn_i, | |
| output logic usb_d_o, | |
| output logic usb_se0_o, | |
| output logic usb_oe_o, | |
| output logic usb_pullup_en_o, | |
| input logic usb_sense_i, | |
| // receive (OUT or SETUP) side | |
| input logic [NEndpoints-1:0] rx_setup_i, | |
| input logic [NEndpoints-1:0] rx_out_i, | |
| input logic [NEndpoints-1:0] rx_stall_i, | |
| input logic av_rvalid_i, | |
| output logic av_rready_o, | |
| input logic [AVFifoWidth - 1: 0]av_rdata_i, | |
| output logic event_av_empty_o, | |
| output logic rx_wvalid_o, | |
| input logic rx_wready_i, | |
| output logic [RXFifoWidth - 1:0] rx_wdata_o, | |
| output logic event_rx_full_o, | |
| output logic setup_received_o, | |
| output logic [3:0] out_endpoint_o, | |
| output logic out_endpoint_val_o, | |
| // transmit (IN) side | |
| input logic [NBufWidth - 1:0] in_buf_i, | |
| input logic [PktW:0] in_size_i, | |
| input logic [NEndpoints-1:0] in_stall_i, | |
| input logic [NEndpoints-1:0] in_rdy_i, | |
| output logic set_sent_o, | |
| output logic [3:0] in_endpoint_o, | |
| output logic in_endpoint_val_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 logic enable_i, | |
| input logic [6:0] devaddr_i, | |
| output logic clr_devaddr_o, | |
| input logic [NEndpoints-1:0] ep_iso_i, | |
| input logic cfg_eop_single_bit_i, // 1: detect a single SE0 bit as EOP | |
| input logic cfg_rx_differential_i, // 1: use differential rx data on usb_d_i | |
| input logic tx_osc_test_mode_i, // Oscillator test mode: constant JK output | |
| input logic [NEndpoints-1:0] data_toggle_clear_i, // Clear the data toggles for an EP | |
| // status | |
| output logic frame_start_o, | |
| output logic [10:0] frame_o, | |
| output logic [2:0] link_state_o, | |
| output logic link_disconnect_o, | |
| output logic link_connect_o, | |
| output logic link_reset_o, | |
| output logic link_active_o, | |
| output logic link_suspend_o, | |
| output logic link_resume_o, | |
| output logic link_in_err_o, | |
| output logic link_out_err_o, | |
| output logic host_lost_o, | |
| output logic rx_crc_err_o, | |
| output logic rx_pid_err_o, | |
| output logic rx_bitstuff_err_o | |
| ); | |
| assign usb_pullup_en_o = enable_i; | |
| // OUT or SETUP direction | |
| logic [PktW:0] out_max_used_d, out_max_used_q; | |
| 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 [NEndpoints-1:0] out_ep_setup, out_ep_full, out_ep_stall; | |
| logic [NEndpoints-1:0] setup_blocked, out_blocked; | |
| logic [31:0] wdata; | |
| logic mem_read; | |
| logic [SramAw-1:0] mem_waddr, mem_raddr; | |
| logic link_reset; | |
| logic sof_valid; | |
| // Make sure out_endpoint_o can safely be used to index signals of NEndpoints width. | |
| assign out_endpoint_val_o = int'(out_ep_current) < NEndpoints; | |
| assign out_endpoint_o = out_endpoint_val_o ? out_ep_current : '0; | |
| assign link_reset_o = link_reset; | |
| assign clr_devaddr_o = ~enable_i | link_reset; | |
| assign frame_start_o = sof_valid; | |
| assign link_out_err_o = out_ep_rollback; | |
| always_comb begin | |
| if (out_ep_acked || out_ep_rollback) begin | |
| out_max_used_d = 0; | |
| end else if (out_ep_data_put) begin | |
| // In the normal case <MaxPktSizeByte this is out_max_used_q <= out_ep_put_addr | |
| // Following all ones out_max_used_q 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 | |
| if (int'(out_max_used_q) < MaxPktSizeByte - 1) begin | |
| out_max_used_d = {1'b0, out_ep_put_addr}; | |
| end else if (int'(out_max_used_q) < MaxPktSizeByte + 1) begin | |
| out_max_used_d = out_max_used_q + 1; | |
| end else begin | |
| out_max_used_d = out_max_used_q; | |
| end | |
| end else begin | |
| out_max_used_d = out_max_used_q; | |
| end | |
| end // always_comb | |
| // don't write if the address has wrapped (happens for two CRC bytes after max data) | |
| logic std_write_d, std_write_q; | |
| assign std_write_d = out_ep_data_put & ((int'(out_max_used_q) < MaxPktSizeByte - 1) & | |
| (out_ep_put_addr[1:0] == 2'b11)); | |
| always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin | |
| if (!rst_ni) begin | |
| out_max_used_q <= '0; | |
| wdata <= '0; | |
| std_write_q <= 1'b0; | |
| end else begin | |
| out_max_used_q <= out_max_used_d; | |
| std_write_q <= std_write_d; | |
| if (out_ep_data_put) begin | |
| unique 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 | |
| default: begin | |
| wdata[7:0] <= out_ep_data; | |
| end | |
| endcase | |
| end | |
| 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_q | | |
| (~out_max_used_q[PktW] & (out_max_used_q[1:0] != 2'b11) & out_ep_acked); | |
| assign mem_waddr = {av_rdata_i, out_max_used_q[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_endpoint_o]; | |
| 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_q - 1; | |
| assign rx_wdata_o = { | |
| out_endpoint_o, | |
| 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 = {NEndpoints{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 | |
| // This will not trigger, if the AV Buffer is empty, in that case we have replied | |
| // with a NAK, which is illegal anyway | |
| assign setup_received_o = current_setup & rx_wvalid_o; | |
| // IN (device to host) transfers | |
| logic in_ep_xfr_end, in_ep_data_get, in_data_done, in_ep_newpkt, pkt_start_rd; | |
| logic [NEndpoints-1:0] in_ep_data_done; | |
| logic [PktW-1:0] in_ep_get_addr; | |
| logic [7:0] in_ep_data; | |
| logic [3:0] in_ep_current; | |
| // Make sure in_endpoint_o can safely be used to index signals of NEndpoints width. | |
| assign in_endpoint_val_o = int'(in_ep_current) < NEndpoints; | |
| assign in_endpoint_o = in_endpoint_val_o ? in_ep_current : '0; | |
| // 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; | |
| 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_xfr_end; | |
| logic [10:0] frame_index_raw; | |
| logic rx_jjj_det; | |
| usb_fs_nb_pe #( | |
| .NumOutEps (NEndpoints), | |
| .NumInEps (NEndpoints), | |
| .MaxPktSizeByte (MaxPktSizeByte) | |
| ) u_usb_fs_nb_pe ( | |
| .clk_48mhz_i (clk_48mhz_i), | |
| .rst_ni (rst_ni), | |
| .link_reset_i (link_reset), | |
| .cfg_eop_single_bit_i (cfg_eop_single_bit_i), | |
| .cfg_rx_differential_i (cfg_rx_differential_i), | |
| .tx_osc_test_mode_i (tx_osc_test_mode_i), | |
| .data_toggle_clear_i (data_toggle_clear_i), | |
| .usb_d_i (usb_d_i), | |
| .usb_dp_i (usb_dp_i), | |
| .usb_dn_i (usb_dn_i), | |
| .usb_d_o (usb_d_o), | |
| .usb_se0_o (usb_se0_o), | |
| .usb_oe_o (usb_oe_o), | |
| .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), | |
| .out_ep_iso_i (ep_iso_i), | |
| // in endpoint interfaces | |
| .in_ep_current_o (in_ep_current), | |
| .in_ep_rollback_o (link_in_err_o), | |
| .in_ep_xfr_end_o (in_ep_xfr_end), | |
| .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), | |
| .in_ep_iso_i (ep_iso_i), | |
| // rx status | |
| .rx_jjj_det_o (rx_jjj_det), | |
| // error signals | |
| .rx_crc_err_o (rx_crc_err_o), | |
| .rx_pid_err_o (rx_pid_err_o), | |
| .rx_bitstuff_err_o (rx_bitstuff_err_o), | |
| // 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_dp_i (usb_dp_i), | |
| .usb_dn_i (usb_dn_i), | |
| .usb_oe_i (usb_oe_o), | |
| .rx_jjj_det_i (rx_jjj_det), | |
| .sof_valid_i (sof_valid), | |
| .link_disconnect_o (link_disconnect_o), | |
| .link_connect_o (link_connect_o), | |
| .link_reset_o (link_reset), | |
| .link_active_o (link_active_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) | |
| ); | |
| //////////////// | |
| // Assertions // | |
| //////////////// | |
| endmodule |