// // Copyright 2019 Ettus Research, a National Instruments Company // // SPDX-License-Identifier: LGPL-3.0-or-later // // Module: timekeeper // // Description: Timekeeper for RFNoC blocks. This block contains a 64-bit // counter to represent the current time in terms of sample clock cycles. The // counter can be updated and synchronized using the pps input. // // WARNING: All register larger than a single 32-bit word should be read and // written least significant word first to guarantee coherency. // module timekeeper #( parameter BASE_ADDR = 'h00, parameter TIME_INCREMENT = 1 // Amount by which to increment time on each sample clock cycle ) ( input wire tb_clk, // Time-base clock input wire tb_rst, // Time-base reset in tb_clk domain //--------------------------------------------------------------------------- // Control Interface //--------------------------------------------------------------------------- input wire s_ctrlport_clk, input wire s_ctrlport_req_wr, input wire s_ctrlport_req_rd, input wire [19:0] s_ctrlport_req_addr, input wire [31:0] s_ctrlport_req_data, output wire s_ctrlport_resp_ack, output wire [31:0] s_ctrlport_resp_data, //--------------------------------------------------------------------------- // Time //--------------------------------------------------------------------------- input wire sample_rx_stb, // Sample Rx strobe (data valid indicator) input wire pps, // Pulse per second input output reg [63:0] tb_timestamp, // 64-bit global timestamp synchronous to tb_clk output reg [63:0] tb_timestamp_last_pps, // 64-bit global timestamp synchronous to tb_clk output reg [63:0] tb_period_ns_q32 // Time Period of time-base in nanoseconds ); //--------------------------------------------------------------------------- // Register Logic //--------------------------------------------------------------------------- reg set_time_pps; reg set_time_now; reg new_time_ctrl; reg [63:0] time_at_next_event; // Time to load at next timed event reg [31:0] tb_timestamp_hi; // Holding register for reading tb_timestamp reg [31:0] time_at_next_event_lo; // Holding register for writing time_at_next_event reg [31:0] time_at_next_event_hi; // Holding register for reading time_at_next_event reg [31:0] tb_timestamp_last_pps_hi; // Holding register for reading tb_timestamp_last_pps wire s_ctrlport_req_wr_tb; wire s_ctrlport_req_rd_tb; wire [19:0] s_ctrlport_req_addr_tb; wire [31:0] s_ctrlport_req_data_tb; reg s_ctrlport_resp_ack_tb; reg [31:0] s_ctrlport_resp_data_tb; // Clock crossing from ctrlport_clk to tb_clk domain ctrlport_clk_cross ctrlport_clk_cross_tb_i ( .rst (tb_rst), .s_ctrlport_clk (s_ctrlport_clk), .s_ctrlport_req_wr (s_ctrlport_req_wr), .s_ctrlport_req_rd (s_ctrlport_req_rd), .s_ctrlport_req_addr (s_ctrlport_req_addr), .s_ctrlport_req_portid (), .s_ctrlport_req_rem_epid (), .s_ctrlport_req_rem_portid (), .s_ctrlport_req_data (s_ctrlport_req_data), .s_ctrlport_req_byte_en (), .s_ctrlport_req_has_time (), .s_ctrlport_req_time (), .s_ctrlport_resp_ack (s_ctrlport_resp_ack), .s_ctrlport_resp_status (), .s_ctrlport_resp_data (s_ctrlport_resp_data), .m_ctrlport_clk (tb_clk), .m_ctrlport_req_wr (s_ctrlport_req_wr_tb), .m_ctrlport_req_rd (s_ctrlport_req_rd_tb), .m_ctrlport_req_addr (s_ctrlport_req_addr_tb), .m_ctrlport_req_portid (), .m_ctrlport_req_rem_epid (), .m_ctrlport_req_rem_portid (), .m_ctrlport_req_data (s_ctrlport_req_data_tb), .m_ctrlport_req_byte_en (), .m_ctrlport_req_has_time (), .m_ctrlport_req_time (), .m_ctrlport_resp_ack (s_ctrlport_resp_ack_tb), .m_ctrlport_resp_status (), .m_ctrlport_resp_data (s_ctrlport_resp_data_tb) ); //--------------------------------------------------------------------------- // Timekeeper Register Offsets //--------------------------------------------------------------------------- localparam REG_TIME_NOW_LO = 'h00; // Current time count (low word) localparam REG_TIME_NOW_HI = 'h04; // Current time count (high word) localparam REG_TIME_EVENT_LO = 'h08; // Time for next event (low word) localparam REG_TIME_EVENT_HI = 'h0C; // Time for next event (high word) localparam REG_TIME_CTRL = 'h10; // Time control word localparam REG_TIME_LAST_PPS_LO = 'h14; // Time of last PPS pulse edge (low word) localparam REG_TIME_LAST_PPS_HI = 'h18; // Time of last PPS pulse edge (high word) localparam REG_TIME_BASE_PERIOD_LO = 'h1C; // Time Period in nanoseconds (low word) localparam REG_TIME_BASE_PERIOD_HI = 'h20; // Time Period in nanoseconds (high word) // REG_TIME_CTRL bit fields localparam TIME_NOW_POS = 0; localparam TIME_PPS_POS = 1; always @(posedge tb_clk) begin if (tb_rst) begin s_ctrlport_resp_ack_tb <= 0; s_ctrlport_resp_data_tb <= 0; new_time_ctrl <= 0; set_time_pps <= 0; set_time_now <= 0; end else begin // Default assignments s_ctrlport_resp_ack_tb <= 0; s_ctrlport_resp_data_tb <= 0; new_time_ctrl <= 0; // Handle register writes if (s_ctrlport_req_wr_tb) begin case (s_ctrlport_req_addr_tb) BASE_ADDR + REG_TIME_EVENT_LO: begin time_at_next_event_lo <= s_ctrlport_req_data_tb; s_ctrlport_resp_ack_tb <= 1; end BASE_ADDR + REG_TIME_EVENT_HI: begin time_at_next_event[31: 0] <= time_at_next_event_lo; time_at_next_event[63:32] <= s_ctrlport_req_data_tb; s_ctrlport_resp_ack_tb <= 1; end BASE_ADDR + REG_TIME_CTRL: begin set_time_pps <= s_ctrlport_req_data_tb[TIME_PPS_POS]; set_time_now <= s_ctrlport_req_data_tb[TIME_NOW_POS]; new_time_ctrl <= 1; s_ctrlport_resp_ack_tb <= 1; end BASE_ADDR + REG_TIME_BASE_PERIOD_LO: begin tb_period_ns_q32[31:0] <= s_ctrlport_req_data_tb; s_ctrlport_resp_ack_tb <= 1; end BASE_ADDR + REG_TIME_BASE_PERIOD_HI: begin tb_period_ns_q32[63:32] <= s_ctrlport_req_data_tb; s_ctrlport_resp_ack_tb <= 1; end endcase end // Handle register reads if (s_ctrlport_req_rd_tb) begin case (s_ctrlport_req_addr_tb) BASE_ADDR + REG_TIME_NOW_LO: begin s_ctrlport_resp_data_tb <= tb_timestamp[31:0]; tb_timestamp_hi <= tb_timestamp[63:32]; s_ctrlport_resp_ack_tb <= 1; end BASE_ADDR + REG_TIME_NOW_HI: begin s_ctrlport_resp_data_tb <= tb_timestamp_hi; s_ctrlport_resp_ack_tb <= 1; end BASE_ADDR + REG_TIME_EVENT_LO: begin s_ctrlport_resp_data_tb <= time_at_next_event[31:0]; time_at_next_event_hi <= time_at_next_event[63:32]; s_ctrlport_resp_ack_tb <= 1; end BASE_ADDR + REG_TIME_EVENT_HI: begin s_ctrlport_resp_data_tb <= time_at_next_event_hi; s_ctrlport_resp_ack_tb <= 1; end BASE_ADDR + REG_TIME_CTRL: begin s_ctrlport_resp_data_tb <= 0; s_ctrlport_resp_data_tb[TIME_PPS_POS] <= set_time_pps; s_ctrlport_resp_data_tb[TIME_NOW_POS] <= set_time_now; s_ctrlport_resp_ack_tb <= 1; end BASE_ADDR + REG_TIME_LAST_PPS_LO: begin s_ctrlport_resp_data_tb <= tb_timestamp_last_pps[31:0]; tb_timestamp_last_pps_hi <= tb_timestamp_last_pps[63:32]; s_ctrlport_resp_ack_tb <= 1; end BASE_ADDR + REG_TIME_LAST_PPS_HI: begin s_ctrlport_resp_data_tb <= tb_timestamp_last_pps_hi; s_ctrlport_resp_ack_tb <= 1; end BASE_ADDR + REG_TIME_BASE_PERIOD_LO: begin s_ctrlport_resp_data_tb <= tb_period_ns_q32[31:0]; s_ctrlport_resp_ack_tb <= 1; end BASE_ADDR + REG_TIME_BASE_PERIOD_HI: begin s_ctrlport_resp_data_tb <= tb_period_ns_q32[63:32]; s_ctrlport_resp_ack_tb <= 1; end endcase end end end //--------------------------------------------------------------------------- // Pulse Per Second //--------------------------------------------------------------------------- reg pps_del; reg pps_edge; always @(posedge tb_clk) begin if (tb_rst) begin pps_del <= 0; pps_edge <= 0; end else begin pps_del <= pps; pps_edge<= pps_del & ~pps; end end //--------------------------------------------------------------------------- // Time Tracker //--------------------------------------------------------------------------- reg time_event_armed; // Boolean to indicate if we're expecting a timed event wire time_event = time_event_armed && ( set_time_now || (set_time_pps && pps_edge) ); always @(posedge tb_clk) begin if (tb_rst) begin tb_timestamp <= 0; time_event_armed <= 0; end else begin if (time_event) begin // Load the timing info configured prior to the event time_event_armed <= 0; tb_timestamp <= time_at_next_event; end else if (sample_rx_stb) begin // Update time for each sample word received tb_timestamp <= tb_timestamp + TIME_INCREMENT; end if (new_time_ctrl) begin // Indicate that we're expecting a timed event because the time control // register was updated. time_event_armed <= 1; end end end //--------------------------------------------------------------------------- // PPS Tracker //--------------------------------------------------------------------------- always @(posedge tb_clk) begin if (tb_rst) begin tb_timestamp_last_pps <= 64'h0; end else if (pps_edge) begin if (time_event) begin tb_timestamp_last_pps <= time_at_next_event; end else begin tb_timestamp_last_pps <= tb_timestamp + TIME_INCREMENT; end end end endmodule