New external
keyword to pass cells "by reference"
#992
Replies: 2 comments 2 replies
-
This is super awesome!! This seems like a very juicy advancement that will make Calyx quite a bit more expressive. It is nice that this can be "compiled away" into the design pattern that we describe in this part of the docs. The design seems great. The only bikesheddy low-level alternative I could imagine would be making the declarations look more like the
…but that has the obvious downside of making the external components look less like the internal ones, so I think we should reject it. Two other very low-level thoughts:
|
Beta Was this translation helpful? Give feedback.
-
I think that's the case for TVM, not NTT. It would be nice to have some way to enforce read/write-only memories, i.e., only hook in a subset of wires.
Couldn't this potentially lead to name duplication, since nothing stops a user from having a wire named |
Beta Was this translation helpful? Give feedback.
-
We have a standard use case for passing memories "by reference" where instead of instantiating a memory inside a component, we inline a memory interface into the component interface and the calling component is responsible for correctly hooking up the signals to a memory.
For example, here is a component that takes a register "by reference" and increments the value in it:
Note how the interface for some register
r0
has been completely inlined into the interface of the component here. In fact, this is exactly what theexternalize
pass does. However, in addition to being a tedious way to connect hardware modules (which is something we can usually ignore in an IL), there is an important design problem: Inlining interfaces removes any semantic relationship between ports. For example, usually, we can infer that the groupincr_reg
takes one cycle if it is updating a register directly. However, in this case, we have no way to knowing this piece of information since the portsr0_done
andr0_write_en
could be arbitrary ports on different components.This proposal elevates the
@external
attribute to a keyword and gives it specific semantics that enables components to pass subcomponents in a semantically meaningful way. Additionally, the proposal is backwards compatible; we provide a way to compileexternal
cells to normal Calyx programs which enables the existing infrastructure to work properly.The
external
KeywordThe semantics of
@external
attribute specify that the compiler will either inline the interface of the cell into the component (using theexternalize
pass) or, for simulation code, generate$readmemh
commands to read and write initial and final states of the memory. For the default compilation flow, adding@external
attributes to non-main
components generates incorrect code (#987).In this proposal, we elevate
external
to a keyword for cells:Cells defined as
external
are used normally like other cells in the component: their ports and attributes can be used in the same way as a normal cell. This immediately fixes the problem of semantic alignment of ports.invoke
statements syntax is extended to allow them to pass inexternal
cells:Compiling
external
The
external
keyword can be compiled to normal Calyx programs with the following semantics that keep it backwards compatible:main
components, the interfaces ofexternal
cells gets inlined into the component.invoke
statements that pass in external cells get compiled to explicitly pass in the cells using wire assignmentsmain
components,external
cells get a new attribute called@interface
which has the same semantics as the current@external
attribute. Specifically:$readmemh
commandsexternal
keyword in the main component entirely.Changes to IR
ir::Cell
data structure will get a new boolean field forexternal
which will be set to true when the parser parses a cell with theexternal
keyword.ir::Invoke
data structure will get a map calledexternal_cells: Vec<Id, RRC<ir::Cell>>
which will contain the mapping for the external cells.Changes to
validate
The validate pass needs to be changed to make sure that only cells that are actually marked
external
in each component are passed into correspondinginvoke
statements for instances of the component as well as make sure that theinvoke
statements pass in cells with the expected primitives, i.e., if a cell is anexternal std_reg(32)
, then the invoke passes astd_reg(32)
.Inlining Interfaces
For the first step of the compilation, where non-
main
components need to inline the interfaces ofexternal
cells, we can simply use the code from theexternalize
pass directly. We would have to change the behavior of the pass to insert@interface
attributes forexternal
cells inmain
.Compiling
invoke
The pass should compile
invoke
statements that look like this:into:
At the end of the pass, no
invoke
statement should have external cells being passed into it.In the modified version of the
externalize
pass, we will inline eachexternal
cell's interface into the component's signature. Next, the pass should go over each invoke statement and generate additional input and output assignments corresponding to the cell's interface. For example, in the above case, we would generate the inputs:and outputs:
Finally, the pass should clear the invoke data structure's
external_cells
field.Effect on Other Passes
invoke
statement mention all the cells they use in the inputs and outputs. For example, theLiveRangeAnalysis
andReadWriteSet
probably need to be updated to work with the newinvoke
representation.DeadCellRemoval
pass needs to treatexternal
cells the same way it treats@external
cells and make sure they cannot be eliminated.ComponentInliner
can potentially be updated to directly inline the newinvoke
representation which will avoid generating extra continuous assignments.Effect on Cider
Since the change is backwards compatible, the interpreter can continue working with programs generated after the
externalize
pass. @EclecticGriffin can help us outline the changes needed to make the interpreter work withexternal
cells directly.Beta Was this translation helpful? Give feedback.
All reactions