-
Notifications
You must be signed in to change notification settings - Fork 175
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
Deprecate and remove Past/Rose/Fell/... primitives that are difficult to use correctly #526
Comments
What you're hitting is definitely a bug. I'll need a bit of time to dig into it and figure out exactly what's happening. |
I'm grateful that you're looking into this. Thank you!!! <3 |
BTW, I suspect that what should be happening is that Past, Fell, Rose, Stable, and Sample should be forbidden in the combinatorial domain, based on Claire's documentation for SymbiYosys:
As for Past relative to local clocks, I think I should be using the
I remember that there was a way to specify signal attributes in nMigen, but I can't for the life of me remember how... |
@RobertBaruch You may find this helpful as to "how does the presence/lack of I don't know offhand if it helps with your specific problem, but I do remember having a lot of qs about |
@cr1901 It's pretty much what I concluded. At issue here is, I think, nMigen, not SymbiYosys, although I could be wrong. |
BTW, I replaced |
|
Yes, that was also my conclusion. So aside from outputting that diagnostic, the second issue here is that we need a way to specify the Here is the updated python example. Note I replaced one of the from nmigen import Signal, Module, Elaboratable, ClockDomain, Mux, ClockSignal
from nmigen.build import Platform
from nmigen.asserts import Assert, Fell, Past, Cover
from nmigen.cli import main_parser, main_runner
# Simple example that clocks data in into data out when the
# independent signal -- assumed to be asynchrnous with any
# other clock in the system -- has a negative edge.
#
# Run with:
#
# python example.py generate -t il > toplevel.il
# sby -f example.sby
class Example(Elaboratable):
def __init__(self):
self.data_in = Signal(32)
self.data_out = Signal(32)
self.le = Signal()
def elaborate(self, _: Platform) -> Module:
m = Module()
internal_clk = ClockDomain("internal_clk", clk_edge="neg", local=True)
m.domains.internal_clk = internal_clk
internal_clk.clk = self.le
m.d.internal_clk += self.data_out.eq(self.data_in)
return m
def formal():
"""Formal verification for the example."""
parser = main_parser()
args = parser.parse_args()
m = Module()
m.submodules.example = example = Example()
m.d.sync += Cover((example.data_out == 0xAAAAAAAA) & (example.le == 0)
& (Past(example.data_out) == 0xBBBBBBBB)
& (Past(example.le, 2) == 0))
with m.If(Fell(example.le)):
m.d.sync += Assert(example.data_out == Past(example.data_in))
main_runner(parser, args, m, ports=[
example.data_in,
example.le,
])
if __name__ == "__main__":
formal() |
Maybe put |
Hmm, did I put it in the right place? BMC is still failing:
|
Sorry, I forgot you're in a design context. Try |
Nope, still fails. |
Can you upload your IL so I can directly try with that? |
Indeed! |
I think the command I suggested works correctly; it's more that the annotation doesn't seem to help. Try adding |
Hmm. Here's the .v file if that helps, along with the sby for that. Adding
|
Yeah, I can reproduce that. It's really confusing! Are you sure it passes correctly when you go through Verilog? |
I'm not sure how I could tell. I do know that the Cover trace looks absolutely correct. |
Ok so this is something about Verilog. This is incredibly stupid, but works:
|
Also, special thanks to you. I hope this does get fixed without having to do a rewrite, but in the meantime I am definitely unblocked. Thank you!!! |
To be clear this isn't actually a solution I suggest. (Though I think if you use |
Okay, I figured it out. The Try this:
|
No, that fails BMC :( |
Oh yeah sorry, let me figure out why. |
@RobertBaruch Ok well that took longer than expected...
|
Oof. Well, it does work. This is still a workaround, though, right? |
Indeed, and a remarkably gross one. Basically what you want is to do However it's not entirely clear to me yet what is to be done on nMigen language level. Maybe the |
@rroohhh Okay, so there are two problems with this plan, besides the fact that implementing it is a monumental amount of work that would delay usable multiclock formal for at least a year.
|
This is certainly the case (it is required for soundness, in fact!) but I don't believe it is relevant to the issue at hand. |
Understood. I don't think this is so important that it would warrant such a large amount of work. I guess I can just try and implement it out of tree.
I guess I think always using
Well it could handle constants and signals seperate to avoid that problem, right? |
Right now, nMigen values have a single property that determines how they behave when used in expressions: their shape. Although the shape of expressions generally depends on the shape of all of their arguments, it is ultimately rooted in the shape of signals, and in most statements, you only have to look up the immediate arguments of any expression to figure out where its shape is derived from. The interface of an With your proposal, values gain two more such properties: their domain and their constness. Domain/constness of an expression would depend on domain/constness of every argument, but unlike shape, it does not terminate once you reach a signal. In fact, it does not terminate on any boundary. Given a single module that is a part of a large design, domain/constness of an arbitrary value in it, in the worst case, depends on every other part of the design. Note that the issue with |
I argue that because each signal must be driven from a single domain, we wouldn't be guessing - it would be completely determined.
Just because you use the source domain by default doesn't mean you can't specify a domain. I'm also not sure what the alternative to this is that makes sense and doesn't give you (essentially) metastable behavior. |
If you have to specify a domain explicitly in order to get local behavior instead of global behavior that depends on the DUT itself, it follows that any well-written formal specification (at least one that can be used against an open set of DUTs) would specify a domain explicitly. In which case there's no point in having the implicit behavior in first place. This is like if Rust had a |
@whitequark Ok, I agree, unless other compelling places are found where it would be useful to add constness and the domain to the properties of a expression that determine the behaviour it does not seem worth it to just add that for this. |
My opinion is that if you don't need to add the "force forward progress" |
Could you illustrate this with an example? |
I'll see what I can do re: converting multiclk.v to nmigen as an example. |
Summarizing IRC discussion with @awygle:
We currently have m.d.comb += Assert(cnt == Sample(cnt) + 1) to: m.d.comb += Assert(cnt == Sampled(cnt) + 1) |
Maybe this is out of scope for this Issue but I think that it would be nice to have the ability to implement something like |
Completely out of scope. I agree that it would be great to have, but I also think that practically speaking, it is one of the hardest problems to solve in a nice way. |
I've discussed this on the Chipflow team meeting on 2023-01-30. We considered that complex SVA assertions are also quite hard to use correctly in Verilog, and that Given that the conclusion in 2020 was that In this case, Amaranth developers looking to use formal property verification can use I'm planning to deprecate these primitives in 0.4 and remove them in 0.5. |
Deprecated in 0.4; will be removed in 0.5. |
It seems I can write something like
m.d.comb += Assert(Past(signal) == 0)
. Ifmulticlock
is off in the sby file, what exactly isPast
?Here's an example. First,
example.py
:And, the sby file:
This fails BMC when multiclock is on, which kind of makes sense to me, if Past is relative to the last cycle of the global clock. In this case I probably shouldn't be using Past, because really what I want to assert is that
data_out
is equal to whatdata_in
was, whenle
was last high.In that case, do I have to effectively make my own version of Past for the internal clock of the example module?
BMC succeeds when multiclock is off, which also kind of makes sense to me, if signals are not allowed to change except on the positive edge of the global clock. However, now the cover trace looks distinctly odd:
data_out
did change on the negative edge of 'le'... but it also changed whenle
was stable. Is that because I've violated the assumption that there is only one clock in the system and therefore yosys does unspecified behavior?The text was updated successfully, but these errors were encountered: