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).