Combinational.ssa constructor

Combinational.ssa(
  1. List<Conditional> construct(
    1. Logic s(
      1. Logic signal
      )
    ), {
  2. String name = 'combinational_ssa',
})

Constructs a new Combinational where construct generates a list of Conditionals which use the provided remapping function to enable a "static single-asssignment" (SSA) form for procedural execution. The Wikipedia article has a good explanation: https://en.wikipedia.org/wiki/Static_single-assignment_form

In SystemVerilog, an always_comb block can easily produce non-synthesizable or ambiguous design blocks which can lead to subtle bugs and mismatches between simulation and synthesis. Since Combinational maps directly to an always_comb block, it is also susceptible to these types of issues in the path to synthesis.

A large class of these issues can be prevented by avoiding a "write after read" scenario, where a signal is assigned a value after that value would have had an impact on prior procedural assignment in that same Combinational execution.

Combinational.ssa remaps signals such that signals are only "written" once.

The below example shows a simple use case:

Combinational.ssa((s) => [
  s(y) < 1,
  s(y) < s(y) + 1,
]);

Note that every variable in this case must be "initialized" before it can be used.

Note that signals returned by the remapping function (s) are tied to this specific instance of Combinational and shouldn't be used elsewhere or you may see unexpected behavior. Also note that each instance of signal returned by the remapping function should be used in at most one Conditional and on either the receiving or driving side, but not both. These restrictions are generally easy to adhere to unless you do something strange.

There is a construction-time performance penalty for usage of this roughly proportional to the size of the design feeding into this instance. This is because it must search for any remapped signals along the entire combinational and sequential path feeding into each Conditional. This penalty is purely at generation time, not in simulation or the actual generated design. For very large designs, this penalty can be mitigated by constructing the Combinational.ssa before connecting inputs to the rest of the design, but usually the impact is so small that it will not be noticeable.

Implementation

factory Combinational.ssa(
    List<Conditional> Function(Logic Function(Logic signal) s) construct,
    {String name = 'combinational_ssa'}) {
  final context = _ssaContextCounter++;

  final ssas = <_SsaLogic>[];

  Logic getSsa(Logic ref) {
    final newSsa = _SsaLogic(ref, context);
    ssas.add(newSsa);
    return newSsa;
  }

  final conditionals = construct(getSsa);

  ssas.forEach(_updateSsaDriverMap);

  _processSsa(conditionals, context: context);

  // no need to keep any of this old info around anymore
  _signalToSsaDrivers.clear();

  return Combinational(conditionals, name: name);
}