// SPDX-License-Identifier: BSD-2-Clause-Views /* * Copyright (c) 2021-2023 The Regents of the University of California */ // Language: Verilog 2001 `resetall `timescale 1ns / 1ps `default_nettype none /* * PTP hardware clock */ module mqnic_ptp_clock # ( parameter PTP_CLK_PERIOD_NS_NUM = 4, parameter PTP_CLK_PERIOD_NS_DENOM = 1, parameter PTP_CLOCK_PIPELINE = 0, parameter PTP_CLOCK_CDC_PIPELINE = 0, parameter PTP_USE_SAMPLE_CLOCK = 0, parameter PTP_PEROUT_ENABLE = 0, parameter PTP_PEROUT_COUNT = 1, parameter REG_ADDR_WIDTH = 7, parameter REG_DATA_WIDTH = 32, parameter REG_STRB_WIDTH = (REG_DATA_WIDTH/8), parameter RB_BASE_ADDR = 0, parameter RB_NEXT_PTR = 0 ) ( input wire clk, input wire rst, /* * Register interface */ input wire [REG_ADDR_WIDTH-1:0] reg_wr_addr, input wire [REG_DATA_WIDTH-1:0] reg_wr_data, input wire [REG_STRB_WIDTH-1:0] reg_wr_strb, input wire reg_wr_en, output wire reg_wr_wait, output wire reg_wr_ack, input wire [REG_ADDR_WIDTH-1:0] reg_rd_addr, input wire reg_rd_en, output wire [REG_DATA_WIDTH-1:0] reg_rd_data, output wire reg_rd_wait, output wire reg_rd_ack, /* * PTP clock */ input wire ptp_clk, input wire ptp_rst, input wire ptp_sample_clk, output wire ptp_pps, output wire ptp_pps_str, output wire [95:0] ptp_ts_96, output wire ptp_ts_step, output wire ptp_sync_pps, output wire [95:0] ptp_sync_ts_96, output wire ptp_sync_ts_step ); parameter PTP_FNS_WIDTH = 32; parameter PTP_CLK_PERIOD_NS = PTP_CLK_PERIOD_NS_NUM / PTP_CLK_PERIOD_NS_DENOM; parameter PTP_CLK_PERIOD_NS_REM = PTP_CLK_PERIOD_NS_NUM - PTP_CLK_PERIOD_NS*PTP_CLK_PERIOD_NS_DENOM; parameter PTP_CLK_PERIOD_FNS = (PTP_CLK_PERIOD_NS_REM * {32'd1, {PTP_FNS_WIDTH{1'b0}}}) / PTP_CLK_PERIOD_NS_DENOM; parameter PTP_CLK_PERIOD_FNS_REM = (PTP_CLK_PERIOD_NS_REM * {32'd1, {PTP_FNS_WIDTH{1'b0}}}) - PTP_CLK_PERIOD_FNS*PTP_CLK_PERIOD_NS_DENOM; parameter PTP_PERIOD_NS_WIDTH = $clog2(PTP_CLK_PERIOD_NS+1) + 2; parameter PTP_OFFSET_NS_WIDTH = 30; localparam RBB = RB_BASE_ADDR & {REG_ADDR_WIDTH{1'b1}}; // check configuration initial begin if (REG_DATA_WIDTH != 32) begin $error("Error: Register interface width must be 32 (instance %m)"); $finish; end if (REG_STRB_WIDTH * 8 != REG_DATA_WIDTH) begin $error("Error: Register interface requires byte (8-bit) granularity (instance %m)"); $finish; end if (REG_ADDR_WIDTH < 7) begin $error("Error: Register address width too narrow (instance %m)"); $finish; end if (RB_NEXT_PTR >= RB_BASE_ADDR && RB_NEXT_PTR < RB_BASE_ADDR + 7'h60) begin $error("Error: RB_NEXT_PTR overlaps block (instance %m)"); $finish; end end // control registers reg reg_wr_ack_reg = 1'b0; reg [REG_DATA_WIDTH-1:0] reg_rd_data_reg = 0; reg reg_rd_ack_reg = 1'b0; reg [95:0] get_ptp_ts_96_reg = 0; reg [95:0] set_ptp_ts_96_reg = 0; reg set_ptp_ts_96_valid_reg = 0; reg [PTP_PERIOD_NS_WIDTH-1:0] set_ptp_period_ns_reg = PTP_CLK_PERIOD_NS; reg [PTP_FNS_WIDTH-1:0] set_ptp_period_fns_reg = PTP_CLK_PERIOD_FNS; reg set_ptp_period_valid_reg = 0; reg [PTP_OFFSET_NS_WIDTH-1:0] set_ptp_offset_ns_reg = 0; reg [PTP_FNS_WIDTH-1:0] set_ptp_offset_fns_reg = 0; reg [15:0] set_ptp_offset_count_reg = 0; reg set_ptp_offset_valid_reg = 0; wire set_ptp_offset_active; assign reg_wr_wait = 1'b0; assign reg_wr_ack = reg_wr_ack_reg; assign reg_rd_data = reg_rd_data_reg; assign reg_rd_wait = 1'b0; assign reg_rd_ack = reg_rd_ack_reg; always @(posedge clk) begin reg_wr_ack_reg <= 1'b0; reg_rd_data_reg <= 0; reg_rd_ack_reg <= 1'b0; if (reg_wr_en && !reg_wr_ack_reg) begin // write operation reg_wr_ack_reg <= 1'b1; case ({reg_wr_addr >> 2, 2'b00}) // PHC RBB+7'h30: set_ptp_ts_96_reg[15:0] <= reg_wr_data; // PTP set fns RBB+7'h34: set_ptp_ts_96_reg[45:16] <= reg_wr_data; // PTP set ns RBB+7'h38: set_ptp_ts_96_reg[79:48] <= reg_wr_data; // PTP set sec l RBB+7'h3C: begin // PTP set sec h set_ptp_ts_96_reg[95:80] <= reg_wr_data; set_ptp_ts_96_valid_reg <= !set_ptp_ts_96_valid_reg; end RBB+7'h40: set_ptp_period_fns_reg <= reg_wr_data; // PTP period fns RBB+7'h44: begin // PTP period ns set_ptp_period_ns_reg <= reg_wr_data; set_ptp_period_valid_reg <= !set_ptp_period_valid_reg; end RBB+7'h50: set_ptp_offset_fns_reg <= reg_wr_data; // PTP offset fns RBB+7'h54: set_ptp_offset_ns_reg <= reg_wr_data; // PTP offset ns RBB+7'h58: begin // PTP offset count set_ptp_offset_count_reg <= reg_wr_data; set_ptp_offset_valid_reg <= !set_ptp_offset_valid_reg; end default: reg_wr_ack_reg <= 1'b0; endcase end if (reg_rd_en && !reg_rd_ack_reg) begin // read operation reg_rd_ack_reg <= 1'b1; case ({reg_rd_addr >> 2, 2'b00}) // PHC RBB+7'h00: reg_rd_data_reg <= 32'h0000C080; // PHC: Type RBB+7'h04: reg_rd_data_reg <= 32'h00000100; // PHC: Version RBB+7'h08: reg_rd_data_reg <= RB_NEXT_PTR; // PHC: Next header RBB+7'h0C: begin // PHC features reg_rd_data_reg[7:0] <= PTP_PEROUT_ENABLE ? PTP_PEROUT_COUNT : 0; reg_rd_data_reg[15:8] <= 0; reg_rd_data_reg[23:16] <= 0; reg_rd_data_reg[31:24] <= 0; end RBB+7'h10: reg_rd_data_reg <= ptp_sync_ts_96[15:0]; // PTP cur fns RBB+7'h14: reg_rd_data_reg <= ptp_sync_ts_96[45:16]; // PTP cur ns RBB+7'h18: reg_rd_data_reg <= ptp_sync_ts_96[79:48]; // PTP cur sec l RBB+7'h1C: reg_rd_data_reg <= ptp_sync_ts_96[95:80]; // PTP cur sec h RBB+7'h20: begin // PTP get fns get_ptp_ts_96_reg <= ptp_sync_ts_96; reg_rd_data_reg <= ptp_sync_ts_96[15:0]; end RBB+7'h24: reg_rd_data_reg <= get_ptp_ts_96_reg[45:16]; // PTP get ns RBB+7'h28: reg_rd_data_reg <= get_ptp_ts_96_reg[79:48]; // PTP get sec l RBB+7'h2C: reg_rd_data_reg <= get_ptp_ts_96_reg[95:80]; // PTP get sec h RBB+7'h30: reg_rd_data_reg <= set_ptp_ts_96_reg[15:0]; // PTP set fns RBB+7'h34: reg_rd_data_reg <= set_ptp_ts_96_reg[45:16]; // PTP set ns RBB+7'h38: reg_rd_data_reg <= set_ptp_ts_96_reg[79:48]; // PTP set sec l RBB+7'h3C: reg_rd_data_reg <= set_ptp_ts_96_reg[95:80]; // PTP set sec h RBB+7'h40: reg_rd_data_reg <= set_ptp_period_fns_reg; // PTP period fns RBB+7'h44: reg_rd_data_reg <= set_ptp_period_ns_reg; // PTP period ns RBB+7'h48: reg_rd_data_reg <= PTP_CLK_PERIOD_FNS; // PTP nom period fns RBB+7'h4C: reg_rd_data_reg <= PTP_CLK_PERIOD_NS; // PTP nom period ns RBB+7'h50: reg_rd_data_reg <= set_ptp_offset_fns_reg; // PTP offset fns RBB+7'h54: reg_rd_data_reg <= set_ptp_offset_ns_reg; // PTP offset ns RBB+7'h58: reg_rd_data_reg <= set_ptp_offset_count_reg; // PTP offset count RBB+7'h5C: reg_rd_data_reg <= set_ptp_offset_active; // PTP offset status default: reg_rd_ack_reg <= 1'b0; endcase end if (rst) begin reg_wr_ack_reg <= 1'b0; reg_rd_ack_reg <= 1'b0; set_ptp_period_ns_reg <= PTP_CLK_PERIOD_NS; set_ptp_period_fns_reg <= PTP_CLK_PERIOD_FNS; end end (* shreg_extract = "no" *) reg set_ptp_ts_96_valid_sync_1_reg = 1'b0; (* shreg_extract = "no" *) reg set_ptp_ts_96_valid_sync_2_reg = 1'b0; (* shreg_extract = "no" *) reg set_ptp_ts_96_valid_sync_3_reg = 1'b0; (* shreg_extract = "no" *) reg set_ptp_period_valid_sync_1_reg = 1'b0; (* shreg_extract = "no" *) reg set_ptp_period_valid_sync_2_reg = 1'b0; (* shreg_extract = "no" *) reg set_ptp_period_valid_sync_3_reg = 1'b0; (* shreg_extract = "no" *) reg set_ptp_offset_valid_sync_1_reg = 1'b0; (* shreg_extract = "no" *) reg set_ptp_offset_valid_sync_2_reg = 1'b0; (* shreg_extract = "no" *) reg set_ptp_offset_valid_sync_3_reg = 1'b0; always @(posedge ptp_clk) begin set_ptp_ts_96_valid_sync_1_reg <= set_ptp_ts_96_valid_reg; set_ptp_ts_96_valid_sync_2_reg <= set_ptp_ts_96_valid_sync_1_reg; set_ptp_ts_96_valid_sync_3_reg <= set_ptp_ts_96_valid_sync_2_reg; set_ptp_period_valid_sync_1_reg <= set_ptp_period_valid_reg; set_ptp_period_valid_sync_2_reg <= set_ptp_period_valid_sync_1_reg; set_ptp_period_valid_sync_3_reg <= set_ptp_period_valid_sync_2_reg; set_ptp_offset_valid_sync_1_reg <= set_ptp_offset_valid_reg; set_ptp_offset_valid_sync_2_reg <= set_ptp_offset_valid_sync_1_reg; set_ptp_offset_valid_sync_3_reg <= set_ptp_offset_valid_sync_2_reg; end // PTP clock ptp_clock #( .PERIOD_NS_WIDTH(PTP_PERIOD_NS_WIDTH), .OFFSET_NS_WIDTH(PTP_OFFSET_NS_WIDTH), .FNS_WIDTH(PTP_FNS_WIDTH), .PERIOD_NS(PTP_CLK_PERIOD_NS), .PERIOD_FNS(PTP_CLK_PERIOD_FNS), .DRIFT_ENABLE(0), .DRIFT_NS(0), .DRIFT_FNS(PTP_CLK_PERIOD_FNS_REM), .DRIFT_RATE(PTP_CLK_PERIOD_NS_DENOM), .PIPELINE_OUTPUT(PTP_CLOCK_PIPELINE) ) ptp_clock_inst ( .clk(ptp_clk), .rst(ptp_rst), /* * Timestamp inputs for synchronization */ .input_ts_96(set_ptp_ts_96_reg), .input_ts_96_valid(set_ptp_ts_96_valid_sync_2_reg ^ set_ptp_ts_96_valid_sync_3_reg), .input_ts_64(0), .input_ts_64_valid(1'b0), /* * Period adjustment */ .input_period_ns(set_ptp_period_ns_reg), .input_period_fns(set_ptp_period_fns_reg), .input_period_valid(set_ptp_period_valid_sync_2_reg ^ set_ptp_period_valid_sync_3_reg), /* * Offset adjustment */ .input_adj_ns(set_ptp_offset_ns_reg), .input_adj_fns(set_ptp_offset_fns_reg), .input_adj_count(set_ptp_offset_count_reg), .input_adj_valid(set_ptp_offset_valid_sync_2_reg ^ set_ptp_offset_valid_sync_3_reg), // .input_adj_active(set_ptp_offset_active), /* * Drift adjustment */ .input_drift_ns(0), .input_drift_fns(0), .input_drift_rate(0), .input_drift_valid(0), /* * Timestamp outputs */ .output_ts_96(ptp_ts_96), .output_ts_64(), .output_ts_step(ptp_ts_step), /* * PPS output */ .output_pps(ptp_pps) ); // stretched PPS output localparam PPS_CNT_PERIOD = (64'd500_000_000*PTP_CLK_PERIOD_NS_DENOM)/PTP_CLK_PERIOD_NS_NUM; reg [$clog2(PPS_CNT_PERIOD)-1:0] pps_counter_reg = 0; reg pps_str_reg = 0; always @(posedge ptp_clk) begin pps_str_reg <= 1'b0; if (ptp_pps) begin pps_counter_reg <= PPS_CNT_PERIOD; pps_str_reg <= 1'b1; end else if (pps_counter_reg > 0) begin pps_counter_reg <= pps_counter_reg - 1; pps_str_reg <= 1'b1; end end assign ptp_pps_str = pps_str_reg; // sync to core clock domain ptp_clock_cdc #( .TS_WIDTH(96), .NS_WIDTH(PTP_PERIOD_NS_WIDTH), .FNS_WIDTH(16), .USE_SAMPLE_CLOCK(PTP_USE_SAMPLE_CLOCK), .PIPELINE_OUTPUT(PTP_CLOCK_CDC_PIPELINE) ) ptp_cdc_inst ( .input_clk(ptp_clk), .input_rst(ptp_rst), .output_clk(clk), .output_rst(rst), .sample_clk(ptp_sample_clk), .input_ts(ptp_ts_96), .input_ts_step(ptp_ts_step), .output_ts(ptp_sync_ts_96), .output_ts_step(ptp_sync_ts_step), .output_pps(ptp_sync_pps), .locked() ); endmodule `resetall