// SPDX-License-Identifier: CERN-OHL-S-2.0
/*

Copyright (c) 2024-2025 FPGA Ninja, LLC

Authors:
- Alex Forencich

*/

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

/*
 * PTP time distribution ToD timestamp reconstruction module
 */
module taxi_ptp_td_rel2tod #
(
    parameter TS_FNS_W = 16,
    parameter TS_REL_NS_W = 32,
    parameter TS_TOD_S_W = 48,
    parameter TS_REL_W = TS_REL_NS_W + TS_FNS_W,
    parameter TS_TOD_W = TS_TOD_S_W + 32 + TS_FNS_W,
    parameter TD_SDI_PIPELINE = 2
)
(
    input  wire logic  clk,
    input  wire logic  rst,

    /*
     * PTP clock interface
     */
    input  wire logic  ptp_clk,
    input  wire logic  ptp_rst,
    input  wire logic  ptp_td_sdi,

    /*
     * Timestamp conversion
     */
    taxi_axis_if.snk   s_axis_ts_rel,
    taxi_axis_if.src   m_axis_ts_tod
);

localparam TS_ID_W = s_axis_ts_rel.ID_W;
localparam TS_DEST_W = s_axis_ts_rel.DEST_W;
localparam TS_USER_W = s_axis_ts_rel.USER_W;

localparam TS_TOD_NS_W = 30;
localparam TS_NS_W = TS_TOD_NS_W+1;

localparam [30:0] NS_PER_S = 31'd1_000_000_000;

// pipeline to facilitate long input path
wire ptp_td_sdi_pipe[0:TD_SDI_PIPELINE];

assign ptp_td_sdi_pipe[0] = ptp_td_sdi;

for (genvar n = 0; n < TD_SDI_PIPELINE; n = n + 1) begin : pipe_stage

    (* shreg_extract = "no" *)
    reg ptp_td_sdi_reg = 0;

    assign ptp_td_sdi_pipe[n+1] = ptp_td_sdi_reg;

    always_ff @(posedge ptp_clk) begin
        ptp_td_sdi_reg <= ptp_td_sdi_pipe[n];
    end

end

// deserialize data
logic [15:0] td_shift_reg = '0;
logic [4:0] bit_cnt_reg = '0;
logic td_valid_reg = 1'b0;
logic [3:0] td_index_reg = '0;
logic [3:0] td_msg_reg = '0;

logic [15:0] td_tdata_reg = '0;
logic td_tvalid_reg = 1'b0;
logic td_tlast_reg = 1'b0;
logic [7:0] td_tid_reg = '0;
logic td_sync_reg = 1'b0;

always_ff @(posedge ptp_clk) begin
    td_shift_reg <= {ptp_td_sdi_pipe[TD_SDI_PIPELINE], td_shift_reg[15:1]};

    td_tvalid_reg <= 1'b0;

    if (bit_cnt_reg != 0) begin
        bit_cnt_reg <= bit_cnt_reg - 1;
    end else begin
        td_valid_reg <= 1'b0;
        if (td_valid_reg) begin
            td_tdata_reg <= td_shift_reg;
            td_tvalid_reg <= 1'b1;
            td_tlast_reg <= ptp_td_sdi_pipe[TD_SDI_PIPELINE];
            td_tid_reg <= {td_msg_reg, td_index_reg};
            if (td_index_reg == 0) begin
                td_msg_reg <= td_shift_reg[3:0];
                td_tid_reg[7:4] <= td_shift_reg[3:0];
            end
            td_index_reg <= td_index_reg + 1;
            td_sync_reg = !td_sync_reg;
        end
        if (ptp_td_sdi_pipe[TD_SDI_PIPELINE] == 0) begin
            bit_cnt_reg <= 16;
            td_valid_reg <= 1'b1;
        end else begin
            td_index_reg <= 0;
        end
    end

    if (ptp_rst) begin
        bit_cnt_reg <= 0;
        td_valid_reg <= 1'b0;

        td_tvalid_reg <= 1'b0;
    end
end

// sync TD data
logic [15:0] dst_td_tdata_reg = '0;
logic dst_td_tvalid_reg = 1'b0;
logic [7:0] dst_td_tid_reg = '0;

(* shreg_extract = "no" *)
logic td_sync_sync1_reg = 1'b0;
(* shreg_extract = "no" *)
logic td_sync_sync2_reg = 1'b0;
(* shreg_extract = "no" *)
logic td_sync_sync3_reg = 1'b0;

always_ff @(posedge clk) begin
    td_sync_sync1_reg <= td_sync_reg;
    td_sync_sync2_reg <= td_sync_sync1_reg;
    td_sync_sync3_reg <= td_sync_sync2_reg;
end

always_ff @(posedge clk) begin
    dst_td_tvalid_reg <= 1'b0;

    if (td_sync_sync3_reg ^ td_sync_sync2_reg) begin
        dst_td_tdata_reg <= td_tdata_reg;
        dst_td_tvalid_reg <= 1'b1;
        dst_td_tid_reg <= td_tid_reg;
    end

    if (rst) begin
        dst_td_tvalid_reg <= 1'b0;
    end
end

logic ts_sel_reg = 1'b0;

logic [47:0] ts_tod_s_0_reg = '0;
logic [31:0] ts_tod_offset_ns_0_reg = '0;
logic [47:0] ts_tod_s_1_reg = '0;
logic [31:0] ts_tod_offset_ns_1_reg = '0;

logic [TS_TOD_S_W-1:0] output_ts_tod_s_reg = '0, output_ts_tod_s_next;
logic [TS_TOD_NS_W-1:0] output_ts_tod_ns_reg = '0, output_ts_tod_ns_next;
logic [TS_FNS_W-1:0] output_ts_fns_reg = '0, output_ts_fns_next;
logic [TS_ID_W-1:0] output_ts_id_reg = '0, output_ts_id_next;
logic [TS_DEST_W-1:0] output_ts_dest_reg = '0, output_ts_dest_next;
logic [TS_USER_W-1:0] output_ts_user_reg = '0, output_ts_user_next;
logic output_ts_valid_reg = 1'b0, output_ts_valid_next;

logic [TS_NS_W-1:0] ts_tod_ns_0;
logic [TS_NS_W-1:0] ts_tod_ns_1;

assign s_axis_ts_rel.tready = 1'b1;

assign m_axis_ts_tod.tdata = {output_ts_tod_s_reg, 2'b00, output_ts_tod_ns_reg, output_ts_fns_reg};
assign m_axis_ts_tod.tkeep = '1;
assign m_axis_ts_tod.tstrb = m_axis_ts_tod.tkeep;
assign m_axis_ts_tod.tlast = 1'b1;
assign m_axis_ts_tod.tid = output_ts_id_reg;
assign m_axis_ts_tod.tdest = output_ts_dest_reg;
assign m_axis_ts_tod.tuser = output_ts_user_reg;
assign m_axis_ts_tod.tvalid = output_ts_valid_reg;

always_comb begin
    // reconstruct timestamp
    // apply both offsets
    ts_tod_ns_0 = TS_NS_W'(s_axis_ts_rel.tdata[TS_FNS_W +: TS_REL_NS_W] + ts_tod_offset_ns_0_reg);
    ts_tod_ns_1 = TS_NS_W'(s_axis_ts_rel.tdata[TS_FNS_W +: TS_REL_NS_W] + ts_tod_offset_ns_1_reg);

    // pick the correct result
    // 2 MSB clear = lower half of range (0-536,870,911)
    // 1 MSB clear = upper half of range, but could also be over 1 billion (536,870,912-1,073,741,823)
    // 1 MSB set = overflow or underflow
    // prefer 2 MSB clear over 1 MSB clear if neither result was overflow or underflow
    if (ts_tod_ns_0[30:29] == 0 || (ts_tod_ns_0[30] == 0 && ts_tod_ns_1[30:29] != 0)) begin
        output_ts_tod_s_next = ts_tod_s_0_reg;
        output_ts_tod_ns_next = ts_tod_ns_0[TS_TOD_NS_W-1:0];
    end else begin
        output_ts_tod_s_next = ts_tod_s_1_reg;
        output_ts_tod_ns_next = ts_tod_ns_1[TS_TOD_NS_W-1:0];
    end
    output_ts_fns_next = s_axis_ts_rel.tdata[TS_FNS_W-1:0];
    output_ts_id_next = s_axis_ts_rel.tid;
    output_ts_dest_next = s_axis_ts_rel.tdest;
    output_ts_user_next = s_axis_ts_rel.tuser;
    output_ts_valid_next = s_axis_ts_rel.tvalid;
end

always_ff @(posedge clk) begin
    // extract data
    if (dst_td_tvalid_reg) begin
        if (dst_td_tid_reg[3:0] == 4'd0) begin
            ts_sel_reg <= dst_td_tdata_reg[9];
        end
        // current
        if (dst_td_tid_reg == {4'd1, 4'd1}) begin
            if (ts_sel_reg) begin
                ts_tod_offset_ns_1_reg[15:0] <= dst_td_tdata_reg;
            end else begin
                ts_tod_offset_ns_0_reg[15:0] <= dst_td_tdata_reg;
            end
        end
        if (dst_td_tid_reg == {4'd1, 4'd2}) begin
            if (ts_sel_reg) begin
                ts_tod_offset_ns_1_reg[31:16] <= dst_td_tdata_reg;
            end else begin
                ts_tod_offset_ns_0_reg[31:16] <= dst_td_tdata_reg;
            end
        end
        if (dst_td_tid_reg == {4'd0, 4'd3}) begin
            if (ts_sel_reg) begin
                ts_tod_s_1_reg[15:0] <= dst_td_tdata_reg;
            end else begin
                ts_tod_s_0_reg[15:0] <= dst_td_tdata_reg;
            end
        end
        if (dst_td_tid_reg == {4'd0, 4'd4}) begin
            if (ts_sel_reg) begin
                ts_tod_s_1_reg[31:16] <= dst_td_tdata_reg;
            end else begin
                ts_tod_s_0_reg[31:16] <= dst_td_tdata_reg;
            end
        end
        if (dst_td_tid_reg == {4'd0, 4'd5}) begin
            if (ts_sel_reg) begin
                ts_tod_s_1_reg[47:32] <= dst_td_tdata_reg;
            end else begin
                ts_tod_s_0_reg[47:32] <= dst_td_tdata_reg;
            end
        end
        // alternate
        if (dst_td_tid_reg == {4'd2, 4'd1}) begin
            if (ts_sel_reg) begin
                ts_tod_offset_ns_0_reg[15:0] <= dst_td_tdata_reg;
            end else begin
                ts_tod_offset_ns_1_reg[15:0] <= dst_td_tdata_reg;
            end
        end
        if (dst_td_tid_reg == {4'd2, 4'd2}) begin
            if (ts_sel_reg) begin
                ts_tod_offset_ns_0_reg[31:16] <= dst_td_tdata_reg;
            end else begin
                ts_tod_offset_ns_1_reg[31:16] <= dst_td_tdata_reg;
            end
        end
        if (dst_td_tid_reg == {4'd2, 4'd3}) begin
            if (ts_sel_reg) begin
                ts_tod_s_0_reg[15:0] <= dst_td_tdata_reg;
            end else begin
                ts_tod_s_1_reg[15:0] <= dst_td_tdata_reg;
            end
        end
        if (dst_td_tid_reg == {4'd2, 4'd4}) begin
            if (ts_sel_reg) begin
                ts_tod_s_0_reg[31:16] <= dst_td_tdata_reg;
            end else begin
                ts_tod_s_1_reg[31:16] <= dst_td_tdata_reg;
            end
        end
        if (dst_td_tid_reg == {4'd2, 4'd5}) begin
            if (ts_sel_reg) begin
                ts_tod_s_0_reg[47:32] <= dst_td_tdata_reg;
            end else begin
                ts_tod_s_1_reg[47:32] <= dst_td_tdata_reg;
            end
        end
    end

    output_ts_tod_s_reg <= output_ts_tod_s_next;
    output_ts_tod_ns_reg <= output_ts_tod_ns_next;
    output_ts_fns_reg <= output_ts_fns_next;
    output_ts_id_reg <= output_ts_id_next;
    output_ts_dest_reg <= output_ts_dest_next;
    output_ts_user_reg <= output_ts_user_next;
    output_ts_valid_reg <= output_ts_valid_next;

    if (rst) begin
        output_ts_valid_reg <= 1'b0;
    end
end

endmodule

`resetall
