1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
|
//
// Copyright 2021 Ettus Research, a National Instruments Brand
//
// 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.
//
// Parameters:
//
// BASE_ADDR : Base address for the internal CtrlPort registers.
// TIME_INCREMENT : Amount by which to increment tb_timestamp for each radio
// strobe. When 0, the time_increment input is used instead.
//
// Signals:
//
// tb_clk : Time-base clock
// tb_rst : Time-base reset in tb_clk domain
// s_ctrlport_clk : Clock for CtrlPort bus
// s_ctrlport_* : CtrlPort bus for register access
// time_increment : Amount by which to increment timestamp. This is
// only used if TIME_INCREMENT parameter is 0.
// sample_rx_stb : Sample Rx strobe (data valid indicator).
// pps : Pulse-per-second input
// tb_timestamp : 64-bit global timestamp synchronous to tb_clk
// tb_timestamp_last_pps : 64-bit timestamp of the last PPS edge
// tb_period_ns_q32 : Time Period of time-base in nanoseconds
// tb_changed : Pulse indicating the time-base has changed
//
module timekeeper #(
parameter BASE_ADDR = 'h00,
parameter TIME_INCREMENT = 1
) (
input wire tb_clk,
input wire tb_rst,
//---------------------------------------------------------------------------
// 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 (tb_clk domain)
//---------------------------------------------------------------------------
input wire [ 7:0] time_increment,
input wire sample_rx_stb,
input wire pps,
output reg [63:0] tb_timestamp,
output reg [63:0] tb_timestamp_last_pps,
output reg [63:0] tb_period_ns_q32,
output reg tb_changed
);
//---------------------------------------------------------------------------
// 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 & ~pps_del;
end
end
//---------------------------------------------------------------------------
// Time Tracker
//---------------------------------------------------------------------------
// Amount by which to increment the timekeeper each clock cycle
wire [31:0] increment = TIME_INCREMENT ? TIME_INCREMENT : time_increment;
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;
tb_changed <= 0;
end else begin
tb_changed <= 0;
if (time_event) begin
// Load the timing info configured prior to the event
time_event_armed <= 0;
tb_timestamp <= time_at_next_event;
tb_changed <= 1;
end else if (sample_rx_stb) begin
// Update time for each sample word received
tb_timestamp <= tb_timestamp + 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 + increment;
end
end
end
endmodule
|