/*

Copyright (c) 2018 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

`timescale 1ns / 1ps

/*
 * XGMII 10GBASE-R decoder
 */
module xgmii_baser_dec_64 #
(
    parameter DATA_WIDTH = 64,
    parameter CTRL_WIDTH = (DATA_WIDTH/8),
    parameter HDR_WIDTH = 2
)
(
    input  wire                  clk,
    input  wire                  rst,

    /*
     * 10GBASE-R encoded input
     */
    input  wire [DATA_WIDTH-1:0] encoded_rx_data,
    input  wire [HDR_WIDTH-1:0]  encoded_rx_hdr,

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

    /*
     * Status
     */
    output wire                  rx_bad_block
);

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

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

    if (HDR_WIDTH != 2) begin
        $error("Error: HDR_WIDTH must be 2");
        $finish;
    end
end

localparam [7:0]
    XGMII_IDLE   = 8'h07,
    XGMII_LPI    = 8'h06,
    XGMII_START  = 8'hfb,
    XGMII_TERM   = 8'hfd,
    XGMII_ERROR  = 8'hfe,
    XGMII_SEQ_OS = 8'h9c,
    XGMII_RES_0  = 8'h1c,
    XGMII_RES_1  = 8'h3c,
    XGMII_RES_2  = 8'h7c,
    XGMII_RES_3  = 8'hbc,
    XGMII_RES_4  = 8'hdc,
    XGMII_RES_5  = 8'hf7,
    XGMII_SIG_OS = 8'h5c;

localparam [6:0]
    CTRL_IDLE  = 7'h00,
    CTRL_LPI   = 7'h06,
    CTRL_ERROR = 7'h1e,
    CTRL_RES_0 = 7'h2d,
    CTRL_RES_1 = 7'h33,
    CTRL_RES_2 = 7'h4b,
    CTRL_RES_3 = 7'h55,
    CTRL_RES_4 = 7'h66,
    CTRL_RES_5 = 7'h78;

localparam [3:0]
    O_SEQ_OS = 4'h0,
    O_SIG_OS = 4'hf;

localparam [1:0]
    SYNC_DATA = 2'b10,
    SYNC_CTRL = 2'b01;

localparam [7:0]
    BLOCK_TYPE_CTRL     = 8'h1e, // C7 C6 C5 C4 C3 C2 C1 C0 BT
    BLOCK_TYPE_OS_4     = 8'h2d, // D7 D6 D5 O4 C3 C2 C1 C0 BT
    BLOCK_TYPE_START_4  = 8'h33, // D7 D6 D5    C3 C2 C1 C0 BT
    BLOCK_TYPE_OS_START = 8'h66, // D7 D6 D5    O0 D3 D2 D1 BT
    BLOCK_TYPE_OS_04    = 8'h55, // D7 D6 D5 O4 O0 D3 D2 D1 BT
    BLOCK_TYPE_START_0  = 8'h78, // D7 D6 D5 D4 D3 D2 D1    BT
    BLOCK_TYPE_OS_0     = 8'h4b, // C7 C6 C5 C4 O0 D3 D2 D1 BT
    BLOCK_TYPE_TERM_0   = 8'h87, // C7 C6 C5 C4 C3 C2 C1    BT
    BLOCK_TYPE_TERM_1   = 8'h99, // C7 C6 C5 C4 C3 C2    D0 BT
    BLOCK_TYPE_TERM_2   = 8'haa, // C7 C6 C5 C4 C3    D1 D0 BT
    BLOCK_TYPE_TERM_3   = 8'hb4, // C7 C6 C5 C4    D2 D1 D0 BT
    BLOCK_TYPE_TERM_4   = 8'hcc, // C7 C6 C5    D3 D2 D1 D0 BT
    BLOCK_TYPE_TERM_5   = 8'hd2, // C7 C6    D4 D3 D2 D1 D0 BT
    BLOCK_TYPE_TERM_6   = 8'he1, // C7    D5 D4 D3 D2 D1 D0 BT
    BLOCK_TYPE_TERM_7   = 8'hff; //    D6 D5 D4 D3 D2 D1 D0 BT

reg [DATA_WIDTH-1:0] decoded_ctrl;
reg [CTRL_WIDTH-1:0] decode_err;

reg [DATA_WIDTH-1:0] xgmii_rxd_reg = {DATA_WIDTH{1'b0}}, xgmii_rxd_next;
reg [CTRL_WIDTH-1:0] xgmii_rxc_reg = {CTRL_WIDTH{1'b0}}, xgmii_rxc_next;

reg rx_bad_block_reg = 1'b0, rx_bad_block_next;

assign xgmii_rxd = xgmii_rxd_reg;
assign xgmii_rxc = xgmii_rxc_reg;

assign rx_bad_block = rx_bad_block_reg;

integer i;

always @* begin
    xgmii_rxd_next = {8{XGMII_ERROR}};
    xgmii_rxc_next = 8'hff;
    rx_bad_block_next = 1'b0;

    for (i = 0; i < CTRL_WIDTH; i = i + 1) begin
        case (encoded_rx_data[7*i+8 +: 7])
            CTRL_IDLE: begin
                decoded_ctrl[8*i +: 8] = XGMII_IDLE;
                decode_err[i] = 1'b0;
            end
            CTRL_ERROR: begin
                decoded_ctrl[8*i +: 8] = XGMII_ERROR;
                decode_err[i] = 1'b0;
            end
            CTRL_RES_0: begin
                decoded_ctrl[8*i +: 8] = XGMII_RES_0;
                decode_err[i] = 1'b0;
            end
            CTRL_RES_1: begin
                decoded_ctrl[8*i +: 8] = XGMII_RES_1;
                decode_err[i] = 1'b0;
            end
            CTRL_RES_2: begin
                decoded_ctrl[8*i +: 8] = XGMII_RES_2;
                decode_err[i] = 1'b0;
            end
            CTRL_RES_3: begin
                decoded_ctrl[8*i +: 8] = XGMII_RES_3;
                decode_err[i] = 1'b0;
            end
            CTRL_RES_4: begin
                decoded_ctrl[8*i +: 8] = XGMII_RES_4;
                decode_err[i] = 1'b0;
            end
            CTRL_RES_5: begin
                decoded_ctrl[8*i +: 8] = XGMII_RES_5;
                decode_err[i] = 1'b0;
            end
            default: begin
                decoded_ctrl[8*i +: 8] = XGMII_ERROR;
                decode_err[i] = 1'b1;
            end
        endcase
    end

    if (encoded_rx_hdr == SYNC_DATA) begin
        xgmii_rxd_next = encoded_rx_data;
        xgmii_rxc_next = 8'h00;
    end else if (encoded_rx_hdr == SYNC_CTRL) begin
        case (encoded_rx_data[7:0])
            BLOCK_TYPE_CTRL: begin
                // C7 C6 C5 C4 C3 C2 C1 C0 BT
                xgmii_rxd_next = decoded_ctrl;
                xgmii_rxc_next = 8'hff;
            end
            BLOCK_TYPE_OS_4: begin
                // D7 D6 D5 O4 C3 C2 C1 C0 BT
                xgmii_rxd_next[31:0] = decoded_ctrl[31:0];
                xgmii_rxc_next[3:0] = 4'hf;
                if (encoded_rx_data[39:36] == O_SEQ_OS) begin
                    xgmii_rxd_next[63:32] = {encoded_rx_data[63:40], XGMII_SEQ_OS};
                    xgmii_rxc_next[7:4] = 4'h1;
                end else begin
                    xgmii_rxd_next[63:32] = {4{XGMII_ERROR}};
                    xgmii_rxc_next[7:4] = 4'hf;
                end
            end
            BLOCK_TYPE_START_4: begin
                // D7 D6 D5    C3 C2 C1 C0 BT
                xgmii_rxd_next = {encoded_rx_data[63:40], XGMII_START, decoded_ctrl[31:0]};
                xgmii_rxc_next = 8'h1f;
            end
            BLOCK_TYPE_OS_START: begin
                // D7 D6 D5    O0 D3 D2 D1 BT
                if (encoded_rx_data[35:32] == O_SEQ_OS) begin
                    xgmii_rxd_next[31:0] = {encoded_rx_data[31:8], XGMII_SEQ_OS};
                    xgmii_rxc_next[3:0] = 4'h1;
                end else begin
                    xgmii_rxd_next[31:0] = {4{XGMII_ERROR}};
                    xgmii_rxc_next[3:0] = 4'hf;
                end
                xgmii_rxd_next[63:32] = {encoded_rx_data[63:40], XGMII_START};
                xgmii_rxc_next[7:4] = 4'h1;
            end
            BLOCK_TYPE_OS_04: begin
                // D7 D6 D5 O4 O0 D3 D2 D1 BT
                if (encoded_rx_data[35:32] == O_SEQ_OS) begin
                    xgmii_rxd_next[31:0] = {encoded_rx_data[31:8], XGMII_SEQ_OS};
                    xgmii_rxc_next[3:0] = 4'h1;
                end else begin
                    xgmii_rxd_next[31:0] = {4{XGMII_ERROR}};
                    xgmii_rxc_next[3:0] = 4'hf;
                end
                if (encoded_rx_data[39:36] == O_SEQ_OS) begin
                    xgmii_rxd_next[63:32] = {encoded_rx_data[63:40], XGMII_SEQ_OS};
                    xgmii_rxc_next[7:4] = 4'h1;
                end else begin
                    xgmii_rxd_next[63:32] = {4{XGMII_ERROR}};
                    xgmii_rxc_next[7:4] = 4'hf;
                end
            end
            BLOCK_TYPE_START_0: begin
                // D7 D6 D5 D4 D3 D2 D1    BT
                xgmii_rxd_next = {encoded_rx_data[63:8], XGMII_START};
                xgmii_rxc_next = 8'h01;
            end
            BLOCK_TYPE_OS_0: begin
                // C7 C6 C5 C4 O0 D3 D2 D1 BT
                if (encoded_rx_data[35:32] == O_SEQ_OS) begin
                    xgmii_rxd_next[31:0] = {encoded_rx_data[31:8], XGMII_SEQ_OS};
                    xgmii_rxc_next[3:0] = 4'h1;
                end else begin
                    xgmii_rxd_next[31:0] = {4{XGMII_ERROR}};
                    xgmii_rxc_next[3:0] = 4'hf;
                end
                xgmii_rxd_next[63:32] = decoded_ctrl[63:32];
                xgmii_rxc_next[7:4] = 4'hf;
            end
            BLOCK_TYPE_TERM_0: begin
                // C7 C6 C5 C4 C3 C2 C1    BT
                xgmii_rxd_next = {decoded_ctrl[63:8], XGMII_TERM};
                xgmii_rxc_next = 8'hff;
            end
            BLOCK_TYPE_TERM_1: begin
                // C7 C6 C5 C4 C3 C2    D0 BT
                xgmii_rxd_next = {decoded_ctrl[63:16], XGMII_TERM, encoded_rx_data[15:8]};
                xgmii_rxc_next = 8'hfe;
            end
            BLOCK_TYPE_TERM_2: begin
                // C7 C6 C5 C4 C3    D1 D0 BT
                xgmii_rxd_next = {decoded_ctrl[63:24], XGMII_TERM, encoded_rx_data[23:8]};
                xgmii_rxc_next = 8'hfc;
            end
            BLOCK_TYPE_TERM_3: begin
                // C7 C6 C5 C4    D2 D1 D0 BT
                xgmii_rxd_next = {decoded_ctrl[63:32], XGMII_TERM, encoded_rx_data[31:8]};
                xgmii_rxc_next = 8'hf8;
            end
            BLOCK_TYPE_TERM_4: begin
                // C7 C6 C5    D3 D2 D1 D0 BT
                xgmii_rxd_next = {decoded_ctrl[63:40], XGMII_TERM, encoded_rx_data[39:8]};
                xgmii_rxc_next = 8'hf0;
            end
            BLOCK_TYPE_TERM_5: begin
                // C7 C6    D4 D3 D2 D1 D0 BT
                xgmii_rxd_next = {decoded_ctrl[63:48], XGMII_TERM, encoded_rx_data[47:8]};
                xgmii_rxc_next = 8'he0;
            end
            BLOCK_TYPE_TERM_6: begin
                // C7    D5 D4 D3 D2 D1 D0 BT
                xgmii_rxd_next = {decoded_ctrl[63:56], XGMII_TERM, encoded_rx_data[55:8]};
                xgmii_rxc_next = 8'hc0;
            end
            BLOCK_TYPE_TERM_7: begin
                //    D6 D5 D4 D3 D2 D1 D0 BT
                xgmii_rxd_next = {XGMII_TERM, encoded_rx_data[63:8]};
                xgmii_rxc_next = 8'h80;
            end
            default: begin
                // invalid block type
                xgmii_rxd_next = {8{XGMII_ERROR}};
                xgmii_rxc_next = 8'hff;
                rx_bad_block_next = 1'b1;
            end
        endcase
    end else begin
        // invalid header
        xgmii_rxd_next = {8{XGMII_ERROR}};
        xgmii_rxc_next = 8'hff;
        rx_bad_block_next = 1'b1;
    end
end

always @(posedge clk) begin
    xgmii_rxd_reg <= xgmii_rxd_next;
    xgmii_rxc_reg <= xgmii_rxc_next;

    rx_bad_block_reg <= rx_bad_block_next;
end

endmodule
