Skip to content

Commit

Permalink
eDSL: combinational components (#2048)
Browse files Browse the repository at this point in the history
* Attempt at CombComponent in ast

* Attempt to add comb comp to builder

* Test comb groups with tuplify

* Testing for comb comp

* Stray assert

* Fix slice helper, use correctly

* Use HI instead of 1

* Clean up assertion message

* Use cat instead of manually padding

* Document cat
  • Loading branch information
anshumanmohan authored May 26, 2024
1 parent 2c19c76 commit a1c354f
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 16 deletions.
88 changes: 79 additions & 9 deletions calyx-py/calyx/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ def component(self, name: str, latency=None) -> ComponentBuilder:
self._index[name] = comp_builder
return comp_builder

def comb_component(self, name: str) -> ComponentBuilder:
"""Create a new combinational component builder."""
comp_builder = ComponentBuilder(self, name, is_comb=True)
self.program.components.append(comp_builder.component)
self._index[name] = comp_builder
return comp_builder

def get_component(self, name: str) -> ComponentBuilder:
"""Retrieve a component builder by name."""
comp_builder = self._index.get(name)
Expand All @@ -67,18 +74,30 @@ def __init__(
prog: Builder,
name: str,
latency: Optional[int] = None,
is_comb: bool = False,
):
"""Contructs a new component in the current program."""
self.prog = prog
self.component: ast.Component = ast.Component(
name,
attributes=[],
inputs=[],
outputs=[],
structs=list(),
controls=ast.Empty(),
latency=latency,
self.component: Union[ast.Component, ast.CombComponent] = (
ast.Component(
name,
attributes=[],
inputs=[],
outputs=[],
structs=list(),
controls=ast.Empty(),
latency=latency,
)
if not is_comb
else ast.CombComponent(
name,
attributes=[],
inputs=[],
outputs=[],
structs=list(),
)
)

self.index: Dict[str, Union[GroupBuilder, CellBuilder]] = {}
self.continuous = GroupBuilder(None, self)
self.next_gen_idx = 0
Expand Down Expand Up @@ -122,10 +141,18 @@ def this(self) -> ThisBuilder:
@property
def control(self) -> ControlBuilder:
"""Access the component's control program."""
if isinstance(self.component, ast.CombComponent):
raise AttributeError(
"Combinational components do not have control programs."
)
return ControlBuilder(self.component.controls)

@control.setter
def control(self, builder: Union[ast.Control, ControlBuilder]):
if isinstance(self.component, ast.CombComponent):
raise AttributeError(
"Combinational components do not have control programs."
)
if isinstance(builder, ControlBuilder):
self.component.controls = builder.stmt
else:
Expand Down Expand Up @@ -164,6 +191,8 @@ def try_get_cell(self, name: str) -> CellBuilder:

def get_group(self, name: str) -> GroupBuilder:
"""Retrieve a group builder by name."""
if isinstance(self.component, ast.CombComponent):
raise AttributeError("Combinational components do not have groups.")
out = self.index.get(name)
if out and isinstance(out, GroupBuilder):
return out
Expand All @@ -174,6 +203,8 @@ def get_group(self, name: str) -> GroupBuilder:

def try_get_group(self, name: str) -> GroupBuilder:
"""Tries to get a group builder by name. If cannot find it, return None"""
if isinstance(self.component, ast.CombComponent):
raise AttributeError("Combinational components do not have groups.")
out = self.index.get(name)
if out and isinstance(out, GroupBuilder):
return out
Expand All @@ -182,6 +213,8 @@ def try_get_group(self, name: str) -> GroupBuilder:

def group(self, name: str, static_delay: Optional[int] = None) -> GroupBuilder:
"""Create a new group with the given name and (optional) static delay."""
if isinstance(self.component, ast.CombComponent):
raise AttributeError("Combinational components do not have groups.")
group = ast.Group(ast.CompVar(name), connections=[], static_delay=static_delay)
assert group not in self.component.wires, f"group '{name}' already exists"

Expand All @@ -192,6 +225,10 @@ def group(self, name: str, static_delay: Optional[int] = None) -> GroupBuilder:

def comb_group(self, name: str) -> GroupBuilder:
"""Create a new combinational group with the given name."""
if isinstance(self.component, ast.CombComponent):
raise AttributeError(
"Combinational components do not have combinational groups."
)
group = ast.CombGroup(ast.CompVar(name), connections=[])
assert group not in self.component.wires, f"group '{name}' already exists"

Expand All @@ -201,7 +238,9 @@ def comb_group(self, name: str) -> GroupBuilder:
return builder

def static_group(self, name: str, latency: int) -> GroupBuilder:
"""Create a new combinational group with the given name."""
"""Create a new static group with the given name."""
if isinstance(self.component, ast.CombComponent):
raise AttributeError("Combinational components do not have groups.")
group = ast.StaticGroup(ast.CompVar(name), connections=[], latency=latency)
assert group not in self.component.wires, f"group '{name}' already exists"

Expand Down Expand Up @@ -283,6 +322,20 @@ def slice(
"""Generate a StdSlice cell."""
return self.cell(name, ast.Stdlib.slice(in_width, out_width), False, is_ref)

def bit_slice(
self,
name,
in_width: int,
start: int,
end: int,
out_width: int,
is_ref: bool = False,
) -> CellBuilder:
"""Generate a StdBitSlice cell."""
return self.cell(
name, ast.Stdlib.bit_slice(in_width, start, end, out_width), False, is_ref
)

def const(self, name: str, width: int, value: int) -> CellBuilder:
"""Generate a StdConstant cell."""
return self.cell(name, ast.Stdlib.constant(width, value))
Expand Down Expand Up @@ -382,6 +435,13 @@ def lsh(self, size: int, name: str = None, signed: bool = False) -> CellBuilder:
"""Generate a StdLsh cell."""
return self.binary("lsh", size, name, signed)

def cat(self, left_width: int, right_width: int, name: str = None) -> CellBuilder:
"""Generate a StdCat cell."""
return self.cell(
name or self.generate_name("cat"),
ast.Stdlib.cat(left_width, right_width, left_width + right_width),
)

def logic(self, operation, size: int, name: str = None) -> CellBuilder:
"""Generate a logical operator cell, of the flavor specified in `operation`."""
name = name or self.generate_name(operation)
Expand All @@ -393,11 +453,21 @@ def and_(self, size: int, name: str = None) -> CellBuilder:
name = name or self.generate_name("and")
return self.logic("and", size, name)

def or_(self, size: int, name: str = None) -> CellBuilder:
"""Generate a StdOr cell."""
name = name or self.generate_name("or")
return self.logic("or", size, name)

def not_(self, size: int, name: str = None) -> CellBuilder:
"""Generate a StdNot cell."""
name = name or self.generate_name("not")
return self.logic("not", size, name)

def pad(self, in_width: int, out_width: int, name: str = None) -> CellBuilder:
"""Generate a StdPad cell."""
name = name or self.generate_name("pad")
return self.cell(name, ast.Stdlib.pad(in_width, out_width))

def pipelined_mult(self, name: str) -> CellBuilder:
"""Generate a pipelined multiplier."""
self.prog.import_("primitives/pipelined.futil")
Expand Down
77 changes: 73 additions & 4 deletions calyx-py/calyx/py_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def doc(self) -> str:
@dataclass
class Component:
name: str
attributes : list[Attribute]
attributes: list[Attribute]
inputs: list[PortDef]
outputs: list[PortDef]
wires: list[Structure]
Expand Down Expand Up @@ -91,26 +91,87 @@ def doc(self) -> str:
latency_annotation = (
f"static<{self.latency}> " if self.latency is not None else ""
)
attribute_annotation = f"<{', '.join([f'{a.doc()}' for a in self.attributes])}>" if self.attributes else ""
attribute_annotation = (
f"<{', '.join([f'{a.doc()}' for a in self.attributes])}>"
if self.attributes
else ""
)
signature = f"{latency_annotation}component {self.name}{attribute_annotation}({ins}) -> ({outs})"
cells = block("cells", [c.doc() for c in self.cells])
wires = block("wires", [w.doc() for w in self.wires])
controls = block("control", [self.controls.doc()])
return block(signature, [cells, wires, controls])


#Attribute
# CombComponent
@dataclass
class CombComponent:
"""Like a Component, but with no latency and no control."""

name: str
attributes: list[Attribute]
inputs: list[PortDef]
outputs: list[PortDef]
wires: list[Structure]
cells: list[Cell]

def __init__(
self,
name: str,
inputs: list[PortDef],
outputs: list[PortDef],
structs: list[Structure],
attributes: Optional[list[CompAttribute]] = None,
):
self.name = name
self.attributes = attributes
self.inputs = inputs
self.outputs = outputs

# Partition cells and wires.
def is_cell(x):
return isinstance(x, Cell)

self.cells = [s for s in structs if is_cell(s)]
self.wires = [s for s in structs if not is_cell(s)]

def get_cell(self, name: str) -> Cell:
for cell in self.cells:
if cell.id.name == name:
return cell
raise Exception(
f"Cell `{name}' not found in component {self.name}. Currently defined cells: {[c.id.name for c in self.cells]}"
)

def doc(self) -> str:
ins = ", ".join([s.doc() for s in self.inputs])
outs = ", ".join([s.doc() for s in self.outputs])
attribute_annotation = (
f"<{', '.join([f'{a.doc()}' for a in self.attributes])}>"
if self.attributes
else ""
)
signature = (
f"comb component {self.name}{attribute_annotation}({ins}) -> ({outs})"
)
cells = block("cells", [c.doc() for c in self.cells])
wires = block("wires", [w.doc() for w in self.wires])
return block(signature, [cells, wires])


# Attribute
@dataclass
class Attribute(Emittable):
pass


@dataclass
class CompAttribute(Attribute):
name: str
value: int

def doc(self) -> str:
return f"\"{self.name}\"={self.value}"
return f'"{self.name}"={self.value}'


# Ports
Expand Down Expand Up @@ -535,6 +596,14 @@ def op(op: str, bitwidth: int, signed: bool):
def slice(in_: int, out: int):
return CompInst("std_slice", [in_, out])

@staticmethod
def bit_slice(in_: int, start_idx: int, end_idx: int, out: int):
return CompInst("std_bit_slice", [in_, start_idx, end_idx, out])

@staticmethod
def cat(left_width: int, right_width: int, out_width: int):
return CompInst("std_cat", [left_width, right_width, out_width])

@staticmethod
def pad(in_: int, out: int):
return CompInst("std_pad", [in_, out])
Expand Down
32 changes: 32 additions & 0 deletions calyx-py/test/correctness/tuple.data
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"mem1": {
"data": [
0
],
"format": {
"is_signed": false,
"numeric_type": "bitnum",
"width": 64
}
},
"mem2": {
"data": [
0
],
"format": {
"is_signed": false,
"numeric_type": "bitnum",
"width": 32
}
},
"mem3": {
"data": [
0
],
"format": {
"is_signed": false,
"numeric_type": "bitnum",
"width": 32
}
}
}
11 changes: 11 additions & 0 deletions calyx-py/test/correctness/tuple.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"mem1": [
17179869186
],
"mem2": [
4
],
"mem3": [
2
]
}
Loading

0 comments on commit a1c354f

Please sign in to comment.