| // 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 |