1
0
mirror of https://github.com/corundum/corundum.git synced 2025-01-16 08:12:53 +08:00
corundum/rtl/ptp_td_phc.v
Alex Forencich bd8e8e5b20 Add PTP time distribution components
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-11-07 13:07:15 -08:00

604 lines
19 KiB
Verilog

/*
Copyright (c) 2023 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
/*
* PTP time distribution PHC
*/
module ptp_td_phc #
(
parameter PERIOD_NS_NUM = 32,
parameter PERIOD_NS_DENOM = 5
)
(
input wire clk,
input wire rst,
/*
* ToD timestamp control
*/
input wire [47:0] input_ts_tod_s,
input wire [29:0] input_ts_tod_ns,
input wire input_ts_tod_valid,
output wire input_ts_tod_ready,
input wire [29:0] input_ts_tod_offset_ns,
input wire input_ts_tod_offset_valid,
output wire input_ts_tod_offset_ready,
/*
* Relative timestamp control
*/
input wire [47:0] input_ts_rel_ns,
input wire input_ts_rel_valid,
output wire input_ts_rel_ready,
input wire [31:0] input_ts_rel_offset_ns,
input wire input_ts_rel_offset_valid,
output wire input_ts_rel_offset_ready,
/*
* Fractional ns control
*/
input wire [31:0] input_ts_offset_fns,
input wire input_ts_offset_valid,
output wire input_ts_offset_ready,
/*
* Period control
*/
input wire [7:0] input_period_ns,
input wire [31:0] input_period_fns,
input wire input_period_valid,
output wire input_period_ready,
input wire [15:0] input_drift_num,
input wire [15:0] input_drift_denom,
input wire input_drift_valid,
output wire input_drift_ready,
/*
* Time distribution serial data output
*/
output wire ptp_td_sdo,
/*
* PPS output
*/
output wire output_pps,
output wire output_pps_str
);
localparam INC_NS_W = 9+8;
localparam FNS_W = 32;
localparam PERIOD_NS = PERIOD_NS_NUM / PERIOD_NS_DENOM;
localparam PERIOD_NS_REM = PERIOD_NS_NUM - PERIOD_NS*PERIOD_NS_DENOM;
localparam PERIOD_FNS = (PERIOD_NS_REM * {32'd1, {FNS_W{1'b0}}}) / PERIOD_NS_DENOM;
localparam PERIOD_FNS_REM = (PERIOD_NS_REM * {32'd1, {FNS_W{1'b0}}}) - PERIOD_FNS*PERIOD_NS_DENOM;
localparam [30:0] NS_PER_S = 31'd1_000_000_000;
reg [7:0] period_ns_reg = PERIOD_NS;
reg [FNS_W-1:0] period_fns_reg = PERIOD_FNS;
reg [15:0] drift_num_reg = PERIOD_FNS_REM;
reg [15:0] drift_denom_reg = PERIOD_NS_DENOM;
reg [15:0] drift_cnt_reg = 0;
reg [15:0] drift_cnt_d1_reg = 0;
reg drift_apply_reg = 1'b0;
reg [23:0] drift_acc_reg = 0;
reg [INC_NS_W-1:0] ts_inc_ns_reg = 0;
reg [FNS_W-1:0] ts_fns_reg = 0;
reg [32:0] ts_rel_ns_inc_reg = 0;
reg [47:0] ts_rel_ns_reg = 0;
reg ts_rel_updated_reg = 1'b0;
reg [47:0] ts_tod_s_reg = 0;
reg [29:0] ts_tod_ns_reg = 0;
reg ts_tod_updated_reg = 1'b0;
reg [31:0] ts_tod_offset_ns_reg = 0;
reg [47:0] ts_tod_alt_s_reg = 0;
reg [31:0] ts_tod_alt_offset_ns_reg = 0;
reg [7:0] td_update_cnt_reg = 0;
reg td_update_reg = 1'b0;
reg [1:0] td_msg_i_reg = 0;
reg input_ts_tod_ready_reg = 1'b0;
reg input_ts_tod_offset_ready_reg = 1'b0;
reg input_ts_rel_ready_reg = 1'b0;
reg input_ts_rel_offset_ready_reg = 1'b0;
reg input_ts_offset_ready_reg = 1'b0;
reg [17*14-1:0] td_shift_reg = {17*14{1'b1}};
reg [15:0] pps_gen_fns_reg = 0;
reg [9:0] pps_gen_ns_inc_reg = 0;
reg [30:0] pps_gen_ns_reg = 31'h40000000;
reg [9:0] pps_delay_reg = 0;
reg pps_reg = 0;
reg pps_str_reg = 0;
reg [3:0] update_state_reg = 0;
reg [47:0] adder_a_reg = 0;
reg [47:0] adder_b_reg = 0;
reg adder_cin_reg = 0;
wire [47:0] adder_sum;
wire adder_cout;
assign {adder_cout, adder_sum} = adder_a_reg + adder_b_reg + adder_cin_reg;
assign input_ts_tod_ready = input_ts_tod_ready_reg;
assign input_ts_tod_offset_ready = input_ts_tod_offset_ready_reg;
assign input_ts_rel_ready = input_ts_rel_ready_reg;
assign input_ts_rel_offset_ready = input_ts_rel_offset_ready_reg;
assign input_ts_offset_ready = input_ts_offset_ready_reg;
assign input_period_ready = 1'b1;
assign input_drift_ready = 1'b1;
assign output_pps = pps_reg;
assign output_pps_str = pps_str_reg;
assign ptp_td_sdo = td_shift_reg[0];
always @(posedge clk) begin
drift_apply_reg <= 1'b0;
input_ts_tod_ready_reg <= 1'b0;
input_ts_tod_offset_ready_reg <= 1'b0;
input_ts_rel_ready_reg <= 1'b0;
input_ts_rel_offset_ready_reg <= 1'b0;
input_ts_offset_ready_reg <= 1'b0;
// update and message generation cadence
{td_update_reg, td_update_cnt_reg} <= td_update_cnt_reg + 1;
// latch drift setting
if (input_drift_valid) begin
drift_num_reg <= input_drift_num;
drift_denom_reg <= input_drift_denom;
end
// drift
if (drift_denom_reg) begin
if (drift_cnt_reg == 0) begin
drift_cnt_reg <= drift_denom_reg - 1;
drift_apply_reg <= 1'b1;
end else begin
drift_cnt_reg <= drift_cnt_reg - 1;
end
end else begin
drift_cnt_reg <= 0;
end
drift_cnt_d1_reg <= drift_cnt_reg;
// drift accumulation
if (drift_apply_reg) begin
drift_acc_reg <= drift_acc_reg + drift_num_reg;
end
// latch period setting
if (input_period_valid) begin
period_ns_reg <= input_period_ns;
period_fns_reg <= input_period_fns;
end
// PPS generation
if (td_update_reg) begin
{pps_gen_ns_inc_reg, pps_gen_fns_reg} <= {period_ns_reg, period_fns_reg[31:16]} + ts_fns_reg[31:16];
end else begin
{pps_gen_ns_inc_reg, pps_gen_fns_reg} <= {period_ns_reg, period_fns_reg[31:16]} + pps_gen_fns_reg;
end
pps_gen_ns_reg <= pps_gen_ns_reg + pps_gen_ns_inc_reg;
if (!pps_gen_ns_reg[30]) begin
// pps_delay_reg <= 14*17 + 32 + 1;
pps_delay_reg <= 14*17 + 32 + 248;
pps_gen_ns_reg[30] <= 1'b1;
end
pps_reg <= 1'b0;
if (ts_tod_ns_reg[29]) begin
pps_str_reg <= 1'b0;
end
if (pps_delay_reg) begin
pps_delay_reg <= pps_delay_reg - 1;
if (pps_delay_reg == 1) begin
pps_reg <= 1'b1;
pps_str_reg <= 1'b1;
end
end
// update state machine
case (update_state_reg)
0: begin
// idle
// set relative timestamp
if (input_ts_rel_valid) begin
ts_rel_ns_reg <= input_ts_rel_ns;
input_ts_rel_ready_reg <= 1'b1;
ts_rel_updated_reg <= 1'b1;
end
// set ToD timestamp
if (input_ts_tod_valid) begin
ts_tod_s_reg <= input_ts_tod_s;
ts_tod_ns_reg <= input_ts_tod_ns;
input_ts_tod_ready_reg <= 1'b1;
ts_tod_updated_reg <= 1'b1;
end
// compute period 1 - add drift and requested offset
if (drift_apply_reg) begin
adder_a_reg <= drift_acc_reg + drift_num_reg;
end else begin
adder_a_reg <= drift_acc_reg;
end
adder_b_reg <= input_ts_offset_valid ? $signed(input_ts_offset_fns) : 0;
adder_cin_reg <= 0;
if (td_update_reg) begin
drift_acc_reg <= 0;
input_ts_offset_ready_reg <= input_ts_offset_valid;
update_state_reg <= 1;
end else begin
update_state_reg <= 0;
end
end
1: begin
// compute period 2 - add drift and offset to period
adder_a_reg <= adder_sum;
adder_b_reg <= {period_ns_reg, period_fns_reg, 8'd0};
adder_cin_reg <= 0;
update_state_reg <= 2;
end
2: begin
// compute next fns
adder_a_reg <= adder_sum;
adder_b_reg <= ts_fns_reg;
adder_cin_reg <= 0;
update_state_reg <= 3;
end
3: begin
// store fns; compute relative timestamp 1 - add previous value and offset
{ts_inc_ns_reg, ts_fns_reg} <= {adder_cout, adder_sum};
adder_a_reg <= ts_rel_ns_reg;
adder_b_reg <= 0;
adder_cin_reg <= 0;
// offset relative timestamp if requested
if (input_ts_rel_offset_valid) begin
adder_b_reg <= $signed(input_ts_rel_offset_ns);
input_ts_rel_offset_ready_reg <= 1'b1;
ts_rel_updated_reg <= 1'b1;
end
update_state_reg <= 4;
end
4: begin
// compute relative timestamp 2 - add increment
adder_a_reg <= adder_sum;
adder_b_reg <= ts_inc_ns_reg;
adder_cin_reg <= 0;
update_state_reg <= 5;
end
5: begin
// store relative timestamp; compute ToD timestamp 1 - add previous value and increment
ts_rel_ns_reg <= adder_sum;
adder_a_reg <= ts_tod_ns_reg;
adder_b_reg <= ts_inc_ns_reg;
adder_cin_reg <= 0;
update_state_reg <= 6;
end
6: begin
// compute ToD timestamp 2 - add offset
adder_a_reg <= adder_sum;
adder_b_reg <= 0;
adder_cin_reg <= 0;
// offset ToD timestamp if requested
if (input_ts_tod_offset_valid) begin
adder_b_reg <= $signed(input_ts_tod_offset_ns);
input_ts_tod_offset_ready_reg <= 1'b1;
ts_tod_updated_reg <= 1'b1;
end
update_state_reg <= 7;
end
7: begin
// compute ToD timestamp 2 - check for underflow/overflow
ts_tod_ns_reg <= adder_sum;
if (adder_b_reg[47] && !adder_cout) begin
// borrowed; add 1 billion
adder_a_reg <= adder_sum;
adder_b_reg <= NS_PER_S;
adder_cin_reg <= 0;
update_state_reg <= 8;
end else begin
// did not borrow; subtract 1 billion to check for overflow
adder_a_reg <= adder_sum;
adder_b_reg <= -NS_PER_S;
adder_cin_reg <= 0;
update_state_reg <= 9;
end
end
8: begin
// seconds decrement
ts_tod_ns_reg <= adder_sum;
pps_gen_ns_reg[30] <= 1'b1;
adder_a_reg <= ts_tod_s_reg;
adder_b_reg <= -1;
adder_cin_reg <= 0;
update_state_reg <= 10;
end
9: begin
// seconds increment
pps_gen_ns_reg <= adder_sum;
if (!adder_cout) begin
// borrowed; leave seconds alone
adder_a_reg <= ts_tod_s_reg;
adder_b_reg <= 0;
adder_cin_reg <= 0;
end else begin
// did not borrow; decrement seconds
ts_tod_ns_reg <= adder_sum;
adder_a_reg <= ts_tod_s_reg;
adder_b_reg <= 1;
adder_cin_reg <= 0;
end
update_state_reg <= 10;
end
10: begin
// store seconds; compute offset
ts_tod_s_reg <= adder_sum;
if (adder_sum == ts_tod_alt_s_reg) begin
// store previous offset as alternate
ts_tod_alt_s_reg <= ts_tod_s_reg;
ts_tod_alt_offset_ns_reg <= ts_tod_offset_ns_reg;
end
adder_a_reg <= ts_tod_ns_reg;
adder_b_reg <= ~ts_rel_ns_reg;
adder_cin_reg <= 1;
update_state_reg <= 11;
end
11: begin
// store offset
ts_tod_offset_ns_reg <= adder_sum;
adder_a_reg <= adder_sum;
adder_b_reg <= -NS_PER_S;
adder_cin_reg <= 0;
if (ts_tod_ns_reg[29]) begin
// latter half of second; compute offset for next second
adder_b_reg <= -NS_PER_S;
update_state_reg <= 12;
end else begin
// former half of second; compute offset for previous second
adder_b_reg <= NS_PER_S;
update_state_reg <= 14;
end
end
12: begin
// store alternate offset for next second
ts_tod_alt_offset_ns_reg <= adder_sum;
adder_a_reg <= ts_tod_s_reg;
adder_b_reg <= 1;
adder_cin_reg <= 0;
update_state_reg <= 13;
end
13: begin
// store alternate second for next second
ts_tod_alt_s_reg <= adder_sum;
update_state_reg <= 0;
end
14: begin
// store alternate offset for previous second
ts_tod_alt_offset_ns_reg <= adder_sum;
adder_a_reg <= ts_tod_s_reg;
adder_b_reg <= -1;
adder_cin_reg <= 0;
update_state_reg <= 15;
end
15: begin
// store alternate second for previous second
ts_tod_alt_s_reg <= adder_sum;
update_state_reg <= 0;
end
default: begin
// invalid state; return to idle
update_state_reg <= 0;
end
endcase
// time distribution message generation
td_shift_reg <= {1'b1, td_shift_reg} >> 1;
if (td_update_reg) begin
// word 0: control
td_shift_reg[17*0+0 +: 1] <= 1'b0;
td_shift_reg[17*0+1 +: 16] <= 0;
td_shift_reg[17*0+1+0 +: 4] <= td_msg_i_reg;
td_shift_reg[17*0+1+8 +: 1] <= ts_rel_updated_reg;
td_shift_reg[17*0+1+9 +: 1] <= ts_tod_s_reg[0];
ts_rel_updated_reg <= 1'b0;
case (td_msg_i_reg)
2'd0: begin
// msg 0 word 1: current ToD ns 15:0
td_shift_reg[17*1+0 +: 1] <= 1'b0;
td_shift_reg[17*1+1 +: 16] <= ts_tod_ns_reg[15:0];
// msg 0 word 2: current ToD ns 29:16
td_shift_reg[17*2+0 +: 1] <= 1'b0;
td_shift_reg[17*2+1+0 +: 15] <= ts_tod_ns_reg[29:16];
td_shift_reg[17*2+1+15 +: 1] <= ts_tod_updated_reg;
ts_tod_updated_reg <= 1'b0;
// msg 0 word 3: current ToD seconds 15:0
td_shift_reg[17*3+0 +: 1] <= 1'b0;
td_shift_reg[17*3+1 +: 16] <= ts_tod_s_reg[15:0];
// msg 0 word 4: current ToD seconds 31:16
td_shift_reg[17*4+0 +: 1] <= 1'b0;
td_shift_reg[17*4+1 +: 16] <= ts_tod_s_reg[31:16];
// msg 0 word 5: current ToD seconds 47:32
td_shift_reg[17*5+0 +: 1] <= 1'b0;
td_shift_reg[17*5+1 +: 16] <= ts_tod_s_reg[47:32];
td_msg_i_reg <= 2'd1;
end
2'd1: begin
// msg 1 word 1: current ToD ns offset 15:0
td_shift_reg[17*1+0 +: 1] <= 1'b0;
td_shift_reg[17*1+1 +: 16] <= ts_tod_offset_ns_reg[15:0];
// msg 1 word 2: current ToD ns offset 31:16
td_shift_reg[17*2+0 +: 1] <= 1'b0;
td_shift_reg[17*2+1 +: 16] <= ts_tod_offset_ns_reg[31:16];
// msg 1 word 3: drift num
td_shift_reg[17*3+0 +: 1] <= 1'b0;
td_shift_reg[17*3+1 +: 16] <= drift_num_reg;
// msg 1 word 4: drift denom
td_shift_reg[17*4+0 +: 1] <= 1'b0;
td_shift_reg[17*4+1 +: 16] <= drift_denom_reg;
// msg 1 word 5: drift state
td_shift_reg[17*5+0 +: 1] <= 1'b0;
td_shift_reg[17*5+1 +: 16] <= drift_cnt_d1_reg;
td_msg_i_reg <= 2'd2;
end
2'd2: begin
// msg 2 word 1: alternate ToD ns offset 15:0
td_shift_reg[17*1+0 +: 1] <= 1'b0;
td_shift_reg[17*1+1 +: 16] <= ts_tod_alt_offset_ns_reg[15:0];
// msg 2 word 2: alternate ToD ns offset 31:16
td_shift_reg[17*2+0 +: 1] <= 1'b0;
td_shift_reg[17*2+1 +: 16] <= ts_tod_alt_offset_ns_reg[31:16];
// msg 2 word 3: alternate ToD seconds 15:0
td_shift_reg[17*3+0 +: 1] <= 1'b0;
td_shift_reg[17*3+1 +: 16] <= ts_tod_alt_s_reg[15:0];
// msg 2 word 4: alternate ToD seconds 31:16
td_shift_reg[17*4+0 +: 1] <= 1'b0;
td_shift_reg[17*4+1 +: 16] <= ts_tod_alt_s_reg[31:16];
// msg 2 word 5: alternate ToD seconds 47:32
td_shift_reg[17*5+0 +: 1] <= 1'b0;
td_shift_reg[17*5+1 +: 16] <= ts_tod_alt_s_reg[47:32];
td_msg_i_reg <= 2'd0;
end
endcase
// word 6: current fns 15:0
td_shift_reg[17*6+0 +: 1] <= 1'b0;
td_shift_reg[17*6+1 +: 16] <= ts_fns_reg[15:0];
// word 7: current fns 31:16
td_shift_reg[17*7+0 +: 1] <= 1'b0;
td_shift_reg[17*7+1 +: 16] <= ts_fns_reg[31:16];
// word 8: current ns 15:0
td_shift_reg[17*8+0 +: 1] <= 1'b0;
td_shift_reg[17*8+1 +: 16] <= ts_rel_ns_reg[15:0];
// word 9: current ns 31:16
td_shift_reg[17*9+0 +: 1] <= 1'b0;
td_shift_reg[17*9+1 +: 16] <= ts_rel_ns_reg[31:16];
// word 10: current ns 47:32
td_shift_reg[17*10+0 +: 1] <= 1'b0;
td_shift_reg[17*10+1 +: 16] <= ts_rel_ns_reg[47:32];
// word 11: current phase increment fns 15:0
td_shift_reg[17*11+0 +: 1] <= 1'b0;
td_shift_reg[17*11+1 +: 16] <= period_fns_reg[15:0];
// word 12: current phase increment fns 31:16
td_shift_reg[17*12+0 +: 1] <= 1'b0;
td_shift_reg[17*12+1 +: 16] <= period_fns_reg[31:16];
// word 13: current phase increment ns 7:0 + crc
td_shift_reg[17*13+0 +: 1] <= 1'b0;
td_shift_reg[17*13+1 +: 16] <= period_ns_reg[7:0];
end
if (rst) begin
period_ns_reg <= PERIOD_NS;
period_fns_reg <= PERIOD_FNS;
drift_num_reg <= PERIOD_FNS_REM;
drift_denom_reg <= PERIOD_NS_DENOM;
drift_cnt_reg <= 0;
drift_acc_reg <= 0;
ts_fns_reg <= 0;
ts_rel_ns_reg <= 0;
ts_rel_updated_reg <= 0;
ts_tod_s_reg <= 0;
ts_tod_ns_reg <= 0;
ts_tod_updated_reg <= 0;
pps_gen_ns_reg[30] <= 1'b1;
pps_delay_reg <= 0;
pps_reg <= 0;
pps_str_reg <= 0;
td_update_cnt_reg <= 0;
td_update_reg <= 1'b0;
td_msg_i_reg <= 0;
td_shift_reg <= {17*14{1'b1}};
end
end
endmodule
`resetall