/* * betsy.v * * Copyright (C) 2024 Private Island Networks Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * function: top module for three instantiated Ethernet PHYs + controllers * * */ module betsy ( input rstn, input clk, // 25Mhz input phy0_clk, output phy1_clk, // PHY0 RGMII output phy0_rstn, input phy0_rx_clk, input phy0_rx_ctl, input [3:0] phy0_rx_d, output phy0_tx_clk, output phy0_tx_ctl, output [3:0] phy0_tx_d, output phy0_mdc, inout phy0_mdio, input phy0_intn, inout [1:0] phy0_gpio, // PHY1 RGMII output phy1_rstn, input phy1_rx_clk, input phy1_rx_ctl, input [3:0] phy1_rx_d, output phy1_tx_clk, output phy1_tx_ctl, output [3:0] phy1_tx_d, output phy1_mdc, inout phy1_mdio, input phy1_intn, inout [1:0] phy1_gpio, // PHY2 RGMII output phy2_rstn, input phy2_rx_clk, input phy2_rx_ctl, input [3:0] phy2_rx_d, output phy2_tx_clk, output phy2_tx_ctl, output[3:0] phy2_tx_d, output phy2_mdc, inout phy2_mdio, input phy2_intn, inout [1:0] phy2_gpio, // FLASH output flash_clk, input flash_dqs, output flash_seln, inout [7:0] flash_d, // Debug output [2:0] fpga_led ); localparam LINK_NORMAL_INTERFRAME = 2'b00, LINK_PHY_STATUS = 2'b01, LINK_ERROR_DATA_RX = 2'b10, LINK_NORMAL_DATA_RX = 2'b11; localparam MDIO_ROM_ADDR_SZ = 7; reg [25:0] heart_beat_cnt; reg [7:0] datain_h_sig, datain_l_sig; wire [7:0] dataout_h_sig, dataout_l_sig; wire [7:0] rx0_d, rx1_d; wire [1:0] rx0_ctl, rx1_ctl; reg [7:0] rx0_d_m1, rx0_d_m2, rx0_d_m3, rx0_d_m4; reg [1:0] rx0_ctl_m1, rx0_ctl_m2, rx0_ctl_m3, rx0_ctl_m4; reg rx0_sop, rx0_eop; reg [7:0] rx1_d_m1, rx1_d_m2, rx1_d_m3, rx1_d_m4; reg [1:0] rx1_ctl_m1, rx1_ctl_m2, rx1_ctl_m3, rx1_ctl_m4; reg [15:0] rx_cnt; reg [1:0] link_state; reg link_up; reg [1:0] link_speed; // TODO: what's the right IEEE name? reg link_duplex; // MDIO controller and driver wire mdio_cont_work_start, mdio_cont_work_run; wire mdio_cont_work_done; wire [MDIO_ROM_ADDR_SZ-1:0] mdio_routine_addr; wire [1:0] mdio_mux_sel; wire mdio_done, mdo_oe; wire mdo; reg mdi; wire [15:0] mdio_wd; wire [15:0] mdio_rd; wire [4:0] mdio_reg_addr; wire mdio_ld, mdio_run; wire mdio_rwn; wire bin_to_ascii_run; // MDIO Data block wire [MDIO_ROM_ADDR_SZ-1:0] rom_a; wire [7:0] rom_d; wire [4:0] mdio_reg_addr_set; wire [7:0] mdio_w_data_h_set, mdio_w_data_l_set; wire [4:0] mdio_page_set; wire bin_to_ascii_we, mdio_rd_we, cont_rd_we; // Debug wire pcs_pclk; // main fabric clock for Ethernet pipeline wire pll_locked; pll pll_0( .areset(~rstn), .inclk0(clk), .c0(phy1_clk), .c1(pcs_pclk), .locked(pll_locked)); // TODO: I have the h/l assignments backwards, SDC is wrong ddri rgmi_rx_0 ( .datain ( {phy0_rx_ctl, phy0_rx_d} ), .inclock ( phy0_rx_clk ), .dataout_h ( {rx0_ctl[1],rx0_d[7:4]} ), .dataout_l ( {rx0_ctl[0],rx0_d[3:0]} ) ); ddri rgmi_rx_1 ( .datain ( {phy1_rx_ctl, phy1_rx_d} ), .inclock ( phy1_rx_clk ), .dataout_h ( {rx1_ctl[1],rx1_d[7:4]} ), .dataout_l ( {rx1_ctl[0],rx1_d[3:0]} ) ); assign phy0_tx_clk = phy1_rx_clk; ddro rgmii_tx_0( .aclr(1'b0), .datain_l({rx1_ctl_m4[1],rx1_d_m4[7:4]}), .datain_h({rx1_ctl_m4[0],rx1_d_m4[3:0]}), .oe(1'b1), .outclock(phy1_rx_clk), .dataout({phy0_tx_ctl, phy0_tx_d})); assign phy1_tx_clk = phy0_rx_clk; ddro rgmii_tx_1( .aclr(1'b0), .datain_l({rx0_ctl_m4[1],rx0_d_m4[7:4]}), .datain_h({rx0_ctl_m4[0],rx0_d_m4[3:0]}), .oe(1'b1), .outclock(phy0_rx_clk), .dataout({phy1_tx_ctl, phy1_tx_d})); assign phy2_tx_clk = phy0_rx_clk; ddro rgmii_tx_2( .aclr(1'b0), .datain_h({rx0_ctl_m4[1],rx0_d_m4[7:4]}), .datain_l({rx0_ctl_m4[0],rx0_d_m4[3:0]}), .oe(1'b1), .outclock(phy0_rx_clk), .dataout({phy2_tx_ctl, phy2_tx_d})); // pipeline regs always @(posedge phy0_rx_clk, negedge rstn) if (!rstn) begin rx0_d_m1 <= 8'h0; rx0_d_m2 <= 8'h0; rx0_d_m3 <= 8'h0; rx0_d_m4 <= 8'h0; end else begin rx0_d_m1 <= rx0_d; rx0_d_m2 <= rx0_d_m1; rx0_d_m3 <= rx0_d_m2; rx0_d_m4 <= rx0_d_m3; end always @(posedge phy0_rx_clk, negedge rstn) if (!rstn) begin rx0_ctl_m1 <= 2'h0; rx0_ctl_m2 <= 2'h0; rx0_ctl_m3 <= 2'h0; rx0_ctl_m4 <= 2'h0; end else begin rx0_ctl_m1 <= rx0_ctl; rx0_ctl_m2 <= rx0_ctl_m1; rx0_ctl_m3 <= rx0_ctl_m2; rx0_ctl_m4 <= rx0_ctl_m3; end // TODO: take into account errors RXERR always @(posedge phy0_rx_clk, negedge rstn) if (!rstn) rx0_sop <= 1'b0; else if (!rx0_ctl_m1[0] && rx0_ctl[0]) rx0_sop <= 1'b1; else rx0_sop <= 1'b0; // TODO: take into account errors take into account errors always @(posedge phy0_rx_clk, negedge rstn) if (!rstn) rx0_eop <= 1'b0; else if (rx0_ctl_m1[0] && !rx0_ctl[0]) rx0_eop <= 1'b1; else rx0_eop <= 1'b0; // pipeline regs always @(posedge phy0_rx_clk, negedge rstn) if (!rstn) begin rx1_d_m1 <= 8'h0; rx1_d_m2 <= 8'h0; rx1_d_m3 <= 8'h0; rx1_d_m4 <= 8'h0; end else begin rx1_d_m1 <= rx1_d; rx1_d_m2 <= rx1_d_m1; rx1_d_m3 <= rx1_d_m2; rx1_d_m4 <= rx1_d_m3; end always @(posedge phy0_rx_clk, negedge rstn) if (!rstn) begin rx1_ctl_m1 <= 2'h0; rx1_ctl_m2 <= 2'h0; rx1_ctl_m3 <= 2'h0; rx1_ctl_m4 <= 2'h0; end else begin rx1_ctl_m1 <= rx1_ctl; rx1_ctl_m2 <= rx1_ctl_m1; rx1_ctl_m3 <= rx1_ctl_m2; rx1_ctl_m4 <= rx1_ctl_m3; end // capture link metrics during normal inter-frame always @(posedge phy0_rx_clk, negedge rstn) if (!rstn) begin link_up <= 1'b0; link_speed <= 2'b11; // reserved link_duplex <= 1'b0; end else if (rx0_ctl_m1 == 2'b00) begin link_up <= rx0_d_m1[0]; link_speed <= rx0_d_m1[2:1]; link_duplex <= rx0_d_m1[3]; end // capture link_state always @(posedge phy0_rx_clk, negedge rstn) if (!rstn) link_state <= LINK_NORMAL_INTERFRAME; else link_state <= rx0_ctl_m1; assign phy0_rstn = 1'b1; assign phy1_rstn = pll_locked; assign phy2_rstn = pll_locked; assign flash_d = 8'hab; assign flash_seln = 1'b1; assign flash_clk = clk; //Heart beat by 50MHz clock always @(posedge clk or negedge rstn) if (!rstn) begin heart_beat_cnt <= 26'h0; //0x3FFFFFF end else begin heart_beat_cnt <= heart_beat_cnt + 1'b1; end assign phy0_mdc = clk; assign phy1_mdc = clk; assign phy2_mdc = clk; assign phy0_mdio = mdo_oe ? mdo : 1'bz; assign phy1_mdio = 1'b0; assign phy2_mdio = 1'b0; assign fpga_led[0] = heart_beat_cnt[22]; assign fpga_led[1] = heart_beat_cnt[23]; assign fpga_led[2] = heart_beat_cnt[24]; assign phy2_gpio[0] = rx0_sop; assign phy2_gpio[1] = rx0_eop; mdio_controller #(.ADDR_SZ( MDIO_ROM_ADDR_SZ )) mdio_controller_0 ( .rstn(rstn), .clk(clk), .work_start(mdio_cont_work_start), .work_run(mdio_cont_work_run), .work_done(mdio_cont_work_done), .routine_addr( mdio_routine_addr ), .buffer_full(1'b0), .addr(rom_a), .di(rom_d), .reg_addr(mdio_reg_addr), .dout(mdio_wd), .ld(mdio_ld), .rwn(mdio_rwn), .done(mdio_done) ); mdio_data_ti #(.ADDR_SZ( MDIO_ROM_ADDR_SZ )) mdio_data_ti_0( .ad( rom_a ), .page( mdio_page_set ), .reg_addr( mdio_reg_addr_set ), .data_in_h( mdio_w_data_h_set ), .data_in_l( mdio_w_data_l_set ), .d( rom_d ), .oe( 1'b1 ) ); mdio mdio_0( .rstn(rstn), .mdc(clk_10), // MDIO .mdi(mdi), .mdo(mdo), .mdo_oe(mdo_oe), // mdio controller interface .rwn(mdio_rwn), .phy_addr(5'h00), .reg_addr(mdio_reg_addr), .di(mdio_wd), .ld(mdio_ld), .run( mdio_run ), .done(mdio_done), // signal controller that mdio xfer is done // output port to converter .dout(mdio_rd), .we( mdio_rd_we ) ); endmodule