Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/source/description_files.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ ports:
- [{ip_name/hierarchy_name, port_name}]
interfaces:
in:
- {ext_interface_name}
- name: {ext_interface_name}
bound: [[dim0_high, dim1_low], ..., [dimN_high, dimN, low]]
# note that `inout:` is invalid in the interfaces section

hierarchies:
Expand Down Expand Up @@ -278,7 +279,7 @@ Each signal (for both port and interface definitions) can be specified in one of
The signal is defined by a YAML object, with the following properties:

- `name` (optional) - the name of the signal.
- `bound` (optional) - the bounds of the bit range, determining the width.
- `bound` (optional) - the bounds of the signal. The signal can be multidimensional.
- `slice` (optional) - the bounds of the slice bit range (only applicable to interface definitions).
- `default` (optional) - default value to assign to this port if nothing is connected to it, applicable only to input ports.
- `type` (optional) - the name of the type (defined in the `types` section) used for this signal
Expand Down
20 changes: 20 additions & 0 deletions tests/tests_ir/backend/test_sv.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from topwrap.backend.sv.backend import GeneratorNotImplementedError, SystemVerilogBackend
from topwrap.backend.sv.common import serialize_select, serialize_type, sv_varname
from topwrap.backend.sv.design import Design, _SystemVerilogDesignData
from topwrap.frontend.yaml.design import DesignDescriptionFrontend
from topwrap.interconnects.wishbone_rr import WishboneInterconnect, WishboneRRParams
from topwrap.model.connections import (
ConstantConnection,
Expand Down Expand Up @@ -374,6 +375,25 @@ def test_external_inv_port_conn(self, svdb: _SystemVerilogDesignData, inv_des: D


class TestSystemVerilogBackend:
def test_multidimensional_port_from_yaml(self):
design_yaml = """
name: top
external:
ports:
in:
- name: in_arr
bound: [[1, 0], [7, 0], [3, 0]]
"""

design, _ = DesignDescriptionFrontend().parse_str(design_yaml)
backend = SystemVerilogBackend(desc_comms=False)
[output] = backend.serialize(backend.represent(design.parent), combine=True)

assert (
"module top (\n input logic [1:0][7:0][3:0] in_arr\n);\n\nendmodule"
== output.content
)

def test_repr_empty_package(self):
back = SystemVerilogBackend(desc_comms=False)

Expand Down
80 changes: 80 additions & 0 deletions tests/tests_ir/backend/test_yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
Bits,
BitStruct,
Dimensions,
LogicArray,
LogicBitSelect,
LogicFieldSelect,
LogicSelect,
Expand Down Expand Up @@ -266,6 +267,49 @@ def test_complex_port(self):
},
}

def test_multidimensional_port(self):
ty = Bits(
dimensions=[
Dimensions(upper=ElaboratableValue(1), lower=ElaboratableValue(0)),
Dimensions(upper=ElaboratableValue(7), lower=ElaboratableValue(0)),
]
)

top = Module(
id=Identifier(name="top"),
ports=[
Port(
name="foo",
direction=PortDirection.IN,
type=ty,
default_value=ElaboratableValue(4),
),
],
)

backend = IpCoreDescriptionBackend()

out = backend.represent(top)
[out] = backend.serialize(out)
tree = yaml.safe_load(out.content)

assert tree == {
"id": {"name": "top", "library": "libdefault", "vendor": "vendor", "version": "0.1"},
"signals": {
"in": [
{
"name": "foo",
"bound": [["1", "0"], ["7", "0"]],
"default": "4",
},
],
},
}

frontend = IPCoreDescriptionFrontend()
mod = frontend.parse_str(out.content)
_compare_modules(top, mod)

def test_parameters(self):
mod = Module(
id=Identifier(name="top"),
Expand Down Expand Up @@ -688,6 +732,42 @@ def test_config_output(self):
expected_obj: dict[str, str] = {name: h.to_str() for name, h in rep.items()}
assert repo_dict == expected_obj

def test_multidimensional_top_level_ports_roundtrip(self):
design_yaml = """
name: top
external:
ports:
in:
- name: in_arr
bound: [[1, 0], [7, 0], [3, 0]]
out:
- name: out_vec
bound: [[15, 0]]
"""

front = DesignDescriptionFrontend()
orig_des, _ = front.parse_str(design_yaml)

back = DesignDescriptionBackend()
out = back.represent(orig_des.parent)
[out] = back.serialize(out)

new_des, _ = front.parse_str(out.content)

in_arr = new_des.parent.ports.find_by_name_or_error("in_arr")
out_vec = new_des.parent.ports.find_by_name_or_error("out_vec")

assert isinstance(in_arr.type, LogicArray)
assert in_arr.type.dimensions == [
Dimensions(upper=ElaboratableValue(1), lower=ElaboratableValue(0)),
Dimensions(upper=ElaboratableValue(7), lower=ElaboratableValue(0)),
Dimensions(upper=ElaboratableValue(3), lower=ElaboratableValue(0)),
]
assert isinstance(out_vec.type, LogicArray)
assert out_vec.type.dimensions == [
Dimensions(upper=ElaboratableValue(15), lower=ElaboratableValue(0))
]


class TestDesignPositionsBackend:
def test_positions(self):
Expand Down
60 changes: 60 additions & 0 deletions tests/tests_ir/frontend/test_yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,68 @@ def test_config_field(self):
assert all(type(out.config.repositories[k]) is type(rep[k]) for k in keys)
assert all(out.config.repositories[k].to_str() == rep[k].to_str() for k in keys)

def test_multidimensional_top_level_ports(self):
des = """
name: top
external:
ports:
in:
- name: in_arr
bound:
- [1, 0]
- [7, 0]
- [3, 0]
out:
- name: out_vec
bound:
- [15, 0]
"""

mod, _ = DesignDescriptionFrontend().parse_str(des)

in_arr = mod.parent.ports.find_by_name_or_error("in_arr")
out_vec = mod.parent.ports.find_by_name_or_error("out_vec")

assert isinstance(in_arr.type, LogicArray)
assert in_arr.type.dimensions == [
Dimensions(upper=ElaboratableValue(1), lower=ElaboratableValue(0)),
Dimensions(upper=ElaboratableValue(7), lower=ElaboratableValue(0)),
Dimensions(upper=ElaboratableValue(3), lower=ElaboratableValue(0)),
]
assert isinstance(out_vec.type, LogicArray)
assert out_vec.type.dimensions == [
Dimensions(upper=ElaboratableValue(15), lower=ElaboratableValue(0))
]


class TestIPCoreDescriptionFrontend:
def test_multidimensional_signal(self):
ip = """
id:
name: top
vendor: vendor
library: libdefault
signals:
in:
- name: in_arr
bound:
- [1, 0]
- [7, 0]
- [3, 0]
default: 4
"""

mod = IPCoreDescriptionFrontend().parse_str(ip)
in_arr = mod.ports.find_by_name_or_error("in_arr")

assert isinstance(in_arr.type, LogicArray)
assert in_arr.type.dimensions == [
Dimensions(upper=ElaboratableValue(1), lower=ElaboratableValue(0)),
Dimensions(upper=ElaboratableValue(7), lower=ElaboratableValue(0)),
Dimensions(upper=ElaboratableValue(3), lower=ElaboratableValue(0)),
]
assert in_arr.default_value == ElaboratableValue("4")

def test_parse_on_mem_yaml(self):
ip = Path("examples/ir_examples/interconnect/ips/mem.yaml")
mod = IPCoreDescriptionFrontend().parse_file(ip)
Expand Down
22 changes: 17 additions & 5 deletions topwrap/backend/yaml/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
ConnectionsSection,
DesignDescription,
DesignExternalIntfs,
DesignExternalPortDefinition,
DesignExternalPorts,
DesignExternalSection,
DesignInverterPosition,
Expand Down Expand Up @@ -157,10 +158,13 @@ def _represent_signal(
if slice:
raise ValueError("Trying to slice a single bit")
elif isinstance(type, Bits):
if len(type.dimensions) > 1:
raise ValueError("IP core YAML format only supports one-dimensional bit vectors")

bound = (type.dimensions[0].upper.value, type.dimensions[0].lower.value)
if len(type.dimensions) > 1:
return IPCoreComplexSignal(
name=name,
bound=tuple((d.upper.value, d.lower.value) for d in type.dimensions),
default=default.value if default else None,
)
else:
logger.warning(f"Got unexpected type {type} for signal in IP core YAML backend")

Expand Down Expand Up @@ -586,11 +590,19 @@ def _represent_external_ports(self, mod: Module) -> DesignExternalPorts:
outputs = []
inouts = []

def represent_port(port: Port):
if isinstance(port.type, LogicArray) and isinstance(port.type.item, Bit):
return DesignExternalPortDefinition(
name=port.name,
bound=[(dim.upper.value, dim.lower.value) for dim in port.type.dimensions],
)
return port.name

for port in mod.non_intf_ports():
if port.direction is PortDirection.IN:
inputs.append(port.name)
inputs.append(represent_port(port))
elif port.direction is PortDirection.OUT:
outputs.append(port.name)
outputs.append(represent_port(port))
elif port.direction is PortDirection.INOUT:
# Look for connection that this port is a part of, then from that
# find the module port it's connected to.
Expand Down
18 changes: 15 additions & 3 deletions topwrap/backend/yaml/common/ip_core_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
Tuple,
Type,
Union,
cast,
)

import marshmallow
Expand All @@ -33,12 +34,14 @@
from topwrap.util import get_config, get_interface_by_id

_StrOrInt = Union[str, int]
IPCoreDimension = Tuple[_StrOrInt, _StrOrInt]
IPCoreBounds = Union[IPCoreDimension, Tuple[IPCoreDimension, ...]]


@marshmallow_dataclass.dataclass(frozen=True)
class IPCoreComplexSignal(MarshmallowDataclassExtensions):
name: Optional[str] = ext_field(None)
bound: Optional[tuple[_StrOrInt, _StrOrInt]] = ext_field(None)
bound: Optional[IPCoreBounds] = ext_field(None)
slice: Optional[tuple[_StrOrInt, _StrOrInt]] = ext_field(None)
default: Optional[_StrOrInt] = ext_field(None)
path: Optional[PortSelectorT] = ext_field(None)
Expand All @@ -52,6 +55,9 @@ def _validate(self, self_obj: Dict[str, Any], **kwargs: Any) -> bool:
if self_obj["bound"] is not None and self_obj["type"] is not None:
raise marshmallow.ValidationError("Signal requires either a bound or a type, not both")

if isinstance(self_obj["bound"], tuple) and len(self_obj["bound"]) == 0:
raise marshmallow.ValidationError("Signal dimensions cannot be empty")

return True


Expand Down Expand Up @@ -93,11 +99,17 @@ def from_sig_and_dir(sig: Signal, dir: LegacyPortDirection) -> "IPCorePort":
name = sig.name if sig.name is not None else str(sig.path)
assert name is not None

bounds = sig.bound
if bounds is not None and isinstance(bounds[0], tuple):
bounds = cast(IPCoreDimension, bounds[0])
elif bounds is not None:
bounds = cast(IPCoreDimension, bounds)

return IPCorePort(
name=name,
direction=dir,
upper_bound=sig.bound[0] if sig.bound else 0,
lower_bound=sig.bound[1] if sig.bound else 0,
upper_bound=bounds[0] if bounds else 0,
lower_bound=bounds[1] if bounds else 0,
upper_slice=sig.slice[0] if sig.slice else 0,
lower_slice=sig.slice[1] if sig.slice else 0,
)
Expand Down
Loading
Loading