NOTE This is an experimental feature, you will likely encounter bugs. Please use GitHub Issues to report any problems or to provide us with feedback on how to improve the syntax.
Overview
The goal of the m.inline_combinational syntax is to improve the ergonomics of
using the m.combinational syntax described here.
inline_combinational avoids having to define function parameters, pass
arguments, and assign return values when defining a combinational block. Instead,
the user can define a combinational function inside their m.Circuit class
defintiion and refer directly to magma values in the scope.
Here's a simple example:
class Main(m.Circuit):
io = m.IO(invert=m.In(m.Bit), O0=m.Out(m.Bit), O1=m.Out(m.Bit))
io += m.ClockIO()
reg = m.Register(m.Bit)()
O1 = m.Bit()
@m.inline_combinational(debug=True, file_name="inline_comb.py")
def logic():
if io.invert:
reg.I @= ~reg.O
O1 @= ~reg.O
else:
reg.I @= reg.O
O1 @= reg.O
io.O0 @= reg.O
io.O1 @= O1
Notice that the first 3 lines of Main's definition are standard magma.
Inside the function logic that has been decorated with
@m.inline_combinational, the user can refer to reg (a normal magma
instance) and it's ports to perform logic and wiring.
Notice that the code wires the reg.I using the @= operator inside the if
statement. The combinational rewrite logic will change these statements to
assign to a temporary value, which will then get process by the SSA pass to
produce the final value (output of a mux or chain of muxes) which is then wired
to the original target (reg.I in this case).
Internal Details
For more details on the rewrites, here are two dumps of the intermediate code
during the inline_combinational rewrite process:
1. Introduce temporary values. At this point, the wiring targets (LHS of @=
operators) are replaced with a temporary values
(using the prefix auto_prefix0, so auto_prefix00 is the first temporary,
auto_prefix01 is the second temporary, the 2nd digit is used for the
unique id). These temporary values are returned from the function.
python
@m.inline_combinational(debug=True, file_name='inline_comb.py')
def logic():
if io.invert:
_auto_prefix_00 = ~reg.O
_auto_prefix_01 = ~reg.O
else:
_auto_prefix_00 = reg.O
_auto_prefix_01 = reg.O
return _auto_prefix_00, _auto_prefix_01
2. Run the standard combinational passes. This removes the if statements by
transforming them into muxes using SSA. Notice the temporaries have an
extra digit attached, which is a unique identifier introduced by SSA.
python
@m.inline_combinational(debug=True, file_name='inline_comb.py')
def logic():
_auto_prefix_000 = ~reg.O
_auto_prefix_010 = ~reg.O
_auto_prefix_001 = reg.O
_auto_prefix_011 = reg.O
_auto_prefix_002 = __phi(io.invert, _auto_prefix_000, _auto_prefix_001)
_auto_prefix_012 = __phi(io.invert, _auto_prefix_010, _auto_prefix_011)
__return_value0 = _auto_prefix_002, _auto_prefix_012
return __return_value0
After this rewrite process, the inline_combinational logic calls the function
to produce the return value. The function execution uses the enclosing scope
(so references like reg.O should behave as expected). The return values are
wired to their target (e.g. reg.I @= _auto_prefix_002).