| // 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 |