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 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
jqcommand-line JSON processor, which the templateMakefileuses 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.
| Path | Contents |
|---|---|
signaloid-soc-application/main.c | The device application that runs on the Signaloid SoC and performs the UxHw computation. |
signaloid-soc-application/config.mk | The 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. |
Makefile | Drives 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. With one command, four input floats, and two output floats, every
part of the round trip stays small enough to read in one sitting.
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
The MMIO Interfacing Model.
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 afloatthat carries a distribution, andinputDistributionA + inputDistributionBis plain C addition, and the hardware carries the distributions through it. You query the result with functions such asUxHwFloatNthMoment(). The full API surface is in the UxHw API documentation. - The hardware abstraction layer hides the MMIO details. The
C0HAL*functions and thekC0HALInputBufferFloatandkC0HALOutputBufferFloatarrays map onto theCOMMANDandSTATUSregisters and onto the two halves of the single shared MMIO buffer of the MMIO interfacing model. - Commands are a shared contract. The
SignaloidSoCCommandenum inmain.cand theCommandsenum inpython-host-application/host_application.pymust stay in sync, and 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.
- Set
DEVICEto the device path of your C0-microSD+, for example/dev/disk4. - Set
DEVICE_TYPEtoSIGNALOID_C0_MICROSD_PLUS. This makes theMakefileoffer the C0-microSD+ core IDs and use theC0_SD_toolkit.pyflashing and core-control commands for this module. The variable stays on your host and is not passed to the cloud build, which derives its ownDEVICE_TYPEfrom the core ID you select next. That is what selects the C0-microSD+ hardware abstraction layer and register map insignaloid-soc-application/config.mk. - Set
CORE_IDto the C0 core to build for. The core determines the distributional representation precision and whether the build tracks correlations, and theMakefilelists the core IDs of the available C0-microSD+ core variants. See Execution Cores 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.
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.
The C0-microSD+ can run an application from the 320 KiB of on-chip SRAM, and it also supports execute in place (XIP), running code directly from the 16 MiB SPI flash so that only writable data has to live in SRAM.
You select between the two with the MODE variable at the top of
signaloid-soc-application/config.mk, the same file that lists your source files in the
SOURCES variable. The template ships with MODE set to lram, which runs the whole
application from the on-chip SRAM. Set it to xip to build the application to execute in
place from the SPI flash.
MODE := xip
The two modes trade speed against capacity. lram is the fastest, and it bounds your whole
application by the 320 KiB of SRAM. xip suits larger binaries, and its performance is
bounded by the flash latency and bandwidth. Commit and push the change to your fork, then
run make again to rebuild with the new mode.
Flash and run
Flashing and running your application takes two steps, because the host application brings the core up for you.
-
Flash the downloaded binary into the SPI flash user data region of the module.
make flashThe
Makefilefirst stops the core, so that the running application does not access flash while it is being written, and then flashes the binary with theC0_SD_toolkit.pyscript from the Signaloid-Compute-Module-Utilities submodule. -
Set up and run the Python host application from the repository root, passing
--reset-on-launchso that it restarts the core on the binary you just flashed.python3 -m venv .venvsource .venv/bin/activatepip install -r python-host-application/requirements.txtcd python-host-applicationsudo ../.venv/bin/python3 host_application.py --device-path /dev/disk4 --variant C0-microSD+ --reset-on-launch CalculateAddition 5.0 6.0 4.0 7.5Replace
/dev/disk4with your device path. The command adds a uniform distribution over 5.0 to 6.0 to a uniform distribution over 4.0 to 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 plainsudo python3may resolve to a different interpreter that does not have the packages you installed in the virtual environment.
Bringing the core up from the host application
You do not need a separate step to start the core, because the host application always
brings it up during initialization and then waits until the device application reports the
waiting for command status before it sends anything. What --reset-on-launch changes is
how the core is brought up.
- Without
--reset-on-launch, the host application starts the core. A core that is already running stays running, so the device application keeps whatever state it was left in by an earlier run. - With
--reset-on-launch, the host application stops the core, waits briefly, and starts it again. That restarts the device application from its entry point with its state cleared.
Pass --reset-on-launch on the run that follows a flash, and whenever an earlier run left
the device application halted on a trap or stopped part way through a transaction. It is the
flag to reach for on every edit, build, and run cycle, because it guarantees that the code
now executing is the binary you just flashed.
The matching --stop-on-exit flag stops the core and turns off the on-board LEDs when the
host application finishes, which leaves the module idle in its slot rather than spinning in
the polling loop of your device application.
For the register and buffer contract your host code writes against, see Host Interface and Protocol. For driving the core directly with the toolkit, see Start and stop the core.
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.
- 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.
- Copy the computation into
signaloid-soc-application/main.cas a new command. Add an entry to theSignaloidSoCCommandenum and a matchingcasethat reads inputs fromkC0HALInputBufferFloatand writes results tokC0HALOutputBufferFloat. - Mirror the new command in the host application. Extend the
Commandsenum and the argument parsing inpython-host-application/host_application.py, and adapt the input packing and output parsing helpers inpython-host-application/app_helpers.py. - If you split your code into more
.cfiles, add them to theSOURCESvariable insignaloid-soc-application/config.mk. - 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
- Using the Hardware Abstraction Layer, the C library the device application uses to reach the registers and buffers.
- Using the Python Host Interface, the package the host application uses to reach them from the other side.
- Host Interface and Protocol, the register and buffer contract behind both libraries.
- Using the C0_SD_toolkit, the full command reference for flashing, starting, and stopping the core.