Packtype is a Python framework for describing packed data structures for use in low-level hardware design, verification, and firmware development. From this single specification, equivalent implementations for different languages can be generated (for example SystemVerilog).
The support matrix below shows the current support:
Language | Constants | Enums | Structs | Unions | Packages |
---|---|---|---|---|---|
Python | Yes | Yes | Yes | Yes | Yes |
SystemVerilog | Yes | Yes | Yes | Yes | Yes |
NOTE Support for C and C++ is currently missing from version 2, but was present in version 1 - this regression will be addressed in a future revision.
The easiest way to install Packtype is to use PyPI:
$> python3 -m pip install packtype
Alternatively, you can install the latest version directly from this repository:
$> python3 -m pip install git+git://github.com/Intuity/packtype
Packtype provides the packtype
command line utility which can be used in
conjuction with a specification (detailed below) to generate different types of
outputs.
A Packtype specification can be rendered into another programming language, for example SystemVerilog:
# Render SystemVerilog version of the specification
# python -m packtype <SPEC> code <LANG> <OUTDIR>
$> python -m packtype examples/structs/spec.py code sv ./output_dir
The positional arguments are:
SPEC
- path to the Packtype specification file to process;code
- command to Packtype to render specification to code;LANG
- language to render (sv
selects SystemVerilog);OUTDIR
- path to the output directory to write rendered files.
Then options are available to modify the behaviour:
--debug
- generate debug messages as the tool runs.--help
- show the help prompt.
A Packtype struct
can also be rendered to an SVG using the svg
command:
# Render an SVG version of a struct
# python3 -m packtype <SPEC> svg <STRUCT> <OUTPUT>
$> python3 -m packtype examples/union/spec.py svg Encodings.Sub test.svg
The positional arguments are:
SPEC
- path to the Packtype specification file to render;svg
- command to Packtype to render specification to an SVG;STRUCT
- hierarchy of the structure to render (e.g.<PACKAGE>.<STRUCT>
);OUTPUT
- file to write the SVG to, if omitted it will print the output to STDOUT.
A number of examples are provided in the examples
folder - each of these can be run by executing the test.sh
file within the directory.
Packtype specifications use a decorators and classes to declare the different data structures. Once a specification has been written, the Packtype utility can be used to generate code for different languages.
Packages are signified using the @packtype.package()
decorator, this declares
a root object onto which constants, typedefs, enums, structs, and unions can be
bound.
import packtype
@packtype.package()
class SomeProtocolPkg:
"""Contains definitions for some protocol"""
...
Numeric constants can be attached to the root of a package to share fixed values between different parts of implementation, verification, and firmware.
import packtype
from packtype import Constant
@packtype.package()
class SomeProtocolPkg:
"""Contains definitions for some protocol"""
ADDRESS_WIDTH : Constant = 16
DATA_WIDTH : Constant = 32
SIZE_WIDTH : Constant = 8
Simple bitvector types can be declared within a package using the Scalar
type,
the parameterisation determines the bit-width of the structure:
import packtype
from packtype import Constant, Scalar
@packtype.package()
class SomeProtocolPkg:
"""Contains definitions for some protocol"""
# Constants
ADDRESS_WIDTH : Constant = 16
# Simple Types
Address : Scalar[ADDRESS_WIDTH]
Enumerations are declared using the @<PKG>.enum()
decorator and can accept the
following two attributes:
-
width
- sets a fixed bit-width of the enumeration, this must be a positive integer; -
mode
- sets how entries of the enumeration are assigned values, the supported modes are:EnumMode.INDEXED
- Each value increments by one, starting at zero.EnumMode.ONE_HOT
- Each value sets exactly one bit position high (e.g.1
,2
,4
,8
).EnumMode.GRAY
- Values form a Gray code where only one bit flips between any two consecutive values.
Enumerations can either be declared with explicit or automatically incrementing values, or a mix of the two.
import packtype
from packtype import EnumMode
@packtype.package()
class DecoderPkg:
...
@DecoderPkg.enum(width=12)
class MessageType:
""" Different message types with explicit values """
PINGPONG : Constant = 0x123
SHUTDOWN : Constant = 0x439
POWERUP : Constant = 0x752
@DecoderPkg.enum(mode=Enum.GRAY)
class DecoderState:
""" Gray-coded states of the decoder FSM """
DISABLED : Constant
IDLE : Constant
HEADER : Constant
PAYLOAD : Constant
Structs are declared using the @<PKG>.struct()
decorator and can contain any
number of fields that are simple scalar values, enumerations, or nested structs
or unions. The decorator supports the following attributes:
width
- sets a fixed bit-width of the struct, this must be a positive integer;packing
- determines the order in which declared fields are placed in the packed version:Packing.FROM_LSB
- place fields starting at the least-significant bit;Packing.FROM_MSB
- place fields starting from the most-significant bit.
import packtype
from packtype import Scalar
@packtype.package()
class DecoderPkg:
...
@DecoderPkg.struct(width=32)
class MessageHeader:
""" Common header for all messages """
target_id : Scalar[8]
msg_type : MessageType
@DecoderPkg.struct() # Width calculated from field sizes
class PingPongPayload:
""" Payload of a ping-pong keepalive message """
source_id : Scalar[ 8]
is_pong : Scalar[ 1]
ping_value : Scalar[15]
timestamp : Scalar[ 8]
@DecoderPkg.struct()
class PingPongMessage:
""" Full message including header and payload """
header : MessageHeader
payload : PingPongPayload
By default, fields are packed into data structures from the LSB - but this can be reversed to pack from the MSB by providing the packing=Packing.FROM_MSB
argument to the decorator. For example:
import packtype
from packtype import Packing, Scalar
@packtype.package()
class DecoderPkg:
...
@DecoderPkg.struct(package=MyPackage, packing=Packing.FROM_MSB, width=32)
class PingPongPayload:
""" Payload of a ping-pong keepalive message """
source_id : Scalar[ 8]
is_pong : Scalar[ 1]
ping_value : Scalar[15]
timestamp : Scalar[ 8]
Unions allow different data structures to form different projections over the same raw bits - this is especially useful in protocol decoders where a bus may carry different formats and structures of data in different cycles. All components of a union must be of the same size, otherwise the tool will raise an error.
import packtype
from packtype import Scalar
@packtype.package()
class DecoderPkg:
...
@DecoderPkg.union()
class MessageBus:
""" Union of the different message phases of the bus """
raw : Scalar[32]
header : MessageHeader
ping_pong : PingPongPayload
Using Packtype objects within a testbench can cause significant overhead if many pack and unpack operations are being performed. To help diagnose performance issues, you can enable profiling which counts the number of each type of object that are created during a run:
from packtype.base import Base
def my_test_function():
Base._pt_enable_profiling(limit=10)
The limit
keyword will filter out any objects from the report that have less
than this number of creations by the end of the run, this can be useful to
filter out noise of many small unique objects.