-
Notifications
You must be signed in to change notification settings - Fork 177
Implement solution to AsyncFIFO resets, as in #328 #329
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
Conversation
Codecov Report
@@ Coverage Diff @@
## master #329 +/- ##
==========================================
+ Coverage 82.47% 82.53% +0.06%
==========================================
Files 35 35
Lines 5913 5922 +9
Branches 1198 1198
==========================================
+ Hits 4877 4888 +11
+ Misses 876 875 -1
+ Partials 160 159 -1
Continue to review full report at Codecov.
|
nmigen/lib/fifo.py
Outdated
r_rst = Signal() | ||
|
||
# Create synthetic clock domain to work around ResetSynchronizer expecting a domain | ||
fake_domain = ClockDomain(name="rst_cdc", async_reset=True, local=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this characterization isn't correct. You are adding some FFs with a different control set (one that includes w_rst), which in fact form a (very small) new clock domain. This doesn't inherently have anything to do with the way ResetSynchronizer
works; nMigen requires you to create a clock domain object for every distinct async control set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But ResetSynchronizer
creates such a clock domain internally (right?), so this one is unnecessary maybe "incidental" is a better word.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're totally right. I was thinking of ResetSynchronizer
delegating to platform code, in which case it generally won't use an nMigen clock domain, but that's a different problem.
Do you feel like fixing it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Happy to but the right solution wasn't obvious to me, other than making something like a AsyncAssertSyncDeassertSynchronizer
module which ResetSynchronizer
would use under the hood, and I didn't want to try to create a new cdc module without checking with you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about (a) adding a rst
attribute (output), alongside arst
(input), and (b) allowing passing domain=None
to the constructor? Then the platform code would assert reset_sync.rst
, which in the case when domain is specified will be assigned to the domain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That should work. It seems a bit more complex to me, and it doesn't address a case where you might want to use this functionality for something besides a reset (admittedly I don't know what use case that would be), but it's a nice way to enhance ResetSynchronizer
without (I think) having to touch all the platform code.
I lean towards creating the module I described (a more reasonable name might be ReleaseSynchronizer
), but I'm open to the solution you described as well. I just think a generic module that gets replaced with platform-specific modules if available is a cleaner mental model.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
without (I think) having to touch all the platform code.
You have to touch all the platform code with my suggestion.
I just think a generic module that gets replaced with platform-specific modules if available is a cleaner mental model.
I'm a bit confused here. ResetSynchronizer
does get replaced with platform-specific modules; that's half the reason for its existence and being in nmigen.lib
.
If you mean that you want to make ResetSynchronizer
a thin wrapper around some other module that just does ResetSignal(self._domain).eq(...)
then I'm not really opposed to it, but it seems like the overhead (having two modules that do almost the same thing, duplicate constructor arguments, documentation, etc) is not worth the benefit (maybe slightly cleaner separation of concerns).
admittedly I don't know what use case that would be
Yes. "Chain of FFs with asynchronous preset and synchronous clear" is a very specific use case. Also, sometime in the future we'll have to extend it to handle negative polarity resets (#185).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me just push what I'm talking about and we can discuss it directly instead of each talking about the picture in our head of the picture in the other person's head :) can always roll back.
(There's a few style nits I skipped for now.)
I don't have any especially good ideas. Like you I find testing HDL (especially multiclock) to be pretty frustrating and I don't have silver bullets. |
Are you willing to merge this without tests of that functionality? If not, it seems like the most logical thing would be to add some properties to |
There's no rush to support this, so I would prefer if it landed with tests and using the improved |
In the process of trying to write what I was describing I realized it wouldn't work. I'm not sure what you described works either, because you still need a domain in order to get at the clock for the synchronous release. So I added a EDIT: Agreed |
Argh, you're right, it doesn't. But I still think it's really confusing to have it called |
What about adding something like I did say earlier that adding a new entity here has a cost, but it seems like overloading the functionality of |
That's basically what I did except I still took a If you agree in principle with this approach I can make those changes. I do think it should remain a |
Yes, that make sense. Could you do it as a part of another PR, please? |
Sure. You want me to pull all the |
Sure. |
Also use ReleaseSynchronizer in AsyncFIFO.
Most prominently, modify platform support to produce AsyncFFSynchronizers with configurable edges rather than ResetSynchronizers.
Also document async_edge.
Mark both read domain counters as reset-less, then synchronize write domain reset into read domain, On write domain reset, force empty flag and set read counters == write counters.
Also tone down language describing creation of new domain.
nmigen/hdl/cd.py
Outdated
@@ -21,6 +21,8 @@ class ClockDomain: | |||
If ``True``, the domain does not use a reset signal. Registers within this domain are | |||
still all initialized to their reset state once, e.g. through Verilog `"initial"` | |||
statements. | |||
clock_edge : str |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The actual parameter is called clk_edge
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(But maybe it should be called clock_edge
instead? We have a rst
signal and reset_less
parameter. What should we call the parameter that sets reset polarity.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
derp. i fix. that's what i get for adding extras :p
Reset polarity could be rst_edge
for consistency... but it's not really an edge-active signal, so rst_pol
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking rst_active="pos"
/="neg"
, since resets can be both synchronous and asynchronous.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, that's reasonable. I don't have too strong of opinions on this subject.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document the behavior of AsyncFIFO with regards to reset in the docstring.
nmigen/lib/fifo.py
Outdated
@@ -331,7 +332,7 @@ def elaborate(self, platform): | |||
m.d.comb += produce_enc.i.eq(produce_w_nxt), | |||
m.d[self._w_domain] += produce_w_gry.eq(produce_enc.o) | |||
|
|||
consume_r_gry = Signal(self._ctr_bits) | |||
consume_r_gry = Signal(self._ctr_bits, reset_less = True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consume_r_gry = Signal(self._ctr_bits, reset_less = True) | |
consume_r_gry = Signal(self._ctr_bits, reset_less=True) |
nmigen/lib/fifo.py
Outdated
|
||
# Async-set-sync-release synchronizer avoids CDC hazards | ||
rst_cdc = m.submodules.rst_cdc = \ | ||
AsyncFFSynchronizer(w_rst, r_rst, domain = "rst_cdc") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AsyncFFSynchronizer(w_rst, r_rst, domain = "rst_cdc") | |
AsyncFFSynchronizer(w_rst, r_rst, domain="rst_cdc") |
Oh oops, I didn't realize what Github was asking me - I made the requested formatting changes in the new commit. |
I've never used it before. Wasn't sure if it was more polite to use that or just edit the PR; the latter seems like it'd be a problem if you force pushed, but is less busywork... |
nmigen/lib/fifo.py
Outdated
|
||
# Async-set-sync-release synchronizer avoids CDC hazards | ||
rst_cdc = m.submodules.rst_cdc = \ | ||
AsyncFFSynchronizer(w_rst, r_rst, domain="rst_cdc") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be:
AsyncFFSynchronizer(w_rst, r_rst, domain="rst_cdc") | |
AsyncFFSynchronizer(w_rst, r_rst, domain=self._r_domain) |
nmigen/lib/fifo.py
Outdated
@@ -317,7 +321,8 @@ def elaborate(self, platform): | |||
m.d.comb += produce_w_nxt.eq(produce_w_bin + do_write) | |||
m.d[self._w_domain] += produce_w_bin.eq(produce_w_nxt) | |||
|
|||
consume_r_bin = Signal(self._ctr_bits) | |||
# Note: Both read-domain counters must be reset_less |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you expand this to explain why the counters must be reset_less
?
nmigen/lib/fifo.py
Outdated
@@ -366,6 +371,25 @@ def elaborate(self, platform): | |||
self.r_rdy.eq(~r_empty), | |||
] | |||
|
|||
# Reset handling to maintain FIFO and CDC invariants in the presence of a write-domain | |||
# reset. For further discussion, see https://github.com/nmigen/nmigen/issues/181. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think you could summarize it here? You have all the relevant context right now, whereas that issue is fairly long and meandering. In cases like this I normally describe the problem, the possible solution, and the rationale for choosing the one I implemented, in a comment near the code. This way, anyone who reads the source will know exactly why it works the way it does, without having to parse an entire discussion.
Thanks! |
Mark both read domain counters as reset-less, then synchronize write
domain reset into read domain, On write domain reset, force empty flag
and set read counters == write counters.
Marking this as WIP because the reset functionality isn't really tested yet. Interested in thoughts on how that might be done.