Skip to content

Latest commit

 

History

History
639 lines (462 loc) · 23.1 KB

File metadata and controls

639 lines (462 loc) · 23.1 KB

Generated Package

Output Structure

PeakRDL Python generates a python Register Access Layer (RAL) from a System RDL design. The generated python code is contained in a package, structured as shown below. The root_name will be based on the top level address map name with the package was generated

<root_name>
├── lib
├── lib_test
├── sim_lib
├── reg_model
│ └── <root_name>.py
├── sim
│ └── <root_name>.py
└── tests
└── test_<root_name>.py

In the folder structure above:

  • <root_name>.py - This is the register access layer code for the design
  • test_<root_name>.py - This is a set of autogenerated unittests to verify the register access layer (The unit test generation can be skipped, see skip_test_case_generation)
  • lib - This is a package of base classes used by the register access layer (The copy of this can be skipped, see :ref:`skipping-lib-copy`)
  • lib_test - This is a package of base classes autogenerated unit tests (The copy of this can be skipped, see :ref:`skipping-lib-copy`)
  • sim_lib - This is a package of base classes used by the register access layer simulator (The copy of this can be skipped, see :ref:`skipping-lib-copy`)
.. versionchanged:: 2.0.0

    The ``reg_model`` was changed in version 2.0.0 to split it out into multiple modules rather
    than building the whole register model in a single python module. This helps avoid
    excessively large files which helps speed up the generation and loading time.

.. versionchanged:: 3.0.0

    The auto-generated unit tests were changed in version 3.0.0 to split to make use
    of a test library, which significantly reduced the size of the generated code.

Top Level Classes

.. versionchanged:: 2.0.0

    A new class aliases were added to the ``reg_model`` and ``sim`` packages to allow the register
    model and simulator to be imported more easily. See the example below using ``RegModel`` and
    ``Simulator``.

Class Names

.. versionchanged:: 2.0.0

    In order to reduce duplication within the generated model a hashing algortihm is applied to the
    nodes in the design to determine which nodes are unique. This hash is appended to the name
    of all the python classes in the register model

.. versionadded:: 2.1.0

    There are two possible algorithms for the hashing, this is selectable by the user:

    * The builtin python ``hash`` function, this is fast but is a salted hash so changes hashes export to
      export
    * Use the ``SHA256`` hash from the python ``hashlib`` standard library, this may slow down the export
      of large register models but will be consistent, therefore is useful if the resultant code is being
      checked into a version control system (such as GIT) and the differences are being reviewed

    .. tip::

        Use the default builtin ``hash`` option unless you need to use the alternative



Running the Unit Tests

There are many ways to run Python Unit tests. A good place to start is the unittest module included in the Python standard installation.

Callbacks

The Register Access Layer will typically interfaced to a driver that allows accesses the chip.

Tip

The simulator generated with the register model can be used as an alternative to a hardware connection

In order to operate the register access layer typically requires the following:

  • A callback for a single register write, this not required if there is no writable register in the register access layer
  • A callback for a single register read, this not required if there is no writable register in the register access layer

In addition the register access layer can make use of block operations where a block of the address space is read in a single transaction. Not all drivers support these

The examples of these two methods are included within the generated register access layer package so that it can be used from the console:

def read_addr_space(addr: int, width: int, accesswidth: int) -> int:
    """
    Callback to simulate the operation of the package, everytime the read is called, it will
    request the user input the value to be read back.

    Args:
        addr: Address to write to
        width: Width of the register in bits
        accesswidth: Minimum access width of the register in bits

    Returns:
        value inputted by the used
    """
    assert isinstance(addr, int)
    assert isinstance(width, int)
    assert isinstance(accesswidth, int)
    return input('value to read from address:0x%X' % addr)

def write_addr_space(addr: int, width: int, accesswidth: int, data: int) -> None:
    """
    Callback to simulate the operation of the package, everytime the write is called, it will
    print out the result.

    Args:
        addr: Address to write to
        width: Width of the register in bits
        accesswidth: Minimum access width of the register in bits
        data: value to be written to the register

    Returns:
        None
    """
    assert isinstance(addr, int)
    assert isinstance(width, int)
    assert isinstance(accesswidth, int)
    assert isinstance(data, int)
    print('write data:0x%X to address:0x%X' % (data, addr))

In a real system these call backs will be connected to a driver.

In addition there is also an option to use async callbacks if the package is built asyncoutput set to True.

Callback Set

The callbacks are passed into the register access layer using either:

  • NormalCallbackSet for standard python function callbacks
  • AsyncCallbackSet for async python function callbacks, these are called from the library using await

Legacy Block Callback and Block Access

.. versionchanged:: 0.9.0

   Previous versions of PeakRDL Python used the python ``array.array`` for efficiently moving blocks
   of data. This was changed in version 0.9.0 in order to accommodate memories which were larger
   than 64 bit wide which could not be supported as the array type only support entries of up to
   64 bit.

   .. warning::
      The developers apologise for making a breaking change, however, not being able to fully the
      systemRDL specification was determined to be a major limitation that needed to be addressed.

      It could have left this as a future compatibility mode before making a breaking change but
      that would just delay the pain it was felt to be better to get as many users onto the new
      API as soon as possible whilst PeakRDL Python is in beta.

   If you really want to just keep on with the array based interface and make only minimal changes
   to existing code, there are two simple steps:

   1. The northbound interfaces that are provided by the generated package expect lists of integers
      rather than array. The old interfaces can be retained by using the ``legacy_block_access``
      build option.
   2. The southbound interfaces into the callbacks again need to use lists for the
      ``read_block_callback`` and ``write_block_callback`` methods. If you want to continue to use
      the old scheme use the following callback classes which are part of the callbacks:
      * ``NormalCallbackSetLegacy`` for standard python function callbacks
      * ``AsyncCallbackSetLegacy`` for async python function callbacks, these are called from the library using ``await``

.. versionchanged:: 3.0.0

    The ``legacy_block_access`` will now default to ``False``

.. versionchanged:: 4.0.0

    The ``legacy_block_access`` was removed, the ``NormalCallbackSetLegacy`` and ``AsyncCallbackSetLegacy`` are no longer supported

Using the Register Access Layer

The register access layer package is intended to integrated into another piece of code. That code could be a simple test script for blinking an LED on a GPIO or it could be a more complex application with a GUI.

The following example is a chip that has a GPIO block. The GPIO block has two registers:

  1. one register that controls the direction of the GPIO pin, at address 0x4
  2. one register that controls driven state of the GPIO pin, at address 0x8

This can be described with the following systemRDL code:

.. literalinclude :: ../example/simulating_callbacks/chip_with_a_GPIO.rdl
   :language: systemrdl

This systemRDL code can be built using the command line tool as follows (assuming it is stored in a file called chip_with_a_GPIO.rdl:

peakrdl python chip_with_a_GPIO.rdl -o python_output
python -m unittest discover -s python_output

Tip

It is always good practice to run the unittests on the generated code.

Once the register access layer has been generated and it can be used. The following example does not actually use a device driver. Instead it chip simulator with a Tkinter GUI, incorporating a RED circle to represent the LED. The chip simulator has read and write methods ( equivalent to those offered by a hardware device driver), in this case they use the simulator provided by PeakRDL Python.

.. literalinclude :: ../example/simulating_callbacks/flashing_the_LED.py
   :language: python

Enumerated Fields

Enumerations are a good practice to implicitly encode that have special meanings which can not be easily understood from the field name. The SystemRDL enumerations are implemented using python

.. literalinclude :: ../example/enumerated_fields/enumerated_fields.rdl
   :language: systemrdl

This systemRDL code can be built using the command line tool as follows (assuming it is stored in a file called enumerated_fields.rdl):

peakrdl python enumerated_fields.rdl -o .

The following example shows the usage of the enumeration

Note

In order to set the value of an enumerated field, using the write() method. The correct enumerated class is needed. This can be retrieved from the field itself with the enum_cls property

.. literalinclude :: ../example/enumerated_fields/demo_enumerated_fields.py
   :language: python

Array Access

SystemRDL supports multi-dimensional arrays, the following example shows an definition with an 1D and 3D array with various methods to access individual elements of the array and use of the iterators to walk through elements in loops

.. literalinclude :: ../example/array_access/array_access.rdl
   :language: systemrdl

This systemRDL code can be built using the command line tool as follows (assuming it is stored in a file called array_access.rdl):

peakrdl python array_access.rdl -o .
.. literalinclude :: ../example/array_access/demo_array_access.py
   :language: python

Optimised Access

Working with individual registers

Each time the read or write method for a register field is accessed the hardware is read and or written (a write to a field will normally require a preceding read). When accessing multiple fields in the same register, it may be desirable to use one of the optimised access methods.

Consider the following example of an GPIO block with 4 GPIO pins (configured in a single register):

.. literalinclude :: ../example/optimised_access/optimised_access.rdl
   :language: systemrdl

In the to configure gpio_0 and gpio_1 whilst leaving the other two unaffected it can be done in two methods:

  • using the write_fields method of the register
  • using the register context manager

Both demonstrated in the following code example:

.. literalinclude :: ../example/optimised_access/demo_optimised_access.py
   :language: python

Working with registers arrays

In many systems it is more efficient to read and write in block operations rather than using individual register access.

Consider the following example of an GPIO block with 8 GPIO pins (configured in a 8 registers):

.. literalinclude :: ../example/optimised_access/optimised_array_access.rdl
   :language: systemrdl

In order to configure all the GPIOs a range of operations are shown with the use of the context managers to make more efficient operations

.. literalinclude :: ../example/optimised_access/demo_optimised_array_access.py
   :language: python

Walking the Structure

The following two example show how to use the generators within the register access layer package to traverse the structure.

Both examples use the following register set which has a number of features to demonstrate the structures

.. literalinclude :: ../example/tranversing_address_map/chip_with_registers.rdl
   :language: systemrdl

This systemRDL code can be built using the command line tool as follows (assuming it is stored in a file called chip_with_registers.rdl):

peakrdl python chip_with_registers.rdl -o chip_with_registers

Traversing without Unrolling Loops

The first example is reading all the readable registers from the register map and writing them into a JSON file. To exploit the capabilities of a JSON file the arrays of registers and register files must be converted to python lists, therefore the loops must not be unrolled, the array objects are accessed directly.

.. literalinclude :: ../example/tranversing_address_map/dumping_register_state_to_json_file.py
   :language: python

This will create a JSON file as follows:

{
    "regfile_array": [
        {
            "single_reg": {
                "first_field": 0,
                "second_field": 0
            },
            "reg_array": [
                {
                    "first_field": 0,
                    "second_field": 0
                },
                {
                    "first_field": 0,
                    "second_field": 0
                },
                {
                    "first_field": 0,
                    "second_field": 0
                },
                {
                    "first_field": 0,
                    "second_field": 0
                }
            ]
        },
        {
            "single_reg": {
                "first_field": 0,
                "second_field": 0
            },
            "reg_array": [
                {
                    "first_field": 0,
                    "second_field": 0
                },
                {
                    "first_field": 0,
                    "second_field": 0
                },
                {
                    "first_field": 0,
                    "second_field": 0
                },
                {
                    "first_field": 0,
                    "second_field": 0
                }
            ]
        }
    ],
    "single_regfile": {
        "single_reg": {
            "first_field": 0,
            "second_field": 0
        },
        "reg_array": [
            {
                "first_field": 0,
                "second_field": 0
            },
            {
                "first_field": 0,
                "second_field": 0
            },
            {
                "first_field": 0,
                "second_field": 0
            },
            {
                "first_field": 0,
                "second_field": 0
            }
        ]
    }
}

Traversing without Unrolling Loops

The second example is setting every register in the address map back to its default values. In this case the loops are unrolled to conveniently access all the register without needing to worry if they are in an array or not.

.. literalinclude :: ../example/tranversing_address_map/reseting_registers.py
   :language: python

Exposing User Defined Properties

SystemRDL allows properties to be added to any component (Field, Memory, Register, Register File, Address Map), so called User Defined Properties (UDP).

There are two methods to expose user defined properties:

  • A list of strings to include in the package
  • A Regular Expression which will include any UDP which matches the regular expression

Consider the following systemRDL example with a user defined property: component_usage

.. literalinclude :: ../example/user_defined_properties/user_defined_properties.rdl
   :language: systemrdl

User Defined Properties are not automatically included they must be specified, as shown:

peakrdl python user_defined_properties.rdl -o . --udp component_usage

Alternatively the User Defined Properties can be included with a regular expression. In the following case all UDPs are included, except the ones used by PeakRDL python

peakrdl python user_defined_properties.rdl -o . --udp_regex "^(?!python_hide$)(?!python_name$).+"

Warning

Attempting to use both the list and regular expression approach is not supported and will generate an error

The user defined properties are stored in a udp property of all component in the generated register access and can be accessed as follows:

.. literalinclude :: ../example/user_defined_properties/demo_user_defined_properties.py
   :language: python

.. versionadded:: 2.0.0

    Regular Expression matching for User Defined Properties was added in version 2.0.0

Python Safe Names

The systemRDL structure is converted to a python class structure, there are two concerns:

  • if any systemRDL node name is a python keyname
  • if any systemRDL node name clashes with part of the peakrdl_standard types, for example all register nodes have an address property that would clash with a field of that register called address

consider the following example:

addrmap my_addr_map {

    reg {
        default sw = rw;
        default hw = r;
        field { fieldwidth=1; } in;
    } address;
};

This would create an object attribute address which would clash with an existing property of the my_addr_map object. The register field can not be called in as this is a python keyword. Therefore peakrdl python will use the name field_in in the generated code to avoid the clash. The algorithm for renaming node to avoid name clashes does not need to be known to an end user, the names can be looked up.

User Defined Property

PeakRDL Python recognises a SystemRDL User Defined Propery (UDP) that can be used to force the names used in the generated python code for node. In this case following names will be overridden:

  • name of the register will be overridden_reg_a rather than reg_a
  • the name of the field will be overridden_field_a rather than field_a
.. literalinclude :: ../example/overridden_names/overridden_names.rdl
   :language: systemrdl

Name lookup

When names have been altered (either to avoid a name clash or by the python_inst_name User Defined Property), attributes can be accessed using the get_child_by_system_rdl_name method of any object in the register model. The following example shows both methods to access the field from the example above

.. literalinclude :: ../example/overridden_names/demo_over_ridden_names.py
   :language: python

Hidden Elements

Commonly some parts of the register map want to be hidden from some users, for example register included to reserve space or test functions.

User Defined Property

PeakRDL Python supports a User Defined Property (UDP): python_hide that can be used to hide items that should not appear in the generated python wrappers.

In the following example, python wrapper generated would have the registers:

  • explictly_visible_reg
  • implicitly_visible_reg

However the hidden_reg would not be included in the python wrappers

property python_hide { type = boolean; component = addrmap | regfile | reg | field | mem; };

addrmap my_addr_map {

    reg {
        default sw = rw;
        default hw = r;
        python_hide = true;
        field { fieldwidth=1; } field_a;
    } hidden_reg;

   reg {
        default sw = rw;
        default hw = r;
        python_hide = false;
        field { fieldwidth=1; } field_a;
    } explictly_visible_reg;

   reg {
        default sw = rw;
        default hw = r;
        field { fieldwidth=1; } field_a;
    } implicitly_visible_reg;

};

The python_hide property can be overridden with the show_hidden argument to the peakrdl command line tool or the export method.

Regular Expression

PeakRDL Python supports hiding elements of the based on a regular expression.

Note

The expression uses the python re.match, for example to hide all fields, registers, regfiles, address maps or memories with the name RSVD, the regular expression must match on the full name e.g. (?:[\w_\[\]]+\.)+RSVD

Autoformatting

The generated code is not perfect it often has lots of spare black lines, over time this will improve but the quickest way to resolve these issue is to include an autoformatter post-generation. Previous versions of peakrdl-python included the option to run an autoformatter to clean up the generated code. This had two issues:

  • It created maintenance issues when the autoformatter changed
  • The choice of autoformatter is an individual one, rather than force an autoformatter on people it is better to let people choose their own.

peakrdl-python uses the Black Black in the CI tests to check that the generated code is compatible with an autoformatter.

Simulator

PeakRDL Python also generates an simulator, this can be used to test and develop using the generated package. The simulator is used in a the examples shown earlier in this section. The simulator has the option to attach a callback to the read and write operations of either a register or field. In addition there is a value property that allows access to the register or field content, this allows the contents to be accessed or updated without activating the callbacks, this is intended to allow the simulator to be extended with behaviour that is not fully described by the systemRDL.

Warning

The PeakRDL Python simulator is not intended to replace an RTL simulation of the design. It does not simulate the hardware, it is intended as a simple tool for development and testing of the python wrappers or code that uses them.

Skipping the Library Copy

In some cases it may be desirable to skip the copy of the library from the generated pacakge. This may be useful in development of PeakRDL-python. It can also be used to avoid distributing licensed code.

Warning

If the libraries are not distributed with the package, it is very important to ensure users download the matching version of the PeakRDL-python package to use it