mirror of
https://github.com/fpganinja/taxi.git
synced 2025-12-09 08:58:40 -08:00
ptp: Add PTP TD rel2tod timestamp reconstruction module and testbench
Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
303
rtl/ptp/taxi_ptp_td_rel2tod.sv
Normal file
303
rtl/ptp/taxi_ptp_td_rel2tod.sv
Normal file
@@ -0,0 +1,303 @@
|
||||
// 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
|
||||
Reference in New Issue
Block a user