Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add blackbox and stub passes #702

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions magma/compile.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
from inspect import getouterframes, currentframe
from pathlib import PurePath
from .backend import verilog, blif, firrtl, dot, coreir_compiler
from .compiler import Compiler
from .config import get_compile_dir
from .uniquification import uniquification_pass, UniquificationMode
from .passes.clock import WireClockPass
from .passes.drive_undriven import DriveUndrivenPass
from .passes.terminate_unused import TerminateUnusedPass

from magma.backend import verilog, blif, firrtl, dot, coreir_compiler
from magma.bind import BindPass
from magma.compiler import Compiler
from magma.config import get_compile_dir
from magma.inline_verilog import ProcessInlineVerilogPass
from magma.passes.black_box import BlackBoxPass
from magma.passes.stub import StubPass
from magma.passes.clock import WireClockPass
from magma.passes.drive_undriven import DriveUndrivenPass
from magma.passes.terminate_unused import TerminateUnusedPass
from magma.uniquification import uniquification_pass, UniquificationMode


__all__ = ["compile"]

Expand Down Expand Up @@ -38,6 +42,14 @@ def _get_basename(basename):
return basename


def _run_if_in(dct, key, fn):
try:
value = dct[key]
except KeyError:
return
fn(value)


def compile(basename, main, output="coreir-verilog", **kwargs):
if hasattr(main, "circuit_definition"):
main = main.circuit_definition
Expand All @@ -54,6 +66,12 @@ def compile(basename, main, output="coreir-verilog", **kwargs):
# Bind after uniquification so the bind logic works on unique modules
BindPass(main, compile).run()

# Black box circuits if requested.
_run_if_in(opts, "black_boxes", lambda bb: BlackBoxPass(main, bb).run())

# Black box circuits if requested.
_run_if_in(opts, "stubs", lambda stub: StubPass(main, stub).run())

if opts.get("drive_undriven", False):
DriveUndrivenPass(main).run()
if opts.get("terminate_unused", False):
Expand Down
17 changes: 17 additions & 0 deletions magma/passes/black_box.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from typing import Iterable

from magma.circuit import CircuitKind
from magma.passes.passes import CircuitPass


class BlackBoxPass(CircuitPass):
def __init__(self, main: CircuitKind, black_boxes: Iterable[CircuitKind]):
super().__init__(main)
self._black_boxes = set(black_boxes)

def __call__(self, cls):
try:
self._black_boxes.remove(cls)
except KeyError:
return # @cls not in black_boxes, don't need to do anything
cls._is_definition = False
30 changes: 30 additions & 0 deletions magma/passes/stub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from typing import Iterable

from magma.circuit import CircuitKind
from magma.passes.passes import CircuitPass


def _drive_if_undriven_input(port):
if port.is_mixed():
for p in port:
_drive_if_undriven_input(p)
return
# NOTE(rsetaluri): This may override previously wired inputs, resulting in a
# warning. In this case, this warning is benign.
if port.is_input():
port.undriven()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should disable warnings in this pass? One simple way would be to set a magma "global" that disables warning reporting, which we can enable/disable in this pass. A more fine-grained way might be to pass a flag through undriven that says ignore drivers or something. Ideally we can avoid generating warnings internally, even if they are benign



class StubPass(CircuitPass):
def __init__(self, main: CircuitKind, stubs: Iterable[CircuitKind]):
super().__init__(main)
self._stubs = set(stubs)

def __call__(self, cls):
try:
self._stubs.remove(cls)
except KeyError:
return # @cls not in stubs, don't need to do anything
with cls.open():
for port in cls.interface.ports.values():
_drive_if_undriven_input(port)
3 changes: 0 additions & 3 deletions tests/doit

This file was deleted.

29 changes: 29 additions & 0 deletions tests/test_passes/gold/test_passes_black_box_basic.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{"top":"global._Top",
"namespaces":{
"global":{
"modules":{
"_Foo":{
"type":["Record",[
["I","BitIn"],
["O","Bit"]
]]
},
"_Top":{
"type":["Record",[
["I","BitIn"],
["O","Bit"]
]],
"instances":{
"_Foo_inst0":{
"modref":"global._Foo"
}
},
"connections":[
["self.I","_Foo_inst0.I"],
["self.O","_Foo_inst0.O"]
]
}
}
}
}
}
32 changes: 32 additions & 0 deletions tests/test_passes/gold/test_passes_black_box_noop.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{"top":"global._Top",
"namespaces":{
"global":{
"modules":{
"_Foo":{
"type":["Record",[
["I","BitIn"],
["O","Bit"]
]],
"connections":[
["self.O","self.I"]
]
},
"_Top":{
"type":["Record",[
["I","BitIn"],
["O","Bit"]
]],
"instances":{
"_Foo_inst0":{
"modref":"global._Foo"
}
},
"connections":[
["self.I","_Foo_inst0.I"],
["self.O","_Foo_inst0.O"]
]
}
}
}
}
}
37 changes: 37 additions & 0 deletions tests/test_passes/gold/test_passes_stub_basic.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{"top":"global._Top",
"namespaces":{
"global":{
"modules":{
"_Foo":{
"type":["Record",[
["I","BitIn"],
["O","Bit"]
]],
"instances":{
"corebit_undriven_inst0":{
"modref":"corebit.undriven"
}
},
"connections":[
["self.O","corebit_undriven_inst0.out"]
]
},
"_Top":{
"type":["Record",[
["I","BitIn"],
["O","Bit"]
]],
"instances":{
"_Foo_inst0":{
"modref":"global._Foo"
}
},
"connections":[
["self.I","_Foo_inst0.I"],
["self.O","_Foo_inst0.O"]
]
}
}
}
}
}
32 changes: 32 additions & 0 deletions tests/test_passes/gold/test_passes_stub_noop.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{"top":"global._Top",
"namespaces":{
"global":{
"modules":{
"_Foo":{
"type":["Record",[
["I","BitIn"],
["O","Bit"]
]],
"connections":[
["self.O","self.I"]
]
},
"_Top":{
"type":["Record",[
["I","BitIn"],
["O","Bit"]
]],
"instances":{
"_Foo_inst0":{
"modref":"global._Foo"
}
},
"connections":[
["self.I","_Foo_inst0.I"],
["self.O","_Foo_inst0.O"]
]
}
}
}
}
}
63 changes: 63 additions & 0 deletions tests/test_passes/test_black_box.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import pytest

import magma as m
from magma.passes.black_box import BlackBoxPass
from magma.testing import check_files_equal


# NOTE(rsetaluri): We need this fixture per function, since each function may
# run a pass to modify the circuits. However, the setup is identical for each so
# we reuse this fixture across all the test.
@pytest.fixture(scope="function")
def ckts():

class _Foo(m.Circuit):
io = m.IO(I=m.In(m.Bit), O=m.Out(m.Bit))
io.O @= io.I


class _Bar(m.Circuit):
io = m.IO(I=m.In(m.Bit), O=m.Out(m.Bit))
io.O @= io.I


class _Top(m.Circuit):
io = m.IO(I=m.In(m.Bit), O=m.Out(m.Bit))
io.O @= _Foo()(io.I)

return (_Foo, _Bar, _Top)


def test_noop(ckts):
_Foo, _Bar, _Top = ckts
m.compile("build/test_passes_black_box_noop", _Top, output="coreir")
assert check_files_equal(__file__,
f"build/test_passes_black_box_noop.json",
f"gold/test_passes_black_box_noop.json")


def test_basic(ckts):
_Foo, _Bar, _Top = ckts
BlackBoxPass(_Top, (_Foo,)).run()
m.compile("build/test_passes_black_box_basic", _Top, output="coreir")
assert check_files_equal(__file__,
f"build/test_passes_black_box_basic.json",
f"gold/test_passes_black_box_basic.json")


def test_unused(ckts):
_Foo, _Bar, _Top = ckts
BlackBoxPass(_Top, (_Bar,)).run()
m.compile("build/test_passes_black_box_unused", _Top, output="coreir")
assert check_files_equal(__file__,
f"build/test_passes_black_box_unused.json",
f"gold/test_passes_black_box_noop.json")


def test_compiler_args(ckts):
_Foo, _Bar, _Top = ckts
m.compile("build/test_passes_black_box_compiler_args", _Top,
output="coreir", black_boxes=(_Foo,))
assert check_files_equal(__file__,
f"build/test_passes_black_box_compiler_args.json",
f"gold/test_passes_black_box_basic.json")
32 changes: 32 additions & 0 deletions tests/test_passes/test_passes_black_box_noop.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{"top":"global._Top",
"namespaces":{
"global":{
"modules":{
"_Foo":{
"type":["Record",[
["I","BitIn"],
["O","Bit"]
]],
"connections":[
["self.O","self.I"]
]
},
"_Top":{
"type":["Record",[
["I","BitIn"],
["O","Bit"]
]],
"instances":{
"_Foo_inst0":{
"modref":"global._Foo"
}
},
"connections":[
["self.I","_Foo_inst0.I"],
["self.O","_Foo_inst0.O"]
]
}
}
}
}
}
Loading