Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add glitch filters to I^2C inputs #285

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

elliotb-lowrisc
Copy link
Contributor

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.

So far only tested in simulator in ideal conditions, not on real hardware nor exposed to glitches.

Copy link
Contributor

@GregAC GregAC left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @elliotb-lowrisc this LGTM modulo some coding style nits.

On the meta-stability point we do have a synchroniser in the i2c top-level

// sync the incoming SCL and SDA signals
prim_flop_2sync #(
.Width(1),
.ResetValue(1'b1)
) u_i2c_sync_scl (
.clk_i,
.rst_ni,
.d_i (scl_i),
.q_o (scl_sync)
);
prim_flop_2sync #(
.Width(1),
.ResetValue(1'b1)
) u_i2c_sync_sda (
.clk_i,
.rst_ni,
.d_i (sda_i),
.q_o (sda_sync)
);

So I'd be tempted to remove that synchronizer and implement it inside here instead (i.e. apply prim_flop_2sync to raw_i in this module and then use the output of the synchronizer everywhere you currently use raw_i). I'm a little nervous as to what a real glitch would do if it causes metastability here and given it gets synced eventually may as well push that upstream to here. What we'd like to avoid is repeating the synchronization though. I think in OT this filter is implemented via an analogue component, something like a Schmitt Trigger in the pad so it doesn't have the same metastability concerns.

It's a little fiddly to do this as you'll need to adjust the vendoring patch files and rerun the vendor script, @marnovandermaas or @alees24 can advise.

// 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use _d and _q suffixes. Here you'd have shift_counter_q in place of shift_counter and shift_counter_d in place of shift_counter_plus1. The always_ff block below teeters on the edge of acceptable complexity for an always_ff block (see https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md#sequential-logic-registers) but I think I'm happy to leave it as is because it makes the shift_counter_d logic simpler.

logic [FilterCycles-1:0] shift_counter;

// MUX select signal
logic select;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd make this a more descriptive name select_raw maybe?

// MUX select signal
logic select;
// Internal filter output signal
logic result;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be better to unify the names here, we've got prev_out, result and filtered_o all effectively referring to the same bit, either the immediate combinational version of that bit or the flopped version.

Perhaps switch to filtered_o, filtered_q (previously prev_out) and filtered_d (previously result).

Comment on lines 75 to 84
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add names to the generate blocks, e.g.

Suggested change
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
if (FilterCycles == 1) begin : g_one_filter_cycle
// Only one bit, so no shifting
assign shift_counter_plus1 = 1'b1;
end else if (FilterCycles == 2) begin : g_two_filter_cycles
// Two bits, so only one bit gets shifted
assign shift_counter_plus1 = {shift_counter[0], 1'b1};
end else begin : g_many_filter_cycles
// Many bits, so a range get shifted
assign shift_counter_plus1 = {shift_counter[FilterCycles-2:0], 1'b1};
end

@alees24
Copy link
Contributor

alees24 commented Oct 29, 2024

I should perhaps have thought of this sooner, but we do vendor in the primitive prim_filter (via the lowrisc_ibex vendoring) which does also support Async inputs (CDC).

@GregAC
Copy link
Contributor

GregAC commented Oct 30, 2024

Ah yes I hadn't realised that existed!

Apologies @elliotb-lowrisc but I think we're best off using the existing primitive, sorry for the wasted design time. Would you mind incorporating prim_filter with the synchronizer enabled for use on the I2C inputs and patching the existing I2C to remove its synchronizers as discussed above?

@elliotb-lowrisc
Copy link
Contributor Author

Righto, I'll have a go at the prim_filter approach. At least this was a nice little design exercise and reminder to refresh myself on the style guide.

I^2C Fast-mode requires a 50 ns glitch filter on SCL and SDA inputs.
This is an attempt at implementing such a filter using the existing
prim_filter IP.

Note, as it is operating in the digital domain it could fall victim
to inexact clock division.
Happily the clock divides nicely at our current 40 MHz system clock.
@elliotb-lowrisc
Copy link
Contributor Author

Reworked to use prim_filter and rebased

@elliotb-lowrisc
Copy link
Contributor Author

This PR is currently causing my (2021.1) builds to fail timing by a considerable margin for some unknown reason. I shall attempt to investigate.

@elliotb-lowrisc elliotb-lowrisc marked this pull request as ready for review October 31, 2024 10:46
@elliotb-lowrisc
Copy link
Contributor Author

Timing seems fine now

@elliotb-lowrisc elliotb-lowrisc marked this pull request as draft November 1, 2024 14:38
@elliotb-lowrisc
Copy link
Contributor Author

Notable increase in area utilisation observed from this PR. Seems to be related to an I^2C distributed memory no longer being inferred, which may be related to some signals being merged that were not before. Will revisit after 1.0 release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants