|
| 1 | +// Copyright lowRISC contributors. |
| 2 | +// Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| 3 | +// SPDX-License-Identifier: Apache-2.0 |
| 4 | + |
| 5 | +// I^2C Fast-mode spike suppression filter |
| 6 | +// |
| 7 | +// Suppress input spikes with a pulse width less than or equal to 50 ns (t_sp). |
| 8 | +// To be used on SDA and SCL inputs that are to operate at Fast-mode or above. |
| 9 | +// See I^2C User Manual for details. |
| 10 | +// |
| 11 | +// The filter is implemented as a MUX that selects between a registered copy |
| 12 | +// of the previous output and a combinatorial passthrough of the current input. |
| 13 | +// |
| 14 | +// The pulse width filter threshold is rounded down to whole clock cycles. |
| 15 | +// Pulses with a width equal to or less than the threshold (in clock cycles) |
| 16 | +// will NOT be passed through to the filter output. |
| 17 | +// |
| 18 | +// Note that neither metastability nor inexact clock cycle division have been |
| 19 | +// accounted for in the design of this filter. |
| 20 | + |
| 21 | +module i2c_filter #( |
| 22 | + parameter int unsigned SysClkFreq = 40_000_000 |
| 23 | +) ( |
| 24 | + input clk_i, |
| 25 | + input rst_ni, |
| 26 | + |
| 27 | + input raw_i, |
| 28 | + output filtered_o |
| 29 | +); |
| 30 | + |
| 31 | + // Calculate the number whole clock cycles that fit in a 50 ns period. |
| 32 | + // Round down rather than to closest to avoid violating the 50 ns |
| 33 | + // maximum pulse width specification for the filter. |
| 34 | + localparam int unsigned FilterCycles = int'($floor(50e-9 * SysClkFreq)); |
| 35 | + |
| 36 | + // Previous filtered output |
| 37 | + logic prev_out; |
| 38 | + // Width counter. Implement as shift register to keep logic |
| 39 | + // on the path from the counter to the datapath to a minimum. |
| 40 | + logic [FilterCycles-1:0] shift_counter_plus1; |
| 41 | + logic [FilterCycles-1:0] shift_counter; |
| 42 | + |
| 43 | + // MUX select signal |
| 44 | + logic select; |
| 45 | + // Internal filter output signal |
| 46 | + logic result; |
| 47 | + |
| 48 | + // Register output of filter for use next cycle |
| 49 | + always_ff @(posedge clk_i or negedge rst_ni) begin |
| 50 | + if (!rst_ni) begin |
| 51 | + prev_out <= '0; |
| 52 | + end else begin |
| 53 | + prev_out <= result; |
| 54 | + end |
| 55 | + end |
| 56 | + |
| 57 | + // Implement shift-counter |
| 58 | + always_ff @(posedge clk_i or negedge rst_ni) begin |
| 59 | + if (!rst_ni) begin |
| 60 | + shift_counter <= '0; |
| 61 | + end else begin |
| 62 | + if (raw_i == prev_out) begin |
| 63 | + // Clear counter |
| 64 | + shift_counter <= '0; |
| 65 | + end else begin |
| 66 | + // Increment counter |
| 67 | + shift_counter <= shift_counter_plus1; |
| 68 | + end |
| 69 | + end |
| 70 | + end |
| 71 | + |
| 72 | + // Incremented counter signal (shift right, filling LSB with a 'one'). |
| 73 | + // Exact implementation depends on counter width. |
| 74 | + // Have to do conditional generation outside of an always block. |
| 75 | + if (FilterCycles == 1) begin |
| 76 | + // Only one bit, so no shifting |
| 77 | + assign shift_counter_plus1 = 1'b1; |
| 78 | + end else if (FilterCycles == 2) begin |
| 79 | + // Two bits, so only one bit gets shifted |
| 80 | + assign shift_counter_plus1 = {shift_counter[0], 1'b1}; |
| 81 | + end else begin |
| 82 | + // Many bits, so a range get shifted |
| 83 | + assign shift_counter_plus1 = {shift_counter[FilterCycles-2:0], 1'b1}; |
| 84 | + end |
| 85 | + |
| 86 | + // Use MSB (final bit) of shift counter selection trigger. |
| 87 | + // The design of the counter avoids the need for additional logic to |
| 88 | + // detect the target count has been reached, keeping the timing path fast. |
| 89 | + assign select = shift_counter[FilterCycles-1]; |
| 90 | + |
| 91 | + // MUX to select whether to output the current input or the registered |
| 92 | + // previous output. If an input pulse matches the threshold width |
| 93 | + // (in clock cycles) exactly, then the MUX select changes for one cycle |
| 94 | + // but the filter output remains unchanged due to the input having reverted. |
| 95 | + // Otherwise, if the pulse is longer the new value reaches the output and |
| 96 | + // is registered, or if it is shorter then the MUX select never fires. |
| 97 | + assign result = select ? raw_i : prev_out; |
| 98 | + |
| 99 | + assign filtered_o = result; |
| 100 | + |
| 101 | +endmodule |
0 commit comments