Skip to content

Commit ae942ad

Browse files
committed
Add ptp_td_rel2tod module for timestamp reconstruction
Signed-off-by: Alex Forencich <[email protected]>
1 parent 685a6f3 commit ae942ad

File tree

5 files changed

+630
-0
lines changed

5 files changed

+630
-0
lines changed

rtl/ptp_td_rel2tod.v

+313
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
/*
2+
3+
Copyright (c) 2024 Alex Forencich
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.
22+
23+
*/
24+
25+
// Language: Verilog 2001
26+
27+
`resetall
28+
`timescale 1ns / 1fs
29+
`default_nettype none
30+
31+
/*
32+
* PTP time distribution ToD timestamp reconstruction module
33+
*/
34+
module ptp_td_rel2tod #
35+
(
36+
parameter TS_FNS_W = 16,
37+
parameter TS_REL_NS_W = 32,
38+
parameter TS_TOD_S_W = 48,
39+
parameter TS_REL_W = TS_REL_NS_W + TS_FNS_W,
40+
parameter TS_TOD_W = TS_TOD_S_W + 32 + TS_FNS_W,
41+
parameter TS_TAG_W = 8,
42+
parameter TD_SDI_PIPELINE = 2
43+
)
44+
(
45+
input wire clk,
46+
input wire rst,
47+
48+
/*
49+
* PTP clock interface
50+
*/
51+
input wire ptp_clk,
52+
input wire ptp_rst,
53+
input wire ptp_td_sdi,
54+
55+
/*
56+
* Timestamp conversion
57+
*/
58+
input wire [TS_REL_W-1:0] input_ts_rel,
59+
input wire [TS_TAG_W-1:0] input_ts_tag,
60+
input wire input_ts_valid,
61+
output wire [TS_TOD_W-1:0] output_ts_tod,
62+
output wire [TS_TAG_W-1:0] output_ts_tag,
63+
output wire output_ts_valid
64+
);
65+
66+
localparam TS_TOD_NS_W = 30;
67+
localparam TS_NS_W = TS_TOD_NS_W+1;
68+
69+
localparam [30:0] NS_PER_S = 31'd1_000_000_000;
70+
71+
// pipeline to facilitate long input path
72+
wire ptp_td_sdi_pipe[0:TD_SDI_PIPELINE];
73+
74+
assign ptp_td_sdi_pipe[0] = ptp_td_sdi;
75+
76+
generate
77+
78+
genvar n;
79+
80+
for (n = 0; n < TD_SDI_PIPELINE; n = n + 1) begin : pipe_stage
81+
82+
(* shreg_extract = "no" *)
83+
reg ptp_td_sdi_reg = 0;
84+
85+
assign ptp_td_sdi_pipe[n+1] = ptp_td_sdi_reg;
86+
87+
always @(posedge ptp_clk) begin
88+
ptp_td_sdi_reg <= ptp_td_sdi_pipe[n];
89+
end
90+
91+
end
92+
93+
endgenerate
94+
95+
// deserialize data
96+
reg [15:0] td_shift_reg = 0;
97+
reg [4:0] bit_cnt_reg = 0;
98+
reg td_valid_reg = 1'b0;
99+
reg [3:0] td_index_reg = 0;
100+
reg [3:0] td_msg_reg = 0;
101+
102+
reg [15:0] td_tdata_reg = 0;
103+
reg td_tvalid_reg = 1'b0;
104+
reg td_tlast_reg = 1'b0;
105+
reg [7:0] td_tid_reg = 0;
106+
reg td_sync_reg = 1'b0;
107+
108+
always @(posedge ptp_clk) begin
109+
td_shift_reg <= {ptp_td_sdi_pipe[TD_SDI_PIPELINE], td_shift_reg[15:1]};
110+
111+
td_tvalid_reg <= 1'b0;
112+
113+
if (bit_cnt_reg) begin
114+
bit_cnt_reg <= bit_cnt_reg - 1;
115+
end else begin
116+
td_valid_reg <= 1'b0;
117+
if (td_valid_reg) begin
118+
td_tdata_reg <= td_shift_reg;
119+
td_tvalid_reg <= 1'b1;
120+
td_tlast_reg <= ptp_td_sdi_pipe[TD_SDI_PIPELINE];
121+
td_tid_reg <= {td_msg_reg, td_index_reg};
122+
if (td_index_reg == 0) begin
123+
td_msg_reg <= td_shift_reg[3:0];
124+
td_tid_reg[7:4] <= td_shift_reg[3:0];
125+
end
126+
td_index_reg <= td_index_reg + 1;
127+
td_sync_reg = !td_sync_reg;
128+
end
129+
if (ptp_td_sdi_pipe[TD_SDI_PIPELINE] == 0) begin
130+
bit_cnt_reg <= 16;
131+
td_valid_reg <= 1'b1;
132+
end else begin
133+
td_index_reg <= 0;
134+
end
135+
end
136+
137+
if (ptp_rst) begin
138+
bit_cnt_reg <= 0;
139+
td_valid_reg <= 1'b0;
140+
141+
td_tvalid_reg <= 1'b0;
142+
end
143+
end
144+
145+
// sync TD data
146+
reg [15:0] dst_td_tdata_reg = 0;
147+
reg dst_td_tvalid_reg = 1'b0;
148+
reg [7:0] dst_td_tid_reg = 0;
149+
150+
(* shreg_extract = "no" *)
151+
reg td_sync_sync1_reg = 1'b0;
152+
(* shreg_extract = "no" *)
153+
reg td_sync_sync2_reg = 1'b0;
154+
(* shreg_extract = "no" *)
155+
reg td_sync_sync3_reg = 1'b0;
156+
157+
always @(posedge clk) begin
158+
td_sync_sync1_reg <= td_sync_reg;
159+
td_sync_sync2_reg <= td_sync_sync1_reg;
160+
td_sync_sync3_reg <= td_sync_sync2_reg;
161+
end
162+
163+
always @(posedge clk) begin
164+
dst_td_tvalid_reg <= 1'b0;
165+
166+
if (td_sync_sync3_reg ^ td_sync_sync2_reg) begin
167+
dst_td_tdata_reg <= td_tdata_reg;
168+
dst_td_tvalid_reg <= 1'b1;
169+
dst_td_tid_reg <= td_tid_reg;
170+
end
171+
172+
if (rst) begin
173+
dst_td_tvalid_reg <= 1'b0;
174+
end
175+
end
176+
177+
reg ts_sel_reg = 0;
178+
179+
reg [47:0] ts_tod_s_0_reg = 0;
180+
reg [31:0] ts_tod_offset_ns_0_reg = 0;
181+
reg [47:0] ts_tod_s_1_reg = 0;
182+
reg [31:0] ts_tod_offset_ns_1_reg = 0;
183+
184+
reg [TS_TOD_S_W-1:0] output_ts_tod_s_reg = 0, output_ts_tod_s_next;
185+
reg [TS_TOD_NS_W-1:0] output_ts_tod_ns_reg = 0, output_ts_tod_ns_next;
186+
reg [TS_FNS_W-1:0] output_ts_fns_reg = 0, output_ts_fns_next;
187+
reg [TS_TAG_W-1:0] output_ts_tag_reg = 0, output_ts_tag_next;
188+
reg output_ts_valid_reg = 0, output_ts_valid_next;
189+
190+
reg [TS_NS_W-1:0] ts_tod_ns_0;
191+
reg [TS_NS_W-1:0] ts_tod_ns_1;
192+
193+
assign output_ts_tod = {output_ts_tod_s_reg, 2'b00, output_ts_tod_ns_reg, output_ts_fns_reg};
194+
assign output_ts_tag = output_ts_tag_reg;
195+
assign output_ts_valid = output_ts_valid_reg;
196+
197+
always @* begin
198+
// reconstruct timestamp
199+
// apply both offsets
200+
ts_tod_ns_0 = (input_ts_rel >> TS_FNS_W) + ts_tod_offset_ns_0_reg;
201+
ts_tod_ns_1 = (input_ts_rel >> TS_FNS_W) + ts_tod_offset_ns_1_reg;
202+
203+
// pick the correct result
204+
// 2 MSB clear = lower half of range (0-536,870,911)
205+
// 1 MSB clear = upper half of range, but could also be over 1 billion (536,870,912-1,073,741,823)
206+
// 1 MSB set = overflow or underflow
207+
// prefer 2 MSB clear over 1 MSB clear if neither result was overflow or underflow
208+
if (ts_tod_ns_0[30:29] == 0 || (ts_tod_ns_0[30] == 0 && ts_tod_ns_1[30:29] != 0)) begin
209+
output_ts_tod_s_next = ts_tod_s_0_reg;
210+
output_ts_tod_ns_next = ts_tod_ns_0;
211+
end else begin
212+
output_ts_tod_s_next = ts_tod_s_1_reg;
213+
output_ts_tod_ns_next = ts_tod_ns_1;
214+
end
215+
output_ts_fns_next = input_ts_rel;
216+
output_ts_tag_next = input_ts_tag;
217+
output_ts_valid_next = input_ts_valid;
218+
end
219+
220+
always @(posedge clk) begin
221+
// extract data
222+
if (dst_td_tvalid_reg) begin
223+
if (dst_td_tid_reg[3:0] == 4'd0) begin
224+
ts_sel_reg <= dst_td_tdata_reg[9];
225+
end
226+
// current
227+
if (dst_td_tid_reg == {4'd1, 4'd1}) begin
228+
if (ts_sel_reg) begin
229+
ts_tod_offset_ns_1_reg[15:0] <= dst_td_tdata_reg;
230+
end else begin
231+
ts_tod_offset_ns_0_reg[15:0] <= dst_td_tdata_reg;
232+
end
233+
end
234+
if (dst_td_tid_reg == {4'd1, 4'd2}) begin
235+
if (ts_sel_reg) begin
236+
ts_tod_offset_ns_1_reg[31:16] <= dst_td_tdata_reg;
237+
end else begin
238+
ts_tod_offset_ns_0_reg[31:16] <= dst_td_tdata_reg;
239+
end
240+
end
241+
if (dst_td_tid_reg == {4'd0, 4'd3}) begin
242+
if (ts_sel_reg) begin
243+
ts_tod_s_1_reg[15:0] <= dst_td_tdata_reg;
244+
end else begin
245+
ts_tod_s_0_reg[15:0] <= dst_td_tdata_reg;
246+
end
247+
end
248+
if (dst_td_tid_reg == {4'd0, 4'd4}) begin
249+
if (ts_sel_reg) begin
250+
ts_tod_s_1_reg[31:16] <= dst_td_tdata_reg;
251+
end else begin
252+
ts_tod_s_0_reg[31:16] <= dst_td_tdata_reg;
253+
end
254+
end
255+
if (dst_td_tid_reg == {4'd0, 4'd5}) begin
256+
if (ts_sel_reg) begin
257+
ts_tod_s_1_reg[47:32] <= dst_td_tdata_reg;
258+
end else begin
259+
ts_tod_s_0_reg[47:32] <= dst_td_tdata_reg;
260+
end
261+
end
262+
// alternate
263+
if (dst_td_tid_reg == {4'd2, 4'd1}) begin
264+
if (ts_sel_reg) begin
265+
ts_tod_offset_ns_0_reg[15:0] <= dst_td_tdata_reg;
266+
end else begin
267+
ts_tod_offset_ns_1_reg[15:0] <= dst_td_tdata_reg;
268+
end
269+
end
270+
if (dst_td_tid_reg == {4'd2, 4'd2}) begin
271+
if (ts_sel_reg) begin
272+
ts_tod_offset_ns_0_reg[31:16] <= dst_td_tdata_reg;
273+
end else begin
274+
ts_tod_offset_ns_1_reg[31:16] <= dst_td_tdata_reg;
275+
end
276+
end
277+
if (dst_td_tid_reg == {4'd2, 4'd3}) begin
278+
if (ts_sel_reg) begin
279+
ts_tod_s_0_reg[15:0] <= dst_td_tdata_reg;
280+
end else begin
281+
ts_tod_s_1_reg[15:0] <= dst_td_tdata_reg;
282+
end
283+
end
284+
if (dst_td_tid_reg == {4'd2, 4'd4}) begin
285+
if (ts_sel_reg) begin
286+
ts_tod_s_0_reg[31:16] <= dst_td_tdata_reg;
287+
end else begin
288+
ts_tod_s_1_reg[31:16] <= dst_td_tdata_reg;
289+
end
290+
end
291+
if (dst_td_tid_reg == {4'd2, 4'd5}) begin
292+
if (ts_sel_reg) begin
293+
ts_tod_s_0_reg[47:32] <= dst_td_tdata_reg;
294+
end else begin
295+
ts_tod_s_1_reg[47:32] <= dst_td_tdata_reg;
296+
end
297+
end
298+
end
299+
300+
output_ts_tod_s_reg <= output_ts_tod_s_next;
301+
output_ts_tod_ns_reg <= output_ts_tod_ns_next;
302+
output_ts_fns_reg <= output_ts_fns_next;
303+
output_ts_tag_reg <= output_ts_tag_next;
304+
output_ts_valid_reg <= output_ts_valid_next;
305+
306+
if (rst) begin
307+
output_ts_valid_reg <= 1'b0;
308+
end
309+
end
310+
311+
endmodule
312+
313+
`resetall

syn/vivado/ptp_td_rel2tod.tcl

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Copyright (c) 2019-2024 Alex Forencich
2+
#
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy
4+
# of this software and associated documentation files (the "Software"), to deal
5+
# in the Software without restriction, including without limitation the rights
6+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
# copies of the Software, and to permit persons to whom the Software is
8+
# furnished to do so, subject to the following conditions:
9+
#
10+
# The above copyright notice and this permission notice shall be included in
11+
# all copies or substantial portions of the Software.
12+
#
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
15+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
# THE SOFTWARE.
20+
21+
# PTP time distribution ToD timestamp reconstruction module
22+
23+
foreach inst [get_cells -hier -regexp -filter {(ORIG_REF_NAME =~ "ptp_td_rel2tod(__\w+__\d+)?" ||
24+
REF_NAME =~ "ptp_td_rel2tod(__\w+__\d+)?")}] {
25+
puts "Inserting timing constraints for ptp_td_rel2tod instance $inst"
26+
27+
# get clock periods
28+
set input_clk [get_clocks -of_objects [get_pins "$inst/td_sync_reg_reg/C"]]
29+
set output_clk [get_clocks -of_objects [get_pins "$inst/td_sync_sync1_reg_reg/C"]]
30+
31+
set input_clk_period [if {[llength $input_clk]} {get_property -min PERIOD $input_clk} {expr 1.0}]
32+
set output_clk_period [if {[llength $output_clk]} {get_property -min PERIOD $output_clk} {expr 1.0}]
33+
34+
# TD data sync
35+
set_property ASYNC_REG TRUE [get_cells -hier -regexp ".*/dst_td_(tdata|tid)_reg_reg(\\\[\\d+\\\])?" -filter "PARENT == $inst"]
36+
37+
set_max_delay -from [get_cells "$inst/td_tdata_reg_reg[*]"] -to [get_cells "$inst/dst_td_tdata_reg_reg[*]"] -datapath_only $output_clk_period
38+
set_bus_skew -from [get_cells "$inst/td_tdata_reg_reg[*]"] -to [get_cells "$inst/dst_td_tdata_reg_reg[*]"] $input_clk_period
39+
set_max_delay -from [get_cells "$inst/td_tid_reg_reg[*]"] -to [get_cells "$inst/dst_td_tid_reg_reg[*]"] -datapath_only $output_clk_period
40+
set_bus_skew -from [get_cells "$inst/td_tid_reg_reg[*]"] -to [get_cells "$inst/dst_td_tid_reg_reg[*]"] $input_clk_period
41+
42+
set sync_ffs [get_cells -quiet -hier -regexp ".*/td_sync_sync\[12\]_reg_reg" -filter "PARENT == $inst"]
43+
44+
if {[llength $sync_ffs]} {
45+
set_property ASYNC_REG TRUE $sync_ffs
46+
47+
set_max_delay -from [get_cells "$inst/td_sync_reg_reg"] -to [get_cells "$inst/td_sync_sync1_reg_reg"] -datapath_only $input_clk_period
48+
}
49+
}

0 commit comments

Comments
 (0)