|  | // 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_dp_o, | 
|  | output logic                     usb_dn_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                     rx_wvalid_o, | 
|  | input  logic                     rx_wready_i, | 
|  | output logic [RXFifoWidth - 1:0] rx_wdata_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                     in_ep_xact_end_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, | 
|  |  | 
|  | // time reference | 
|  | input  logic                     us_tick_i, | 
|  |  | 
|  | // control | 
|  | input  logic                     connect_en_i, | 
|  | input  logic [6:0]               devaddr_i, | 
|  | output logic                     clr_devaddr_o, | 
|  | input  logic [NEndpoints-1:0]    in_ep_enabled_i, | 
|  | input  logic [NEndpoints-1:0]    out_ep_enabled_i, | 
|  | input  logic [NEndpoints-1:0]    out_ep_iso_i, | 
|  | input  logic [NEndpoints-1:0]    in_ep_iso_i, | 
|  | input  logic                     diff_rx_ok_i, // 1: differential symbols (K/J) are valid | 
|  | input  logic                     cfg_eop_single_bit_i, // 1: detect a single SE0 bit as EOP | 
|  | input  logic                     cfg_use_diff_rcvr_i, // 1: use single-ended rx data on usb_d_i | 
|  | input  logic                     cfg_pinflip_i, // 1: Treat outputs and inputs as though D+/D- | 
|  | // are flipped | 
|  | 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 | 
|  | input  logic                     resume_link_active_i, // Jump from LinkPowered to LinkResuming | 
|  |  | 
|  | // status | 
|  | output logic                     frame_start_o, // Pulses with host-generated and internal SOF | 
|  | output logic [10:0]              frame_o, | 
|  | output logic                     sof_valid_o, // Pulses with only host-generated SOF. | 
|  | // Used for clock sync. | 
|  | output logic [2:0]               link_state_o, | 
|  | output logic                     link_disconnect_o, | 
|  | output logic                     link_powered_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 | 
|  | ); | 
|  |  | 
|  | // Enable pull-up resistor only if VBUS is active | 
|  | assign usb_pullup_en_o = connect_en_i & usb_sense_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; | 
|  | logic [NEndpoints-1:0]             out_ep_setup, out_ep_full, out_ep_stall; | 
|  | logic [NEndpoints-1:0]             out_blocked; | 
|  | logic [31:0]                       wdata_q, wdata_d; | 
|  | logic                              mem_read; | 
|  | logic [SramAw-1:0]                 mem_waddr, mem_raddr; | 
|  | logic                              link_reset; | 
|  |  | 
|  | // 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  = ~connect_en_i | link_reset; | 
|  | 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_comb begin | 
|  | wdata_d = wdata_q; | 
|  |  | 
|  | unique case (out_ep_put_addr[1:0]) | 
|  | 0:       wdata_d[7:0]   = out_ep_data; | 
|  | 1:       wdata_d[15:8]  = out_ep_data; | 
|  | 2:       wdata_d[23:16] = out_ep_data; | 
|  | 3:       wdata_d[31:24] = out_ep_data; | 
|  | default: wdata_d[7:0]   = out_ep_data; | 
|  | endcase | 
|  | end | 
|  |  | 
|  | always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin | 
|  | if (!rst_ni) begin | 
|  | out_max_used_q <= '0; | 
|  | wdata_q        <= '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 | 
|  | wdata_q <= wdata_d; | 
|  | 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 = av_rvalid_i & (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_q; | 
|  | 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; | 
|  | // 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 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); | 
|  |  | 
|  | assign out_ep_full = {NEndpoints{all_out_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) transactions | 
|  | logic                  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]); | 
|  |  | 
|  | logic [10:0]     frame_index_raw; | 
|  | logic            rx_idle_det; | 
|  | logic            rx_j_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), | 
|  | .link_active_i         (link_active_o), | 
|  |  | 
|  | .cfg_eop_single_bit_i  (cfg_eop_single_bit_i), | 
|  | .cfg_use_diff_rcvr_i   (cfg_use_diff_rcvr_i), | 
|  | .cfg_pinflip_i         (cfg_pinflip_i), | 
|  | .tx_osc_test_mode_i    (tx_osc_test_mode_i), | 
|  | .data_toggle_clear_i   (data_toggle_clear_i), | 
|  | .diff_rx_ok_i          (diff_rx_ok_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_dp_o              (usb_dp_o), | 
|  | .usb_dn_o              (usb_dn_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_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_enabled_i      (out_ep_enabled_i), | 
|  | .out_ep_control_i      (rx_setup_i), | 
|  | .out_ep_full_i         (out_ep_full), | 
|  | .out_ep_stall_i        (out_ep_stall), | 
|  | .out_ep_iso_i          (out_ep_iso_i), | 
|  |  | 
|  | // in endpoint interfaces | 
|  | .in_ep_current_o       (in_ep_current), | 
|  | .in_ep_rollback_o      (link_in_err_o), | 
|  | .in_ep_xact_end_o      (in_ep_xact_end_o), | 
|  | .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_enabled_i       (in_ep_enabled_i), | 
|  | .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           (in_ep_iso_i), | 
|  |  | 
|  | // rx status | 
|  | .rx_idle_det_o         (rx_idle_det), | 
|  | .rx_j_det_o            (rx_j_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_o), | 
|  | .frame_index_o         (frame_index_raw) | 
|  | ); | 
|  |  | 
|  | // Capture frame number (host sends every 1ms) | 
|  | // Generate an internal SOF if the host's is missing. | 
|  | logic do_internal_sof; | 
|  | logic [10:0] frame_d, frame_q; | 
|  |  | 
|  | assign frame_o = frame_q; | 
|  | assign frame_start_o = (frame_q != frame_d); | 
|  |  | 
|  | always_comb begin | 
|  | frame_d = frame_q; | 
|  | if (sof_valid_o) begin | 
|  | frame_d = frame_index_raw; | 
|  | end else if (do_internal_sof) begin | 
|  | frame_d = frame_q + 1; | 
|  | end | 
|  | end | 
|  |  | 
|  | always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin | 
|  | if (!rst_ni) begin | 
|  | frame_q <= '0; | 
|  | end else begin | 
|  | frame_q <= frame_d; | 
|  | end | 
|  | end | 
|  |  | 
|  | usbdev_linkstate u_usbdev_linkstate ( | 
|  | .clk_48mhz_i           (clk_48mhz_i), | 
|  | .rst_ni                (rst_ni), | 
|  | .us_tick_i             (us_tick_i), | 
|  | .usb_sense_i           (usb_sense_i), | 
|  | .usb_dp_i              (usb_dp_i), | 
|  | .usb_dn_i              (usb_dn_i), | 
|  | .usb_oe_i              (usb_oe_o), | 
|  | .usb_pullup_en_i       (connect_en_i), | 
|  | .rx_idle_det_i         (rx_idle_det), | 
|  | .rx_j_det_i            (rx_j_det), | 
|  | .sof_valid_i           (sof_valid_o), | 
|  | .resume_link_active_i  (resume_link_active_i), | 
|  | .link_disconnect_o     (link_disconnect_o), | 
|  | .link_powered_o        (link_powered_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), | 
|  | .sof_missed_o          (do_internal_sof) | 
|  | ); | 
|  |  | 
|  | //////////////// | 
|  | // Assertions // | 
|  | //////////////// | 
|  | `ASSERT_INIT(ParamNEndpointsValid, (NEndpoints > 0) && (NEndpoints <= 16)) | 
|  | `ASSERT_INIT(ParamAVFifoWidthValid, AVFifoWidth > 0) | 
|  | `ASSERT_INIT(ParamRXFifoWidthValid, RXFifoWidth > 0) | 
|  | `ASSERT_INIT(ParamMaxPktSizeByteValid, MaxPktSizeByte == 64) | 
|  | `ASSERT_INIT(ParamNBufValid, NBuf > 1) | 
|  | // The SRAM should be large enough for all the buffers of max size (4-byte | 
|  | // data width) | 
|  | `ASSERT_INIT(ParamSramAwValid, SramAw >= NBufWidth + PktW - 2) | 
|  |  | 
|  | endmodule |