diff --git a/calyx-py/calyx/builder.py b/calyx-py/calyx/builder.py index 5d274265e5..bb28a7511a 100644 --- a/calyx-py/calyx/builder.py +++ b/calyx-py/calyx/builder.py @@ -208,6 +208,28 @@ def control(self, builder: Union[ast.Control, ControlBuilder]): else: self.component.controls = builder + # NOTE: Could also be a GroupBuilder + Controllable = Union[ast.Control, str, ast.Group, list, set, ast.Empty, None] + + def case( + self, signal: ExprBuilder, cases: Dict[int, Controllable], signed=False + ) -> None: + """Add the required cells, wiring, and `if` statements to enable `case` + like semantics in the component. Does not support `default` cases. + Branches are implemented via mutually exclusive `if` statements in the + component's `control` block.""" + width = self.infer_width(signal) + ifs = [] + for branch, controllable in cases.items(): + std_eq = self.eq(width, f"{signal.name}_eq_{branch}", signed) + + with self.continuous: + std_eq.left = signal + std_eq.right = const(width, branch) + ifs.append(if_(std_eq["out"], controllable)) + + return par(*ifs) + def port_width(self, port: ExprBuilder) -> int: """Get the width of an expression, which may be a port of this component.""" name = ExprBuilder.unwrap(port).item.id.name diff --git a/calyx-py/calyx/pifo_tree_oracle.py b/calyx-py/calyx/pifo_tree_oracle.py index 17d66f2edb..3ad3130111 100644 --- a/calyx-py/calyx/pifo_tree_oracle.py +++ b/calyx-py/calyx/pifo_tree_oracle.py @@ -6,7 +6,6 @@ if __name__ == "__main__": - max_cmds, len = int(sys.argv[1]), int(sys.argv[2]) keepgoing = "--keepgoing" in sys.argv commands, values, _ = queue_util.parse_json() diff --git a/calyx-py/calyx/py_ast.py b/calyx-py/calyx/py_ast.py index f4cc6f9e6b..6f0d661d29 100644 --- a/calyx-py/calyx/py_ast.py +++ b/calyx-py/calyx/py_ast.py @@ -176,11 +176,13 @@ class CompAttribute(Attribute): def doc(self) -> str: return f'"{self.name}"={self.value}' + @dataclass class PortAttribute(Attribute): name: str value: Optional[int] = None + @dataclass class PortAttribute(Attribute): name: str @@ -204,6 +206,9 @@ class CompPort(Port): def doc(self) -> str: return f"{self.id.doc()}.{self.name}" + def get_name(self) -> str: + return f"{self.id.doc()}_{self.name}" + @dataclass class ThisPort(Port): @@ -212,6 +217,9 @@ class ThisPort(Port): def doc(self) -> str: return self.id.doc() + def get_name(self) -> str: + return self.id.get_name() + @dataclass class HolePort(Port): @@ -221,6 +229,9 @@ class HolePort(Port): def doc(self) -> str: return f"{self.id.doc()}[{self.name}]" + def get_name(self) -> str: + return self.name + @dataclass class ConstantPort(Port): @@ -244,6 +255,9 @@ def port(self, port: str) -> CompPort: def add_suffix(self, suffix: str) -> CompVar: return CompVar(f"{self.name}{suffix}") + def get_name(self) -> str: + return self.name + @dataclass class PortDef(Emittable): @@ -362,6 +376,10 @@ class Atom(GuardExpr): def doc(self) -> str: return self.item.doc() + @property + def name(self) -> str: + return f"{self.item.get_name()}" + @dataclass class Not(GuardExpr): diff --git a/calyx-py/test/case.expect b/calyx-py/test/case.expect new file mode 100644 index 0000000000..d201c6428f --- /dev/null +++ b/calyx-py/test/case.expect @@ -0,0 +1,28 @@ +import "primitives/core.futil"; +import "primitives/binary_operators.futil"; +component my_comp(in_1: 8) -> (out_1: 16) { + cells { + comp_reg = std_reg(1); + in_1_eq_1 = std_eq(8); + in_1_eq_2 = std_eq(8); + } + wires { + group my_group { + + } + in_1_eq_1.left = in_1; + in_1_eq_1.right = 8'd1; + in_1_eq_2.left = in_1; + in_1_eq_2.right = 8'd2; + } + control { + par { + if in_1_eq_1.out { + my_group; + } + if in_1_eq_2.out { + invoke comp_reg(in=1'd1)(); + } + } + } +} diff --git a/calyx-py/test/case.py b/calyx-py/test/case.py new file mode 100644 index 0000000000..34d313e74e --- /dev/null +++ b/calyx-py/test/case.py @@ -0,0 +1,28 @@ +from calyx.builder import Builder, invoke + +#Creates a component the has a case statement. +def add_case(prog): + # Inputs/Outputs + my_comp = prog.component("my_comp") + comp_reg = my_comp.reg(1, "comp_reg") + in_1 = my_comp.input("in_1", 8) + out_1 = my_comp.output("out_1", 16) + + with my_comp.group("my_group") as my_group: + # Some assignments + my_comp.out_1 = 24 + + my_invoke = invoke(comp_reg, in_in=1) + in_1_comps = my_comp.case(in_1, {1: my_group, 2: my_invoke}) + my_comp.control += in_1_comps + + +def build(): + prog = Builder() + add_case(prog) + return prog.program + + +if __name__ == "__main__": + build().emit() + diff --git a/calyx-py/test/correctness/queues/fifo.py b/calyx-py/test/correctness/queues/fifo.py index 84a84f7d84..ac4401672b 100644 --- a/calyx-py/test/correctness/queues/fifo.py +++ b/calyx-py/test/correctness/queues/fifo.py @@ -40,18 +40,31 @@ def insert_fifo(prog, name, queue_len_factor=QUEUE_LEN_FACTOR, val_width=32): len = fifo.reg(bits_needed(max_queue_len)) # The active length of the FIFO. raise_err = fifo.reg_store(err, 1, "raise_err") # err := 1 - # The user called pop/peek. + # The user called pop. # If the queue is empty, we should raise an error. # Otherwise, we should proceed with the core logic - pop_peek_logic = cb.if_with( + pop_logic = cb.if_with( fifo.eq_use(len.out, 0), raise_err, [ - # `pop` or `peek` has been called, and the queue is not empty. - fifo.mem_load_d1(mem, read.out, ans, "read_payload_from_mem"), + # `pop` has been called, and the queue is not empty. + # Write the answer to the answer register, increment `read`, and decrement `len`. + fifo.mem_load_d1(mem, read.out, ans, "read_payload_from_mem_pop"), + fifo.incr(read), + fifo.decr(len), + ], + ) + + # The user called peek. + # If the queue is empty, we should raise an error. + # Otherwise, we should proceed with the core logic + peek_logic = cb.if_with( + fifo.eq_use(len.out, 0), + raise_err, + [ + # `peek` has been called, and the queue is not empty. # Write the answer to the answer register. - # If the user called pop, increment `read` and decrement `len`. - cb.if_with(fifo.eq_use(cmd, 0), [fifo.incr(read), fifo.decr(len)]), + fifo.mem_load_d1(mem, read.out, ans, "read_payload_from_mem_peek"), ], ) @@ -69,15 +82,16 @@ def insert_fifo(prog, name, queue_len_factor=QUEUE_LEN_FACTOR, val_width=32): ], ) - fifo.control += cb.par( - # Was it a (pop/peek), or a push? We can do those two cases in parallel. - # The logic is shared for pops and peeks, with just a few differences. - # Did the user call pop/peek? - cb.if_with(fifo.lt_use(cmd, 2), pop_peek_logic), - # Did the user call push? - cb.if_with(fifo.eq_use(cmd, 2), push_logic), - # Did the user call an invalid command? - cb.if_with(fifo.eq_use(cmd, 3), raise_err), + # Was it a pop, peek, push, or an invalid command? + # We can do those four cases in parallel. + fifo.control += fifo.case( + cmd, + { + 0: pop_logic, + 1: peek_logic, + 2: push_logic, + 3: raise_err, + }, ) return fifo