diff --git a/rtl/eth/taxi_rgmii_phy_if.f b/rtl/eth/taxi_rgmii_phy_if.f new file mode 100644 index 0000000..7012b12 --- /dev/null +++ b/rtl/eth/taxi_rgmii_phy_if.f @@ -0,0 +1,5 @@ +taxi_rgmii_phy_if.sv +../io/taxi_ssio_ddr_in.sv +../io/taxi_iddr.sv +../io/taxi_oddr.sv +../sync/taxi_sync_reset.sv diff --git a/rtl/eth/taxi_rgmii_phy_if.sv b/rtl/eth/taxi_rgmii_phy_if.sv new file mode 100644 index 0000000..4581495 --- /dev/null +++ b/rtl/eth/taxi_rgmii_phy_if.sv @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: CERN-OHL-S-2.0 +/* + +Copyright (c) 2015-2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +*/ + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * RGMII PHY interface + */ +module taxi_rgmii_phy_if # +( + // simulation (set to avoid vendor primitives) + parameter logic SIM = 1'b0, + // vendor ("GENERIC", "XILINX", "ALTERA") + parameter VENDOR = "XILINX", + // device family + parameter FAMILY = "virtex7", + // Use 90 degree clock for RGMII transmit + parameter logic USE_CLK90 = 1'b1 +) +( + input wire logic gtx_clk, + input wire logic gtx_clk90, + input wire logic gtx_rst, + + /* + * GMII interface to MAC + */ + output wire logic mac_gmii_rx_clk, + output wire logic mac_gmii_rx_rst, + output wire logic [7:0] mac_gmii_rxd, + output wire logic mac_gmii_rx_dv, + output wire logic mac_gmii_rx_er, + output wire logic mac_gmii_tx_clk, + output wire logic mac_gmii_tx_rst, + output wire logic mac_gmii_tx_clk_en, + input wire logic [7:0] mac_gmii_txd, + input wire logic mac_gmii_tx_en, + input wire logic mac_gmii_tx_er, + + /* + * RGMII interface to PHY + */ + input wire logic phy_rgmii_rx_clk, + input wire logic [3:0] phy_rgmii_rxd, + input wire logic phy_rgmii_rx_ctl, + output wire logic phy_rgmii_tx_clk, + output wire logic [3:0] phy_rgmii_txd, + output wire logic phy_rgmii_tx_ctl, + + /* + * Control + */ + input wire logic [1:0] speed +); + +// receive + +wire rgmii_rx_ctl_1; +wire rgmii_rx_ctl_2; + +taxi_ssio_ddr_in #( + .SIM(SIM), + .VENDOR(VENDOR), + .FAMILY(FAMILY), + .WIDTH(5) +) +rx_ssio_ddr_inst ( + .input_clk(phy_rgmii_rx_clk), + .input_d({phy_rgmii_rxd, phy_rgmii_rx_ctl}), + .output_clk(mac_gmii_rx_clk), + .output_q1({mac_gmii_rxd[3:0], rgmii_rx_ctl_1}), + .output_q2({mac_gmii_rxd[7:4], rgmii_rx_ctl_2}) +); + +assign mac_gmii_rx_dv = rgmii_rx_ctl_1; +assign mac_gmii_rx_er = rgmii_rx_ctl_1 ^ rgmii_rx_ctl_2; + +// transmit + +logic rgmii_tx_clk_1_reg = 1'b1; +logic rgmii_tx_clk_2_reg = 1'b0; +logic rgmii_tx_clk_en_reg = 1'b1; + +logic [5:0] count_reg = 6'd0, count_next; + +always_ff @(posedge gtx_clk) begin + rgmii_tx_clk_1_reg <= rgmii_tx_clk_2_reg; + + if (speed == 2'b00) begin + // 10M + count_reg <= count_reg + 1; + rgmii_tx_clk_en_reg <= 1'b0; + if (count_reg == 24) begin + rgmii_tx_clk_1_reg <= 1'b1; + rgmii_tx_clk_2_reg <= 1'b1; + end else if (count_reg >= 49) begin + rgmii_tx_clk_2_reg <= 1'b0; + rgmii_tx_clk_en_reg <= 1'b1; + count_reg <= 0; + end + end else if (speed == 2'b01) begin + // 100M + count_reg <= count_reg + 1; + rgmii_tx_clk_en_reg <= 1'b0; + if (count_reg == 2) begin + rgmii_tx_clk_1_reg <= 1'b1; + rgmii_tx_clk_2_reg <= 1'b1; + end else if (count_reg >= 4) begin + rgmii_tx_clk_2_reg <= 1'b0; + rgmii_tx_clk_en_reg <= 1'b1; + count_reg <= 0; + end + end else begin + // 1000M + rgmii_tx_clk_1_reg <= 1'b1; + rgmii_tx_clk_2_reg <= 1'b0; + rgmii_tx_clk_en_reg <= 1'b1; + end + + if (gtx_rst) begin + rgmii_tx_clk_1_reg <= 1'b1; + rgmii_tx_clk_2_reg <= 1'b0; + rgmii_tx_clk_en_reg <= 1'b1; + count_reg <= 0; + end +end + +logic [3:0] rgmii_txd_1; +logic [3:0] rgmii_txd_2; +logic rgmii_tx_ctl_1; +logic rgmii_tx_ctl_2; + +logic gmii_clk_en; + +always_comb begin + if (speed == 2'b00) begin + // 10M + rgmii_txd_1 = mac_gmii_txd[3:0]; + rgmii_txd_2 = mac_gmii_txd[3:0]; + if (rgmii_tx_clk_1_reg) begin + rgmii_tx_ctl_1 = mac_gmii_tx_en ^ mac_gmii_tx_er; + rgmii_tx_ctl_2 = mac_gmii_tx_en ^ mac_gmii_tx_er; + end else begin + rgmii_tx_ctl_1 = mac_gmii_tx_en; + rgmii_tx_ctl_2 = mac_gmii_tx_en; + end + gmii_clk_en = rgmii_tx_clk_en_reg; + end else if (speed == 2'b01) begin + // 100M + rgmii_txd_1 = mac_gmii_txd[3:0]; + rgmii_txd_2 = mac_gmii_txd[3:0]; + if (rgmii_tx_clk_1_reg) begin + rgmii_tx_ctl_1 = mac_gmii_tx_en ^ mac_gmii_tx_er; + rgmii_tx_ctl_2 = mac_gmii_tx_en ^ mac_gmii_tx_er; + end else begin + rgmii_tx_ctl_1 = mac_gmii_tx_en; + rgmii_tx_ctl_2 = mac_gmii_tx_en; + end + gmii_clk_en = rgmii_tx_clk_en_reg; + end else begin + // 1000M + rgmii_txd_1 = mac_gmii_txd[3:0]; + rgmii_txd_2 = mac_gmii_txd[7:4]; + rgmii_tx_ctl_1 = mac_gmii_tx_en; + rgmii_tx_ctl_2 = mac_gmii_tx_en ^ mac_gmii_tx_er; + gmii_clk_en = 1'b1; + end +end + +taxi_oddr #( + .SIM(SIM), + .VENDOR(VENDOR), + .FAMILY(FAMILY), + .WIDTH(1) +) +clk_oddr_inst ( + .clk(USE_CLK90 ? gtx_clk90 : gtx_clk), + .d1(rgmii_tx_clk_1_reg), + .d2(rgmii_tx_clk_2_reg), + .q(phy_rgmii_tx_clk) +); + +taxi_oddr #( + .SIM(SIM), + .VENDOR(VENDOR), + .FAMILY(FAMILY), + .WIDTH(5) +) +data_oddr_inst ( + .clk(gtx_clk), + .d1({rgmii_txd_1, rgmii_tx_ctl_1}), + .d2({rgmii_txd_2, rgmii_tx_ctl_2}), + .q({phy_rgmii_txd, phy_rgmii_tx_ctl}) +); + +assign mac_gmii_tx_clk = gtx_clk; + +assign mac_gmii_tx_clk_en = gmii_clk_en; + +// reset sync +taxi_sync_reset #( + .N(4) +) +tx_reset_sync_inst ( + .clk(mac_gmii_tx_clk), + .rst(gtx_rst), + .out(mac_gmii_tx_rst) +); + +taxi_sync_reset #( + .N(4) +) +rx_reset_sync_inst ( + .clk(mac_gmii_rx_clk), + .rst(gtx_rst), + .out(mac_gmii_rx_rst) +); + +endmodule + +`resetall diff --git a/syn/vivado/taxi_rgmii_phy_if.tcl b/syn/vivado/taxi_rgmii_phy_if.tcl new file mode 100644 index 0000000..cd55f5c --- /dev/null +++ b/syn/vivado/taxi_rgmii_phy_if.tcl @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: CERN-OHL-S-2.0 +# +# Copyright (c) 2019-2025 FPGA Ninja, LLC +# +# Authors: +# - Alex Forencich +# + +# RGMII PHY IF timing constraints + +foreach inst [get_cells -hier -regexp -filter {(ORIG_REF_NAME =~ "taxi_rgmii_phy_if(__\w+__\d+)?" || + REF_NAME =~ "taxi_rgmii_phy_if(__\w+__\d+)?")}] { + puts "Inserting timing constraints for taxi_rgmii_phy_if instance $inst" + + # clock output + set_property ASYNC_REG TRUE [get_cells $inst/clk_oddr_inst/oddr[0].oddr_inst] + + set src_clk [get_clocks -of_objects [get_pins $inst/rgmii_tx_clk_1_reg_reg/C]] + + set src_clk_period [if {[llength $src_clk]} {get_property -min PERIOD $src_clk} {expr 8.0}] + + set_max_delay -from [get_cells $inst/rgmii_tx_clk_1_reg_reg] -to [get_cells $inst/clk_oddr_inst/oddr[0].oddr_inst] -datapath_only [expr $src_clk_period/4] + set_max_delay -from [get_cells $inst/rgmii_tx_clk_2_reg_reg] -to [get_cells $inst/clk_oddr_inst/oddr[0].oddr_inst] -datapath_only [expr $src_clk_period/4] +}