Guide to Writing UDDF Drivers#
UDDF Driver Model#
The UDDF driver model is centered around two concepts: driver interfaces and driver objects.
In UDDF, a driver interface is a C++ class that defines only pure virtual methods—it contains no data or code. Interfaces provide all communication between a driver and its environment. Driver interfaces come in two flavors:
DDI: interfaces implemented by the driver.
CDI: interfaces provided to the driver (discussed later).
Driver interfaces lack ownership semantics; they act only as views into a given driver object instance.
A driver object represents a concrete driver instance. UDDF finds, probes, and loads these objects as requested by the driver consumer. In UDDF, driver objects implement a singular method to provide driver interfaces. Beyond that, their internal composition is otherwise entirely opaque to UDDF.
In practice, consumers of your driver will request various interfaces, such as an interface to update the exposure of a sensor. UDDF forwards this request to your driver. If your driver supports this capability, it returns the corresponding sensor control interface.

UDDF Driver Model Overview#
Device Driver Interface (DDI)#
Driver Interfaces#
DDIs are implemented by drivers and called by the UDDF client. Methods in these interfaces are often called driver entrypoints.
Every interface in DDI has the following:
A name (beginning with “I”).
A GUID (a 128-bit globally unique identifier).
A static member named
id
containing the GUID, which identifies the interface.Inheritance from the empty base class
IInterface
.
For example, the interface for reading and writing sensor devices on an I2C bus is named IReadWriteI2C
and is defined in a file named IReadWriteI2C.hpp
, and its GUID is UUID(0xacc5ff36, 0x22a0, 0x4d8a, 0x8271, 0xab, 0x97, 0x5b, 0xa8, 0x84, 0xeb)
(defined in that header file). The interface has two methods: ReadI2C()
and WriteI2C()
.
Note
Drivers don’t directly use IReadWriteI2C
to program hardware. It’s an interface provided by a driver, not an interface used by a driver.
The declaration for an example interface IReadWriteI2C
for reading and writing sensor devices on an I2C bus resembles the following code:
/** The unique identifier for the IReadWriteI2C interface type. */
inline UUID IREADWRITEI2C_INTERFACE_ID = UUID(0xacc5ff36, 0x22a0, 0x4d8a, 0x8271, 0xab, 0x97, 0x5b, 0xa8, 0x84, 0xeb);
class IReadWriteI2C: public IInterface { // Note the inheritance from the IInterface class!
public:
static constexpr UUID id { IREADWRITEI2C_INTERFACE_ID }; // The GUID for this interface
/** Return values from read and write operations. */
enum I2CResult {
RWI2C_SUCCESS, ///< Success
RWI2C_ERROR_NACK, ///< NACK received
RWI2C_ERROR_BUS_TIMEOUT, ///< Bus timeout
RWI2C_ERROR_ARBLOST, ///< Arbitration lost
RWI2C_OUT_OF_RANGE, ///< Address, offset, or length is out of range
RWI2C_ERROR_UNKNOWN, ///< Unknown error
};
/**
* Read from the I2C bus.
*
* @param[in] sensorIndex The index of the sensor to read from.
* @param[in] address The address of the device to read from.
* @param[in] offset The starting offset to read from.
* @param[out] data The data read from the I2C bus.
* @param[in] length The number of bytes to read.
*/
virtual I2CResult ReadI2C(uint8_t sensorIndex, uint16_t address, uint16_t offset,
uint8_t* data, uint16_t length) = 0;
/**
* Write to the I2C bus.
*
* @param[in] sensorIndex The index of the sensor to write to.
* @param[in] address The address of the device to write to.
* @param[in] offset The starting offset to write to.
* @param[in] data The data to write to the I2C bus.
* @param[in] length The number of bytes to write.
*/
virtual I2CResult WriteI2C(uint8_t sensorIndex, uint16_t address, uint16_t offset,
uint8_t const* data, uint16_t length) = 0;
};
For a driver to support the IReadWriteI2C
interface, it must implement the ReadI2C()
and WriteI2C()
methods.
Driver Objects#
All UDDF drivers must inherit from and implement IDriver
and the singular GetInterface()
method. All driver interfaces supported by your driver will be retrieved via this method. For example, driver consumers can request the I2C read/write interface as follows:
IDriver* driver = ...;
UUID READ_WRITE_I2C_GUID = IReadWriteI2C::id; // This is the GUID for the IReadWriteI2C interface
IReadWriteI2C* rwI2C = static_cast<IReadWriteI2C*>(driver->GetInterface(READ_WRITE_I2C_GUID));
if (rwI2C == nullptr) {
std::cerr << "Sorry, I2C read/write functionality is unavailable" << std::endl;
}
GetInterface()
returns a pointer to an interface if the requested interface is supported, or nullptr
otherwise.
Putting it all together, a driver that implements IReadWriteI2C
(and no other interfaces) might resemble the following code:
#include "uddf/ddi/interfaces/IReadWriteI2C.hpp"
class MyDriver: public IDriver, public IReadWriteI2C
{
public:
/*-- IDriver methods --*/
IInterface* GetInterface(const UUID& uuid) noexcept override {
if (uuid == IReadWriteI2C::id) {
return static_cast<IReadWriteI2C*>(this);
} else {
return nullptr; // interface not supported
}
}
/*-- IReadWriteI2C methods --*/
I2CResult ReadI2C(uint8_t sensorIndex, uint16_t address, uint16_t offset,
uint8_t* data, uint16_t length) override {
// implement I2C read
}
I2CResult WriteI2C(uint8_t sensorIndex, uint16_t address, uint16_t offset,
uint8_t const* data, uint16_t length) override {
// implement I2C write
}
};
Alternative Implementation#
Although the preceding example shows MyDriver
implementing required driver interface and driver object methods, this need not always be the case. The internal structure of any driver object implementation is entirely opaque to UDDF. Consider the alternative driver structure, which is an equally valid UDDF driver.
#include "uddf/ddi/interfaces/IReadWriteI2C.hpp"
/* Defined by your driver */
class MyI2CReadWriteBackend : public IReadWriteI2C
{
/* Elided */
};
class MyDriver: public IDriver
{
public:
/*-- IDriver methods --*/
IInterface* GetInterface(const UUID& uuid) noexcept override {
if (uuid == IReadWriteI2C::id) {
return static_cast<IReadWriteI2C*>(m_i2cReadWriteBackend);
} else {
return nullptr; // interface not supported
}
}
private:
MyI2CReadWriteBackend m_i2cReadWriteBackend;
};
UDDF Driver Model: Camera Driver Interface (CDI)#
UDDF CDI interfaces are provided to the driver so that the driver can do its work. In most DDI interfaces, they are provided to every driver entrypoint.
The most important CDI interface for most drivers is named IHardwareAccess
. This interface lets the driver communicate with the device that it’s managing. The interface looks like the following code:
class IHardwareAccess {
public:
/**
* Acquire a HSL sequence object for building sequences at runtime.
*/
virtual HSLDynamicSequence& GetDynamicSequence() = 0;
/**
* Submit a dynamic HSL sequence.
*
* @param[in] sequence A reference to the HSLDynamicSequence object.
*/
virtual HSLResult SubmitSequence(HSLDynamicSequence& sequence) = 0;
/**
* Submit a pre-defined static HSL sequence.
*
* @param[in] sequence The HSLStaticSequence object containing the data.
*/
HSLResult SubmitSequence(const HSLStaticSequence& sequence) = 0;
/**
* Read bytes from an I2C device.
*
* @param[in] deviceIndex The index of the I2C device in the driver's device table.
* @param[in] startOffset The starting offset in the I2C device.
* @param[in] length The number of bytes to read.
* @param[out] buffer The buffer to store the read bytes.
*/
virtual bool ReadI2C(size_t deviceIndex, uint16_t startOffset, uint16_t length, uint8_t* buffer) = 0;
};
UDDF drivers communicate with hardware primarily through the Hardware Sequence Language (HSL). In this interface, HSLStaticSequence
refers to HSL bytecode that is precompiled and built into the driver. (Details on how to do this appear later in this document.) HSLDynamicSequence
is an object that allows the driver to construct new HSL sequences on the fly.
Note that the ReadI2C()
method is the only method that doesn’t relate to HSL.
CDI also defines an interface named IDriverServices
, which provides basic services such as logging.
Hardware Sequence Language (HSL)#
HSL is a simple language for specifying I2C and GPIO hardware accesses. HSL’s standard source language is named PyHSL and generates HSL bytecode.
Drivers use HSL in two ways:
They can create sequences of bytecode on the fly (and send them to hardware). This is known as dynamic HSL.
They can send precompiled sequences of bytecode directly to hardware. This is known as static HSL.

HSL Submission Model for UDDF#
Dynamic HSL#
UDDF drivers submit all I2C and GPIO commands through HSL. In the case of dynamic HSL, this is largely abstracted away from the driver through the II2CBuilder
interface. This interface contains several APIs for both simple and compound I2C transactions (such as read-modify-write). As the II2CBuilder
interface name suggests, drivers build lists of I2C commands before submitting the entire list to UDDF CDI for fulfillment. UDDF creates one I2C builder interface for each I2C device requested by the driver. Consequently, each I2C builder interface builds I2C commands for only one I2C device. You can use multiple I2C builder interfaces to create I2C command lists targeting multiple devices. All I2C builder interfaces are retrieved by the UDDF-supplied HSLDynamicSequence
object.
UDDF drivers can request one of these objects at any time as long as the IHardwareAccess
interface is supplied to the calling DDI entrypoint.
After submission, the Sequence
object is cleared of any enqueued commands and can safely be used again.
The full workflow is as follows:
The UDDF driver requests a
HSLDynamicSequence
object from the suppliedIHardwareAccess
implementation.The UDDF driver requests a
II2CBuilder
interface for a specific I2C device and begins enqueuing HSL commands.When finished, the UDDF driver submits the sequence to the
IHardwareAccess
implementation. The sequence is turned into HSL bytecode behind the scenes and submitted to hardware. TheHSLDynamicSequence
object is ready to be used again.
Static HSL#
UDDF drivers are encouraged to use static HSL sequences where possible. Instead of generating commands on-the-fly, HSL is written in the PyHSL source language. When your UDDF driver is compiled, so too is the HSL source file. The resultant bytecode is transformed into a C++ header ready for inclusion into your driver. This workflow is discussed in the HSL Toolchain Support section.
The following code snippet shows an HSLStaticSequence
object generated by the aforementioned workflow. UDDF drivers must pass this object to the IHardwareAccess
interface that was supplied to the calling DDI entrypoint.
/**
* @brief HSL bytecode sequence for init_device
*/
inline constexpr uddf::cdi::HSLStaticSequence<134U> init_device { {{
0x0c, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00,
0x02, 0x00, 0x01, 0x05, 0x00, 0x7a, 0x00, 0x02, 0x01, 0x14, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x01, 0x00, 0x1b, 0x1a, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x53, 0x65,
0x6e, 0x73, 0x6f, 0x72, 0x20, 0x44, 0x72, 0x69, 0x76, 0x65, 0x72, 0x20, 0x49, 0x6e, 0x69, 0x74,
0x00, 0x02, 0x01, 0x00, 0x05, 0x03, 0x01, 0x00, 0xc1, 0x04, 0x03, 0x01, 0x02, 0xe3, 0x05, 0x08,
0x00, 0x20, 0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf6, 0x15, 0x01, 0x00, 0x17, 0x06, 0x00, 0x30, 0x00,
0x00, 0x00, 0x02, 0x17, 0x06, 0x00, 0x32, 0x00, 0x02, 0x00, 0x02, 0x15, 0x01, 0x01, 0x17, 0x06,
0x00, 0x34, 0x00, 0x00, 0x00, 0x01,
}} };
/* Example usage */
auto ret = context.hwAccess->SubmitSequence(init_device);
The full workflow is as follows:
Author HSL in the PyHSL source language.
Use the provided HSL toolkit tools to compile and transform your sequences into C++ headers.
Submit the sequences directly to the
IHardwareAccess
implementation.
Dynamic and Static HSL#
Good news: Both HSL submission models can be combined! Even in the same function, UDDF drivers may submit a static sequence and then generate a few dynamic HSL commands based on some runtime condition for a follow-up sequence. Any combination is supported - the only invariant that exists is that static HSL sequences are read-only.
Discovery and Enumeration#
UDDF drivers are loaded from driver shared libraries via dynamic linking. Each driver shared library can contain one or more driver object implementations (IDriver
). A UDDF driver library is identified by an exported uddf_discover_drivers()
symbol. The UDDF framework invokes this symbol to gather information on all of the available drivers within the library.
Driver Information#
Each available driver is described with a small DriverInfo
structure. This structure defines the name of the driver and other associated information. Additionally, it defines the type of driver that this descriptor describes. The recognized driver types are defined by UDDF.
/**
* @brief Contains descriptive information about a specific driver implementation.
*
* This structure holds metadata about a driver, such as its name, vendor, and
* the unique type ID of the IDriver interface it implements. This information is
* used by UDDF to identify and select appropriate drivers.
*/
struct DriverInfo {
std::string name;
std::string description;
std::string vendor;
std::string revision;
UUID driverTypeId;
};
Driver Enumeration#
The DriverInfo
structures are provided by a small proxy object returned through the exported discovery symbol. This object returns the number of contained driver descriptors and a symbol to create each corresponding driver.
/**
* @brief Interface for discovering available driver implementations provided by
* a driver library.
*
* Driver libraries must implement this interface to allow the UDDF framework
* to query the drivers they contain. A single instance of a class derived from
* IDriverEnumerator is returned by the library's entry point, @ref uddf_discover_drivers().
*/
class IDriverEnumerator {
public:
virtual std::string_view GetName() const = 0;
virtual size_t GetDriverCount() const = 0;
virtual const DriverInfo* GetDriverInfo(size_t index) const = 0;
virtual std::unique_ptr<IDriver> CreateDriver(size_t index) = 0;
};
The following diagram shows two libraries. The simple library contains one constructible driver that is of type COE_MODULE_DRIVER_ID
. The second library advertises three constructible drivers. The first two descriptors expose two different variations of the same underlying IDriver
object. Because the internal structure of an IDriver
implementation is opaque to UDDF, driver writers are free to use this mechanism to construct variations or other customizations of the same driver. The third descriptor is an entirely different driver.

UDDF Driver Discovery and Enumeration#
Drivers in Practice#
C++ Usage#
UDDF drivers are written entirely in C++, and this section discusses relevant language details.
Language Standard#
UDDF interfaces require C++17. Ensure that your UDDF driver is compiled against this standard. If you are using CMake, for example, use the following commands:
add_library(MY_UDDF_DRIVER driver.cpp)
target_compile_features(MY_UDDF_DRIVER PRIVATE cxx_std_17)
Namespace Conventions#
UDDF uses C++ namespaces to precisely name interfaces and datatypes. For example, every DDI interface is in the uddf::ddi::interfaces
namespace. An instance of the ICameraModule
interface might be declared like this:
uddf::ddi::interfaces::ICameraModule* cameraModule;
Consider using namespace aliases or using
declarations to increase code readability.
/* Namespace Alias */
namespace DDI_IF = uddf::ddi::interfaces;
DDI_IF::ICameraModule* cam;
/* Using-Declaration */
using uddf::ddi::interfaces::ICameraModule;
ICameraModule* cam;
Exceptions#
C++ exceptions are not used in UDDF. Drivers must never throw exceptions from any entrypoint.
Best Practices#
To simplify development and to be a “good citizen” in the UDDF ecosystem, we strongly recommend that drivers follow these guidelines:
Use static HSL sequences as much as possible. They allow developers to author (and test) those sequences offline using PyHSL and can greatly simplify driver code.
Maintain as little state as possible. CDI interfaces and configuration data are usually passed into every entrypoint, so they don’t need to be cached inside a driver object.
Avoid dynamic memory allocation within a driver.
Use the provided
IDriverServices
interface for logging, instead of printing tostdout
orstderr
.
Checklist for UDDF Driver Development#
Driver Architecture and Implementation#
Inherit from the
IDriver
base class (uddf/ddi/IDriver.hpp
) and implement theGetInterface()
method.Implement appropriate driver-specific interfaces. For example:
ICoEModuleControl
for CoE drivers.ISensorControl
for sensor functionality (uddf/ddi/interfaces/ISensorControl.hpp
).
Define driver type ID using constants from
uddf/ddi/DriverTypeIds.hpp
:uddf::ddi::UUID GetID() const noexcept override { return uddf::ddi::drivers::COE_MODULE_DRIVER_ID; }
Implement
GetInterface()
with proper interface resolution. For example:uddf::ddi::IInterface* GetInterface(const uddf::ddi::UUID& uuid) noexcept override { if (uuid == ICoEModuleControl::id) { return static_cast<ICoEModuleControl*>(this); } if (uuid == ISensorControl::id) { return static_cast<ISensorControl*>(this); } return nullptr; }
Handle unsupported interface requests by returning
nullptr
fromGetInterface()
.
Hardware Access and HSL#
Populate
DeviceTable
inConfigureDriver()
with all I2C devices (uddf/ddi/DeviceTable.hpp
):bool ConfigureDriver(const CoEModuleContext& context, uddf::ddi::DeviceTable& deviceTable) override { deviceTable.push_back(uddf::ddi::DeviceTableEntry{ .i2cAddress = 0x7A, // 7-bit I2C address .offsetWidth = 2, // 16-bit register offsets .dataWidth = 1, // 8-bit data .flags = 0, // Additional flags }); return true; }
Validate device table entries against HSL I2C device definitions:
# HSL: I2CDevice(0x7A, 16, 8, 'coe_module_ctrl_i2c') # Must match: address=0x7A, offsetWidth=2 (16-bit), dataWidth=1 (8-bit)
Author HSL sequences in PyHSL source language (
.py
files). For example:coe_module_ctrl_i2c = I2CDevice(0x7A, 16, 8, 'coe_module_ctrl_i2c') with ph_sequence('Init') as seq: seq.annotate('Sample Driver Initialization') with coe_module_ctrl_i2c: write(0x0100, 0xC1) write(0x0102, 0xE3, noverify=True)
Configure build system integration (CMake
hsl_add_driver_sources
):hsl_add_driver_sources(sample_coe_driver SOURCES Sequence_Lifecycle.py NAMESPACE uddf::samples::coe::hsl )
Include generated HSL headers (
.hpp
files) in the driver source code:#include "Sequence_Lifecycle.hpp" // Generated from .py file
Submit static sequences by using
IHardwareAccess::SubmitSequence()
:context.hwAccess->SubmitSequence(coe::hsl::Init);
For dynamic HSL, obtain
HSLDynamicSequence
objects viaIHardwareAccess::GetDynamicSequence().
The I2C builder indices correspond directly to the indices in the driver-provided device table entry list.HSLDynamicSequence& sequence = context.hwAccess->GetDynamicSequence(); /* Use the I2C builder for the first registered I2C device (index zero) */ sequence.getI2CBuilder(0)->write(0x100, 0xAA); HSLResult result = context.hwAccess->SubmitSequence(sequence);
Prefer static HSL where possible for performance and validation.
Use appropriate sequence annotations for debugging and documentation.
Sequence objects are automatically reset after submission.
Driver Discovery and Export#
Implement the
uddf_discover_drivers()
export function (uddf/ddi/IDriverEnumerator.hpp
):extern "C" { uddf::ddi::IDriverEnumerator* uddf_discover_drivers() { return new MyDriverEnumerator(); } }
Create the
IDriverEnumerator
implementation class:class MyDriverEnumerator : public uddf::ddi::IDriverEnumerator { size_t GetDriverCount() const override { return 1; } const DriverInfo* GetDriverInfo(size_t index) const override; std::unique_ptr<IDriver> CreateDriver(size_t index) override; };
Provide accurate
DriverInfo
structures. Thename
field is the primary identifier for your driver.static const uddf::ddi::DriverInfo driverInfo = { .name = "Sample CoE Module Driver", .description = "Sample implementation for CoE camera modules", .vendor = "NVIDIA Corporation", .revision = "1.0.0", .driverTypeId = uddf::ddi::drivers::COE_MODULE_DRIVER_ID };
Static HSL Toolchain Support in Depth#

UDDF Static HSL Compilation Workflow#
HSL Header Generation (drvhsl)#
The HSL toolkit’s drvhsl
tool converts compiled HSL bytecode files into C++ header files. Although driver developers typically don’t invoke this tool directly, it serves an essential role in making HSL bytecode accessible to your UDDF driver. The tool takes binary HSL container files (.hslc
) and transforms them into ready-to-include C++ headers containing HSLStaticSequence
objects with embedded bytecode as compile-time constants. This process lets your driver directly reference precompiled HSL sequences without needing runtime file I/O or dynamic memory allocation. The drvhsl
tool handles the mechanical work of converting binary data to properly formatted C++ arrays, managing namespace organization, constant naming, and header guards. The included CMake HSL module automatically invokes this tool, making static HSL sequences as straightforward to use as including any other header file.
usage: drvhsl.py [-h] [-i source-file] [-d output-directory] [-b basename] [-p prefix] [-n namespace]
[-l {debug,info,warning,error,critical}]
Convert a .hslc file into C++ code to retrieve HSL bytecode blobs
options:
-h, --help show this help message and exit
-i source-file, --input source-file
source .hslc file to process
-d output-directory, --directory output-directory
output directory for generated files
-b basename, --basename basename
basename for generated files (defaults to input filename)
-p prefix, --prefix prefix
prefix for generated constants
-n namespace, --namespace namespace
namespace for the generated code (e.g., "mydriver::hsl")
-l {debug,info,warning,error,critical}, --loglevel {debug,info,warning,error,critical}
Logging level
HSL CMake Module#
The HSL CMake integration module provides a streamlined way to compile PyHSL scripts into C++ header files and integrate them into your UDDF drivers. This module handles the compilation pipeline from HSL source files to generated C++ headers that you can directly include in your driver code.
Prerequisites#
Python 3 and the HSL PDK package.
CMake 3.15 or later.
Configuring the HSL Module#
Add the HSL CMake integration to your project by including the module in your CMakeLists.txt
file:
list(APPEND CMAKE_MODULE_PATH "${PATH_TO_HSL_PDK}/integrations/cmake")
include(hsl_compile_rules)
The module requires two HSL paths to be configured: the location of the HSL tools (compiler, etc.) and any compiled Python modules. For the HSL PDK, these paths are identical.
set(HSL_PYTHON_SCRIPT_DIR "${PATH_TO_HSL_PDK}/pyhsl")
set(HSL_PYTHON_MODULE_DIR "${PATH_TO_HSL_PDK}/pyhsl")
Usage#
A single function, hsl_add_driver_sources
, handles all compilation including setting up build dependencies.
hsl_add_driver_sources(<TARGET>
SOURCES <hsl_file1.py> [<hsl_file2.py> ...]
[OUTPUT_SUBDIR <subdir>]
[CONFIG_PARAMS <param1=value1> [<param2=value2> ...]]
[NAMESPACE <cpp_namespace>]
[CONSTANT_PREFIX <prefix>]
[DEPENDENCIES <dep1.py> [<dep2.py> ...]]
)
This function is used as follows:
hsl_add_driver_sources(my_camera_driver
SOURCES camera_sequences.py
NAMESPACE my_driver::hsl
)
hsl_add_driver_sources(advanced_driver
SOURCES
init_sequences.py
streaming_sequences.py
power_sequences.py
NAMESPACE advanced_driver::hsl
CONSTANT_PREFIX ADV_
)
Configuration Variables#
Parameter |
Type |
Required |
Description |
---|---|---|---|
|
String |
Yes |
The CMake target to add HSL compilation to. |
|
List |
Yes |
List of HSL Python source files to compile. |
|
String |
No |
Output subdirectory in build directory (default: |
|
List |
No |
Configuration parameters passed to |
|
String |
No |
C++ namespace for generated headers. |
|
String |
No |
Prefix for generated constants. |
|
List |
No |
Additional Python files that HSL sources depend on. Use this variable to establish proper rebuilds for any dependent non-PyHSL source files. |
Generated Files#
For each HSL source file, the following files are generated:
<basename>.hslc
: Compiled HSL bytecode (intermediate).<basename>.hpp
: C++ header with sequence definitions.
To use the compiled output in your driver, simply #include <basename>.hpp
.
More Information#
For complete examples, see sample drivers in
sipl/uddf/samples/drivers/
.To construct HSL sequences, check the HSL documentation: PyHSL.