blob: 613f17388c1d8a55fa1d66b0f42e904a71b4fc40 [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)
);
logic ref_valid;
logic [RefCntWidth-1:0] ref_cnt;
always_ff @(posedge clk_ref_i or negedge rst_ref_ni) begin
if (!rst_ref_ni) begin
ref_cnt <= '0;
ref_valid <= '0;
end else if (!ref_en && |ref_cnt) begin
ref_cnt <= '0;
ref_valid <= '0;
end else if (ref_en && (ref_cnt == RefCnt - 1)) begin
// restart count and measure
ref_cnt <= '0;
ref_valid <= 1'b1;
end else if (ref_en) begin
ref_cnt <= ref_cnt + 1'b1;
ref_valid <= 1'b0;
end
end
//////////////////////////
// Input Clock Logic
//////////////////////////
logic valid;
// The valid pulse causes the count to reset and start counting again
// for each reference cycle.
// The count obtained during the the last refernece cycle is then 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_valid),
.clk_dst_i(clk_i),
.rst_dst_ni(rst_ni),
.dst_pulse_o(valid)
);
logic cnt_en;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
cnt_en <= '0;
end else if (!en_i) begin
cnt_en <= '0;
end else if (en_i && valid) begin
cnt_en <= 1'b1;
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_en && cnt_ovfl) begin
cnt <= '{default: '1};
end else if (cnt_en) begin
{cnt_ovfl, cnt} <= cnt + 1'b1;
end
end
assign valid_o = en_i & 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