diff options
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/utils/timekeeper.v')
-rw-r--r-- | fpga/usrp3/lib/rfnoc/utils/timekeeper.v | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/rfnoc/utils/timekeeper.v b/fpga/usrp3/lib/rfnoc/utils/timekeeper.v new file mode 100644 index 000000000..404f45758 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/utils/timekeeper.v @@ -0,0 +1,279 @@ +// +// 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 |