Skip to content

Commit 935ec69

Browse files
authored
Add support for multi-dimensional arrays (#38)
* Adds support for multidimensional packed arrays into both the Python and custom grammars * Deprecates the previous `X * Type` syntax in favour of `Type[X]` * Extends `ArraySpec` and `PackedArray` to support any number of dimensions * Supports inline foreign references in the custom grammar (i.e. `my_type_t : pkg_a::some_type_t[1][2]` works) * Documentation, tests, and examples updated * Custom grammar parser now supports multiple packages appearing in one file
1 parent b7646a9 commit 935ec69

40 files changed

Lines changed: 1090 additions & 399 deletions

docs/syntax/arrays.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
Multi-dimensional arrays are often used to represented dimensionally structured
2+
data. Packtype's syntax allows any type to be arrayed with an arbitrary number
3+
of dimensions and dimension sizes. The base type can be a simple [scalar](scalar.md),
4+
or can reference a more complex type like a [struct](struct.md) or [union](union.md),
5+
you can even reference another multi-dimensional array!
6+
7+
## Example
8+
9+
The Packtype definition can either use a Python dataclass style or the Packtype
10+
custom grammar:
11+
12+
=== "Python (.py)"
13+
14+
```python linenums="1"
15+
import packtype
16+
from packtype import Constant, Scalar
17+
18+
@packtype.package()
19+
class Package1D:
20+
Scalar1D : Scalar[4]
21+
22+
@Package1D.struct()
23+
class Struct1D:
24+
field_a : Scalar[2]
25+
field_b : Scalar[3]
26+
27+
@packtype.package()
28+
class Package3D:
29+
Scalar3D : Package1D.Scalar1D[4][5]
30+
Struct3D : Package1D.Struct1D[3][2]
31+
```
32+
33+
=== "Packtype (.pt)"
34+
35+
```sv linenums="1"
36+
package package_1d {
37+
scalar_1d_t : scalar[4]
38+
39+
struct struct_1d_t {
40+
field_a : scalar[2]
41+
field_b : scalar[3]
42+
}
43+
}
44+
45+
package package_3d {
46+
scalar_3d_t : package_1d::scalar_1d_t[4][5]
47+
struct_3d_t : package_1d::struct_1d_t[3][2]
48+
}
49+
```
50+
51+
As rendered to SystemVerilog
52+
53+
```sv linenums="1"
54+
package package_1d;
55+
56+
typedef logic [3:0] scalar_1d_t;
57+
58+
typedef struct packed {
59+
logic [2:0] field_b;
60+
logic [1:0] field_a;
61+
} struct_1d_t;
62+
63+
endpackage : package_1d
64+
65+
package package_3d;
66+
67+
import package_1d::scalar_1d_t;
68+
import package_1d::struct_1d_t;
69+
70+
typedef scalar_1d_t [4:0][3:0] scalar_3d_t;
71+
typedef struct_1d_t [1:0][2:0] struct_3d_t;
72+
73+
endpackage : package_3d
74+
```
75+
76+
!!! warning
77+
78+
The order of dimensions is _reversed_ when compared to declaring a packed
79+
multi-dimensional array in SystemVerilog. For example `scalar[4][5][6]`
80+
declares a 6x5 array of 4-bit elements, which in SystemVerilog would be
81+
written `logic [5:0][4:0][3:0]`. This is done to make it easier to parse the
82+
syntax, as decisions can be made reading left-to-right.
83+
84+
## Helper Properties and Methods
85+
86+
Struct definitions expose a collection of helper functions for properties related
87+
to the type:
88+
89+
* `<ARRAY>._pt_width` - property that returns the bit width of the entire array;
90+
* `<ARRAY>._pt_pack()` - packs all values contained within the array into a
91+
singular integer value (can also be achieved by casting to an int, e.g.
92+
`int(<ARRAY>)`);
93+
* `<ARRAY>._pt_unpack(packed: int)` - unpacks an integer value into the entries
94+
of the array;
95+
* `len(<ARRAY>)` - returns the size of the outermost dimension of the array;
96+
* `<ARRAY>[X]` - accesses element X within the array, which may return either
97+
an instance of the base type _or_ another packed array depending on the
98+
number of dimensions.

docs/syntax/scalar.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,21 @@ custom grammar:
99

1010
=== "Python (.py)"
1111

12-
```python linenums="1"
13-
import packtype
14-
from packtype import Constant, Scalar
15-
16-
@packtype.package()
17-
class MyPackage:
18-
# Constants
19-
TYPE_A_W : Constant = 29
20-
TYPE_B_W : Constant = 13
21-
22-
# Typedefs
23-
TypeA : Scalar[TYPE_A_W]
24-
TypeB : Scalar[TYPE_B_W]
25-
TypeC : Scalar[7]
26-
```
12+
```python linenums="1"
13+
import packtype
14+
from packtype import Constant, Scalar
15+
16+
@packtype.package()
17+
class MyPackage:
18+
# Constants
19+
TYPE_A_W : Constant = 29
20+
TYPE_B_W : Constant = 13
21+
22+
# Typedefs
23+
TypeA : Scalar[TYPE_A_W]
24+
TypeB : Scalar[TYPE_B_W]
25+
TypeC : Scalar[7]
26+
```
2727

2828
=== "Packtype (.pt)"
2929

examples/arrays/spec.pt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package one_dimension {
2+
scalar_1d : scalar[4]
3+
4+
struct struct_1d {
5+
field_a : scalar[4][3][2]
6+
field_b : scalar[2][3][4]
7+
}
8+
9+
enum enum_1d {
10+
VAL_A: constant
11+
VAL_B: constant
12+
VAL_C: constant
13+
}
14+
15+
union union_1d {
16+
member_a: struct_1d[2][3]
17+
member_b: scalar[4][12][2][3]
18+
}
19+
}
20+
21+
package two_dimension {
22+
scalar_2d : one_dimension::scalar_1d[3]
23+
enum_2d : one_dimension::enum_1d[2]
24+
struct_2d : one_dimension::struct_1d[4]
25+
union_2d : one_dimension::union_1d[2]
26+
}
27+
28+
package three_dimension {
29+
scalar_3d : two_dimension::scalar_2d[2]
30+
enum_3d : two_dimension::enum_2d[4]
31+
struct_3d : two_dimension::struct_2d[5]
32+
union_3d : two_dimension::union_2d[3]
33+
}

examples/arrays/spec.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import packtype
2+
from packtype import Constant, Scalar
3+
4+
5+
@packtype.package()
6+
class OneDimension:
7+
Scalar1D: Scalar[4]
8+
9+
10+
@OneDimension.struct()
11+
class Struct1D:
12+
field_a: Scalar[4][3][2]
13+
field_b: Scalar[2][3][4]
14+
15+
16+
@OneDimension.enum()
17+
class Enum1D:
18+
VAL_A: Constant
19+
VAL_B: Constant
20+
VAL_C: Constant
21+
22+
23+
@OneDimension.union()
24+
class Union1D:
25+
member_a: Struct1D[2][3]
26+
member_b: Scalar[4][12][2][3]
27+
28+
29+
@packtype.package()
30+
class TwoDimension:
31+
Scalar2D: OneDimension.Scalar1D[3]
32+
Enum2D: OneDimension.Enum1D[2]
33+
Struct2D: OneDimension.Struct1D[4]
34+
Union2D: OneDimension.Union1D[2]
35+
36+
37+
@packtype.package()
38+
class ThreeDimension:
39+
Scalar3D: TwoDimension.Scalar2D[2]
40+
Enum3D: TwoDimension.Enum2D[4]
41+
Struct3D: TwoDimension.Struct2D[5]
42+
Union3D: TwoDimension.Union2D[3]

examples/arrays/test.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
3+
# Copyright 2023-2025, Peter Birch, mailto:peter@intuity.io
4+
# SPDX-License-Identifier: Apache-2.0
5+
#
6+
7+
# Credit to Dave Dopson: https://stackoverflow.com/questions/59895/how-can-i-get-the-source-directory-of-a-bash-script-from-within-the-script-itsel
8+
this_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
9+
10+
# Setup PYTHONPATH to get access to packtype
11+
export PYTHONPATH=${this_dir}/../..:$PYTHONPATH
12+
13+
# Invoke packtype on Python syntax
14+
python3 -m packtype --debug code package sv ${this_dir}/out_py ${this_dir}/spec.py
15+
16+
# Invoke packtype on Packtype syntax
17+
python3 -m packtype --debug code package sv ${this_dir}/out_pt ${this_dir}/spec.pt

examples/axi4l_registers/registers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class ResetControl:
4747

4848
@packtype.registers.group()
4949
class ControlGroup:
50-
core_reset: 4 * ResetControl
50+
core_reset: ResetControl[4]
5151

5252

5353
# === Communications ===
@@ -76,4 +76,4 @@ class CommsGroup:
7676
class Control:
7777
device: DeviceGroup
7878
control: ControlGroup
79-
comms: 2 * CommsGroup
79+
comms: CommsGroup[2]

examples/raw_registers/registers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class ResetControl:
4747

4848
@packtype.registers.group()
4949
class ControlGroup:
50-
core_reset: 4 * ResetControl
50+
core_reset: ResetControl[4]
5151

5252

5353
# === Communications ===
@@ -76,4 +76,4 @@ class CommsGroup:
7676
class Control:
7777
device: DeviceGroup
7878
control: ControlGroup
79-
comms: 2 * CommsGroup
79+
comms: CommsGroup[2]

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ nav:
3535
- Packtype: index.md
3636
- Syntax:
3737
- Alias: syntax/alias.md
38+
- Arrays: syntax/arrays.md
3839
- Constants: syntax/constant.md
3940
- Enumerations: syntax/enum.md
4041
- Packages: syntax/package.md

0 commit comments

Comments
 (0)