Skip to main content

Using the C0_SD_toolkit

The C0_SD_toolkit.py script from the Signaloid-Compute-Module-Utilities repository is how you drive a C0-microSD+ from a host computer. It flashes a compiled device application into the SPI flash of the module, starts and stops the RISC-V core of the Signaloid SoC, updates the FPGA bitstream, and reports what the module and the device application are doing.

The toolkit works over ordinary SD block reads and writes, so you need neither a driver nor external programming hardware. Everything on this page happens while the module stays in its slot.

Before you begin

  • Install Python 3.10 or later. The C0_SD_toolkit.py script uses nothing outside the Python standard library. See Python Environment.
  • Get the toolkit, which ships in the Signaloid-Compute-Module-Utilities repository. Cloning the Signaloid-Compute-Module-Application-Template repository recursively also brings the toolkit in as a submodule.
  • Insert the C0-microSD+ into a microSD slot or a card reader. It enumerates as an unformatted block-storage device of 268.4 MB on macOS and 256 MiB on Linux and Windows. If your operating system offers to format it, do not format the device and dismiss the prompt.
  • Identify the device path of the module. On macOS the path looks like /dev/disk4, and on Linux it looks like /dev/sdb. See Identify your module for the exact commands. The examples on this page assume /dev/disk4, so substitute your own path.
  • Run every command with sudo, because raw block-device access requires root privileges.

The toolkit takes the device path as a positional argument before the command, and config is an alias for the configure command.

Command reference

Running C0_SD_toolkit.py --help prints every command, together with the configuration actions of every supported variant. Pass --variant to narrow that action list to a single module. The listing below is the output for the C0-microSD+.

usage: C0_SD_toolkit.py [-h] [--variant {C0-microSD+,C0-SD}]
[--regmap-path REGMAP_PATH]
target_device <command> ...

Signaloid C0-SD toolkit. Version 2.5

positional arguments:
target_device Target device path
<command>
info Print target device info and bitstream metadata.
status Print verbose status (COMMAND, CONFIG, STATUS, and
SD_CONFIG on C0-SD).
flash-application Flash an application binary
flash-bitstream Flash a bitstream file
configure (config) Apply a configuration action (per-variant; see the
action list in the main --help).

options:
-h, --help show this help message and exit
--variant {C0-microSD+,C0-SD}
Hardware variant. Default: auto-detect from the
device's bitstream; required if it cannot be
identified.
--regmap-path REGMAP_PATH
Path to the regmap package directory for the selected
--variant (defaults to the built-in regmaps).

commands:
info
Decode and print bitstream metadata, and verify the bitstream CRC.
status
Print the COMMAND, CONFIG and STATUS registers (plus SD_CONFIG where
present).
flash-application <app_path> [-p SIZE]
Flash an application binary to the device's user-data flash region
(optionally zero-padded to SIZE).
flash-bitstream <bs_path> [-p SIZE]
Stop the SoC core, unlock the bitstream region, flash a bitstream,
then re-lock it (optionally zero-padded to SIZE).
configure / config <action>
Apply a single configuration action (see 'configure actions' below).

configure actions (use as: configure <action>):
C0-microSD+:
blue-led-off
blue-led-on
core-start
core-stop
debug-pin-0-off
debug-pin-0-on
debug-pin-1-off
debug-pin-1-on
debug-pin-2-off
debug-pin-2-on
green-led-off
green-led-on
lock-bitstream
red-led-off
red-led-on
sw-led-off
sw-led-on
unlock-bitstream (prompts for confirmation)
write-crc-force-ok-disable
write-crc-force-ok-enable
write-crc-force-write-disable
write-crc-force-write-enable
write-crc-irq-clear
write-crc-irq-connect
write-crc-irq-disconnect

The --variant option is optional. The toolkit reads the device identity region of the module to identify which one it is talking to, and that region is accessible whether the core is running or stopped. Use --variant=C0-microSD+ if you wish to explicitly specify the variant. See Confirm the module identity.

Each command takes options of its own, which --help lists separately. info accepts --raw and --stop-core.

sudo python3 C0_SD_toolkit.py /dev/disk4 info --help

Flash an application

Flash an application in two steps. Stop the core first, so that the application currently on the module cannot access the SPI flash while the toolkit writes to it.

  1. Stop the core.

    sudo python3 C0_SD_toolkit.py /dev/disk4 config core-stop
  2. Flash the binary. Pass the binary to the flash-application command.

    sudo python3 C0_SD_toolkit.py /dev/disk4 flash-application program.bin

    The toolkit writes the binary into the SPI flash user data region, reads the same range back, and compares the two. It makes up to five attempts and reports success once the readback matches. Add -p SIZE to zero-pad the input file to a target size before writing.

MicroSD-to-SD card adapters usually carry a small write-protect switch on one side. When that switch sits in the locked position, the host refuses to write to the module and the toolkit fails with a permission error. Slide the switch to the unlocked position and run the command again.

Where the binary is written

The flash-application command writes to the SPI flash user data region, which starts at address 0x00200000 and runs to the top of the 16 MiB flash.

RegionBase addressSizeContents
SPI flash, bitstream region0x000000002 MiBSignaloid-supplied FPGA bitstream
SPI flash, user data region0x0020000014 MiBYour application binary and its data

Addresses are the same for the host and for the Signaloid SoC, so the region your host writes over the SD interface is the region the core reads. For the whole map, see the unified address map.

The BITSTREAM_UNLOCK register gates writes into the bitstream region: a write is accepted only when the register holds the unlock key. Flashing an application writes to the user data region, so it cannot damage the bitstream. See The bitstream region lock.

Neither region is accessible from the host while the core is running, which is why step 1 above stops the core. See SD access while the core runs.

Start and stop the core

The core is held in reset by bit 0 of the CONFIG register at 0x08204000. That bit is named rstn and is active low, so writing 1 releases the core from reset and lets it run, and writing 0 holds the core in reset. Starting, stopping, and resetting the core all come down to that one bit.

Start the Core

sudo python3 C0_SD_toolkit.py /dev/disk4 config core-start

This sets bit 0 of CONFIG to 1 and releases the core, which begins executing the application you flashed into the user data region of the SPI flash at 0x00200000. The core keeps running until you stop it, until the application halts itself, or until the module loses power.

Starting a core that is already running is harmless. The toolkit reads CONFIG, sets bit 0, and writes the whole register back, so a second core-start leaves a running core running.

Because the toolkit always performs a read, a modify, and a write, the LED and debug pin bits of CONFIG keep their current values. If you drive CONFIG directly from your own host application rather than through the toolkit, read the register first and preserve the other bits. Writing a bare bit mask clears rstn and stops the core.

Stop the Core

sudo python3 C0_SD_toolkit.py /dev/disk4 config core-stop

This clears bit 0 of CONFIG and holds the core in reset. The FPGA bitstream keeps running, so the module stays enumerated as a block-storage device and every register and buffer stays readable and writable from the host while the core is stopped. Only the RISC-V core stops.

A device application can also halt itself from inside the SoC by calling C0HALStopCore(), which suits a one-shot workload that should not keep spinning after it has produced its result. Restarting such an application from the host is a core-start away.

Stop the core in the following situations.

  • Before any host access to the SPI flash or the flash OTP. While the core is running, the SD interface cannot access either, so a read returns zeros and a write is discarded. This covers flashing from your own host code, reading a binary back, and running info. Commands flash-application and flash-bitstream stop the core, so you only need to use them when you drive the flash directly.
  • Before you unlock the bitstream region. The hardware forces the unlock key to 0x00000000 while the core runs, so an unlock only takes effect once the core is stopped.
  • When a workload is complete and you want the module idle in its slot rather than spinning in the polling loop of your application.
  • When a device application has stopped responding and you want to inspect its registers without the core continuing to write to them.

Reset the Core

There is no separate reset action. A reset is a stop followed by a start.

sudo python3 C0_SD_toolkit.py /dev/disk4 config core-stop
sudo python3 C0_SD_toolkit.py /dev/disk4 config core-start

Leave about one second between the two commands so that the core settles in reset before you release it. The reset_core() helper in the signaloid-utilities Python package does exactly this, stopping the core, waiting one second, starting the core, and waiting one second more.

A reset restarts your device application from its entry point. It does not re-flash the application and it does not touch the FPGA bitstream, so the core comes back running the same binary it was running before.

Check the state of the Core

The status command reports the registers that describe what the core and the device application are doing.

% sudo python3 C0_SD_toolkit.py /dev/disk4 status

Detected compute module: C0-microSD+
COMMAND: 0x00000000
CONFIG: 0x00000001
STATUS: 0x00000000
SD_CONFIG: 0x00000001

Read the report as follows.

RegisterExample valueHow to read it
COMMAND0x00000000The application-defined operation code the host wrote most recently. In the example no command is pending.
CONFIG0x00000001Bit 0 (rstn) is set, so the core is running. A value with bit 0 clear, for example 0x00000000, means the core is held in reset.
STATUS0x00000000The application-defined status the device application wrote most recently. 0x00000000 is the conventional waiting for command value.
SD_CONFIG0x00000001The SD block-write CRC handling configuration. Leave the default in place unless you are bringing up an SD host controller you are debugging.

The example above shows a module whose core is already running, so the CONFIG value is not the power-up default. A module that has just powered up reports CONFIG as 0x00000000, with the core held in reset. For the reset value of every register, see MMIO Memory Map and Registers.

The fastest check of whether the core is running is bit 0 of CONFIG. A CONFIG value whose low bit is 1 means the core has been released, and a value whose low bit is 0 means the core is held in reset. For the meaning of every other bit, see Host Interface and Protocol.

status reads only the CSRs, so it works whether the core is running or not. This is why you can use the command on a running module.

Print the module information and the decoded bitstream metadata with the info command. Unlike status, info reads the SPI flash and the flash OTP, so it needs the core stopped.

sudo python3 C0_SD_toolkit.py /dev/disk4 config core-stop
sudo python3 C0_SD_toolkit.py /dev/disk4 info
sudo python3 C0_SD_toolkit.py /dev/disk4 config core-start

Pass --stop-core to have info do the stop and the restart for you. See Stop the core before you run info.

LED behavior

The config command also exposes actions that drive the on-board LEDs directly, namely sw-led-on, sw-led-off, red-led-on, red-led-off, green-led-on, green-led-off, blue-led-on, and blue-led-off. From inside a device application, C0HALSetLed() drives the green LED.

Next steps