axis_xgmii_rx_64

开源链接 https://github.com/alexforencich/verilog-ethernet

  与上层模块的例化关系。

  axis_xgmii_rx_64模块有一点复杂,逻辑功能上容易理解,就是从xgmii_rxd和xgmii_rxc中解出我们需要的数据,但是代码阅读上有一点困难,本地参数如下,前导码8'h55和8'hD5不用多说。

  START (0xFB):帧开始,复位 CRC 计算器。在 XGMII 数据流中标识一个以太网帧的开始,必须在字节 0 或 4。

  TERM (0xFD):帧结束,触发 CRC 验证。标识以太网帧的终止位置(通常在 FCS 字段之后)。

  IDLE (0x07):链路空闲时填充的字符,无实际数据。

  ERROR (0xFE):标记传输错误(如 PHY 层故障)。

  IDLE (0x07)和ERROR (0xFE)在帧中出现则标记错误。

  xgmii_rxc是与xgmii_rxd 同步的按字节标记信号(每 8 位数据对应 1 位 rxc),用于区分数据和控制字符:rxc[n] = 1:对应的 rxd[8n+7:8n] 是控制字符(如 START、TERM、IDLE)。rxc[n] = 0:对应的 rxd[8n+7:8n] 是普通数据(即使数据值等于 0x07 也不会被视为 IDLE)。

  STATE_IDLE:等待 XGMII 的 START 字符(0xFB)。检测到 START 后跳转到 STATE_PAYLOAD,并复位 CRC 计算器。

  STATE_PAYLOAD:持续接收数据,直到检测到 TERM 字符(0xFD)。检查错误(如非数据字符出现在帧中)。若 TERM 出现在第 0~4 字节,直接结束帧;否则进入 STATE_LAST。

  STATE_LAST:处理跨时钟周期的帧结束(如 TERM 出现在第 5~7 字节)。验证 CRC 并标记错误(通过 tuser[0] 和 error_bad_fcs)。

  这里根据xgmii_rxc是否为1,为数据附加掩膜,得到实际数据xgmii_rxd_masked,xgmii_term则是表明TERM在哪个字节。

  这里先贴出状态机部分的代码。

点击查看代码
always @* begin
    state_next = STATE_IDLE;

    reset_crc = 1'b0;

    m_axis_tdata_next = xgmii_rxd_d1;
    m_axis_tkeep_next = {KEEP_WIDTH{1'b1}};
    m_axis_tvalid_next = 1'b0;
    m_axis_tlast_next = 1'b0;
    m_axis_tuser_next = m_axis_tuser_reg;
    m_axis_tuser_next[0] = 1'b0;

    error_bad_frame_next = 1'b0;
    error_bad_fcs_next = 1'b0;

    case (state_reg)
        STATE_IDLE: begin
            // idle state - wait for packet
            reset_crc = 1'b1;

            if (xgmii_start_d1 && cfg_rx_enable) begin
                // start condition

                reset_crc = 1'b0;
                state_next = STATE_PAYLOAD;
            end else begin
                state_next = STATE_IDLE;
            end
        end
        STATE_PAYLOAD: begin
            // read payload
            m_axis_tdata_next = xgmii_rxd_d1;
            m_axis_tkeep_next = {KEEP_WIDTH{1'b1}};
            m_axis_tvalid_next = 1'b1;
            m_axis_tlast_next = 1'b0;
            m_axis_tuser_next[0] = 1'b0;

            if (PTP_TS_ENABLE) begin
                m_axis_tuser_next[1 +: PTP_TS_WIDTH] = (!PTP_TS_FMT_TOD || ptp_ts_borrow_reg) ? ptp_ts_reg : ptp_ts_adj_reg;
            end

            if (framing_error_reg || framing_error_d0_reg) begin
                // control or error characters in packet
                m_axis_tlast_next = 1'b1;
                m_axis_tuser_next[0] = 1'b1;
                error_bad_frame_next = 1'b1;
                reset_crc = 1'b1;
                state_next = STATE_IDLE;
            end else if (term_present_reg) begin
                reset_crc = 1'b1;
                if (term_lane_reg <= 4) begin
                    // end this cycle
                    m_axis_tkeep_next = {KEEP_WIDTH{1'b1}} >> (CTRL_WIDTH-4-term_lane_reg);
                    m_axis_tlast_next = 1'b1;
                    if ((term_lane_reg == 0 && crc_valid_save[7]) ||
                        (term_lane_reg == 1 && crc_valid[0]) ||
                        (term_lane_reg == 2 && crc_valid[1]) ||
                        (term_lane_reg == 3 && crc_valid[2]) ||
                        (term_lane_reg == 4 && crc_valid[3])) begin
                        // CRC valid
                    end else begin
                        m_axis_tuser_next[0] = 1'b1;
                        error_bad_frame_next = 1'b1;
                        error_bad_fcs_next = 1'b1;
                    end
                    state_next = STATE_IDLE;
                end else begin
                    // need extra cycle
                    state_next = STATE_LAST;
                end
            end else begin
                state_next = STATE_PAYLOAD;
            end
        end
        STATE_LAST: begin
            // last cycle of packet
            m_axis_tdata_next = xgmii_rxd_d1;
            m_axis_tkeep_next = {KEEP_WIDTH{1'b1}} >> (CTRL_WIDTH+4-term_lane_d0_reg);
            m_axis_tvalid_next = 1'b1;
            m_axis_tlast_next = 1'b1;
            m_axis_tuser_next[0] = 1'b0;

            reset_crc = 1'b1;

            if ((term_lane_d0_reg == 5 && crc_valid_save[4]) ||
                (term_lane_d0_reg == 6 && crc_valid_save[5]) ||
                (term_lane_d0_reg == 7 && crc_valid_save[6])) begin
                // CRC valid
            end else begin
                m_axis_tuser_next[0] = 1'b1;
                error_bad_frame_next = 1'b1;
                error_bad_fcs_next = 1'b1;
            end

            if (xgmii_start_d1 && cfg_rx_enable) begin
                // start condition

                reset_crc = 1'b0;
                state_next = STATE_PAYLOAD;
            end else begin
                state_next = STATE_IDLE;
            end
        end
    endcase
end

integer i;

always @(posedge clk) begin
    state_reg <= state_next;

    m_axis_tdata_reg <= m_axis_tdata_next;
    m_axis_tkeep_reg <= m_axis_tkeep_next;
    m_axis_tvalid_reg <= m_axis_tvalid_next;
    m_axis_tlast_reg <= m_axis_tlast_next;
    m_axis_tuser_reg <= m_axis_tuser_next;

    start_packet_reg <= 2'b00;
    error_bad_frame_reg <= error_bad_frame_next;
    error_bad_fcs_reg <= error_bad_fcs_next;

    swap_rxd <= xgmii_rxd_masked[63:32];
    swap_rxc <= xgmii_rxc[7:4];
    swap_rxc_term <= xgmii_term[7:4];

    xgmii_start_swap <= 1'b0;
    xgmii_start_d0 <= xgmii_start_swap;

    if (PTP_TS_ENABLE && PTP_TS_FMT_TOD) begin
        // ns field rollover
        ptp_ts_adj_reg[15:0] <= ptp_ts_reg[15:0];
        {ptp_ts_borrow_reg, ptp_ts_adj_reg[45:16]} <= $signed({1'b0, ptp_ts_reg[45:16]}) - $signed(31'd1000000000);
        ptp_ts_adj_reg[47:46] <= 0;
        ptp_ts_adj_reg[95:48] <= ptp_ts_reg[95:48] + 1;
    end

    // lane swapping and termination character detection
    if (lanes_swapped) begin
        xgmii_rxd_d0 <= {xgmii_rxd_masked[31:0], swap_rxd};
        xgmii_rxc_d0 <= {xgmii_rxc[3:0], swap_rxc};

        term_lane_reg <= 0;
        term_present_reg <= 1'b0;
        framing_error_reg <= {xgmii_rxc[3:0], swap_rxc} != 0;

        for (i = CTRL_WIDTH-1; i >= 0; i = i - 1) begin
            if ({xgmii_term[3:0], swap_rxc_term} & (1 << i)) begin
                term_lane_reg <= i;
                term_present_reg <= 1'b1;
                framing_error_reg <= ({xgmii_rxc[3:0], swap_rxc} & ({CTRL_WIDTH{1'b1}} >> (CTRL_WIDTH-i))) != 0;
                lanes_swapped <= 1'b0;
            end
        end
    end else begin
        xgmii_rxd_d0 <= xgmii_rxd_masked;
        xgmii_rxc_d0 <= xgmii_rxc;

        term_lane_reg <= 0;
        term_present_reg <= 1'b0;
        framing_error_reg <= xgmii_rxc != 0;

        for (i = CTRL_WIDTH-1; i >= 0; i = i - 1) begin
            if (xgmii_rxc[i] && (xgmii_rxd[i*8 +: 8] == XGMII_TERM)) begin
                term_lane_reg <= i;
                term_present_reg <= 1'b1;
                framing_error_reg <= (xgmii_rxc & ({CTRL_WIDTH{1'b1}} >> (CTRL_WIDTH-i))) != 0;
                lanes_swapped <= 1'b0;
            end
        end
    end

    // start control character detection
    if (xgmii_rxc[0] && xgmii_rxd[7:0] == XGMII_START) begin
        lanes_swapped <= 1'b0;

        xgmii_start_d0 <= 1'b1;

        term_lane_reg <= 0;
        term_present_reg <= 1'b0;
        framing_error_reg <= xgmii_rxc[7:1] != 0;
    end else if (xgmii_rxc[4] && xgmii_rxd[39:32] == XGMII_START) begin
        lanes_swapped <= 1'b1;

        xgmii_start_swap <= 1'b1;

        term_lane_reg <= 0;
        term_present_reg <= 1'b0;
        framing_error_reg <= xgmii_rxc[7:5] != 0;
    end

    // capture timestamps
    if (xgmii_start_swap) begin
        start_packet_reg <= 2'b10;
        if (PTP_TS_FMT_TOD) begin
            ptp_ts_reg[45:0] <= ptp_ts[45:0] + (ts_inc_reg >> 1);
            ptp_ts_reg[95:48] <= ptp_ts[95:48];
        end else begin
            ptp_ts_reg <= ptp_ts + (ts_inc_reg >> 1);
        end
    end

    if (xgmii_start_d0) begin
        if (!lanes_swapped) begin
            start_packet_reg <= 2'b01;
            ptp_ts_reg <= ptp_ts;
        end
    end

    term_lane_d0_reg <= term_lane_reg;
    framing_error_d0_reg <= framing_error_reg;

    if (reset_crc) begin
        crc_state <= 32'hFFFFFFFF;
    end else begin
        crc_state <= crc_next;
    end

    crc_valid_save <= crc_valid;

    xgmii_rxd_d1 <= xgmii_rxd_d0;
    xgmii_start_d1 <= xgmii_start_d0;

    last_ts_reg <= ptp_ts;
    ts_inc_reg <= ptp_ts - last_ts_reg;

    if (rst) begin
        state_reg <= STATE_IDLE;

        m_axis_tvalid_reg <= 1'b0;

        start_packet_reg <= 2'b00;
        error_bad_frame_reg <= 1'b0;
        error_bad_fcs_reg <= 1'b0;

        xgmii_rxc_d0 <= {CTRL_WIDTH{1'b0}};

        xgmii_start_swap <= 1'b0;
        xgmii_start_d0 <= 1'b0;
        xgmii_start_d1 <= 1'b0;

        lanes_swapped <= 1'b0;
    end
end

  状态机开始于这里。当START在第1字节和第5字节时,标志数据的开始,在第1字节时立即赋值1给xgmii_start_d0,在第5字节时,则赋值1给lanes_swapped和xgmii_start_swap表明需要翻转数据,xgmii_start_swap会在下一周期被赋值0同时原值赋值给xgmii_start_d0,即xgmii_start_d0 为最终的开始信号。framing_error_reg为错误标志,因为该帧信号内不能在START后立即出现控制字符,出现即视为逻辑错误。

  忽略PTP,可以看到START在第1字节start_packet_reg赋值2'01,START在第5字节start_packet_reg赋值2'10,其中if (!lanes_swapped)的判断是为了防止上一周期赋值的2'10被覆盖掉,但start_packet_reg在本模块没有什么用,他与输出接口start_packet是一致的。

  之前提到过,xgmii_start_d0 为最终的开始信号,其实这里有打拍一次赋值给xgmii_start_d1,xgmii_rxd_d1为随路数据,为xgmii_rxd_d0打拍得到。

  xgmii_rxd_d0来自于xgmii_rxd_masked和swap_rxd,下图中xgmii_rxd_d0 <= {xgmii_rxd_masked[31:0], swap_rxd};表明START在第5字节,所以需要对数据打拍拼凑,而xgmii_rxd_d0 <= xgmii_rxd_masked;表明START在第1字节,不需要拼凑,直接赋值即可,是否拼凑取决于lanes_swapped,而lanes_swapped只会在前面判断到START在第5字节的时候才会被赋值为1。

  下面分析lanes_swapped被赋值0的条件,其中之一是START在第1字节的情况,这里不多讨论,关注下面红框中的部分。之前提到过,xgmii_term是一个检测TERM的字节,哪个bit为1,即为哪个对应字节为TERM,这里的FOR循环就是在遍历xgmii_term的8个bit位,查看哪个为1,检测到了就赋值1给term_present_reg表明当前数据中检测到终止字符,term_lane_reg被赋值i表明当前数据中终止字符在哪个字节里,同时遍历后面的字节,后面字节里不能出现终止字符。出现了则framing_error_reg赋值1。同时上述过程结束后lanes_swapped被赋值0,因为数据接收结束,不需要拼凑了。

  关注一下时序,xgmii_rxd_masked与xgmii_rxd、xgmii_rxc是同周期信号,START在第1字节的情况下,xgmii_rxd_d0是xgmii_rxd_masked的打拍信号,xgmii_start_d0则是START的时序逻辑检测信号,所以也会慢一拍,所以xgmii_rxd_d0和xgmii_start_d0是同周期信号,没有时序错误,START在第5字节的情况下,xgmii_start_d0会慢两拍(原因在于通过lanes_swapped拼凑数据需要1周期),xgmii_rxd_d0中一半数据是慢一拍的数据,而另一半数据是慢两拍的数据,变相实现了时序对齐,也没有时序错误。

  我们来看组合状态机,reg其实就是next的时序逻辑对齐信号,所以只需要关注next部分就行。STATE_IDLE状态下,在没有检测到xgmii_start_d1的开始信号时(cfg_rx_enable默认为1),一直复位crc,检测到后,放开对crc的复位,同时state_next变为STATE_PAYLOAD,这里有人会注意到在IDLE状态里,默认m_axis_tdata_next = xgmii_rxd_d1;m_axis_tkeep_next = {KEEP_WIDTH{1'b1}};m_axis_tvalid_next = 1'b0;m_axis_tlast_next = 1'b0;而xgmii_rxd_d1又是和xgmii_start_d1同步,m_axis_tvalid_next默认0会导致这个在IDLE状态下m_axis_tdata_next = xgmii_rxd_d1的数据不会被写入,确实会有这个问题,虽然我问deepseek他死不承认,我认为这里其实是要舍弃第一个数据,原因在于xgmii_start_d1为高时,对应的数据为XGMII_START-(6)ETH_PRE-ETH_SFD,也就是START起始符和前导码,所以可有可无了,但代码里没有体现出来,所以不太好辨认。

  来看STATE_PAYLOAD状态,默认情况下为数据全接收状态,当有控制字符错误时,立即停止后续传输,拉高last信号,但代码里没有错误的处理方案,而且framing_error_reg和framing_error_d0_reg或的意义感觉不大,因为framing_error_reg与xgmii_rxd_d0同周期,framing_error_d0_reg与xgmii_rxd_d1同周期,可能是为了防止framing_error_reg被覆盖掉?

  term_present_reg是指当前的xgmii_rxd_d0中有TERM终止字符,所以最后一个数据可以通过检测term_present_reg的逻辑来判断是否拉高tlast信号和根据term_lane_reg给tkeep赋值不同的数,这里要注意的是当前m_axis_tlast_next拉高对应的m_axis_tdata_next是xgmii_rxd_d1。而xgmii_rxd_d1是xgmii_rxd_d0的打拍信号,所以这里就会引入一个疑问,term_present_reg表明的是当前的xgmii_rxd_d0中是否有终止字符,用term_present_reg来判断当前xgmii_rxd_d1的数据对应的m_axis_tlast_next是否应该拉高是不是会导致丢掉这一个周期xgmii_rxd_d0的数据?

  这里其实需要注意为什么需要这个判断if (term_lane_reg <= 4) begin,当终止字符在0-4字节时,当前的xgmii_rxd_d0其实没有有效数据,因为当前xgmii_rxd_d0中的组成为部分或全部checksum(4字节)+终止字符+无效数据,所以这里就可以对当前xgmii_rxd_d1的数据对应的m_axis_tlast_next拉高了,而当终止字符在5、6、7字节时,当前xgmii_rxd_d0中的组成中还包含有效数据,所以是需要在下一个周期拉高,这也就是STATE_LAST状态的由来。

  关于STATE_LAST可能需要重点关注以下红框的部分,最后的start启动条件的判断,分析一下,在STATE_LAST状态,xgmii_start_d1为1?那么xgmii_rxd_d1就要为包含START字符和前导码的数据?这明显不对,因为STATE_LAST状态里xgmii_rxd_d1必然为终止字符在567位置的数据,这里后续我们仿真一下,目前不清楚,理论上讲,在STATE_LAST阶段,xgmii_start_d1不会为1,关于仿真和crc的校验下一节再讲。

点击查看代码
/*

Copyright (c) 2015-2017 Alex Forencich

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

// Language: Verilog 2001

`resetall
`timescale 1ns / 1ps
`default_nettype none

/*
 * AXI4-Stream XGMII frame receiver (XGMII in, AXI out)
 */
module axis_xgmii_rx_64 #
(
    parameter DATA_WIDTH = 64,
    parameter KEEP_WIDTH = (DATA_WIDTH/8),
    parameter CTRL_WIDTH = (DATA_WIDTH/8),
    parameter PTP_TS_ENABLE = 0,
    parameter PTP_TS_FMT_TOD = 1,
    parameter PTP_TS_WIDTH = PTP_TS_FMT_TOD ? 96 : 64,
    parameter USER_WIDTH = (PTP_TS_ENABLE ? PTP_TS_WIDTH : 0) + 1
)
(
    input  wire                     clk,
    input  wire                     rst,

    /*
     * XGMII input
     */
    input  wire [DATA_WIDTH-1:0]    xgmii_rxd,
    input  wire [CTRL_WIDTH-1:0]    xgmii_rxc,

    /*
     * AXI output
     */
    output wire [DATA_WIDTH-1:0]    m_axis_tdata,
    output wire [KEEP_WIDTH-1:0]    m_axis_tkeep,
    output wire                     m_axis_tvalid,
    output wire                     m_axis_tlast,
    output wire [USER_WIDTH-1:0]    m_axis_tuser,

    /*
     * PTP
     */
    input  wire [PTP_TS_WIDTH-1:0]  ptp_ts,

    /*
     * Configuration
     */
    input  wire                     cfg_rx_enable,

    /*
     * Status
     */
    output wire [1:0]               start_packet,
    output wire                     error_bad_frame,
    output wire                     error_bad_fcs
);

// bus width assertions
initial begin
    if (DATA_WIDTH != 64) begin
        $error("Error: Interface width must be 64");
        $finish;
    end

    if (KEEP_WIDTH * 8 != DATA_WIDTH || CTRL_WIDTH * 8 != DATA_WIDTH) begin
        $error("Error: Interface requires byte (8-bit) granularity");
        $finish;
    end
end

localparam [7:0]
    ETH_PRE = 8'h55,
    ETH_SFD = 8'hD5;

localparam [7:0]
    XGMII_IDLE = 8'h07,
    XGMII_START = 8'hfb,
    XGMII_TERM = 8'hfd,
    XGMII_ERROR = 8'hfe;

localparam [1:0]
    STATE_IDLE = 2'd0,
    STATE_PAYLOAD = 2'd1,
    STATE_LAST = 2'd2;

reg [1:0] state_reg = STATE_IDLE, state_next;

// datapath control signals
reg reset_crc;

reg lanes_swapped = 1'b0;
reg [31:0] swap_rxd = 32'd0;
reg [3:0] swap_rxc = 4'd0;
reg [3:0] swap_rxc_term = 4'd0;

reg [DATA_WIDTH-1:0] xgmii_rxd_masked = {DATA_WIDTH{1'b0}};
reg [CTRL_WIDTH-1:0] xgmii_term = {CTRL_WIDTH{1'b0}};
reg [2:0] term_lane_reg = 0, term_lane_d0_reg = 0;
reg term_present_reg = 1'b0;
reg framing_error_reg = 1'b0, framing_error_d0_reg = 1'b0;

reg [DATA_WIDTH-1:0] xgmii_rxd_d0 = {DATA_WIDTH{1'b0}};
reg [DATA_WIDTH-1:0] xgmii_rxd_d1 = {DATA_WIDTH{1'b0}};

reg [CTRL_WIDTH-1:0] xgmii_rxc_d0 = {CTRL_WIDTH{1'b0}};

reg xgmii_start_swap = 1'b0;
reg xgmii_start_d0 = 1'b0;
reg xgmii_start_d1 = 1'b0;

reg [DATA_WIDTH-1:0] m_axis_tdata_reg = {DATA_WIDTH{1'b0}}, m_axis_tdata_next;
reg [KEEP_WIDTH-1:0] m_axis_tkeep_reg = {KEEP_WIDTH{1'b0}}, m_axis_tkeep_next;
reg m_axis_tvalid_reg = 1'b0, m_axis_tvalid_next;
reg m_axis_tlast_reg = 1'b0, m_axis_tlast_next;
reg [USER_WIDTH-1:0] m_axis_tuser_reg = {USER_WIDTH{1'b0}}, m_axis_tuser_next;

reg [1:0] start_packet_reg = 2'b00;
reg error_bad_frame_reg = 1'b0, error_bad_frame_next;
reg error_bad_fcs_reg = 1'b0, error_bad_fcs_next;

reg [PTP_TS_WIDTH-1:0] ptp_ts_reg = 0;
reg [PTP_TS_WIDTH-1:0] ptp_ts_adj_reg = 0;
reg ptp_ts_borrow_reg = 0;

reg [31:0] crc_state = 32'hFFFFFFFF;

wire [31:0] crc_next;

wire [7:0] crc_valid;
reg [7:0] crc_valid_save;

assign crc_valid[7] = crc_next == ~32'h2144df1c;
assign crc_valid[6] = crc_next == ~32'hc622f71d;
assign crc_valid[5] = crc_next == ~32'hb1c2a1a3;
assign crc_valid[4] = crc_next == ~32'h9d6cdf7e;
assign crc_valid[3] = crc_next == ~32'h6522df69;
assign crc_valid[2] = crc_next == ~32'he60914ae;
assign crc_valid[1] = crc_next == ~32'he38a6876;
assign crc_valid[0] = crc_next == ~32'h6b87b1ec;

reg [4+16-1:0] last_ts_reg = 0;
reg [4+16-1:0] ts_inc_reg = 0;

assign m_axis_tdata = m_axis_tdata_reg;
assign m_axis_tkeep = m_axis_tkeep_reg;
assign m_axis_tvalid = m_axis_tvalid_reg;
assign m_axis_tlast = m_axis_tlast_reg;
assign m_axis_tuser = m_axis_tuser_reg;

assign start_packet = start_packet_reg;
assign error_bad_frame = error_bad_frame_reg;
assign error_bad_fcs = error_bad_fcs_reg;

lfsr #(
    .LFSR_WIDTH(32),
    .LFSR_POLY(32'h4c11db7),
    .LFSR_CONFIG("GALOIS"),
    .LFSR_FEED_FORWARD(0),
    .REVERSE(1),
    .DATA_WIDTH(64),
    .STYLE("AUTO")
)
eth_crc (
    .data_in(xgmii_rxd_d0),
    .state_in(crc_state),
    .data_out(),
    .state_out(crc_next)
);

// Mask input data
integer j;

always @* begin
    for (j = 0; j < 8; j = j + 1) begin
        xgmii_rxd_masked[j*8 +: 8] = xgmii_rxc[j] ? 8'd0 : xgmii_rxd[j*8 +: 8];
        xgmii_term[j] = xgmii_rxc[j] && (xgmii_rxd[j*8 +: 8] == XGMII_TERM);
    end
end

always @* begin
    state_next = STATE_IDLE;

    reset_crc = 1'b0;

    m_axis_tdata_next = xgmii_rxd_d1;
    m_axis_tkeep_next = {KEEP_WIDTH{1'b1}};
    m_axis_tvalid_next = 1'b0;
    m_axis_tlast_next = 1'b0;
    m_axis_tuser_next = m_axis_tuser_reg;
    m_axis_tuser_next[0] = 1'b0;

    error_bad_frame_next = 1'b0;
    error_bad_fcs_next = 1'b0;

    case (state_reg)
        STATE_IDLE: begin
            // idle state - wait for packet
            reset_crc = 1'b1;

            if (xgmii_start_d1 && cfg_rx_enable) begin
                // start condition

                reset_crc = 1'b0;
                state_next = STATE_PAYLOAD;
            end else begin
                state_next = STATE_IDLE;
            end
        end
        STATE_PAYLOAD: begin
            // read payload
            m_axis_tdata_next = xgmii_rxd_d1;
            m_axis_tkeep_next = {KEEP_WIDTH{1'b1}};
            m_axis_tvalid_next = 1'b1;
            m_axis_tlast_next = 1'b0;
            m_axis_tuser_next[0] = 1'b0;

            if (PTP_TS_ENABLE) begin
                m_axis_tuser_next[1 +: PTP_TS_WIDTH] = (!PTP_TS_FMT_TOD || ptp_ts_borrow_reg) ? ptp_ts_reg : ptp_ts_adj_reg;
            end

            if (framing_error_reg || framing_error_d0_reg) begin
                // control or error characters in packet
                m_axis_tlast_next = 1'b1;
                m_axis_tuser_next[0] = 1'b1;
                error_bad_frame_next = 1'b1;
                reset_crc = 1'b1;
                state_next = STATE_IDLE;
            end else if (term_present_reg) begin
                reset_crc = 1'b1;
                if (term_lane_reg <= 4) begin
                    // end this cycle
                    m_axis_tkeep_next = {KEEP_WIDTH{1'b1}} >> (CTRL_WIDTH-4-term_lane_reg);
                    m_axis_tlast_next = 1'b1;
                    if ((term_lane_reg == 0 && crc_valid_save[7]) ||
                        (term_lane_reg == 1 && crc_valid[0]) ||
                        (term_lane_reg == 2 && crc_valid[1]) ||
                        (term_lane_reg == 3 && crc_valid[2]) ||
                        (term_lane_reg == 4 && crc_valid[3])) begin
                        // CRC valid
                    end else begin
                        m_axis_tuser_next[0] = 1'b1;
                        error_bad_frame_next = 1'b1;
                        error_bad_fcs_next = 1'b1;
                    end
                    state_next = STATE_IDLE;
                end else begin
                    // need extra cycle
                    state_next = STATE_LAST;
                end
            end else begin
                state_next = STATE_PAYLOAD;
            end
        end
        STATE_LAST: begin
            // last cycle of packet
            m_axis_tdata_next = xgmii_rxd_d1;
            m_axis_tkeep_next = {KEEP_WIDTH{1'b1}} >> (CTRL_WIDTH+4-term_lane_d0_reg);
            m_axis_tvalid_next = 1'b1;
            m_axis_tlast_next = 1'b1;
            m_axis_tuser_next[0] = 1'b0;

            reset_crc = 1'b1;

            if ((term_lane_d0_reg == 5 && crc_valid_save[4]) ||
                (term_lane_d0_reg == 6 && crc_valid_save[5]) ||
                (term_lane_d0_reg == 7 && crc_valid_save[6])) begin
                // CRC valid
            end else begin
                m_axis_tuser_next[0] = 1'b1;
                error_bad_frame_next = 1'b1;
                error_bad_fcs_next = 1'b1;
            end

            if (xgmii_start_d1 && cfg_rx_enable) begin
                // start condition

                reset_crc = 1'b0;
                state_next = STATE_PAYLOAD;
            end else begin
                state_next = STATE_IDLE;
            end
        end
    endcase
end

integer i;

always @(posedge clk) begin
    state_reg <= state_next;

    m_axis_tdata_reg <= m_axis_tdata_next;
    m_axis_tkeep_reg <= m_axis_tkeep_next;
    m_axis_tvalid_reg <= m_axis_tvalid_next;
    m_axis_tlast_reg <= m_axis_tlast_next;
    m_axis_tuser_reg <= m_axis_tuser_next;

    start_packet_reg <= 2'b00;
    error_bad_frame_reg <= error_bad_frame_next;
    error_bad_fcs_reg <= error_bad_fcs_next;

    swap_rxd <= xgmii_rxd_masked[63:32];
    swap_rxc <= xgmii_rxc[7:4];
    swap_rxc_term <= xgmii_term[7:4];

    xgmii_start_swap <= 1'b0;
    xgmii_start_d0 <= xgmii_start_swap;

    if (PTP_TS_ENABLE && PTP_TS_FMT_TOD) begin
        // ns field rollover
        ptp_ts_adj_reg[15:0] <= ptp_ts_reg[15:0];
        {ptp_ts_borrow_reg, ptp_ts_adj_reg[45:16]} <= $signed({1'b0, ptp_ts_reg[45:16]}) - $signed(31'd1000000000);
        ptp_ts_adj_reg[47:46] <= 0;
        ptp_ts_adj_reg[95:48] <= ptp_ts_reg[95:48] + 1;
    end

    // lane swapping and termination character detection
    if (lanes_swapped) begin
        xgmii_rxd_d0 <= {xgmii_rxd_masked[31:0], swap_rxd};
        xgmii_rxc_d0 <= {xgmii_rxc[3:0], swap_rxc};

        term_lane_reg <= 0;
        term_present_reg <= 1'b0;
        framing_error_reg <= {xgmii_rxc[3:0], swap_rxc} != 0;

        for (i = CTRL_WIDTH-1; i >= 0; i = i - 1) begin
            if ({xgmii_term[3:0], swap_rxc_term} & (1 << i)) begin
                term_lane_reg <= i;
                term_present_reg <= 1'b1;
                framing_error_reg <= ({xgmii_rxc[3:0], swap_rxc} & ({CTRL_WIDTH{1'b1}} >> (CTRL_WIDTH-i))) != 0;
                lanes_swapped <= 1'b0;
            end
        end
    end else begin
        xgmii_rxd_d0 <= xgmii_rxd_masked;
        xgmii_rxc_d0 <= xgmii_rxc;

        term_lane_reg <= 0;
        term_present_reg <= 1'b0;
        framing_error_reg <= xgmii_rxc != 0;

        for (i = CTRL_WIDTH-1; i >= 0; i = i - 1) begin
            if (xgmii_rxc[i] && (xgmii_rxd[i*8 +: 8] == XGMII_TERM)) begin
                term_lane_reg <= i;
                term_present_reg <= 1'b1;
                framing_error_reg <= (xgmii_rxc & ({CTRL_WIDTH{1'b1}} >> (CTRL_WIDTH-i))) != 0;
                lanes_swapped <= 1'b0;
            end
        end
    end

    // start control character detection
    if (xgmii_rxc[0] && xgmii_rxd[7:0] == XGMII_START) begin
        lanes_swapped <= 1'b0;

        xgmii_start_d0 <= 1'b1;

        term_lane_reg <= 0;
        term_present_reg <= 1'b0;
        framing_error_reg <= xgmii_rxc[7:1] != 0;
    end else if (xgmii_rxc[4] && xgmii_rxd[39:32] == XGMII_START) begin
        lanes_swapped <= 1'b1;

        xgmii_start_swap <= 1'b1;

        term_lane_reg <= 0;
        term_present_reg <= 1'b0;
        framing_error_reg <= xgmii_rxc[7:5] != 0;
    end

    // capture timestamps
    if (xgmii_start_swap) begin
        start_packet_reg <= 2'b10;
        if (PTP_TS_FMT_TOD) begin
            ptp_ts_reg[45:0] <= ptp_ts[45:0] + (ts_inc_reg >> 1);
            ptp_ts_reg[95:48] <= ptp_ts[95:48];
        end else begin
            ptp_ts_reg <= ptp_ts + (ts_inc_reg >> 1);
        end
    end

    if (xgmii_start_d0) begin
        if (!lanes_swapped) begin
            start_packet_reg <= 2'b01;
            ptp_ts_reg <= ptp_ts;
        end
    end

    term_lane_d0_reg <= term_lane_reg;
    framing_error_d0_reg <= framing_error_reg;

    if (reset_crc) begin
        crc_state <= 32'hFFFFFFFF;
    end else begin
        crc_state <= crc_next;
    end

    crc_valid_save <= crc_valid;

    xgmii_rxd_d1 <= xgmii_rxd_d0;
    xgmii_start_d1 <= xgmii_start_d0;

    last_ts_reg <= ptp_ts;
    ts_inc_reg <= ptp_ts - last_ts_reg;

    if (rst) begin
        state_reg <= STATE_IDLE;

        m_axis_tvalid_reg <= 1'b0;

        start_packet_reg <= 2'b00;
        error_bad_frame_reg <= 1'b0;
        error_bad_fcs_reg <= 1'b0;

        xgmii_rxc_d0 <= {CTRL_WIDTH{1'b0}};

        xgmii_start_swap <= 1'b0;
        xgmii_start_d0 <= 1'b0;
        xgmii_start_d1 <= 1'b0;

        lanes_swapped <= 1'b0;
    end
end

endmodule

`resetall