-
Notifications
You must be signed in to change notification settings - Fork 177
Lowering pass generates Firrtl with memory's clock connected to validIf #702
Comments
@chick @jackkoenig I see two options forward. The first is to redo how Chirrtl->High-Firrtl works, by pulling the clock connection to the port out of the when statement. The problem with this approach is the following circuit would trigger a reference to a not-yet-declared node. Input Chirrtl:
Resulting High Firrtl:
The second is the redo how when's and clocks interact, which is to say that clock assignments are unaffected by when statements (e.g. clock assignment holds regardless of whether the when predicate is true). If a clock is ever assigned to twice, it is an error. I'm in favor of this because it feels less hacky, and will allow us more options in the future to figure out how to support clock muxing natively in FIRRTL. Thoughts? |
I'd lean to method two for the reasons you state. it seems to me that the firrtl parser and various checks needs to be more explicit about what is correct behavior. I think you can stick arbitrary expression in as the clock terms on registers and memories. Chisel is sort of a gatekeeper but there may be other ways to produce firrtl and construction needs to be correct. |
Two more thoughts. First, what if we don't assign to a clock on both sides of the when? For non-clocks, this would trigger an uninitialized reference exception. Should this be an error, or should it be ok?
If we make this illegal, then we need to force users to invalidate Secondly, should this fix be part of ExpandWhens, or RemoveValidIf? If its the latter, then it would be easier to write a custom pass after ExpandWhens which could do clock-related transforms of ValidIf's of clocks. If its part of ExpandWhens, then that information is permanently lost. |
After more thinking, it might make sense to go back to the first option of pulling the clock connection up to the memory declaration in ChirrtlToHighFIRRTL. We can check if the value is already declared (by far the most common case), and error otherwise, telling the user not to do weird things. This would keep FIRRTL clock/when semantics clear (clocks aren't treated differently) and give Chisel users a special error message. Chirrtl-> HighFIRRTL is a tad hacky anyways, so its not a bad thing to put this there. |
I don't see why the
should be legal. Why would it be a good idea to have a clock float. |
So it would be legal but not float:
would resolve to
Anyways, I think its better to just deal with it in Chirrtl->HighFirrtl |
Ok, after trying option 1, it works pretty well with all previous problems. However, there is a second problem. If a new clock is declared between the memory declaration and the port declaration, it fails:
Notice this is independent of |
Ok, I'm convinced that Option 1 won't work, because with the new class MultiClockMem extends Module {
val io = IO(new Bundle {
val en = Input(Bool())
val other = Input(Clock())
val addr = Input(UInt(2.W))
val out = Output(UInt(32.W))
})
val mem = Mem(4, UInt(32.W))
when (io.en) {
mem(io.addr) := 7.U
io.out := 0.U
}.otherwise {
val local = Wire(Clock())
local := io.other
withClock(local) {
io.out := mem(io.addr)
}
}
} This means we actually need to formally resolve how Clock types interact with when statements in FIRRTL (it's no longer simply a product of how Chisel interfaces with Chisel). However, I don't like making Clocks ignore when statements either, it means you can't express Clock muxing with whens, which would exclude a future pass which could swap out generic clock muxes with a custom clock mux. So... maybe a third option is in order: Set the default clock to be asClock(0.U). This would work in simulation, but would require more changes to ReplSeqMem and VerilogEmitter to recognize you are muxing the mem port's clock on the same enable as the data enable (and thus can optimize away the mux). I'll post another reply after this summarizing all options. |
Updated the issue with summary of the discussion. |
After discussions, decided we should do option 4: Add mport as a first class FIRRTL construct which can reference DefMemory's, and which get's eliminated before MiddleFIRRTL. Eliminate ChirrtlForm and Chirrtl. |
This should be addressed/linked to #727. |
This is too open ended to languish as an open issue. It has been moved to |
When all references to a memory are within the scope of a when statement, Firrtl connects the memory's clock through a validIf using the when's condition.
Type of issue
Steps to reproduce the problem:
The following Chisel circuit:
generates the following selected low Firrtl.
Current behavior?
validIf
behavior is undefined whenio_en
is false. meaning it could randomly cycle the memory's clock.Expected behavior?
memory.clk
should be directly connected to clock ormux
ed onenable
to beclock
orUInt<1>("h0")
.What is the use case for changing the behavior?
The existing behavior causes inconsistent results in the interpreter and could affect other simulations or synthesized code.
Impact
Development Phase
Discussion Summary (Developer Edits)
Underlying Problem: how to translate from a Chirrtl memory to a Firrtl memory.
Chisel's memory API is to separate the memory declaration from the port declaration:
However, FIRRTL has a single memory declaration which includes declaring all ports:
To make generating FIRRTL easier, we introduced CHIRRTL which also separates memory declarations from port declarations, making Chisel's generation easier:
So the problem lies in converting the CHIRRTL memory representation into FIRRTL's. Fundamentally, a
when
statement (in any other context) implies a mux. However, awhen
around a CHIRRTLmport
actually implies an enable on that port. Thus, the question is how to specially handle the address, enable, and clock values specified at the port declaration (and when) in CHIRRTL, but at the memory declaration in FIRRTL.Fortunately, the enable and address are easy. For the enable, we can set it to zero at the memory declaration, and set it to one at the port declaration. Normal expand whens + constprop will resolve this to be correct. For the address, we can invalidate it at the memory declaration, before we expand when's. This will generate a
validif
on the address value after expand when's, but this is still behaviorally correct and has no impact on area (don't have to generate a mux from it).The problem is what to do with that (stinking) port clock. The current API is to set it to invalid, but as @chick pointed out, that obviously isn't correct and a bug (unless we redefine its semantics). We have 4 options:
Discussion of 1:
Pro - Simple implementation (like ~2 loc changes)
Con - API decision about how clocks and last connect semantics work, which probably has negative implications for future attempts to support generic clock muxing. Can be non-intuitive to users.
Discussion of 2:
Pro - Somewhat simple implementation (~100 loc changes)
Con - If a local clock, declared after the memory declaration, is used by a port to the memory, then you get a really non-intuitive error. I don't see an easy way of solving this.
Discussion of 3:
Pro - Clean resolution of how clocks/whens interact, without loss of expressivity. Opens the door to future generic clock muxes.
Con - Not a pure transformation, in that it will change the behavior of multiple-cycle writes on memories.
Current status: Tried out 1, 2, and 3 already, but I'm dissatisfied. Trying out option 4.
The text was updated successfully, but these errors were encountered: