Skip to content

Commit

Permalink
Add glitch filters to I^2C inputs
Browse files Browse the repository at this point in the history
I^2C Fast-mode requires a 50 ns glitch filter on SCL and SDA inputs.
This is an attempt at implementing such a filter.

Note, as it is operating in the digital domain and on an unregistered
input, it could fall victim to inexact clock division and metastability.
Happily the clock divides nicely at our current 40 MHz system clock.
  • Loading branch information
elliotb-lowrisc committed Oct 29, 2024
1 parent fe432d4 commit 51e4dcc
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 2 deletions.
101 changes: 101 additions & 0 deletions rtl/system/i2c_filter.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

// I^2C Fast-mode spike suppression filter
//
// Suppress input spikes with a pulse width less than or equal to 50 ns (t_sp).
// To be used on SDA and SCL inputs that are to operate at Fast-mode or above.
// See I^2C User Manual for details.
//
// The filter is implemented as a MUX that selects between a registered copy
// of the previous output and a combinatorial passthrough of the current input.
//
// The pulse width filter threshold is rounded down to whole clock cycles.
// Pulses with a width equal to or less than the threshold (in clock cycles)
// will NOT be passed through to the filter output.
//
// Note that neither metastability nor inexact clock cycle division have been
// accounted for in the design of this filter.

module i2c_filter #(
parameter int unsigned SysClkFreq = 40_000_000
) (
input clk_i,
input rst_ni,

input raw_i,
output filtered_o
);

// Calculate the number whole clock cycles that fit in a 50 ns period.
// Round down rather than to closest to avoid violating the 50 ns
// maximum pulse width specification for the filter.
localparam int unsigned FilterCycles = int'($floor(50e-9 * SysClkFreq));

// Previous filtered output
logic prev_out;
// Width counter. Implement as shift register to keep logic
// on the path from the counter to the datapath to a minimum.
logic [FilterCycles-1:0] shift_counter_plus1;
logic [FilterCycles-1:0] shift_counter;

// MUX select signal
logic select;
// Internal filter output signal
logic result;

// Register output of filter for use next cycle
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
prev_out <= '0;
end else begin
prev_out <= result;
end
end

// Implement shift-counter
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
shift_counter <= '0;
end else begin
if (raw_i == prev_out) begin
// Clear counter
shift_counter <= '0;
end else begin
// Increment counter
shift_counter <= shift_counter_plus1;
end
end
end

// Incremented counter signal (shift right, filling LSB with a 'one').
// Exact implementation depends on counter width.
// Have to do conditional generation outside of an always block.
if (FilterCycles == 1) begin
// Only one bit, so no shifting
assign shift_counter_plus1 = 1'b1;
end else if (FilterCycles == 2) begin
// Two bits, so only one bit gets shifted
assign shift_counter_plus1 = {shift_counter[0], 1'b1};
end else begin
// Many bits, so a range get shifted
assign shift_counter_plus1 = {shift_counter[FilterCycles-2:0], 1'b1};
end

// Use MSB (final bit) of shift counter selection trigger.
// The design of the counter avoids the need for additional logic to
// detect the target count has been reached, keeping the timing path fast.
assign select = shift_counter[FilterCycles-1];

// MUX to select whether to output the current input or the registered
// previous output. If an input pulse matches the threshold width
// (in clock cycles) exactly, then the MUX select changes for one cycle
// but the filter output remains unchanged due to the input having reverted.
// Otherwise, if the pulse is longer the new value reaches the output and
// is registered, or if it is shorter then the MUX select never fires.
assign result = select ? raw_i : prev_out;

assign filtered_o = result;

endmodule
25 changes: 23 additions & 2 deletions rtl/system/sonata_system.sv
Original file line number Diff line number Diff line change
Expand Up @@ -910,10 +910,31 @@ module sonata_system
logic i2c_scl_h2d [I2C_NUM];
logic i2c_scl_en_h2d[I2C_NUM];
logic i2c_scl_d2h [I2C_NUM];
logic i2c_scl_d2h_f [I2C_NUM];
logic i2c_sda_h2d [I2C_NUM];
logic i2c_sda_en_h2d[I2C_NUM];
logic i2c_sda_d2h [I2C_NUM];
logic i2c_sda_d2h_f [I2C_NUM];
for (genvar i = 0; i < I2C_NUM; i++) begin : gen_i2c_hosts
// SCL & SDA input glitch filters for I^2C Fast-mode
i2c_filter #(
.SysClkFreq(SysClkFreq)
) u_i2c_filter_scl (
.clk_i (clk_sys_i),
.rst_ni (rst_sys_ni),
.raw_i (i2c_scl_d2h[i]),
.filtered_o (i2c_scl_d2h_f[i])
);
i2c_filter #(
.SysClkFreq(SysClkFreq)
) u_i2c_filter_sda (
.clk_i (clk_sys_i),
.rst_ni (rst_sys_ni),
.raw_i (i2c_sda_d2h[i]),
.filtered_o (i2c_sda_d2h_f[i])
);

// I^2C host
i2c u_i2c (
.clk_i (clk_sys_i),
.rst_ni (rst_sys_ni),
Expand All @@ -924,10 +945,10 @@ module sonata_system
.tl_o (tl_i2c_d2h[i]),

// Generic IO.
.cio_scl_i (i2c_scl_d2h [i]),
.cio_scl_i (i2c_scl_d2h_f [i]),
.cio_scl_o (i2c_scl_h2d [i]),
.cio_scl_en_o (i2c_scl_en_h2d[i]),
.cio_sda_i (i2c_sda_d2h [i]),
.cio_sda_i (i2c_sda_d2h_f [i]),
.cio_sda_o (i2c_sda_h2d [i]),
.cio_sda_en_o (i2c_sda_en_h2d[i]),

Expand Down
1 change: 1 addition & 0 deletions sonata_system.core
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ filesets:
- rtl/system/sram.sv
- rtl/system/rst_sync.sv
- rtl/system/rv_timer.sv
- rtl/system/i2c_filter.sv
- vendor/cheriot_safe/ip/msftDvIp_mmreg.sv
file_type: systemVerilogSource

Expand Down

0 comments on commit 51e4dcc

Please sign in to comment.