| // Copyright lowRISC contributors. | 
 | // Copyright ETH Zurich. | 
 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
 | // SPDX-License-Identifier: Apache-2.0 | 
 | // | 
 | // USB IO Mux | 
 | // | 
 | // Muxes the USB IO signals from register access, differential signaling, single-ended signaling | 
 | // and swaps D+/D- if configured. The incomming signals are also muxed and synchronized to the | 
 | // corresponding clock domain. | 
 |  | 
 | module usbdev_iomux | 
 |   import usbdev_reg_pkg::*; | 
 | ( | 
 |   input  logic                          clk_i, | 
 |   input  logic                          rst_ni, | 
 |   input  logic                          clk_usb_48mhz_i, // use usb_ prefix for signals in this clk | 
 |   input  logic                          rst_usb_48mhz_ni, | 
 |  | 
 |   // Register interface (system clk) | 
 |   output usbdev_hw2reg_phy_pins_sense_reg_t sys_hw2reg_sense_o, | 
 |   input  usbdev_reg2hw_phy_pins_drive_reg_t sys_reg2hw_drive_i, | 
 |   input  usbdev_reg2hw_phy_config_reg_t     sys_reg2hw_config_i, | 
 |   output logic                              sys_usb_sense_o, | 
 |  | 
 |   // External USB Interface(s) (async) | 
 |   input  logic                          cio_usb_d_i, | 
 |   input  logic                          cio_usb_dp_i, | 
 |   input  logic                          cio_usb_dn_i, | 
 |  | 
 |   output logic                          cio_usb_d_o, | 
 |   output logic                          cio_usb_se0_o, | 
 |   output logic                          cio_usb_dp_o, | 
 |   output logic                          cio_usb_dn_o, | 
 |   output logic                          cio_usb_oe_o, | 
 |  | 
 |   output logic                          cio_usb_tx_mode_se_o, | 
 |   input  logic                          cio_usb_sense_i, | 
 |   output logic                          cio_usb_dp_pullup_en_o, | 
 |   output logic                          cio_usb_dn_pullup_en_o, | 
 |   output logic                          cio_usb_suspend_o, | 
 |  | 
 |   // Internal USB Interface (usb clk) | 
 |   output logic                          usb_rx_d_o, | 
 |   output logic                          usb_rx_dp_o, | 
 |   output logic                          usb_rx_dn_o, | 
 |   input  logic                          usb_tx_d_i, | 
 |   input  logic                          usb_tx_se0_i, | 
 |   input  logic                          usb_tx_oe_i, | 
 |   output logic                          usb_pwr_sense_o, | 
 |   input  logic                          usb_pullup_en_i, | 
 |   input  logic                          usb_suspend_i | 
 | ); | 
 |  | 
 |   logic cio_usb_d_flipped; | 
 |   logic cio_usb_dp_pullup_en, cio_usb_dn_pullup_en; | 
 |  | 
 |   logic async_pwr_sense, sys_usb_sense; | 
 |   logic cio_usb_dp, cio_usb_dn, cio_usb_d; | 
 |   logic pinflip; | 
 |   logic unused_eop_single_bit; | 
 |   logic unused_rx_differential_mode; | 
 |   logic unused_usb_ref_disable; | 
 |   logic unused_tx_osc_test_mode; | 
 |  | 
 |   assign unused_eop_single_bit       = sys_reg2hw_config_i.eop_single_bit.q; | 
 |   assign unused_usb_ref_disable      = sys_reg2hw_config_i.usb_ref_disable.q; | 
 |   assign unused_tx_osc_test_mode     = sys_reg2hw_config_i.tx_osc_test_mode.q; | 
 |   assign unused_rx_differential_mode = sys_reg2hw_config_i.rx_differential_mode.q; | 
 |  | 
 |   ////////// | 
 |   // CDCs // | 
 |   ////////// | 
 |  | 
 |   // USB pins sense (to sysclk) | 
 |   prim_flop_2sync #( | 
 |     .Width (10) | 
 |   ) cdc_io_to_sys ( | 
 |     .clk_i  (clk_i), | 
 |     .rst_ni (rst_ni), | 
 |     .d_i    ({cio_usb_dp_i, | 
 |               cio_usb_dn_i, | 
 |               cio_usb_d_i, | 
 |               cio_usb_dp_o, | 
 |               cio_usb_dn_o, | 
 |               usb_tx_d_i, | 
 |               usb_tx_se0_i, | 
 |               usb_tx_oe_i, | 
 |               usb_suspend_i, | 
 |               cio_usb_sense_i}), | 
 |     .q_o   ({sys_hw2reg_sense_o.rx_dp_i.d, | 
 |               sys_hw2reg_sense_o.rx_dn_i.d, | 
 |               sys_hw2reg_sense_o.rx_d_i.d, | 
 |               sys_hw2reg_sense_o.tx_dp_o.d, | 
 |               sys_hw2reg_sense_o.tx_dn_o.d, | 
 |               sys_hw2reg_sense_o.tx_d_o.d, | 
 |               sys_hw2reg_sense_o.tx_se0_o.d, | 
 |               sys_hw2reg_sense_o.tx_oe_o.d, | 
 |               sys_hw2reg_sense_o.suspend_o.d, | 
 |               sys_usb_sense}) | 
 |   ); | 
 |  | 
 |   assign sys_usb_sense_o                = sys_usb_sense; | 
 |   assign sys_hw2reg_sense_o.pwr_sense.d = sys_usb_sense; | 
 |  | 
 |   // USB input pins (to usbclk) | 
 |   prim_flop_2sync #( | 
 |     .Width (4) | 
 |   ) cdc_io_to_usb ( | 
 |     .clk_i  (clk_usb_48mhz_i), | 
 |     .rst_ni (rst_usb_48mhz_ni), | 
 |     .d_i    ({cio_usb_dp_i, | 
 |               cio_usb_dn_i, | 
 |               cio_usb_d_i, | 
 |               async_pwr_sense}), | 
 |     .q_o    ({cio_usb_dp, | 
 |               cio_usb_dn, | 
 |               cio_usb_d, | 
 |               usb_pwr_sense_o}) | 
 |   ); | 
 |  | 
 |   //////////////////////// | 
 |   // USB output pin mux // | 
 |   //////////////////////// | 
 |  | 
 |   // D+/D- can be swapped based on a config register. | 
 |   assign pinflip = sys_reg2hw_config_i.pinflip.q; | 
 |  | 
 |   prim_generic_clock_mux2 #( | 
 |     .NoFpgaBufG(1) | 
 |   ) i_mux_tx_d_flip ( | 
 |     .clk0_i (usb_tx_d_i), | 
 |     .clk1_i (~usb_tx_d_i), | 
 |     .sel_i  (pinflip), | 
 |     .clk_o  (cio_usb_d_flipped) | 
 |   ); | 
 |   prim_generic_clock_mux2 #( | 
 |     .NoFpgaBufG(1) | 
 |   ) i_mux_dp_pull_flip ( | 
 |     .clk0_i (usb_pullup_en_i), | 
 |     .clk1_i (1'b0), | 
 |     .sel_i  (pinflip), | 
 |     .clk_o  (cio_usb_dp_pullup_en) | 
 |   ); | 
 |   prim_generic_clock_mux2 #( | 
 |     .NoFpgaBufG(1) | 
 |   ) i_mux_dn_pull_flip ( | 
 |     .clk0_i (1'b0), | 
 |     .clk1_i (usb_pullup_en_i), | 
 |     .sel_i  (pinflip), | 
 |     .clk_o  (cio_usb_dn_pullup_en) | 
 |   ); | 
 |  | 
 |   always_comb begin : proc_drive_out | 
 |     // Defaults | 
 |     cio_usb_dn_o           = 1'b0; | 
 |     cio_usb_dp_o           = 1'b0; | 
 |  | 
 |     if (sys_reg2hw_drive_i.en.q) begin | 
 |       // Override from registers | 
 |       cio_usb_dp_o           = sys_reg2hw_drive_i.dp_o.q; | 
 |       cio_usb_dn_o           = sys_reg2hw_drive_i.dn_o.q; | 
 |       cio_usb_dp_pullup_en_o = sys_reg2hw_drive_i.dp_pullup_en_o.q; | 
 |       cio_usb_dn_pullup_en_o = sys_reg2hw_drive_i.dn_pullup_en_o.q; | 
 |       cio_usb_tx_mode_se_o   = sys_reg2hw_drive_i.tx_mode_se_o.q; | 
 |       cio_usb_suspend_o      = sys_reg2hw_drive_i.suspend_o.q; | 
 |  | 
 |     end else begin | 
 |       // Signals from the peripheral core | 
 |       cio_usb_dp_pullup_en_o = cio_usb_dp_pullup_en; | 
 |       cio_usb_dn_pullup_en_o = cio_usb_dn_pullup_en; | 
 |       cio_usb_suspend_o      = usb_suspend_i; | 
 |  | 
 |       if(sys_reg2hw_config_i.tx_differential_mode.q) begin | 
 |         // Differential TX mode (physical IO takes d and se0) | 
 |         // i.e. expect the "else" logic to be in the physical interface | 
 |         cio_usb_tx_mode_se_o   = 1'b0; | 
 |  | 
 |       end else begin | 
 |         // Single-ended mode (physical IO takes dp and dn) | 
 |         cio_usb_tx_mode_se_o   = 1'b1; | 
 |         if (usb_tx_se0_i) begin | 
 |           cio_usb_dp_o = 1'b0; | 
 |           cio_usb_dn_o = 1'b0; | 
 |         end else begin | 
 |           cio_usb_dp_o = cio_usb_d_flipped; | 
 |           cio_usb_dn_o = ~cio_usb_d_flipped; | 
 |         end | 
 |       end | 
 |     end | 
 |   end | 
 |  | 
 |   // Use explicit muxes for the critical output signals, we do this | 
 |   // to avoid glitches from synthesized logic on these signals. | 
 |   // Clock muxes should be used here to achieve the best match between | 
 |   // rising and falling edges on an ASIC. This mismatch on the data line | 
 |   // degrades performance in the JK-KJ jitter test. | 
 |   prim_generic_clock_mux2 #( | 
 |     .NoFpgaBufG(1) | 
 |   ) i_mux_tx_d ( | 
 |     .clk0_i (cio_usb_d_flipped), | 
 |     .clk1_i (sys_reg2hw_drive_i.d_o.q), | 
 |     .sel_i  (sys_reg2hw_drive_i.en.q), | 
 |     .clk_o  (cio_usb_d_o) | 
 |   ); | 
 |   prim_generic_clock_mux2 #( | 
 |     .NoFpgaBufG(1) | 
 |   ) i_mux_tx_se0 ( | 
 |     .clk0_i (usb_tx_se0_i), | 
 |     .clk1_i (sys_reg2hw_drive_i.se0_o.q), | 
 |     .sel_i  (sys_reg2hw_drive_i.en.q), | 
 |     .clk_o  (cio_usb_se0_o) | 
 |   ); | 
 |   prim_generic_clock_mux2 #( | 
 |     .NoFpgaBufG(1) | 
 |   ) i_mux_tx_oe ( | 
 |     .clk0_i (usb_tx_oe_i), | 
 |     .clk1_i (sys_reg2hw_drive_i.oe_o.q), | 
 |     .sel_i  (sys_reg2hw_drive_i.en.q), | 
 |     .clk_o  (cio_usb_oe_o) | 
 |   ); | 
 |  | 
 |   /////////////////////// | 
 |   // USB input pin mux // | 
 |   /////////////////////// | 
 |  | 
 |   // D+/D- can be swapped based on a config register. | 
 |   assign usb_rx_dp_o = pinflip ?  cio_usb_dn : cio_usb_dp; | 
 |   assign usb_rx_dn_o = pinflip ?  cio_usb_dp : cio_usb_dn; | 
 |   assign usb_rx_d_o  = pinflip ? ~cio_usb_d  : cio_usb_d; | 
 |  | 
 |   // Power sense mux | 
 |   always_comb begin : proc_mux_pwr_sense | 
 |     if (sys_reg2hw_config_i.override_pwr_sense_en.q) begin | 
 |       async_pwr_sense = sys_reg2hw_config_i.override_pwr_sense_val.q; | 
 |     end else begin | 
 |       async_pwr_sense = cio_usb_sense_i; | 
 |     end | 
 |   end | 
 |  | 
 | endmodule |