Skip to main content

Developing UxHw® Applications

The Signaloid SoC in the C0-microSD can compute directly on probability distributions using Signaloid's UxHw technology. Your application performs ordinary arithmetic on distributional values, and the hardware propagates the distributions from inputs to outputs in a single execution (see UxHw in Silicon). This guide shows you how to develop such an application, starting from the official Signaloid-Compute-Module-Application-Template repository, a minimal, working application pair (a C device application and a Python host application) that you extend with your own computation.

Compiling UxHw applications does not require a local cross-compilation toolchain. The template uses the Signaloid CLI to build your C application on the Signaloid Cloud Compute Engine, targeting the C0-microSD, and downloads the resulting binary for you to flash.

Prerequisites

  • A C0-microSD and its device path on your host; see Identify Your Module.
  • A Signaloid account on the Signaloid Cloud Developer Platform (SCDP) and an API key.
  • A GitHub account, so that you can fork the template repository and push your changes, connected to your Signaloid account as shown in GitHub Login.
  • The Signaloid CLI, installed and authenticated as shown in its installation and authentication documentation.
  • Python 3.10 or later for the host application and the flashing toolkit. See Python Environment.
  • The jq command-line JSON processor, which the template Makefile uses to parse Signaloid CLI responses.
  • Root privileges (sudo) for raw block-device access.

Fork and clone the template

The Signaloid Cloud Compute Engine builds your application from your repository on GitHub, so any modification you make must live in a repository you can push to. Start by forking the Signaloid-Compute-Module-Application-Template repository to your own GitHub account. Make sure you have connected your GitHub account to your Signaloid account (see GitHub Login), so that the Signaloid Cloud Compute Engine can access your fork.

Then clone your fork recursively, so that you also get the Signaloid-Compute-Module-Utilities submodule (the flashing toolkit and the hardware abstraction layer the template builds on). Replace <your-username> with your GitHub username.

git clone --recursive https://github.com/<your-username>/Signaloid-Compute-Module-Application-Template.git

The repository contains one application in two halves, plus the build machinery.

PathContents
signaloid-soc-application/main.cThe device application that runs on the Signaloid SoC and performs the UxHw computation.
signaloid-soc-application/config.mkThe build configuration, which lists your source files, include paths, and build flags.
python-host-application/The host application that sends inputs to the module and reads back results.
MakefileDrives the whole flow, building through the Signaloid CLI, downloading the binary, and flashing it.

The template application

The template implements one example command, CalculateAddition, and it is the easiest entry point for understanding the whole flow. The host application sends four floats, the bounds of two uniform distributions X and Y. The device application constructs the two distributions, adds them, and returns the mean and variance of the resulting distribution for the host to print.

The core of main.c is a loop that waits for a command from the host, dispatches on it, and reports status, following the polled handshake documented in Host Interface and Protocol. The kCalculateAddition case is where the UxHw computation happens.

case kCalculateAddition:
/*
* Create distributional values from inputs
*/
inputDistributionA = UxHwFloatUniformDist(kC0HALInputBufferFloat[0], kC0HALInputBufferFloat[1]);
inputDistributionB = UxHwFloatUniformDist(kC0HALInputBufferFloat[2], kC0HALInputBufferFloat[3]);

/*
* Calculate
*/
result = inputDistributionA + inputDistributionB;

/*
* Compute the mean (first moment) and variance
* (second centralised moment) of the result.
*/
mean = UxHwFloatNthMoment(result, 1);
variance = UxHwFloatNthMoment(result, 2);

/*
* Return the mean and variance as two floats by writing them
* to the output buffer.
*/
kC0HALOutputBufferFloat[0] = mean;
kC0HALOutputBufferFloat[1] = variance;

Three details are worth noting.

  • UxHw values are ordinary C floats. UxHwFloatUniformDist() returns a float that carries a distribution, and inputDistributionA + inputDistributionB is plain C addition, and the hardware carries the distributions through it. You query the result with functions such as UxHwFloatNthMoment(). The full API surface is in the UxHw API documentation.
  • The hardware abstraction layer hides the MMIO details. The C0HAL* functions and the kC0HALInputBufferFloat / kC0HALOutputBufferFloat buffers map onto the command and status registers and the MOSI and MISO buffers of the MMIO interfacing model.
  • Commands are a shared contract. The SignaloidSoCCommand enum in main.c and the Commands enum in python-host-application/host_application.py must stay in sync; each new computation you add is a new entry in both.

Build with the Signaloid CLI

The Signaloid CLI is the command-line interface to the Signaloid Cloud Compute Engine. It connects a GitHub repository to your Signaloid account, starts builds of that repository on a Signaloid core, and downloads the build products (see the Signaloid CLI introduction). Building on the Signaloid Cloud Compute Engine is what gives your application its UxHw capabilities, because the platform provides the compiler and the uxhw.h API, matched to the C0 core you build for.

You do not need to invoke the CLI by hand, because the template's Makefile drives it. First, configure three variables at the top of the Makefile.

  1. Set DEVICE to the device path of your C0-microSD, for example /dev/disk4.
  2. Set DEVICE_TYPE to SIGNALOID_C0_MICROSD.
  3. Set CORE_ID to the C0 core to build for. The core determines the distributional representation precision and whether the build tracks correlations, and the Makefile lists the core IDs of the available C0-microSD core variants. See the execution cores guide for how cores differ.

Then start the build.

make

On the first run, the Makefile uses the CLI to look up (or connect) the repository and branch on the Signaloid Cloud Compute Engine, pointing it at the signaloid-soc-application/ directory and your selected core. It then starts a build, streams the build status until completion, and downloads the resulting binary as signaloid-soc-application/<build-id>.main.bin. The build inputs (source files, include paths, and build flags) are defined in signaloid-soc-application/config.mk.

note

The Signaloid Cloud Compute Engine builds the repository as it exists on GitHub, so commit and push your changes to your fork before running make.

Flash and run

  1. Flash the downloaded binary into the SPI flash user data region of the module.

    make flash

    The Makefile checks that the binary fits in the 128 KiB the Signaloid SoC loads at reset, and flashes it with the C0_microSD_toolkit.py script from the Signaloid-Compute-Module-Utilities submodule. Flashing happens in Bootloader mode, and the toolkit handles the mode switching for you. If the module is in Signaloid SoC mode, the toolkit first switches the boot mode and asks you to power cycle the device (eject and re-insert it). After writing and verifying the binary, the toolkit switches the module back to Signaloid SoC mode and asks for one more power cycle. The module has finished flashing when the green LED lights solid. For the mode model itself, see Switch Between Operation Modes.

  2. Set up and run the Python host application from the repository root.

    python3 -m venv .venv
    source .venv/bin/activate
    pip install -r python-host-application/requirements.txt
    cd python-host-application
    sudo ../.venv/bin/python3 host_application.py --device-path /dev/disk4 --variant C0-microSD CalculateAddition 5.0 6.0 4.0 7.5

    Replace /dev/disk4 with your device path. The command adds a uniform distribution over [5.0, 6.0] to a uniform distribution over [4.0, 7.5] on the module, and the host prints the mean and variance of the result. You invoke the interpreter of the virtual environment directly, because a plain sudo python3 may resolve to a different interpreter that does not have the packages you installed in the virtual environment.

Make it your own

Use the following workflow to turn the template into your own application. The modifications in these steps must reach GitHub for the Signaloid Cloud Compute Engine to build them, so make them in your fork of the template.

  1. Develop and test your computation in the Code Playground of the Signaloid Cloud Developer Platform, where you can iterate on UxHw code without flashing hardware.
  2. Copy the computation into signaloid-soc-application/main.c as a new command. Add an entry to the SignaloidSoCCommand enum and a matching case that reads inputs from the input buffer and writes results to the output buffer.
  3. Mirror the new command in the host application. Extend the Commands enum and the argument parsing in python-host-application/host_application.py, and adapt the input packing and output parsing helpers in python-host-application/app_helpers.py.
  4. If you split your code into more .c files, add them to the SOURCES variable in signaloid-soc-application/config.mk.
  5. Commit and push your changes to your fork, then build, flash, and run as shown above.

For complete, working applications built this way, see Examples and Demos.

Next steps