I’m proposing the RTL-dialect adopt a single-connect semantics for inout values. Specifically, only one connect operation would be allowed to drive inout values. Right now, mostly by accident I think, you can have multiple drivers of inout values, and in the absense of control flow and as an artifact of processing order, this usually results in last-connect behavior during lowering to verilog. Single connect should greatly simplify analysis and transform. If we were to not adopt this semantic, we must at least define the semantics of multiple connects.
This aligns RTL with Low FIRRTL which also has single-connect semantics.
This does leave open connect semantics in the presence of control flow, sv.if blocks for example. Right now, those are generated by lowering registers from other dialects to the register-free RTL + SV dialects and would mostly be solved in this common case with actual registers in RTL.
I am not sure, but I’m concerned that this won’t compose well. This wasn’t an accident. Verilog supports multiple connects to a wire both because of control flow (as you mention) but also because of Z values, where you can have:
x <= thing1
x <= thing2
with the assumption that one or the other will be z at any given time. This is semantically equivaelnt to:
x <= rtl.merge(thing1, thing2)
for us, where rtl.merge is the SSA dataflow version of connections.
First observation is that rtl.wire doesn’t really make sense for the RTL dialect, it is more of an SV thing, because its only needed purpose is to generate named wires in the output verilog. For all other things you can use rtl.merge (or the value directly) because the RTL dialect allows cycles in the graph.
Second observation is that we don’t have a model for what wires we want to persist (as mentioned on this patch) and that will be really necessary at some point. This is a human issue, not a dataflow optimization issue, so pushing this into SV instead of RTL makes sense.
If you agree it needs to be in the SV dialect to support hte need of humans, then this gets to the question of “should we do this”? It is trivially that any multiconnect could be implemented in terms of a single connect and merge, but I see two problems:
- This won’t generate as nice of Verilog, and won’t track location info in the same way (where each connect will have its own loc)
- This doesn’t actually make dataflow analysis any easier.
TLDR, I think the model we need here is:
- eliminate as many wires as we can that are not important for humans (e.g. the _T wires which get an empty name in FIRParser),
- retain the wires at the SV level that are left and use them as optimization blocks (because, e.g. a verification engineer wants to be able to probe them in the ultimate verilog etc)
- Synthesis or other tools would lower them harder in an explicit pass (not a canonicalization) to rtl.merges or other things that make synthesis convenient when they aren’t burdened with generating verilog.
Likewise, multi-connect doesn’t compose well, since control flow is in other dialects, so any RTL pass will have to be aware of these dialects, or be conservative about blocks.
I wouldn’t be sad to eliminate wires completely from RTL, moving it into SV makes sense (maybe as something more limited even), but then we have both RTL and SV having name-preserving constructs (ports and wires). If moving into SV, it might be nice to have a more restricted operation for naming, one that essentially just names a value, and then export is responsible for ensuring that is a named wire in verilog.
As for ‘z’, I think these are different enough from everything else that we need to model them. If that way is wires and connects, fine, but then it seems odd to use the same structure to model registers. Relatedly, this has already been seen for output ports, where connects have been replaced with output.
So, if wires and inout ports and conenct are how we represent ‘z’, fine, but then we should be more intentional about registers representation since modeling them as ‘z’-like things seems unhelpful.