diff --git a/magma/fromverilog.py b/magma/fromverilog.py index 170f86b316..9cc529d405 100755 --- a/magma/fromverilog.py +++ b/magma/fromverilog.py @@ -146,7 +146,8 @@ def ParseVerilogModule(node, type_map): return node.name, args -def FromVerilog(source, func, type_map, target_modules=None, shallow=False): +def FromVerilog(source, func, type_map, target_modules=None, shallow=False, + external_modules={}): parser = VerilogParser() ast = parser.parse(source) visitor = ModuleVisitor() @@ -159,7 +160,11 @@ def _get_lines(start_line, end_line): lines = source.split("\n") return "\n".join(lines[start_line - 1:end_line]) - magma_defns = {} + if not external_modules.keys().isdisjoint(visitor.defns.keys()): + intersection = external_modules.keys() & visitor.defns.keys() + raise Exception(f"Modules defined in both external_modules and in " + f"parsed verilog: {intersection}") + magma_defns = external_modules.copy() for name, verilog_defn in visitor.defns.items(): parsed_name, args = ParseVerilogModule(verilog_defn, type_map) assert parsed_name == name @@ -180,16 +185,18 @@ def _get_lines(start_line, end_line): logger.warning(f"Did not import any modules from verilog, either could " f"not parse or could not find any of the target_modules " f"({target_modules})") + # Filter back out external modules. + magma_defns = {name : magma_defns[name] for name in visitor.defns} if target_modules is None: return list(magma_defns.values()) # Filter modules based on target_modules list. return [v for k, v in magma_defns.items() if k in target_modules] -def FromVerilogFile(file, func, type_map, target_modules=None, shallow=False): +def FromVerilogFile(file, func, type_map, target_modules=None, shallow=False, external_modules={}): if file is None: return None verilog = open(file).read() - result = FromVerilog(verilog, func, type_map, target_modules, shallow) + result = FromVerilog(verilog, func, type_map, target_modules, shallow, external_modules) # Store the original verilog file name, currently used by m.compile to # generate a .sv when compiling a circuit that was defined from a verilog # file @@ -221,13 +228,13 @@ def DeclareFromTemplatedVerilogFile(file, type_map={}, **kwargs): return FromTemplatedVerilogFile(file, DeclareCircuit, type_map, **kwargs) -def DefineFromVerilog(source, type_map={}, target_modules=None, shallow=False): +def DefineFromVerilog(source, type_map={}, target_modules=None, shallow=False, external_modules={}): return FromVerilog(source, DefineCircuit, type_map, target_modules, - shallow=shallow) + shallow=shallow, external_modules=external_modules) -def DefineFromVerilogFile(file, target_modules=None, type_map={}, shallow=False): +def DefineFromVerilogFile(file, target_modules=None, type_map={}, shallow=False, external_modules={}): return FromVerilogFile(file, DefineCircuit, type_map, target_modules, - shallow=shallow) + shallow=shallow, external_modules=external_modules) def DefineFromTemplatedVerilog(source, type_map={}, **kwargs): return FromTemplatedVerilog(source, DefineCircuit, type_map, **kwargs) diff --git a/tests/test_verilog/test_from_file.py b/tests/test_verilog/test_from_file.py index ab229d8503..e9c2adcd3a 100644 --- a/tests/test_verilog/test_from_file.py +++ b/tests/test_verilog/test_from_file.py @@ -1,6 +1,8 @@ import magma as m import magma.testing import os +import pytest + def check_port(definition, port, type, direction): assert hasattr(definition, port) @@ -151,3 +153,54 @@ def test_verilog_dependency_out_of_order(): m.compile(f"build/{FILENAME}", top, output="coreir") assert m.testing.check_files_equal(__file__, f"build/{FILENAME}.json", f"gold/{FILENAME}.json") + + +def test_from_verilog_external_modules(): + [foo] = m.DefineFromVerilog(""" +module foo(input I, output O); + assign O = I; +endmodule""") + [bar] = m.DefineFromVerilog(""" +module bar(input I, output O); + foo foo_inst(I, O); +endmodule""", external_modules={"foo": foo}) + top = m.DefineCircuit("top", "I", m.In(m.Bit), "O", m.Out(m.Bit)) + bar_inst = bar() + m.wire(top.I, bar_inst.I) + m.wire(bar_inst.O, top.O) + m.EndDefine() + FILENAME = "test_verilog_dependency_top" + m.compile(f"build/{FILENAME}", top, output="coreir") + assert m.testing.check_files_equal(__file__, f"build/{FILENAME}.json", + f"gold/{FILENAME}.json") + + +def test_from_verilog_external_modules_missing(): + with pytest.raises(Exception) as pytest_e: + m.DefineFromVerilog(""" +module bar(input I, output O); + foo foo_inst(I, O); +endmodule""") + assert False + assert pytest_e.type is KeyError + assert pytest_e.value.args == ("foo",) + + +def test_from_verilog_external_modules_duplicate(): + with pytest.raises(Exception) as pytest_e: + [foo] = m.DefineFromVerilog(""" +module foo(input I, output O); + assign O = I; +endmodule""") + [bar] = m.DefineFromVerilog(""" +module foo(input I, output O); + assign O = I; +endmodule + +module bar(input I, output O); + foo foo_inst(I, O); +endmodule""", external_modules={"foo": foo}) + assert False + assert pytest_e.type is Exception + assert pytest_e.value.args == \ + ("Modules defined in both external_modules and in parsed verilog: {'foo'}",) # nopep8