LUT pass
- Dominant language
- C++
- Stars
- 1.9k
- Forks
- 283
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 135
Description
The table_switch_pass currently analyzes the compute graph to find select chains that can be turned into lookup tables. In theory, we could generalize this to turn any piece of the compute graph into a lookup table.
The simplest version of this is a pass that determines for each node what params are in its transitive closure (TC), and then if the transitive closure does not contain edges out into other nodes (i.e.: nodes in the TC are only shared with other nodes in the TC), we can determine the area consumed by the table via `2 ^ (sum of param sizes) * node output bitwidth * area per bit`, and then compare that against the area actually consumed by the transitive closure. If the lookup table delay and area are less than the transitive closure delay and area, then replacing the transitive closure with a lookup table is always a win.
An improvement on this is to generalize to any connected piece of the compute graph, instead of only pieces where the root nodes are literals and params as above. This raises the question: how do we choose such a piece? In the earlier example, the transitive closures are uniquely defined by the node that we choose to replace with a lookup table node, but in this case there are many possible choices. One insight is that the property we want the root nodes of such a connected piece to have is that they do not produce many bits of information (since that number is exponentiated base 2). That gives a hint that something like a minimum cut is desirable. We already use minimum cuts in the scheduler to choose pipeline stage boundaries, so one easy way to get an improvement in this dimension is to run the LUT pass after scheduling and simply use pipeline stage inputs instead of params as above.
Another improvement is to avoid the constraint that nodes in a connected piece must not be shared outside the connected piece. One way to do this is to somehow hoist the sharing edge to the top node of the connected piece. A way to do this is by a dataflow analysis that does "reverse computation"; i.e.: it answers the question "for each node, what nodes in its transitive closure can I compute from its value by undoing operations that happened in its transitive closure". For example, when executed on a node `a = concat(b, c)`, it will store `{a -> {b -> bit_slice(a, ...), c -> bit_slice(a, ...)}}`, but if executed on an irreversible operation it will simply return `{}`. Then, if the area/delay saved by replacing the connected piece with a LUT minus the area/delay consumed by the reverse computation is positive, it is worth it to replace the connected piece with a LUT.
Yet another improvement is that, since we are running this pass after scheduling, we can use scheduling information to determine when delay actually matters. If a node is on the critical path, then it might be worth it to trade off area against delay, but if it's not on the critical path we should only replace a node with a LUT if it saves area. In practice, LUTs are probably usually going to be larger than the circuits they replace, so this actually could help reduce the computational complexity of the LUT pass: we can just look at nodes on the critical path instead of all nodes. However, replacing nodes on the critical path with LUTs does have a weakness: it invalidates the schedule. So we might want to run the scheduler again afterward or even multiple times; the tradeoff of performance against compile time will require careful thinking.
It's possible that with all these competing factors there just aren't many places in existing designs where a LUT is actually a win (though it obviously depends on where on the area/delay pareto curve we are, and the exact details of our delay/area model). However, one thing we can strongly suspect is that, much like the range analysis, this allows designs to be written less carefully -- a designer can simply put a function in knowing that if it's more efficient to turn it into a lookup table, XLS is very likely to.
The reverse computation dataflow analysis written for this is also useful for rematerialization in the scheduler, in a manner similar to the algorithm described in [this paper](https://hal.inria.fr/inria-00607323/document). Choices made during a rematerialization pass may interact with the LUT optimization, so it may be beneficial to combine them into a single decision-making loop.
Contributor guide
Assessment
This issue has not been assessed yet.