Part 5. X280 threads · lesson 32 of 42
32. Hello, thread¶
A thread is a C program that runs on one X280 cluster while your Tensix kernels run on the grid; Python loads it with the program and collects what it returns. This lesson writes the smallest thread, one that adds two numbers. It relies on lesson 3, The card and lesson 7, Arguments.
Why a thread¶
The Tensix cores are many and small. Some work does not suit them: a decision that needs every core's numbers, work whose size you only learn while it runs, a loop that sends one command after another. That work belongs on a bigger core, and the X280 cores are the bigger cores on the card. lesson 3, The card showed them in four clusters of four. Cluster 0 is Komorebi's own. Clusters 1, 2 and 3 are yours, and a thread runs on one of them.
A thread is ordinary C, with no standard library, and it starts when load runs. It does
not compute on tiles. This lesson uses it alone, to show how it is written, started and
read; the lessons after it give it work.
The general form¶
The C file:
#include "kmx.h"
int kmx_main(int core) {
const kmx_args* arguments = kmx_args_get();
BODY
kmx_result(CODE, WORDS, COUNT);
return 0;
}
In Python, next to km.Program(...) and before load:
and after load:
#include "kmx.h"brings in the thread's library,kmx. Every call of it starts withkmx_.kmx_mainis the function the library calls on the first of the cluster's four cores, the waykernel_mainis called on a Tensix core. The name is fixed.coreis that core's number, 0 here. Whenkmx_mainreturns, the thread has ended.kmx_args_get()returns the arguments of the thread.arguments->words[0]is the first number that Python gave inwords=,arguments->words[1]the second, and so on.kmx_result(CODE, WORDS, COUNT)stores what Python will read: aCODE, by convention 0 for "worked", andCOUNTnumbers from the arrayWORDS, at most 48.return 0;ends the thread. The value returned is the thread's code, unlesskmx_resultgave one earlier.CLUSTERis the X280 cluster to run on, 1, 2 or 3.FILESare the C files of the thread, as paths.NUMBERSare up to 64 whole numbers for the thread, 32 bits each, not negative. They are the thread's arguments, asargs=is a command's (lesson 7, Arguments).regions={NAME: SIZE}names a block of memory ofSIZEbytes that Python and the thread can both reach. A block like this is a region. A thread must have at least one, and this thread does not use it; lesson 35, Sharing memory is about regions.SECONDSis how longthread_resultwaits for the thread to end.
The Tensix side still needs its three kernel files (lesson 4, Hello, core). Here they are the empty ones, and no command is sent to the grid.
One line at a time¶
program.thread(
cluster=1,
sources=[HERE / "thread" / "hello.c"],
regions={"unused": 64},
words=[20, 22],
)
This is the whole declaration of the thread. The line only describes it, as Program
describes the kernels: nothing runs until load.
/* The smallest X280 thread: it adds the two numbers Python gave it. */
#include "kmx.h"
int kmx_main(int core) {
(void)core;
const kmx_args* arguments = kmx_args_get();
const uint32_t sum = arguments->words[0] + arguments->words[1];
kmx_result(0, &sum, 1);
return 0;
}
The thread reads its two numbers, adds them in a constant sum, and stores sum as the
one result word. &sum is the address of the number: kmx_result takes an array, and a
single number is an array of one.
result = run.thread_result(timeout=10.0)
print(f"the thread ended with state {result.state}, code {result.code}")
print(f"it returned {result.words[0]} = 20 + 22")
print(f"the thread ran for {result.microseconds:.0f} microseconds")
thread_result waits until the thread has ended and returns its result: the state
the thread ended in, its code, the list words that it stored, and how long it ran, in
microseconds.
The whole program¶
"""Hello, thread: a C function on an X280 core adds two numbers and returns the sum."""
from pathlib import Path
import komorebi as km
HERE = Path(__file__).resolve().parent
program = km.Program("hello_thread", kernels=HERE / "kernels")
program.thread(
cluster=1,
sources=[HERE / "thread" / "hello.c"],
regions={"unused": 64},
words=[20, 22],
)
with km.Session() as session:
run = session.load(program)
result = run.thread_result(timeout=10.0)
print(f"the thread ended with state {result.state}, code {result.code}")
print(f"it returned {result.words[0]} = 20 + 22")
print(f"the thread ran for {result.microseconds:.0f} microseconds")
run.stop()
assert (result.code, result.words[0]) == (0, 42)
The three kernel files in kernels/ are those of lesson 4, Hello, core. Run it from the folder
tutorial/32_hello_thread:
the thread ended with state done, code 0
it returned 42 = 20 + 22
the thread ran for 10 microseconds
The state is done, the code 0 that the thread chose, and the sum 42 came from a core
that is not a Tensix core. The thread ran for about ten microseconds.
Common mistakes¶
- No region.
program.thread(...)withoutregions=stops withValueError: 1 to 16 regions, shared ones included. - A cluster that is not 1, 2 or 3.
cluster=4stops withValueError: cluster 4: work clusters are 1 to 3. - Too many numbers, or a negative one. 65 words stop with
ValueError: at most 64 words, andwords=[-1]withValueError: words are 32-bit unsigned. - A thread that does not end. When
timeoutruns out,thread_resultdoes not raise: it asks the thread to stop, and returns a result whoseranisFalse. A thread that loops untilkmx_stop_requested()is true, then returns 7, showsran=False, andcode=7. lesson 37, A core asks the X280 and lesson 39, Finding a bug use this.
Next: lesson 33, A thread that submits lets the thread send a command to the Tensix cores.