Skip to content

Commit

Permalink
Add ports with attributes to python builder (#2069)
Browse files Browse the repository at this point in the history
* Being work on dynamic AXI wrapper.

Currently, have reduced complexity of channels by not tracking transfer
counts and transaction counts and similar. Changes are a bit haphazard,
so be careful about assuming aything is "done". Perhapes the AR and AW
channels are indeed finished.

TODO: Create address-translate module. In particular look at interface
for mulitpliers as we might only have pipelined version

* add new const_mult to py-builder

* AW AR R and W channels might be done.

Need to make sure write channel interface is correct.
Then need to tie everything together according to diagram.

* Finish(?) read and write controller in builder

TODO: Wire up translator, read controller, write controller.
Then test if this works with current test bench/executor

* begin axi dynamic read controller

* refactor reg calls in dynamix yxi generator

* add import for const-mult in py-builder

* WIP: need to work on axi-seq-mem-comp. Namley, need to be able to add attributes to ports

* add support for port attributes with  method

* refactor to also have outputs iwth attributes

* remove extra files added somehow

* Add attributes to (and rename) add_comp_ports

* Fix some formatting issues, should pass runt tests

* find and replace add_comp_params

* refactor to only have single input/output method. TODO: remove commented out code

* Refactor to remove code duplication in builder

* black formatting

* add runt test for port attributes

* update docs to showcase option to add port attributes

* improved example in walkthrough for port attributes

---------

Co-authored-by: eys29 <[email protected]>
  • Loading branch information
nathanielnrn and eys29 authored May 29, 2024
1 parent ad8a828 commit 6fb893e
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 28 deletions.
92 changes: 74 additions & 18 deletions calyx-py/calyx/builder.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import threading
from typing import Dict, Union, Optional, List
from typing import Dict, Tuple, Union, Optional, List
from dataclasses import dataclass
from . import py_ast as ast

Expand Down Expand Up @@ -110,26 +110,65 @@ def generate_name(self, prefix: str) -> str:
if name not in self.index:
return name

def input(self, name: str, size: int) -> ExprBuilder:
# Attributes are expected to be either just an attribute name or an (attribute name, value) tuple
RawPortAttr = Union[str, Tuple[str, int]]

def input(
self, name: str, size: int, attribute_literals: List[RawPortAttr] = []
) -> ExprBuilder:
"""Declare an input port on the component.
Returns an expression builder for the port.
"""
self.component.inputs.append(ast.PortDef(ast.CompVar(name), size))
return self.this()[name]

def output(self, name: str, size: int) -> ExprBuilder:
return self._port_with_attributes(name, size, True, attribute_literals)

def output(
self, name: str, size: int, attribute_literals: List[RawPortAttr] = []
) -> ExprBuilder:
"""Declare an output port on the component.
Returns an expression builder for the port.
"""
self.component.outputs.append(ast.PortDef(ast.CompVar(name), size))
return self.this()[name]
return self._port_with_attributes(name, size, False, attribute_literals)

def attribute(self, name: str, value: int) -> None:
"""Declare an attribute on the component."""
self.component.attributes.append(ast.CompAttribute(name, value))

def _port_with_attributes(
self,
name: str,
size: int,
is_input: bool,
attribute_literals: List[RawPortAttr],
) -> ExprBuilder:
"""Should not be called directly.
Declare a port on the component with attributes.
Returns an expression builder for the port.
"""

attributes = []
for attr in attribute_literals:
if isinstance(attr, str):
attributes.append(ast.PortAttribute(attr))
elif isinstance(attr, tuple):
attributes.append(ast.PortAttribute(attr[0], attr[1]))
else:
raise ValueError(
f"Attempted to add invalid attribute {attr} to {name}. `attr` should be either a `str` or (`str`, `int) tuple."
)
if is_input:
self.component.inputs.append(
ast.PortDef(ast.CompVar(name), size, attributes)
)
else:
self.component.outputs.append(
ast.PortDef(ast.CompVar(name), size, attributes)
)
return self.this()[name]

def this(self) -> ThisBuilder:
"""Get a handle to the component's `this` cell.
Expand Down Expand Up @@ -463,6 +502,14 @@ def not_(self, size: int, name: str = None) -> CellBuilder:
name = name or self.generate_name("not")
return self.logic("not", size, name)

def const_mult(
self, size: int, const: int, name: Optional[str] = None
) -> CellBuilder:
"""Generate a StdConstMult cell."""
name = name or self.generate_name("const_mult")
self.prog.import_("primitives/binary_operators.futil")
return self.cell(name, ast.Stdlib.const_mult(size, const))

def pad(self, in_width: int, out_width: int, name: str = None) -> CellBuilder:
"""Generate a StdPad cell."""
name = name or self.generate_name("pad")
Expand Down Expand Up @@ -1471,23 +1518,32 @@ def static_seq(*args) -> ast.StaticSeqComp:
return ast.StaticSeqComp([as_control(x) for x in args])


def add_comp_params(comp: ComponentBuilder, input_ports: List, output_ports: List):
"""
Adds `input_ports`/`output_ports` as inputs/outputs to comp.
`input_ports`/`output_ports` should contain an (input_name, input_width) pair.
def add_comp_ports(comp: ComponentBuilder, input_ports: List, output_ports: List):
"""Adds `input_ports`/`output_ports` as inputs/outputs to comp.
`input_ports`/`output_ports` should contain either an (input_name, input_width) pair
or an (input_name, input_width, attributes) triple.
"""
for name, width in input_ports:
comp.input(name, width)
for name, width in output_ports:
comp.output(name, width)

def normalize_ports(ports: List):
for port in ports:
if len(port) == 2:
yield (port[0], port[1], [])
elif len(port) == 3:
yield port[0], port[1], port[2]

for name, width, attributes in normalize_ports(input_ports):
comp.input(name, width, attributes)
for name, width, attributes in normalize_ports(output_ports):
comp.output(name, width, attributes)


def add_read_mem_params(comp: ComponentBuilder, name, data_width, addr_width):
"""
Add parameters to component `comp` if we want to read from a mem named
`name` with address width of `addr_width` and data width of `data_width`.
"""
add_comp_params(
add_comp_ports(
comp,
input_ports=[(f"{name}_read_data", data_width)],
output_ports=[(f"{name}_addr0", addr_width)],
Expand All @@ -1499,7 +1555,7 @@ def add_write_mem_params(comp: ComponentBuilder, name, data_width, addr_width):
Add arguments to component `comp` if we want to write to a mem named
`name` with address width of `addr_width` and data width of `data_width`.
"""
add_comp_params(
add_comp_ports(
comp,
input_ports=[(f"{name}_done", 1)],
output_ports=[
Expand All @@ -1514,7 +1570,7 @@ def add_register_params(comp: ComponentBuilder, name, width):
"""
Add params to component `comp` if we want to use a register named `name`.
"""
add_comp_params(
add_comp_ports(
comp,
input_ports=[(f"{name}_done", 1), (f"{name}_out", width)],
output_ports=[
Expand Down
21 changes: 20 additions & 1 deletion calyx-py/calyx/py_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,15 @@ def doc(self) -> str:
return f'"{self.name}"={self.value}'


@dataclass
class PortAttribute(Attribute):
name: str
value: Optional[int] = None

def doc(self) -> str:
return f"@{self.name}" if self.value is None else f"@{self.name}({self.value})"


# Ports
@dataclass
class Port(Emittable):
Expand Down Expand Up @@ -233,9 +242,15 @@ def add_suffix(self, suffix: str) -> CompVar:
class PortDef(Emittable):
id: CompVar
width: int
attributes: List[PortAttribute] = field(default_factory=list)

def doc(self) -> str:
return f"{self.id.doc()}: {self.width}"
attributes = (
""
if len(self.attributes) == 0
else (" ".join([x.doc() for x in self.attributes]) + " ")
)
return f"{attributes}{self.id.doc()}: {self.width}"


# Structure
Expand Down Expand Up @@ -592,6 +607,10 @@ def constant(bitwidth: int, value: int):
def op(op: str, bitwidth: int, signed: bool):
return CompInst(f'std_{"s" if signed else ""}{op}', [bitwidth])

@staticmethod
def const_mult(bitwidth: int, const: int):
return CompInst("std_const_mult", [bitwidth, const])

@staticmethod
def slice(in_: int, out: int):
return CompInst("std_slice", [in_, out])
Expand Down
12 changes: 12 additions & 0 deletions calyx-py/test/port_attributes.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import "primitives/core.futil";
component foo(in_1: 1, @data in_2: 2, @data @write_together(1) in_3: 2) -> (out_1: 1, @data out_2: 1, @data @done(1) out_3: 1) {
cells {

}
wires {

}
control {

}
}
24 changes: 24 additions & 0 deletions calyx-py/test/port_attributes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import calyx.builder as cb


def insert_foo_component(prog):
comp = prog.component("foo")

foo_inputs = [
("in_1", 1),
("in_2", 2, ["data"]),
("in_3", 2, ["data", ("write_together", 1)])
]

cb.add_comp_ports(comp, foo_inputs, [])

comp.output("out_1", 1)
# ANCHOR: port_attributes
comp.output("out_2", 1, ["data"])
comp.output("out_3", 1, ["data", ("done", 1)])
# ANCHOR_END: port_attributes

if __name__ == "__main__":
prog = cb.Builder()
insert_foo_component(prog)
prog.program.emit()
12 changes: 11 additions & 1 deletion docs/builder/walkthrough.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ We specify the names and bitwidths of any ports that we want a component to have
Observe that we have saved handles to the input ports by assigning them to Python variables, but have not done the same with the output port.
We will show shortly how to create a handle to a port after its definition.

It is also possible to create ports that have [attributes]. Attributes can either
be described as a string or a (string, value) tuple.
A string such as `"data"`, which will create an `@data` attribute.
A (string, value) tuple such as `("done", 1)` will create an `@done(1)` attribute.

```python
{{#include ../../calyx-py/test/port_attributes.py:port_attributes}}
```

## Cells

We add cells to the component as follows.
Expand Down Expand Up @@ -442,4 +451,5 @@ comp = prog.component("adder")
[while]: ../lang/ref.md#while
[helloworld]: calyx-py.md
[walkthrough]: https://github.com/calyxir/calyx/blob/master/calyx-py/test/walkthrough.py
[walkthrough_expect]: https://github.com/calyxir/calyx/blob/master/calyx-py/test/walkthrough.expect
[walkthrough_expect]: https://github.com/calyxir/calyx/blob/master/calyx-py/test/walkthrough.expect
[attributes]: https://docs.calyxir.org/lang/attributes.html
2 changes: 1 addition & 1 deletion frontends/systolic-lang/gen_array_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def add_systolic_output_params(comp: cb.ComponentBuilder, row_num, addr_width):
The ouptut arguments alllow the systolic array to expose its outputs for `row_num`
without writing to memory (e.g., r0_valid, r0_value, r0_idx).
"""
cb.add_comp_params(
cb.add_comp_ports(
comp,
input_ports=[],
output_ports=[
Expand Down
2 changes: 1 addition & 1 deletion frontends/systolic-lang/gen_post_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def add_systolic_input_params(comp: cb.ComponentBuilder, row_num, addr_width):
Add ports "r_{row_num}_valid", "r_{row_num}_value", "r_{row_num}_idx" to comp.
These ports are meant to read from the systolic array output.
"""
cb.add_comp_params(
cb.add_comp_ports(
comp,
input_ports=[
(NAME_SCHEME["systolic valid signal"].format(row_num=row_num), 1),
Expand Down
12 changes: 6 additions & 6 deletions yxi/axi-calyx/axi-generator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from calyx.builder import (
Builder,
add_comp_params,
add_comp_ports,
invoke,
while_with,
par,
Expand Down Expand Up @@ -64,7 +64,7 @@ def _add_m_to_s_address_channel(prog, mem, prefix: Literal["AW", "AR"]):
(f"{x}BURST", 2), # for XRT should be tied to 2'b01 for WRAP burst
(f"{x}PROT", 3), # tied to be priviliged, nonsecure, data access request
]
add_comp_params(m_to_s_address_channel, channel_inputs, channel_outputs)
add_comp_ports(m_to_s_address_channel, channel_inputs, channel_outputs)

# Cells
xvalid = m_to_s_address_channel.reg(1, f"{lc_x}valid")
Expand Down Expand Up @@ -165,7 +165,7 @@ def add_read_channel(prog, mem):
("RRESP", 2),
]
channel_outputs = [("RREADY", 1)]
add_comp_params(read_channel, channel_inputs, channel_outputs)
add_comp_ports(read_channel, channel_inputs, channel_outputs)

# Cells

Expand Down Expand Up @@ -284,7 +284,7 @@ def add_write_channel(prog, mem):
("WLAST", 1),
("WDATA", mem[width_key]),
]
add_comp_params(write_channel, channel_inputs, channel_outputs)
add_comp_ports(write_channel, channel_inputs, channel_outputs)

# Cells
# We assume idx_size is exactly clog2(len). See comment in #1751
Expand Down Expand Up @@ -406,7 +406,7 @@ def add_bresp_channel(prog, mem):
# No BRESP because it is ignored, i.e we assume it is tied OKAY
channel_inputs = [("ARESETn", 1), ("BVALID", 1)]
channel_outputs = [("BREADY", 1)]
add_comp_params(bresp_channel, channel_inputs, channel_outputs)
add_comp_ports(bresp_channel, channel_inputs, channel_outputs)

# Cells
bready = bresp_channel.reg(1, "bready")
Expand Down Expand Up @@ -500,7 +500,7 @@ def add_main_comp(prog, mems):
(f"{mem_name}_BID", 1),
]

add_comp_params(wrapper_comp, wrapper_inputs, wrapper_outputs)
add_comp_ports(wrapper_comp, wrapper_inputs, wrapper_outputs)

# Cells
# Read stuff
Expand Down

0 comments on commit 6fb893e

Please sign in to comment.