-
Notifications
You must be signed in to change notification settings - Fork 52
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
Combinational loops created when invoke
d component has single group in control sequence
#2198
Comments
In case it's of interest, the verilog produced can be found here And the error message verilator produces is
|
invoke
s create combinational loops when invoke
d component has single group in control sequence invoke
d component has single group in control sequence
Sorry if this is a dumb question, but what is the easiest way to reproduce this error? As in, what command should I run? |
Not dumb! If the example above is named
|
FWIW, while I continue to dig into this a bit, here's a one-liner for detecting the problem:
|
This one's a doozy!! Thanks for the compact reproduction. understanding the problemI tried to pull out the core of the problem in the lowered Calyx. The offending lines are: In
In
Simplifying all the
That is, the compiler is trying to make sure Another way of seeing the problem is to look at the result of
These lines cause the problem:
The assignment to potential solutionsWay back when, in #621 (comment), @rachitnigam distilled one option:
Which we would have to generalize (disallow combinational paths from any input to the While this requirement seems awkward, maybe it is necessary to make it feasible to implement invokes correctly. We would then have the problem of figuring out what to do with And @nathanielnrn suggests:
This would amount to inlining the an asideAs an aside, it would be very handy to have (but probably not worth it to build) a tool that can detect combinational cycles at the Calyx level, just for debugging compiler bugs, without emitting Verilog... |
Another possible approach I've thought of in the past is making sure that every component gets an FSM, even if there is exactly one group in it. This would allow us to redirect the For assignments of this form:
There is some sort of "done-forwarding" being performed that should not be treated as a normal assignment. This is possibly related to #1034 where we have a similar problem with I think there is some higher-level problem/question about how to think about control vs. data signals (which keeps coming up) and something that we should address with a possible redesign.
I did not know this! Super useful! |
Having thought about this a bit more, I think it can be useful to have a component's/group's
might be the right way forward. |
Thanks for the discussion. To distill the option space, one way to think about it is that we need to respect the Calyx requirement that every (non-combinational) component takes at least 1 cycle to run. I think there are two possible directions:
I think "making sure every component gets an FSM" amounts to a pretty good approach to the second path. |
This works around #2198 causing combinational loops
…ctually used in an `invoke` (#2216) These changes make it so that only signals used by a component are turned into assignments when said component is `invoke`d. I added some comments trying to point out what changes were made as the naming and keeping track of what is held in which data structure can be a bit confusing. For example, consider this reader component, which manually drives a `manually_driven_ref_reg_out` signal ``` component reader() -> (manually_driven_ref_reg_out : 32) { cells { ref ref_reg = std_reg(32); } wires { //reads from the ref register group read_from_ref_reg { manually_driven_ref_reg_out = ref_reg.out; read_from_ref_reg[done] = ref_reg.done; } } control { read_from_ref_reg; } } ``` If we invoke this component in main and pass in a `concrete_cell`: `invoke reader[ref_reg=concrete_reg]()();` This control statement will now get compiled to a group that only assigns to the ports used in `reader`. So in this case, `concrete_reg.out`. Notably, `concrete_reg`'s `write_en` and `in` signals do not appear here. Before this PR, they would have. ``` group invoke0 { reader.manually_driven_ref_reg_out = concrete_reg.out; reader.go = 1'd1; invoke0[done] = reader.done; } ``` This partially address the issue of [passing in `ref` cells in parallel](https://calyx.zulipchat.com/#narrow/stream/423433-general/topic/parallel.20ref.20passing). In particular, correctly models behavior in verilog in cases where ports driven/accessed by components that are passed in the same concrete cell in parallel are mutually exclusive. (i.e. a write component that only drive a register's `in` and `write_en` ports.) So one could now imagine the following control block valid: ``` control { par { invoke reader[ref_reg = concrete_reg]()(); invoke writer[ref_reg = concrete_reg]()(); } } ``` --- PS: Worth noting that the `reader` component as written above would currently run into issues due to #2198
I think the title describes what is happening, but I'm not sure. Any input people have is appreciated!
I’ve tried to pin down where some combinational loops are being generated when using invokes. I think it’s a symptom of the invoke getting “hoisted” in a way such that the invoked component’s done signal relies on an input signal, after hoisting.
It seems like a
main
with a group that writes to a “concrete” register, followed by aninvoke
to a sub-component that writes to that same register, as aref
, creates a combinational loop. Something like this:Looking at the the flattened version of the above program, I think this is a symptom of #621. The empty control block of
invokee
, and the combinational dependence ofinvokee.done
on an input to the component, I think is what @rachitnigam describes in this comment coming into play.In terms of an actionable way forward, one approach could modify the
compile_invoke
pass to flatten such components that only have a single group in their control block into the component doing the invoking itself? So in the flattened example, we would removeinvokee
as a component and haveinvokee
's sole groupinvokee_ref_reg_write
live in thewires
block ofwrapper
. In this way we would avoid the combinational-groups that get produced with the current pass.Happy to hear any thoughts about other possible ways forward.
The text was updated successfully, but these errors were encountered: