diff --git a/docs/operators.md b/docs/operators.md index d13dc37e18..ec66e9154b 100644 --- a/docs/operators.md +++ b/docs/operators.md @@ -46,8 +46,8 @@ Language](https://ieeexplore.ieee.org/document/8299595) (page 256, Table 11-1 #### Assignment | Verilog Operator | Magma Operator | Types | Context | Comments | |------------------|----------------| ----- | ------- | -------- | -| `=` | `m.wire`, **TODO (=)** | Any | All | Assignment cannot be overloaded for arbitrary Python variables, so in general we must use `m.wire`. There are plans to add support for assignment to attributes of magma types, such as `reg.I = io.I`. | -| `+=`, `-=`, `/=`, `*=` | `None` | None | All | Again, unsupported due to the lack of support for overloading assignment. May be added in the future for attributes of magma types | +| `=` | `m.wire`, `<=` (can only be used on magma input values) | Any | All | Assignment cannot be overloaded for arbitrary Python variables, so in general we must use `m.wire`. We have added preliminary for assignment to attributes of magma values using the `<=` operator, which may be familiar for Verilog programmers using non-blocking assignments. Example: `reg.I <= io.I`. `<=` is purely syntactic sugar defiend on output values and calls `m.wire` under the hood. | +| `+=`, `-=`, `/=`, `*=` | `None` | None | All | Support is not planned for these operators because magma cannot provide a clean semantics for them. Assignment only works for inputs to circuit instances and outputs of circuit definitions. AugAssign operators imply a value that is used both as an input an output. For example, `inst.a +=1` would imply a is an output that feeds into binary add with 1, while also an input which consumes the result of the binary add. | | `%=` | `None` | None | All | See above | | `&=`, `\|=`, `^=` | `None` | None | All | See above | | `>>=`, `<<=` | `None` | None | All | See above | diff --git a/magma/__init__.py b/magma/__init__.py index c0276817b7..de26926927 100644 --- a/magma/__init__.py +++ b/magma/__init__.py @@ -58,3 +58,4 @@ def set_mantle_target(t): from .backend.util import set_codegen_debug_info from .enum import Enum +import magma.util diff --git a/magma/operators.py b/magma/operators.py index 7334f76d7d..0754a45f51 100644 --- a/magma/operators.py +++ b/magma/operators.py @@ -46,7 +46,8 @@ def wrapped(self, other): "__mul__", "__div__", "__lt__", - "__le__", + # __le__ skipped because it's used for assignment on inputs + # "__le__", "__gt__", "__ge__" ): @@ -87,7 +88,8 @@ def wrapped(self, other): "__mul__", "__div__", "__lt__", - "__le__", + # __le__ skipped because it's used for assignment on inputs + # "__le__", "__gt__", "__ge__" ): @@ -99,7 +101,8 @@ def wrapped(self, other): "__mul__", "__div__", "__lt__", - "__le__", + # __le__ skipped because it's used for assignment on inputs + # "__le__", "__gt__", "__ge__" ): diff --git a/magma/t.py b/magma/t.py index 7f1d94b140..49fe4fdb02 100644 --- a/magma/t.py +++ b/magma/t.py @@ -67,6 +67,12 @@ def debug_name(self): defn_str = str(self.name.inst.defn.name) + "." return f"{defn_str}{inst_str}{str(self)}" + def __le__(self, other): + if self.isinput(): + self.wire(other) + else: + raise TypeError(f"Cannot use <= to assign to output: {self.debug_name} (trying to assign {other.debug_name})") + class Kind(type): def __init__(cls, name, bases, dct): diff --git a/magma/util.py b/magma/util.py new file mode 100644 index 0000000000..35de69414f --- /dev/null +++ b/magma/util.py @@ -0,0 +1,9 @@ +import magma as m + + +def BitOrBits(width): + if width is None: + return m.Bit + if not isinstance(width, int): + raise ValueError(f"Expected width to be None or int, got {width}") + return m.Bits(width) diff --git a/tests/common.py b/tests/common.py new file mode 100644 index 0000000000..559c478591 --- /dev/null +++ b/tests/common.py @@ -0,0 +1,7 @@ +import magma as m + + +def DeclareAnd(width): + T = m.util.BitOrBits(width) + return m.DeclareCircuit(f'And{width}', "I0", m.In(T), "I1", m.In(T), + "O", m.Out(T)) diff --git a/tests/gold/test_assign_operator2_3_coreir.json b/tests/gold/test_assign_operator2_3_coreir.json new file mode 100644 index 0000000000..3b5085f30c --- /dev/null +++ b/tests/gold/test_assign_operator2_3_coreir.json @@ -0,0 +1,32 @@ +{"top":"global.test_assign_operator2_3_coreir", +"namespaces":{ + "global":{ + "modules":{ + "And3":{ + "type":["Record",[ + ["I0",["Array",3,"BitIn"]], + ["I1",["Array",3,"BitIn"]], + ["O",["Array",3,"Bit"]] + ]] + }, + "test_assign_operator2_3_coreir":{ + "type":["Record",[ + ["a",["Array",3,"BitIn"]], + ["b",["Array",3,"BitIn"]], + ["c",["Array",3,"Bit"]] + ]], + "instances":{ + "inst0":{ + "modref":"global.And3" + } + }, + "connections":[ + ["inst0.O","inst0.I0"], + ["self.a","inst0.I1"], + ["self.c","self.b"] + ] + } + } + } +} +} \ No newline at end of file diff --git a/tests/gold/test_assign_operator2_3_verilog.v b/tests/gold/test_assign_operator2_3_verilog.v new file mode 100644 index 0000000000..5d4e3c26ca --- /dev/null +++ b/tests/gold/test_assign_operator2_3_verilog.v @@ -0,0 +1,6 @@ +module test_assign_operator2_3_verilog (input [2:0] a, input [2:0] b, output [2:0] c); +wire [2:0] inst0_O; +And3 inst0 (.I0(inst0_O), .I1(a), .O(inst0_O)); +assign c = b; +endmodule + diff --git a/tests/gold/test_assign_operator2_None_coreir.json b/tests/gold/test_assign_operator2_None_coreir.json new file mode 100644 index 0000000000..4194488a13 --- /dev/null +++ b/tests/gold/test_assign_operator2_None_coreir.json @@ -0,0 +1,32 @@ +{"top":"global.test_assign_operator2_None_coreir", +"namespaces":{ + "global":{ + "modules":{ + "AndNone":{ + "type":["Record",[ + ["I0","BitIn"], + ["I1","BitIn"], + ["O","Bit"] + ]] + }, + "test_assign_operator2_None_coreir":{ + "type":["Record",[ + ["a","BitIn"], + ["b","BitIn"], + ["c","Bit"] + ]], + "instances":{ + "inst0":{ + "modref":"global.AndNone" + } + }, + "connections":[ + ["inst0.O","inst0.I0"], + ["self.a","inst0.I1"], + ["self.c","self.b"] + ] + } + } + } +} +} \ No newline at end of file diff --git a/tests/gold/test_assign_operator2_None_verilog.v b/tests/gold/test_assign_operator2_None_verilog.v new file mode 100644 index 0000000000..1e1e0067ba --- /dev/null +++ b/tests/gold/test_assign_operator2_None_verilog.v @@ -0,0 +1,6 @@ +module test_assign_operator2_None_verilog (input a, input b, output c); +wire inst0_O; +AndNone inst0 (.I0(inst0_O), .I1(a), .O(inst0_O)); +assign c = b; +endmodule + diff --git a/tests/gold/test_assign_operator_3_coreir.json b/tests/gold/test_assign_operator_3_coreir.json new file mode 100644 index 0000000000..7f3b07beec --- /dev/null +++ b/tests/gold/test_assign_operator_3_coreir.json @@ -0,0 +1,32 @@ +{"top":"global.test_assign_operator_3_coreir", +"namespaces":{ + "global":{ + "modules":{ + "And3":{ + "type":["Record",[ + ["I0",["Array",3,"BitIn"]], + ["I1",["Array",3,"BitIn"]], + ["O",["Array",3,"Bit"]] + ]] + }, + "test_assign_operator_3_coreir":{ + "type":["Record",[ + ["a",["Array",3,"BitIn"]], + ["b",["Array",3,"BitIn"]], + ["c",["Array",3,"Bit"]] + ]], + "instances":{ + "inst0":{ + "modref":"global.And3" + } + }, + "connections":[ + ["self.a","inst0.I0"], + ["self.b","inst0.I1"], + ["self.c","inst0.O"] + ] + } + } + } +} +} \ No newline at end of file diff --git a/tests/gold/test_assign_operator_3_verilog.v b/tests/gold/test_assign_operator_3_verilog.v new file mode 100644 index 0000000000..35a1794797 --- /dev/null +++ b/tests/gold/test_assign_operator_3_verilog.v @@ -0,0 +1,6 @@ +module test_assign_operator_3_verilog (input [2:0] a, input [2:0] b, output [2:0] c); +wire [2:0] inst0_O; +And3 inst0 (.I0(a), .I1(b), .O(inst0_O)); +assign c = inst0_O; +endmodule + diff --git a/tests/gold/test_assign_operator_None_coreir.json b/tests/gold/test_assign_operator_None_coreir.json new file mode 100644 index 0000000000..bc55485ebc --- /dev/null +++ b/tests/gold/test_assign_operator_None_coreir.json @@ -0,0 +1,32 @@ +{"top":"global.test_assign_operator_None_coreir", +"namespaces":{ + "global":{ + "modules":{ + "AndNone":{ + "type":["Record",[ + ["I0","BitIn"], + ["I1","BitIn"], + ["O","Bit"] + ]] + }, + "test_assign_operator_None_coreir":{ + "type":["Record",[ + ["a","BitIn"], + ["b","BitIn"], + ["c","Bit"] + ]], + "instances":{ + "inst0":{ + "modref":"global.AndNone" + } + }, + "connections":[ + ["self.a","inst0.I0"], + ["self.b","inst0.I1"], + ["self.c","inst0.O"] + ] + } + } + } +} +} \ No newline at end of file diff --git a/tests/gold/test_assign_operator_None_verilog.v b/tests/gold/test_assign_operator_None_verilog.v new file mode 100644 index 0000000000..25e1e332f6 --- /dev/null +++ b/tests/gold/test_assign_operator_None_verilog.v @@ -0,0 +1,6 @@ +module test_assign_operator_None_verilog (input a, input b, output c); +wire inst0_O; +AndNone inst0 (.I0(a), .I1(b), .O(inst0_O)); +assign c = inst0_O; +endmodule + diff --git a/tests/test_operators.py b/tests/test_operators.py index 583966382c..54a898b29a 100644 --- a/tests/test_operators.py +++ b/tests/test_operators.py @@ -1,5 +1,8 @@ import magma as m from magma.operators import MantleImportError +from common import DeclareAnd +import pytest +from magma.testing import check_files_equal def test_error(): @@ -10,3 +13,71 @@ def test_error(): "Operator should throw an error since mantle is not imported" except MantleImportError: pass + + +@pytest.mark.parametrize("width", [None, 3]) +@pytest.mark.parametrize("output", ["verilog", "coreir"]) +def test_assign(width, output): + T = m.util.BitOrBits(width) + name = f"test_assign_operator_{width}_{output}" + circ = m.DefineCircuit(name, "a", m.In(T), "b", m.In(T), + "c", m.Out(T)) + and2 = DeclareAnd(width)() + and2.I0 <= circ.a + and2.I1 <= circ.b + circ.c <= and2.O + m.EndDefine() + + m.compile(f"build/{name}", circ, output) + suffix = "v" if output == "verilog" else "json" + assert check_files_equal(__file__, f"build/{name}.{suffix}", + f"gold/{name}.{suffix}") + + + +@pytest.mark.parametrize("width", [None, 3]) +@pytest.mark.parametrize("output", ["verilog", "coreir"]) +def test_assign_to_var(width, output): + T = m.util.BitOrBits(width) + name = f"test_assign_operator2_{width}_{output}" + circ = m.DefineCircuit(name, "a", m.In(T), "b", m.In(T), + "c", m.Out(T)) + and2 = DeclareAnd(width)() + c, I0, I1 = and2.I0, and2.I1, circ.c + I0 <= circ.a + I1 <= circ.b + c <= and2.O + m.EndDefine() + + m.compile(f"build/{name}", circ, output) + suffix = "v" if output == "verilog" else "json" + assert check_files_equal(__file__, f"build/{name}.{suffix}", + f"gold/{name}.{suffix}") + + +@pytest.mark.parametrize("width", [None, 3]) +def test_assign_error_0(width): + T = m.util.BitOrBits(width) + name = f"test_assign_operator_{width}" + circ = m.DefineCircuit(name, "a", m.In(T), "b", m.In(T), + "c", m.Out(T)) + and2 = DeclareAnd(width)() + try: + and2.O <= circ.a + assert False, "Should raise type error" + except TypeError as e: + assert str(e) == f"Cannot use <= to assign to output: {and2.O.debug_name} (trying to assign {circ.a.debug_name})" + + +@pytest.mark.parametrize("width", [None, 3]) +def test_assign_error_1(width): + T = m.util.BitOrBits(width) + name = f"test_assign_operator_{width}" + circ = m.DefineCircuit(name, "a", m.In(T), "b", m.In(T), + "c", m.Out(T)) + and2 = DeclareAnd(width)() + try: + circ.a <= and2.O + assert False, "Should raise type error" + except TypeError as e: + assert str(e) == f"Cannot use <= to assign to output: {circ.a.debug_name} (trying to assign {and2.O.debug_name})"