m.display

Magma supports using verilog display with the coreir-verilog backend using the m.display function. This function accepts a standard display format string as the first argument and a variable number of arguments corresponding to magma values. These magma values will be interpolated into the generated display statement to display their runtime values.

m.display returns an object that supports method-chaining for specifying event triggers and conditions. For example, to trigger display only on the positive edge of a clock, one would write m.display("<str>" *args).when(m.posedge(io.CLK)). Currently magma provides the m.posedge and m.negedge events. You can also guard the display statement with a condition using the if_ method, for example to only display when the clock enable is high, one would write m.display("<str>").if_(io.CE).

Here is a full example:

class Main(m.Circuit):
    io = m.IO(I=m.In(m.Bit), O=m.Out(m.Bit)) + m.ClockIO(has_enable=True)
    ff = FF()
    io.O @= ff(io.I)
    m.display("ff.O=%d, ff.I=%d", ff.O, ff.I).when(m.posedge(io.CLK))\
                                             .if_(io.CE)

This produces the following snippet of verilog for the display statement:

always @(posedge CLK) begin
    if (CE) $display("ff.O=%d, ff.I=%d", FF_inst0.O, FF_inst0.I);
end

m.log

Magma provides a wrapper around display to manage logging verbosity. There are four levels:

DEBUG = 0
INFO = 1
WARNING = 2
ERROR = 3

You can use these wrappers by invoking the corresponding logging functions:

m.debug
m.info
m.warning
m.error

They the provide the same interface as m.display. They will prepend an uppercase string of the form [<LEVEL_NAME>] to the display statement, e.g. [INFO] <display str>.

To control verbosity in the downstream tool, simply set the preprocessor variable MAGMA_LOG_LEVEL to the desired value. Magma will only display log messages when the configured log level is less than or equal to the corresponding logging function's level. So, to only show warnings and errors, set the level to 2, to show all messages, set the level to 0. To show no messages, set the level to 4. The default level is 1.

Here's a full example using verilator and pytest

import magma as m
import magma.testing
import shutil
import os
import fault

def test_log(capsys):
    FF = m.define_from_verilog("""
module FF(input I, output reg O, input CLK, input CE);
always @(posedge CLK) begin
  if (CE) O <= I;
end
endmodule
    """, type_map={"CLK": m.In(m.Clock), "CE": m.In(m.Enable)})[0]

    class TestLog(m.Circuit):
        io = m.IO(I=m.In(m.Bit), O=m.Out(m.Bit)) + m.ClockIO(has_enable=True)
        ff = FF()
        io.O @= ff(io.I)
        m.log.debug("ff.O=%d, ff.I=%d", ff.O, ff.I).when(m.posedge(io.CLK))\
                                                   .if_(io.CE)
        m.log.info("ff.O=%d, ff.I=%d", ff.O, ff.I).when(m.posedge(io.CLK))\
                                                  .if_(io.CE)
        m.log.warning("ff.O=%d, ff.I=%d", ff.O, ff.I).when(m.posedge(io.CLK))\
                                                     .if_(io.CE)
        m.log.error("ff.O=%d, ff.I=%d", ff.O, ff.I).when(m.posedge(io.CLK))\
                                                   .if_(io.CE)

    m.compile("build/TestLog", TestLog)
    assert not os.system('cd tests/test_verilog/build && '
                         'verilator --lint-only TestLog.v')
    assert m.testing.check_files_equal(__file__,
                                       f"build/TestLog.v",
                                       f"gold/TestLog.v")

    tester = fault.SynchronousTester(TestLog, TestLog.CLK)
    tester.poke(TestLog.CE, 1)
    tester.poke(TestLog.I, 1)
    tester.expect(TestLog.O, 0)
    tester.advance_cycle()
    tester.poke(TestLog.I, 0)
    tester.expect(TestLog.O, 1)
    tester.advance_cycle()
    tester.expect(TestLog.O, 0)
    tester.poke(TestLog.CE, 0)
    tester.poke(TestLog.I, 1)
    tester.advance_cycle()
    tester.expect(TestLog.O, 0)
    tester.poke(TestLog.I, 1)
    tester.advance_cycle()
    tester.expect(TestLog.O, 0)
    tester.poke(TestLog.CE, 1)
    tester.advance_cycle()
    tester.expect(TestLog.O, 1)
    tester.advance_cycle()

    directory = f"{os.path.abspath(os.path.dirname(__file__))}/build/"
    tester.compile_and_run(target="verilator", directory=directory,
                           flags=['-Wno-unused'], skip_compile=True,
                           disp_type="realtime")
    out, err = capsys.readouterr()
    # No debug
    assert f"""
[INFO] ff.O=0, ff.I=1
[WARNING] ff.O=0, ff.I=1
[ERROR] ff.O=0, ff.I=1
[INFO] ff.O=1, ff.I=0
[WARNING] ff.O=1, ff.I=0
[ERROR] ff.O=1, ff.I=0
[INFO] ff.O=0, ff.I=1
[WARNING] ff.O=0, ff.I=1
[ERROR] ff.O=0, ff.I=1
[INFO] ff.O=1, ff.I=1
[WARNING] ff.O=1, ff.I=1
[ERROR] ff.O=1, ff.I=1
""" in out, out

    # Force recompile for new define
    shutil.rmtree(os.path.join(directory, "obj_dir"))
    tester.compile_and_run(target="verilator", directory=directory,
                           flags=['-Wno-unused', '+define+MAGMA_LOG_LEVEL=2'], skip_compile=True,
                           disp_type="realtime")
    out, err = capsys.readouterr()
    # No Info
    assert f"""
[WARNING] ff.O=0, ff.I=1
[ERROR] ff.O=0, ff.I=1
[WARNING] ff.O=1, ff.I=0
[ERROR] ff.O=1, ff.I=0
[WARNING] ff.O=0, ff.I=1
[ERROR] ff.O=0, ff.I=1
[WARNING] ff.O=1, ff.I=1
[ERROR] ff.O=1, ff.I=1
""" in out, out

    # Force recompile for new define
    shutil.rmtree(os.path.join(directory, "obj_dir"))
    tester.compile_and_run(target="verilator", directory=directory,
                           flags=['-Wno-unused', '+define+MAGMA_LOG_LEVEL=3'], skip_compile=True,
                           disp_type="realtime")
    out, err = capsys.readouterr()
    # Only Error
    assert f"""
[ERROR] ff.O=0, ff.I=1
[ERROR] ff.O=1, ff.I=0
[ERROR] ff.O=0, ff.I=1
[ERROR] ff.O=1, ff.I=1
""" in out, out

    # Force recompile for new define
    shutil.rmtree(os.path.join(directory, "obj_dir"))
    tester.compile_and_run(target="verilator", directory=directory,
                           flags=['-Wno-unused', '+define+MAGMA_LOG_LEVEL=0'], skip_compile=True,
                           disp_type="realtime")
    out, err = capsys.readouterr()
    # All
    assert f"""
[DEBUG] ff.O=0, ff.I=1
[INFO] ff.O=0, ff.I=1
[WARNING] ff.O=0, ff.I=1
[ERROR] ff.O=0, ff.I=1
[DEBUG] ff.O=1, ff.I=0
[INFO] ff.O=1, ff.I=0
[WARNING] ff.O=1, ff.I=0
[ERROR] ff.O=1, ff.I=0
[DEBUG] ff.O=0, ff.I=1
[INFO] ff.O=0, ff.I=1
[WARNING] ff.O=0, ff.I=1
[ERROR] ff.O=0, ff.I=1
[DEBUG] ff.O=1, ff.I=1
[INFO] ff.O=1, ff.I=1
[WARNING] ff.O=1, ff.I=1
[ERROR] ff.O=1, ff.I=1
""" in out, out