mirror of
https://github.com/fpganinja/taxi.git
synced 2025-12-09 00:48:40 -08:00
314 lines
9.7 KiB
Systemverilog
314 lines
9.7 KiB
Systemverilog
// SPDX-License-Identifier: CERN-OHL-S-2.0
|
|
/*
|
|
|
|
Copyright (c) 2019-2025 FPGA Ninja, LLC
|
|
|
|
Authors:
|
|
- Alex Forencich
|
|
|
|
*/
|
|
|
|
`resetall
|
|
`timescale 1ns / 1ps
|
|
`default_nettype none
|
|
|
|
/*
|
|
* PTP period out module
|
|
*/
|
|
module taxi_ptp_perout #
|
|
(
|
|
parameter logic FNS_EN = 1'b1,
|
|
parameter OUT_START_S = 48'h0,
|
|
parameter OUT_START_NS = 30'h0,
|
|
parameter OUT_START_FNS = 16'h0000,
|
|
parameter OUT_PERIOD_S = 48'd1,
|
|
parameter OUT_PERIOD_NS = 30'd0,
|
|
parameter OUT_PERIOD_FNS = 16'h0000,
|
|
parameter OUT_WIDTH_S = 48'h0,
|
|
parameter OUT_WIDTH_NS = 30'd1000,
|
|
parameter OUT_WIDTH_FNS = 16'h0000
|
|
)
|
|
(
|
|
input wire logic clk,
|
|
input wire logic rst,
|
|
|
|
/*
|
|
* Timestamp input from PTP clock
|
|
*/
|
|
input wire logic [95:0] input_ts_tod,
|
|
input wire logic input_ts_tod_step,
|
|
|
|
/*
|
|
* Control
|
|
*/
|
|
input wire logic enable,
|
|
input wire logic [95:0] input_start,
|
|
input wire logic input_start_valid,
|
|
input wire logic [95:0] input_period,
|
|
input wire logic input_period_valid,
|
|
input wire logic [95:0] input_width,
|
|
input wire logic input_width_valid,
|
|
|
|
/*
|
|
* Status
|
|
*/
|
|
output wire logic locked,
|
|
output wire logic error,
|
|
|
|
/*
|
|
* Pulse output
|
|
*/
|
|
output wire logic output_pulse
|
|
);
|
|
|
|
localparam [1:0]
|
|
STATE_IDLE = 2'd0,
|
|
STATE_UPDATE_RISE = 2'd1,
|
|
STATE_UPDATE_FALL = 2'd2;
|
|
|
|
logic [1:0] state_reg = STATE_IDLE, state_next;
|
|
|
|
logic [47:0] time_s_reg = '0;
|
|
logic [29:0] time_ns_reg = '0;
|
|
logic [15:0] time_fns_reg = '0;
|
|
|
|
logic [47:0] next_rise_s_reg = '0, next_rise_s_next;
|
|
logic [29:0] next_rise_ns_reg = '0, next_rise_ns_next;
|
|
logic [15:0] next_rise_fns_reg = '0, next_rise_fns_next;
|
|
|
|
logic [47:0] next_edge_s_reg = '0, next_edge_s_next;
|
|
logic [29:0] next_edge_ns_reg = '0, next_edge_ns_next;
|
|
logic [15:0] next_edge_fns_reg = '0, next_edge_fns_next;
|
|
|
|
logic [47:0] start_s_reg = 48'(OUT_START_S);
|
|
logic [29:0] start_ns_reg = 30'(OUT_START_NS);
|
|
logic [15:0] start_fns_reg = 16'(OUT_START_FNS);
|
|
|
|
logic [47:0] period_s_reg = 48'(OUT_PERIOD_S);
|
|
logic [29:0] period_ns_reg = 30'(OUT_PERIOD_NS);
|
|
logic [15:0] period_fns_reg = 16'(OUT_PERIOD_FNS);
|
|
|
|
logic [47:0] width_s_reg = 48'(OUT_WIDTH_S);
|
|
logic [29:0] width_ns_reg = 30'(OUT_WIDTH_NS);
|
|
logic [15:0] width_fns_reg = 16'(OUT_WIDTH_FNS);
|
|
|
|
logic [29:0] ts_tod_ns_inc_reg = '0, ts_tod_ns_inc_next;
|
|
logic [15:0] ts_tod_fns_inc_reg = '0, ts_tod_fns_inc_next;
|
|
logic [30:0] ts_tod_ns_ovf_reg = '0, ts_tod_ns_ovf_next;
|
|
logic [15:0] ts_tod_fns_ovf_reg = '0, ts_tod_fns_ovf_next;
|
|
|
|
logic restart_reg = 1'b1;
|
|
logic locked_reg = 1'b0, locked_next;
|
|
logic error_reg = 1'b0, error_next;
|
|
logic ffwd_reg = 1'b0, ffwd_next;
|
|
logic level_reg = 1'b0, level_next;
|
|
logic output_reg = 1'b0, output_next;
|
|
|
|
assign locked = locked_reg;
|
|
assign error = error_reg;
|
|
assign output_pulse = output_reg;
|
|
|
|
always_comb begin
|
|
state_next = STATE_IDLE;
|
|
|
|
next_rise_s_next = next_rise_s_reg;
|
|
next_rise_ns_next = next_rise_ns_reg;
|
|
next_rise_fns_next = next_rise_fns_reg;
|
|
|
|
next_edge_s_next = next_edge_s_reg;
|
|
next_edge_ns_next = next_edge_ns_reg;
|
|
next_edge_fns_next = next_edge_fns_reg;
|
|
|
|
ts_tod_ns_inc_next = ts_tod_ns_inc_reg;
|
|
ts_tod_fns_inc_next = ts_tod_fns_inc_reg;
|
|
|
|
ts_tod_ns_ovf_next = ts_tod_ns_ovf_reg;
|
|
ts_tod_fns_ovf_next = ts_tod_fns_ovf_reg;
|
|
|
|
locked_next = locked_reg;
|
|
error_next = error_reg;
|
|
ffwd_next = ffwd_reg;
|
|
level_next = level_reg;
|
|
output_next = output_reg;
|
|
|
|
case (state_reg)
|
|
STATE_IDLE: begin
|
|
if (ffwd_reg || level_reg) begin
|
|
// fast forward or falling edge, set up for next rising edge
|
|
// set next rise time to previous rise time plus period
|
|
{ts_tod_ns_inc_next, ts_tod_fns_inc_next} = {next_rise_ns_reg, next_rise_fns_reg} + {period_ns_reg, period_fns_reg};
|
|
{ts_tod_ns_ovf_next, ts_tod_fns_ovf_next} = {next_rise_ns_reg, next_rise_fns_reg} + {period_ns_reg, period_fns_reg} - {30'd1_000_000_000, 16'd0};
|
|
end else begin
|
|
// rising edge; set up for next falling edge
|
|
// set next fall time to previous rise time plus width
|
|
{ts_tod_ns_inc_next, ts_tod_fns_inc_next} = {next_rise_ns_reg, next_rise_fns_reg} + {width_ns_reg, width_fns_reg};
|
|
{ts_tod_ns_ovf_next, ts_tod_fns_ovf_next} = {next_rise_ns_reg, next_rise_fns_reg} + {width_ns_reg, width_fns_reg} - {30'd1_000_000_000, 16'd0};
|
|
end
|
|
|
|
// wait for edge
|
|
if ((time_s_reg > next_edge_s_reg) || (time_s_reg == next_edge_s_reg && {time_ns_reg, time_fns_reg} > {next_edge_ns_reg, next_edge_fns_reg})) begin
|
|
if (ffwd_reg || level_reg) begin
|
|
// fast forward or falling edge, set up for next rising edge
|
|
output_next = 1'b0;
|
|
level_next = 1'b0;
|
|
state_next = STATE_UPDATE_RISE;
|
|
end else begin
|
|
// rising edge; set up for next falling edge
|
|
locked_next = 1'b1;
|
|
error_next = 1'b0;
|
|
output_next = enable;
|
|
level_next = 1'b1;
|
|
state_next = STATE_UPDATE_FALL;
|
|
end
|
|
end else begin
|
|
ffwd_next = 1'b0;
|
|
state_next = STATE_IDLE;
|
|
end
|
|
end
|
|
STATE_UPDATE_RISE: begin
|
|
if (!ts_tod_ns_ovf_reg[30]) begin
|
|
// if the overflow lookahead did not borrow, one second has elapsed
|
|
next_edge_s_next = next_rise_s_reg + period_s_reg + 1;
|
|
next_edge_ns_next = ts_tod_ns_ovf_reg[29:0];
|
|
next_edge_fns_next = ts_tod_fns_ovf_reg;
|
|
end else begin
|
|
// no increment seconds field
|
|
next_edge_s_next = next_rise_s_reg + period_s_reg;
|
|
next_edge_ns_next = ts_tod_ns_inc_reg;
|
|
next_edge_fns_next = ts_tod_fns_inc_reg;
|
|
end
|
|
next_rise_s_next = next_edge_s_next;
|
|
next_rise_ns_next = next_edge_ns_next;
|
|
next_rise_fns_next = next_edge_fns_next;
|
|
state_next = STATE_IDLE;
|
|
end
|
|
STATE_UPDATE_FALL: begin
|
|
if (!ts_tod_ns_ovf_reg[30]) begin
|
|
// if the overflow lookahead did not borrow, one second has elapsed
|
|
next_edge_s_next = next_rise_s_reg + width_s_reg + 1;
|
|
next_edge_ns_next = ts_tod_ns_ovf_reg[29:0];
|
|
next_edge_fns_next = ts_tod_fns_ovf_reg;
|
|
end else begin
|
|
// no increment seconds field
|
|
next_edge_s_next = next_rise_s_reg + width_s_reg;
|
|
next_edge_ns_next = ts_tod_ns_inc_reg;
|
|
next_edge_fns_next = ts_tod_fns_inc_reg;
|
|
end
|
|
state_next = STATE_IDLE;
|
|
end
|
|
default: begin
|
|
state_next = STATE_IDLE;
|
|
end
|
|
endcase
|
|
|
|
if (restart_reg || input_ts_tod_step) begin
|
|
// set next rise and next edge to start time
|
|
next_rise_s_next = start_s_reg;
|
|
next_rise_ns_next = start_ns_reg;
|
|
if (FNS_EN) begin
|
|
next_rise_fns_next = start_fns_reg;
|
|
end
|
|
next_edge_s_next = start_s_reg;
|
|
next_edge_ns_next = start_ns_reg;
|
|
if (FNS_EN) begin
|
|
next_edge_fns_next = start_fns_reg;
|
|
end
|
|
locked_next = 1'b0;
|
|
ffwd_next = 1'b1;
|
|
output_next = 1'b0;
|
|
level_next = 1'b0;
|
|
error_next = input_ts_tod_step;
|
|
state_next = STATE_IDLE;
|
|
end
|
|
end
|
|
|
|
always_ff @(posedge clk) begin
|
|
state_reg <= state_next;
|
|
restart_reg <= 1'b0;
|
|
|
|
time_s_reg <= input_ts_tod[95:48];
|
|
time_ns_reg <= input_ts_tod[45:16];
|
|
if (FNS_EN) begin
|
|
time_fns_reg <= input_ts_tod[15:0];
|
|
end
|
|
|
|
if (input_start_valid) begin
|
|
start_s_reg <= input_start[95:48];
|
|
start_ns_reg <= input_start[45:16];
|
|
if (FNS_EN) begin
|
|
start_fns_reg <= input_start[15:0];
|
|
end
|
|
restart_reg <= 1'b1;
|
|
end
|
|
|
|
if (input_period_valid) begin
|
|
period_s_reg <= input_period[95:48];
|
|
period_ns_reg <= input_period[45:16];
|
|
if (FNS_EN) begin
|
|
period_fns_reg <= input_period[15:0];
|
|
end
|
|
restart_reg <= 1'b1;
|
|
end
|
|
|
|
if (input_width_valid) begin
|
|
width_s_reg <= input_width[95:48];
|
|
width_ns_reg <= input_width[45:16];
|
|
if (FNS_EN) begin
|
|
width_fns_reg <= input_width[15:0];
|
|
end
|
|
end
|
|
|
|
next_rise_s_reg <= next_rise_s_next;
|
|
next_rise_ns_reg <= next_rise_ns_next;
|
|
if (FNS_EN) begin
|
|
next_rise_fns_reg <= next_rise_fns_next;
|
|
end
|
|
|
|
next_edge_s_reg <= next_edge_s_next;
|
|
next_edge_ns_reg <= next_edge_ns_next;
|
|
if (FNS_EN) begin
|
|
next_edge_fns_reg <= next_edge_fns_next;
|
|
end
|
|
|
|
ts_tod_ns_inc_reg <= ts_tod_ns_inc_next;
|
|
if (FNS_EN) begin
|
|
ts_tod_fns_inc_reg <= ts_tod_fns_inc_next;
|
|
end
|
|
|
|
ts_tod_ns_ovf_reg <= ts_tod_ns_ovf_next;
|
|
if (FNS_EN) begin
|
|
ts_tod_fns_ovf_reg <= ts_tod_fns_ovf_next;
|
|
end
|
|
|
|
locked_reg <= locked_next;
|
|
error_reg <= error_next;
|
|
ffwd_reg <= ffwd_next;
|
|
level_reg <= level_next;
|
|
output_reg <= output_next;
|
|
|
|
if (rst) begin
|
|
state_reg <= STATE_IDLE;
|
|
|
|
start_s_reg <= 48'(OUT_START_S);
|
|
start_ns_reg <= 30'(OUT_START_NS);
|
|
start_fns_reg <= 16'(OUT_START_FNS);
|
|
|
|
period_s_reg <= 48'(OUT_PERIOD_S);
|
|
period_ns_reg <= 30'(OUT_PERIOD_NS);
|
|
period_fns_reg <= 16'(OUT_PERIOD_FNS);
|
|
|
|
width_s_reg <= 48'(OUT_WIDTH_S);
|
|
width_ns_reg <= 30'(OUT_WIDTH_NS);
|
|
width_fns_reg <= 16'(OUT_WIDTH_FNS);
|
|
|
|
restart_reg <= 1'b1;
|
|
locked_reg <= 1'b0;
|
|
error_reg <= 1'b0;
|
|
output_reg <= 1'b0;
|
|
end
|
|
end
|
|
|
|
endmodule
|
|
|
|
`resetall
|