Skip to content

Commit 9ff4d80

Browse files
committed
[MLIR] Make minor fixes and improvements
* Adds a tool to convert an MLIR file to Verilog with the same options we have in the top down flow. Useful for debugging (e.g. hand-modify .mlir file, recompile to Verilog). * Always checks Verilog compilation for backend tests by default. Previously, this was turned off since (a) CI didn't always have `circt` available; (b) `circt-opt` wasn't standardized; and (c) running `circt-opt` was slow. Now all of these issues are addressed with v2.3.0.
1 parent 7a27517 commit 9ff4d80

File tree

6 files changed

+139
-23
lines changed

6 files changed

+139
-23
lines changed

Diff for: magma/backend/mlir/mlir_to_verilog.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import dataclasses
22
import io
3+
from typing import Optional
34

45
import circt
56
import circt.passmanager
@@ -10,6 +11,7 @@
1011
@dataclasses.dataclass
1112
class MlirToVerilogOpts:
1213
split_verilog: bool = False
14+
split_verilog_directory: Optional[str] = None
1315

1416

1517
def mlir_to_verilog(
@@ -33,6 +35,7 @@ def mlir_to_verilog(
3335
)
3436
pm.run(module.operation)
3537
if opts.split_verilog:
36-
circt.export_split_verilog(module, "")
38+
directory = opts.split_verilog_directory or "."
39+
circt.export_split_verilog(module, directory)
3740
else:
3841
circt.export_verilog(module, ostream)

Diff for: tests/test_backend/test_mlir/golds/register_array_of_bit.v

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Generated by CIRCT circtorg-0.0.0-2220-ge81df61a7
1+
// Generated by CIRCT firtool-1.48.0-34-g7018fb13b
22
module register_array_of_bit(
33
input [3:0] I,
44
input CLK,

Diff for: tests/test_backend/test_mlir/test_mlir_to_verilog.py

+40-20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import io
2+
import pathlib
23
import pytest
4+
import tempfile
5+
import textwrap
36
from typing import Optional
47

58
import circt
@@ -43,7 +46,7 @@ def test_bad_input():
4346

4447
@pytest.mark.parametrize("style", ("plain", "wrapInAtSquareBracket", "none"))
4548
def test_location_info_style(style):
46-
ir = (
49+
ir = textwrap.dedent(
4750
"""
4851
module attributes {{circt.loweringOptions = "locationInfoStyle={style}"}} {{
4952
hw.module @M() -> () {{}}
@@ -57,16 +60,16 @@ def test_location_info_style(style):
5760
line = ostream.readline().rstrip()
5861
expected = "module M();"
5962
if style == "plain":
60-
expected += " // -:3:11"
63+
expected += " // -:3:3"
6164
elif style == "wrapInAtSquareBracket":
62-
expected += " // @[-:3:11]"
65+
expected += " // @[-:3:3]"
6366
assert line == expected
6467

6568

6669
@pytest.mark.parametrize("explicit_bitcast", (False, True))
6770
def test_explicit_bitcast(explicit_bitcast):
6871
explicit_bitcast_attr = ",explicitBitcast" if explicit_bitcast else ""
69-
ir = (
72+
ir = textwrap.dedent(
7073
"""
7174
module attributes {{circt.loweringOptions = "locationInfoStyle=none{explicit_bitcast_attr}"}} {{
7275
hw.module @M(%a: i8, %b: i8) -> (y: i8) {{
@@ -98,7 +101,7 @@ def test_disallow_expression_inlining_in_ports(disallow_expression_inlining_in_p
98101
else ""
99102
)
100103

101-
ir = (
104+
ir = textwrap.dedent(
102105
"""
103106
module attributes {{circt.loweringOptions = "locationInfoStyle=none{disallow_expression_inlining_in_ports_attr}"}} {{
104107
hw.module.extern @Foo(%I: i1) -> (O: i1)
@@ -137,7 +140,7 @@ def test_omit_version_comment(omit_version_comment):
137140
else ""
138141
)
139142

140-
ir = (
143+
ir = textwrap.dedent(
141144
"""
142145
module attributes {{circt.loweringOptions = "locationInfoStyle=none{omit_version_comment_attr}"}} {{
143146
hw.module @M() -> () {{}}
@@ -156,22 +159,39 @@ def test_omit_version_comment(omit_version_comment):
156159
assert first.startswith("// Generated by")
157160

158161

159-
def test_split_verilog():
160-
ir = (
162+
@pytest.mark.parametrize("specify_output_file", (False, True))
163+
def test_split_verilog(specify_output_file):
164+
ir = textwrap.dedent(
161165
"""
162166
module attributes {{circt.loweringOptions = "locationInfoStyle=none"}} {{
163-
hw.module @M() -> () attributes {{output_file = {output_file}}} {{}}
167+
hw.module @M() -> () {attribute_string} {{}}
164168
}}
165169
"""
166170
)
167-
output_file = "tests/test_backend/test_mlir/build/test_mlir_to_verilog_split_verilog.sv"
168-
ir = ir.format(output_file=f"#hw.output_file<\"{output_file}\">")
169-
_, ostream = _run_test(ir, split_verilog=True)
170-
ostream.seek(0)
171-
assert not ostream.readline() # output expected to be empty
172-
173-
# Now read ostream from the expcted output file.
174-
ostream = open(output_file)
175-
ostream.readline() # skip header
176-
assert ostream.readline().rstrip() == "module M();"
177-
assert ostream.readline().rstrip() == "endmodule"
171+
with tempfile.TemporaryDirectory() as tempdir:
172+
output_file = f"{tempdir}/outfile.sv"
173+
if specify_output_file:
174+
attribute_string = (
175+
f"attributes "
176+
f"{{output_file = #hw.output_file<\"{output_file}\">}}"
177+
)
178+
else:
179+
attribute_string = ""
180+
ir = ir.format(attribute_string=attribute_string)
181+
opts = {"split_verilog": True}
182+
if not specify_output_file:
183+
opts["split_verilog_directory"] = tempdir
184+
_, ostream = _run_test(ir, **opts)
185+
ostream.seek(0)
186+
# We expect the output to be empty due to split verilog.
187+
assert not ostream.readline()
188+
189+
# Now read ostream from the expcted output file. If the output file is
190+
# not specificed explicitly, then it goes into <split verilog
191+
# directory>/<module name>.sv (in this case, <tempdir>/M.sv).
192+
if not specify_output_file:
193+
output_file = f"{tempdir}/M.sv"
194+
with open(output_file, "r") as ostream:
195+
ostream.readline() # skip header
196+
assert ostream.readline().rstrip() == "module M();"
197+
assert ostream.readline().rstrip() == "endmodule"

Diff for: tests/test_backend/test_mlir/test_utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
config._register(
2727
test_mlir_check_verilog=EnvConfig(
28-
"TEST_MLIR_CHECK_VERILOG", False, bool))
28+
"TEST_MLIR_CHECK_VERILOG", True, bool))
2929
config._register(
3030
test_mlir_write_output_files=EnvConfig(
3131
"TEST_MLIR_WRITE_OUTPUT_FILES", False, bool))

Diff for: tests/test_tools/test_mlir_to_verilog_main.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import pathlib
2+
import tempfile
3+
import textwrap
4+
5+
from tools.mlir_to_verilog_main import main
6+
7+
8+
def test_basic():
9+
10+
with tempfile.TemporaryDirectory() as tempdir:
11+
tempdir = pathlib.Path(tempdir)
12+
infile = tempdir / "infile.mlir"
13+
with open(infile, "w") as f:
14+
f.write("module {}\n")
15+
outfile = tempdir / "outfile.v"
16+
main([str(infile), "--outfile", str(outfile)])
17+
assert outfile.is_file()
18+
19+

Diff for: tools/mlir_to_verilog_main.py

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import argparse
2+
import dataclasses
3+
import logging
4+
import os
5+
import sys
6+
from typing import Dict
7+
8+
from magma.backend.mlir.mlir_to_verilog import mlir_to_verilog, MlirToVerilogOpts
9+
from magma.common import slice_opts
10+
11+
12+
logging.basicConfig(level=os.environ.get("LOGLEVEL", "WARNING").upper())
13+
14+
15+
def _field_to_argument_params(field: dataclasses.Field) -> Dict:
16+
if field.default_factory is not dataclasses.MISSING:
17+
raise TypeError(field)
18+
params = {}
19+
params["required"] = field.default is dataclasses.MISSING
20+
if field.type is bool and not params["required"] and not field.default:
21+
params["action"] = "store_true"
22+
return params
23+
if not params["required"]:
24+
params["default"] = field.default
25+
params["action"] = "store"
26+
params["type"] = field.type
27+
return params
28+
29+
30+
def _add_dataclass_arguments(parser: argparse.ArgumentParser, cls: type):
31+
assert dataclasses.is_dataclass(cls)
32+
for field in dataclasses.fields(cls):
33+
params = _field_to_argument_params(field)
34+
parser.add_argument(f"--{field.name}", **params)
35+
36+
37+
def main(prog_args = None) -> int:
38+
parser = argparse.ArgumentParser(
39+
"Compile a (MLIR) .mlir file to verilog (.v/.sv)"
40+
)
41+
42+
parser.add_argument(
43+
"infile",
44+
metavar="<input filename>",
45+
action="store",
46+
type=str,
47+
help="Input MLIR file",
48+
)
49+
parser.add_argument(
50+
"--outfile",
51+
metavar="<output filename>",
52+
action="store",
53+
type=argparse.FileType("w"),
54+
required=False,
55+
default=sys.stdout,
56+
)
57+
_add_dataclass_arguments(parser, MlirToVerilogOpts)
58+
59+
args = parser.parse_args(prog_args)
60+
opts = slice_opts(vars(args), MlirToVerilogOpts)
61+
62+
logging.debug(f"Running with opts: {opts}")
63+
if opts.split_verilog and args.outfile is not sys.stdout:
64+
logging.warning(
65+
f"outfile ({args.outfile.name}) ignored with split_verilog enabled"
66+
)
67+
68+
with open(args.infile, "r") as f_in:
69+
mlir_to_verilog(f_in, args.outfile, opts)
70+
71+
72+
if __name__ == "__main__":
73+
exit(main())
74+

0 commit comments

Comments
 (0)