diff --git a/Bender.yml b/Bender.yml index eb10dd7d..6f3e06f2 100644 --- a/Bender.yml +++ b/Bender.yml @@ -11,7 +11,7 @@ package: dependencies: common_verification: { git: "https://github.com/pulp-platform/common_verification.git", version: 0.2.0 } - tech_cells_generic: { git: "https://github.com/pulp-platform/tech_cells_generic.git", version: 0.2.9 } + tech_cells_generic: { git: "https://github.com/pulp-platform/tech_cells_generic.git", version: 0.2.11 } export_include_dirs: - include @@ -81,6 +81,7 @@ sources: - src/stream_delay.sv - src/stream_fifo.sv - src/stream_fork_dynamic.sv + - src/clk_mux_glitch_free.sv # Level 2 - src/cdc_reset_ctrlr.sv - src/cdc_fifo_gray.sv @@ -125,6 +126,7 @@ sources: - test/stream_omega_net_tb.sv - test/stream_xbar_tb.sv - test/clk_int_div_tb.sv + - test/clk_mux_glitch_free_tb.sv - target: synth_test diff --git a/README.md b/README.md index 421528d2..e751914f 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Please note that cells with status *deprecated* are not to be used for new desig | `cdc_fifo_gray` | Clock domain crossing FIFO using a gray-counter, with ready/valid interface | active | | | `cdc_fifo_gray_clearable` | Identical to `cdc_fifo_gray` but supports one-sided async/sync resetting of either src or dst | active | | | `cdc_reset_ctrlr` | Lock-step reset sequencer accross clock domains (internally used by clearable CDCs) | active | | +| `clk_mux_glitch_free` | A glitch-free clock multiplexer with parametrizeable number of inputs. | active | | | `edge_detect` | Rising/falling edge detector | active | | | `edge_propagator` | Propagates a single-cycle pulse across an asynchronous clock domain crossing | active | | | `edge_propagator_ack` | `edge_propagator` with sender-synchronous acknowledge pin (flags received pulse) | active | | diff --git a/common_cells.core b/common_cells.core index 88237bef..ac6a5fb2 100644 --- a/common_cells.core +++ b/common_cells.core @@ -66,6 +66,7 @@ filesets: - src/stream_delay.sv - src/stream_fifo.sv - src/stream_fork_dynamic.sv + - src/clk_mux_glitch_free.sv # Level 2 - src/cdc_reset_ctrlr.sv - src/cdc_fifo_gray.sv diff --git a/src/cdc_reset_ctrlr.sv b/src/cdc_reset_ctrlr.sv index 94a63cd5..3ea5e3b7 100644 --- a/src/cdc_reset_ctrlr.sv +++ b/src/cdc_reset_ctrlr.sv @@ -110,12 +110,12 @@ module cdc_reset_ctrlr import cdc_reset_ctrlr_pkg::*; #( /// The number of synchronization stages to use for the - //clear signal request/acknowledge. Must be less or - //equal to the number of sync stages used in the CDC + /// clear signal request/acknowledge. Must be less than the + /// number of sync stages used in the CDC. parameter int unsigned SYNC_STAGES = 2, /// Whether an asynchronous reset shall cause a clear /// request to be sent to the other side. - parameter logic CLEAR_ON_ASYNC_RESET = 1'b1 + parameter logic CLEAR_ON_ASYNC_RESET = 1'b1 )( // Side A (both sides are symmetric) input logic a_clk_i, @@ -188,12 +188,12 @@ module cdc_reset_ctrlr_half import cdc_reset_ctrlr_pkg::*; #( /// The number of synchronization stages to use for the - //clear signal request/acknowledge. Must be less or - //equal to the number of sync stages used in the CDC + /// clear signal request/acknowledge. Must be less than + /// the number of sync stages used in the CDC parameter int unsigned SYNC_STAGES = 2, /// Whether an asynchronous reset shall cause a clear /// request to be sent to the other side. - parameter logic CLEAR_ON_ASYNC_RESET = 1'b1 + parameter logic CLEAR_ON_ASYNC_RESET = 1'b1 )( // Synchronous side input logic clk_i, diff --git a/src/clk_mux_glitch_free.sv b/src/clk_mux_glitch_free.sv new file mode 100644 index 00000000..e0eb5cdc --- /dev/null +++ b/src/clk_mux_glitch_free.sv @@ -0,0 +1,229 @@ +//----------------------------------------------------------------------------- +// Title : Glitch-free Clock Multiplexer +//----------------------------------------------------------------------------- +// File : clk_mux_glitch_free.sv +// Author : Manuel Eggimann +// Created : 10.12.2022 +//----------------------------------------------------------------------------- +// Description : +// +// This module allows glitch free clock multiplexing between N arbitrary input +// clock with completely unknown phase relation shipts. The module will make +// sure to first synchronize the clock multiplexer signal to the relevant clock +// domains and ensures glitch free switching between the source clock and the +// new target clock by silencing the output at appropriate times. The clock +// signals themselves only pass through: 1 clock-gate, 1 N-input clock-OR Gate, +// 1 2-input clock mux. All these cells are referenced from the tech_cells +// repository and thus no conventional logic gate is directly in the clock path. + +// The correctness of this module is based on the following assumptions: +// 1. The select signal stays stable for a duration of at least min(clks_i +// period) +// 2. Glitches on the select signal are shorter than min(clks_i) - t_setup +// 3. During a transition from clock input a to clock input b, both clocks have +// a stable period. +// +// A clock switching procedure from clock a to clock b has the following timing behavior: +// 1. After at most NUM_SYNC_STAGES clock cycle of clock a, the output clock is +// disabled with its next falling edge. +// 2. After clock cycle of clock a and another NUM_SYNC_STAGES clock cycles of clock b, the output is +// enabled with the next rising edge of clock B. +// +// So in total, an upper bound for the worst case clock switching delay is 2x +// NUM_SYNC_STAGES x max(clock_periods) +// +//----------------------------------------------------------------------------- +// Copyright (C) 2013-2022 ETH Zurich, University of Bologna +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +//----------------------------------------------------------------------------- + + +module clk_mux_glitch_free #( + parameter int unsigned NUM_INPUTS = 2, + parameter int unsigned NUM_SYNC_STAGES = 2, + localparam int unsigned SelWidth = $clog2(NUM_INPUTS) +) ( + input logic [NUM_INPUTS-1:0] clks_i, + input logic test_clk_i, + input logic test_en_i, + input logic async_rstn_i, + input logic [SelWidth-1:0] async_sel_i, + output logic clk_o +); + + + + if (NUM_INPUTS<2) + $error("Num inputs must be parametrized to a value >= 2."); + + // For each input, we generate an enable signal that enables the clock + // propagation through an N-input clock OR gate. The crucial and most critical + // part is to make sure that these clock enable signal transitions are + // non-overlapping and have enough timing separation to prevent any glitches + // on the clock output during the transition. We ensure this as follows: + // + // 1. Decode the sel_i input to a onehot signal. + // 2. For each clock input, take the correspondi onehot signal. For each clock + // input we also have a correspdonding output clock enable signal that + // controls the corresponding clock's clk gate. We thus and-gate the one-hot + // signal of the current clock with the inverse of every other clocks enable + // signal. In other words, we only allow the propagation of the onehot enable + // signal if the clock is currently disabled. + // 3. Filter any glitches on this and-gated signal by passing it through a + // flip-flop clocked by the current clock and and-gating both the output and + // the input. I.e. the output is only becomes active if the signal stays + // stable HIGH for at least one clock period. + // 4. Synchronize this glitch-filtered signal into the current clock domain + // with an M-stage synchronizer. + // 5. Use this synchronized signal as the enable signal for a glitch-free + // clock gate. + // 7. Feed the output of the clock gate to an N-input clock-AND gate. + // 8. Latch the gate enable signal with an active low latch before using the + // signal as a gating signal for the other clock input's onehot signal. + + // Internal signals + logic [NUM_INPUTS-1:0] s_sel_onehot; + (*dont_touch*) + (*async_reg*) + logic [NUM_INPUTS-1:0][1:0] glitch_filter_d, glitch_filter_q; + logic [NUM_INPUTS-1:0] s_glitch_filter_output; + logic [NUM_INPUTS-1:0] s_gate_enable; + logic [NUM_INPUTS-1:0] clock_has_been_disabled_q; + logic [NUM_INPUTS-1:0] s_gated_clock; + logic s_output_clock; + + + // Onehot decoder + always_comb begin + s_sel_onehot = '0; + s_sel_onehot[async_sel_i] = 1'b1; + end + + // Input stages + for (genvar i = 0; i < NUM_INPUTS; i++) begin : gen_input_stages + // Gate onehot signal with other clocks' output gate enable + always_comb begin + glitch_filter_d[i][0] = 1'b1; + for (int j = 0; j < NUM_INPUTS; j++) begin + if (i==j) begin + glitch_filter_d[i][0] &= s_sel_onehot[j]; + end else begin + glitch_filter_d[i][0] &= clock_has_been_disabled_q[j]; + end + end + end + assign glitch_filter_d[i][1] = glitch_filter_q[i][0]; + + // Filter HIGH-pulse glitches + always_ff @(posedge clks_i[i], negedge async_rstn_i) begin + if (!async_rstn_i) begin + glitch_filter_q[i] <= '0; + end else begin + glitch_filter_q[i] <= glitch_filter_d[i]; + end + end + assign s_glitch_filter_output[i] = glitch_filter_q[i][1] & + glitch_filter_q[i][0] & + glitch_filter_d[i][0]; + + // Synchronize to current clock + sync #(.STAGES(NUM_SYNC_STAGES)) i_sync_en( + .clk_i ( clks_i[i] ), + .rst_ni ( async_rstn_i ), + .serial_i ( s_glitch_filter_output[i] ), + .serial_o ( s_gate_enable[i] ) + ); + + // Gate the input clock with the synced enable signal + tc_clk_gating #( + .IS_FUNCTIONAL(1'b1) + ) i_clk_gate ( + .clk_i ( clks_i[i] ), + .en_i ( s_gate_enable[i] ), + .test_en_i ( 1'b0 ), + .clk_o ( s_gated_clock[i] ) + ); + + // Latch the enable signal with the next rising edge of the input clock and + // feed the output back to the other stage's input. If we were to directly + // use the clock gate enable signal to determine wether it is save to enable + // another clock (i.e. the signal becomes low) we would risk enabling the + // other clock to early. This is because the glitch free clock gate will + // only really disable the clock with the next falling edge. By delaying the + // enable signal one more cycle, we ensure that the clock stays low for at + // least one clock period of the original clock input before any other clock + // even has the chance to become active. + + always_ff @(posedge clks_i[i], negedge async_rstn_i) begin + if (!async_rstn_i) begin + clock_has_been_disabled_q[i] <= 1'b0; + end else begin + clock_has_been_disabled_q[i] <= ~s_gate_enable[i]; + end + end + end + + // Output OR-gate. At this stage, we should be already sure that the clocks + // are enabled/disabled at the proper time to prevent any glitches from + // escaping. + + clk_or_tree #(NUM_INPUTS) i_clk_or_tree ( + .clks_i(s_gated_clock), + .clk_o(s_output_clock) + ); + + // Mux between the regular muxed clock and the test_clk_i used for DFT. + tc_clk_mux2 i_test_clk_mux( + .clk0_i(s_output_clock), + .clk1_i(test_clk_i), + .clk_sel_i(test_en_i), + .clk_o + ); + +endmodule + +// Helper Module to generate an N-input clock OR-gate from a tree of tc_clk_or2 cells. +module clk_or_tree #( + parameter int unsigned NUM_INPUTS +) ( + input logic [NUM_INPUTS-1:0] clks_i, + output logic clk_o +); + + if (NUM_INPUTS < 1) begin : gen_error + $error("Cannot parametrize clk_or with less then 1 input but was %0d", NUM_INPUTS); + end else if (NUM_INPUTS == 1) begin : gen_leaf + assign clk_o = clks_i[0]; + end else if (NUM_INPUTS == 2) begin : gen_leaf + tc_clk_or2 i_clk_or2 ( + .clk0_i(clks_i[0]), + .clk1_i(clks_i[1]), + .clk_o + ); + end else begin : gen_recursive + logic branch_a, branch_b; + clk_or_tree #(NUM_INPUTS/2) i_or_branch_a ( + .clks_i(clks_i[0+:NUM_INPUTS/2]), + .clk_o(branch_a) + ); + + clk_or_tree #(NUM_INPUTS/2 + NUM_INPUTS%2) i_or_branch_b ( + .clks_i(clks_i[NUM_INPUTS-1:NUM_INPUTS/2]), + .clk_o(branch_b) + ); + + tc_clk_or2 i_clk_or2 ( + .clk0_i(branch_a), + .clk1_i(branch_b), + .clk_o + ); + end + +endmodule diff --git a/src_files.yml b/src_files.yml index 2233f891..f00937cd 100644 --- a/src_files.yml +++ b/src_files.yml @@ -62,6 +62,7 @@ common_cells_all: - src/stream_delay.sv - src/stream_fifo.sv - src/stream_fork_dynamic.sv + - src/clk_mux_glitch_free.sv # Level 2 - src/cdc_reset_ctrlr.sv - src/cdc_fifo_gray.sv diff --git a/test/clk_mux_glitch_free_tb.sv b/test/clk_mux_glitch_free_tb.sv new file mode 100644 index 00000000..d72c9b4b --- /dev/null +++ b/test/clk_mux_glitch_free_tb.sv @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------------- +// Title : TB to test the glitch free clock multiplexer +//----------------------------------------------------------------------------- +// File : clk_mux_glitch_free_tb.sv +// Author : Manuel Eggimann +// Created : 11.12.2022 +//----------------------------------------------------------------------------- +// Copyright (C) 2013-2022 ETH Zurich, University of Bologna +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +//----------------------------------------------------------------------------- + +`define abs(x) (((x) < 0 )? -(x) : (x)) +`define min(a,b) (((a) < (b))? (a) : (b)) + +module clk_mux_glitch_free_tb; + timeunit 1ns; + timeprecision 1ps; + + parameter int unsigned NUM_INPUTS = 10; + localparam int unsigned SEL_WIDTH = $clog2(NUM_INPUTS); + parameter realtime MIN_PERIOD = 1ns; + parameter realtime MAX_PERIOD = 3ns; + parameter realtime TIME_RESOLUTION = 0.1ns; + parameter int TEST_LENGTH = 1000; + + logic [SEL_WIDTH-1:0] s_sel = '0; + logic [NUM_INPUTS-1:0] s_clocks = '0; + logic s_rstn; + logic s_clock_output; + realtime target_periods [NUM_INPUTS-1:0]; + int num_errors = 0; + + task automatic delay_random(input realtime min_delay, input realtime max_delay); + int delay_int = $urandom_range($rtoi(min_delay/TIME_RESOLUTION), $rtoi(max_delay/TIME_RESOLUTION)); + repeat(delay_int) #TIME_RESOLUTION; + endtask + + // Generate input clocks with random period and random phase shift + for (genvar i = 0; i < NUM_INPUTS; i++) begin : gen_clocks + initial begin : clock_gen + automatic int period_int; + period_int = $urandom_range($rtoi(MIN_PERIOD/TIME_RESOLUTION), $rtoi(MAX_PERIOD/TIME_RESOLUTION)); + target_periods[i] = period_int * TIME_RESOLUTION; + delay_random(0, MAX_PERIOD); + forever begin + s_clocks[i] = 1'b1; + repeat(period_int/2) #(TIME_RESOLUTION); + s_clocks[i] = 1'b0; + repeat(period_int/2) #(TIME_RESOLUTION); + end + end + end // block: gen_clocks + + // Randomly switch between clocks + initial begin : stimulate_sel_input + $info("Starting Test"); + $info("Asserting hard reset"); + s_rstn = 1'b0; + s_sel = $urandom_range(0, NUM_INPUTS-1); + delay_random(0, MAX_PERIOD); + $info("Deasserting hard reset."); + s_rstn = 1'b1; + $info("Switching between clock inputs %0d times", TEST_LENGTH); + for (int i = 0; i < TEST_LENGTH; i++) begin + delay_random(MAX_PERIOD*10, MAX_PERIOD*30); + s_sel = $urandom_range(0, NUM_INPUTS-1); + end + repeat(10) @(posedge s_clock_output); + $info("Test finished with %0d errors.", num_errors); + $stop(); + end + + // Check clock edges + initial begin : check_clock + automatic realtime last_high_pulse_duration = 0ns; + automatic realtime last_low_pulse_duration = 0ns; + automatic realtime pulse_duraton = 0ns; + automatic realtime last_edge = 0ns; + $timeformat(-9, 3, "ns", 0); + // The rules we check are as follows: + // Every high pulse must have a duration of at least + // min(previous period, next period)/2 + // Every low period must have a duration of at leat . + @(posedge s_rstn); + forever begin + @(posedge s_clock_output); + pulse_duraton = $realtime - last_edge; + if (`abs(last_low_pulse_duration - pulse_duraton) > TIME_RESOLUTION) begin + assert(pulse_duraton + TIME_RESOLUTION >= target_periods[s_sel]/2) else + $error("Error #%0d: Clock low pulse duration was %t but should be larger than %t", num_errors++, pulse_duraton, target_periods[s_sel]/2); + end + last_edge = $realtime; + last_low_pulse_duration = pulse_duraton; + @(negedge s_clock_output); + pulse_duraton = $realtime - last_edge; + if (`abs(last_high_pulse_duration - pulse_duraton) > TIME_RESOLUTION) begin + assert( `abs(pulse_duraton - target_periods[s_sel]/2) <= TIME_RESOLUTION) else + $error("Error #%0d: Clock high pulse duration was %t but should be equal to %t", num_errors++, pulse_duraton, target_periods[s_sel]/2); + end + last_edge = $realtime; + last_high_pulse_duration = pulse_duraton; + end + end // block: check_clock + + clk_mux_glitch_free #( + .NUM_INPUTS(NUM_INPUTS) + ) i_dut ( + .clks_i ( s_clocks ), + .test_clk_i ( 1'b0 ), + .test_en_i ( 1'b0 ), + .async_rstn_i ( s_rstn ), + .async_sel_i ( s_sel ), + .clk_o ( s_clock_output ) + ); + +endmodule