blob: e2c2b7f2e3773d2fb7883a8064191cc22e451a04 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// prim_clk_meas is a generic module that measures two clocks against each other.
// One clock is the reference, and another is the input.
// If the input clocks becomes too fast or too slow, an error condition is created.
// The default parameters assume the reference clock is significantly slower than
// the input clock
`include "prim_assert.sv"
module prim_clock_meas #(
// Maximum value of input clock counts over measurement period
parameter int Cnt = 16,
// Maximum value of reference clock counts over measurement period
parameter int RefCnt = 1,
parameter bit ClkTimeOutChkEn = 1,
parameter bit RefTimeOutChkEn = 1,
localparam int CntWidth = prim_util_pkg::vbits(Cnt),
localparam int RefCntWidth = prim_util_pkg::vbits(RefCnt)
) (
input clk_i,
input rst_ni,
input clk_ref_i,
input rst_ref_ni,
input en_i,
input [CntWidth-1:0] max_cnt,
input [CntWidth-1:0] min_cnt,
// the output valid and fast/slow indications are all on the
// input clock domain
output logic valid_o,
output logic fast_o,
output logic slow_o,
// signal on clk_i domain that indicates clk_ref has timed out
output logic timeout_clk_ref_o,
// signal on clk_ref_i domain that indicates clk has timed out
output logic ref_timeout_clk_o
);
//////////////////////////
// Reference clock logic
//////////////////////////
logic ref_en;
prim_flop_2sync #(
.Width(1)
) u_ref_meas_en_sync (
.d_i(en_i),
.clk_i(clk_ref_i),
.rst_ni(rst_ref_ni),
.q_o(ref_en)
);
//////////////////////////
// Domain sequencing
//////////////////////////
// the enable comes in on the clk_i domain, however, to ensure the
// clk and clk_ref domains remain in sync, the following sequencing is
// done to enable/disable the measurement feature
typedef enum logic [1:0] {
StDisable,
StEnabling,
StEnable,
StDisabling
} meas_chk_fsm_e;
// sync reference clock view of enable back into the source domain
logic en_ref_sync;
prim_flop_2sync #(
.Width(1)
) ack_sync (
.clk_i,
.rst_ni,
.d_i(ref_en),
.q_o(en_ref_sync)
);
meas_chk_fsm_e state_d, state_q;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
state_q <= StDisable;
end else begin
state_q <= state_d;
end
end
// The following fsm sequence ensures that even if the source
// side changes the enable too quickly, the measurement controls
// remain consistent.
logic cnt_en;
always_comb begin
state_d = state_q;
cnt_en = '0;
unique case (state_q)
StDisable: begin
if (en_i) begin
state_d = StEnabling;
end
end
StEnabling: begin
if (en_ref_sync) begin
state_d = StEnable;
end
end
StEnable: begin
cnt_en = 1'b1;
if (!en_i) begin
state_d = StDisabling;
end
end
StDisabling: begin
if (!en_ref_sync) begin
state_d = StDisable;
end
end
//VCS coverage off
// pragma coverage off
default:;
//VCS coverage on
// pragma coverage on
endcase // unique case (state_q)
end
//////////////////////////
// Input Clock Logic
//////////////////////////
logic valid_ref;
logic valid;
// The valid pulse causes the count to reset and start counting again
// for each reference cycle.
// The count obtained during the last reference cycle is used
// to measure how fast/slow the input clock is.
prim_pulse_sync u_sync_ref (
.clk_src_i(clk_ref_i),
.rst_src_ni(rst_ref_ni),
.src_pulse_i(ref_en),
.clk_dst_i(clk_i),
.rst_dst_ni(rst_ni),
.dst_pulse_o(valid_ref)
);
if (RefCnt == 1) begin : gen_degenerate_case
// if reference count is one, cnt_ref is always 0.
// So there is no need to maintain a counter, and
// valid just becomes valid_ref
assign valid = valid_ref;
end else begin : gen_normal_case
logic [RefCntWidth-1:0] cnt_ref;
assign valid = valid_ref & (int'(cnt_ref) == RefCnt - 1);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
cnt_ref <= '0;
end else if (!cnt_en && |cnt_ref) begin
cnt_ref <= '0;
end else if (cnt_en && valid) begin
cnt_ref <= '0;
end else if (cnt_en && valid_ref) begin
cnt_ref <= cnt_ref + 1'b1;
end
end
end
logic cnt_ovfl;
logic [CntWidth-1:0] cnt;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
cnt <= '0;
cnt_ovfl <= '0;
end else if (!cnt_en && |cnt) begin
cnt <= '0;
cnt_ovfl <= '0;
end else if (valid_o) begin
cnt <= '0;
cnt_ovfl <= '0;
end else if (cnt_ovfl) begin
cnt <= '{default: '1};
end else if (cnt_en) begin
{cnt_ovfl, cnt} <= cnt + 1'b1;
end
end
assign valid_o = valid & |cnt;
assign fast_o = valid_o & ((cnt > max_cnt) | cnt_ovfl);
assign slow_o = valid_o & (cnt < min_cnt);
//////////////////////////
// Timeout Handling
//////////////////////////
localparam bit TimeOutChkEn = ClkTimeOutChkEn | RefTimeOutChkEn;
// determine ratio between
localparam int ClkRatio = Cnt / RefCnt;
// maximum cdc latency from the perspective of the measured clock
// 1 cycle to output request
// 2 ref cycles for synchronization
// 1 ref cycle to send ack
// 2 cycles to sync ack
// Double for margin
localparam int MaxClkCdcLatency = (1 + 2*ClkRatio + 1*ClkRatio + 2)*2;
// maximum cdc latency from the perspective of the reference clock
// 1 ref cycle to output request
// 2 cycles to sync + 1 cycle to ack are less than 1 cycle of ref based on assertion requirement
// 2 ref cycles to sync ack
// 2 extra ref cycles for margin
localparam int MaxRefCdcLatency = 1 + 1 + 2 + 2;
if (RefTimeOutChkEn) begin : gen_ref_timeout_chk
// check whether reference clock has timed out
prim_clock_timeout #(
.TimeOutCnt(MaxClkCdcLatency)
) u_timeout_clk_to_ref (
.clk_chk_i(clk_ref_i),
.rst_chk_ni(rst_ref_ni),
.clk_i,
.rst_ni,
.en_i,
.timeout_o(timeout_clk_ref_o)
);
end else begin : gen_unused_ref_timeout
assign timeout_clk_ref_o = 1'b0;
end
if (ClkTimeOutChkEn) begin : gen_clk_timeout_chk
// check whether clock has timed out
prim_clock_timeout #(
.TimeOutCnt(MaxRefCdcLatency)
) u_timeout_ref_to_clk (
.clk_chk_i(clk_i),
.rst_chk_ni(rst_ni),
.clk_i(clk_ref_i),
.rst_ni(rst_ref_ni),
.en_i(ref_en),
.timeout_o(ref_timeout_clk_o)
);
end else begin : gen_unused_clk_timeout
assign ref_timeout_clk_o = 1'b0;
end
//////////////////////////
// Assertions
//////////////////////////
if (TimeOutChkEn) begin : gen_timeout_assert
// the measured clock must be faster than the reference clock
`ASSERT_INIT(ClkRatios_A, ClkRatio > 2)
end
// reference count has to be at least 1
`ASSERT_INIT(RefCntVal_A, RefCnt >= 1)
// if we've reached the max count, enable must be 0 next.
// Otherwise the width of the counter is too small to accommodate the usecase
`ASSERT(MaxWidth_A, (cnt == Cnt-1) |=> !cnt_en )
endmodule // prim_clk_meas