Skip to content

Commit

Permalink
[calyx-py] Add case statement support for ComponentBuilders (#2168)
Browse files Browse the repository at this point in the history
* add case statement support for ComponentBuilders

* add runt tests

* change case to return a par block instead of automatically adding to control of a component

* Queues: use case statements in FIFO control (#2171)

* Prepare fifo for case idiom

* Attempt with . Failing due to empty control

* Delete calyx-py/test/correctness/queues/fifo.futil

* improve naming of equalities and add name functions to some classes in builder

* runt tests

* apply black

* remove extra print

* runt tests

* print out signals when possible for case statement signals

* Temporarily checking in fifo.futil to discuss

* Remove accidental double par

* Un-check-in futil file

---------

Co-authored-by: Anshuman Mohan <[email protected]>
Co-authored-by: Anshuman Mohan <[email protected]>
  • Loading branch information
3 people authored Jun 21, 2024
1 parent cd0791c commit f823565
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 16 deletions.
22 changes: 22 additions & 0 deletions calyx-py/calyx/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion calyx-py/calyx/pifo_tree_oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
18 changes: 18 additions & 0 deletions calyx-py/calyx/py_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
28 changes: 28 additions & 0 deletions calyx-py/test/case.expect
Original file line number Diff line number Diff line change
@@ -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)();
}
}
}
}
28 changes: 28 additions & 0 deletions calyx-py/test/case.py
Original file line number Diff line number Diff line change
@@ -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()

44 changes: 29 additions & 15 deletions calyx-py/test/correctness/queues/fifo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
],
)

Expand All @@ -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
Expand Down

0 comments on commit f823565

Please sign in to comment.