Part 5. X280 threads · lesson 33 of 42
33. A thread that submits¶
A thread sends commands to the Tensix cores with kmx_submit and waits for them with
kmx_wait, the same two steps that run.submit and run.wait are in Python. This lesson
relies on lesson 32, Hello, thread, lesson 5, Commands, lesson 6, A result and lesson 7, Arguments.
Why a thread submits¶
Until now Python decided what the cores do next: it sent a command, waited, and sent the next one. Every decision then goes through the host and the cable to the card. A thread sits on the card. It can read a result and send the next command at once, and Python is only told at the end. lesson 31, Streams and graphs sent commands one after another to save that trip; a thread is the other way to save it, and it can decide with real C code.
The general form¶
km_command_t command;
command.sequence = command.land1 = command.land2 = command.land3 = 0;
command.hdr = HANDLER | KM_FLAG_RESULT << 8;
command.group = FIRST | COUNT << 16;
command.a0 = A0;
...
command.a9 = A9;
uint32_t handle;
int sent = kmx_submit(&command, &handle);
kmx_event event;
int waited = kmx_wait(handle, kmx_time() + TICKS, &event);
km_command_tis the command, the very structure that a kernel receives ascommand(lesson 4, Hello, core). The thread fills it field by field. A single assignment such ascommand = (km_command_t){0}may become a call of a library function that the thread does not have, and then it does not link.sequence,land1,land2andland3are fields the library fills in. Set them to 0.hdrholds the handler number (lesson 8, Handlers) and, shifted left by 8 bits, the flags.KM_FLAG_RESULTis the flag that asks for a result word (lesson 6, A result).groupholds the first core and the count:FIRST | COUNT << 16, thegroup=(FIRST, COUNT)of Python.a0toa9are the arguments (lesson 7, Arguments). Set every one, also the ones you do not use.kmx_submit(&command, &handle)stores the command for the cores. It returnsKMX_OKand the handle, or another code, for instanceKMX_FULLwhen no slot is free.kmx_wait(handle, DEADLINE, &event)waits for the event of that handle.DEADLINEis a time on the thread's clock, which ticks 50 million times a second: now,kmx_time(), plus theTICKSyou will wait.KMX_TICKS_PER_SECONDis one second. It returnsKMX_OKand the event, orKMX_LATEwhen the time passed first.event.kindsays how the command ended:KM_EVENT_DONEis the normal end. WithKM_FLAG_RESULTset,event.ticksis the result word that the leader of the group sent; without it, it is the time the command took.
Only core 0 of the cluster submits and waits.
One line at a time¶
The kernel is writer.cpp of lesson 7, Arguments, with a product in place of the sum:
writer.run([&](const km_command_t& command) {
const uint32_t left = command.a0;
const uint32_t right = command.a1;
writer.result(left * right);
return true;
});
The command, built from the two numbers that Python gave the thread:
km_command_t command;
command.sequence = command.land1 = command.land2 = command.land3 = 0;
command.hdr = KM_FLAG_RESULT << 8;
command.group = 0 | 1 << 16;
command.a0 = arguments->words[0];
command.a1 = arguments->words[1];
command.a2 = command.a3 = command.a4 = command.a5 = 0;
command.a6 = command.a7 = command.a8 = command.a9 = 0;
Handler 0, the flag for a result, and the group of the one core 0. The ten arguments are all set.
uint32_t handle;
if (kmx_submit(&command, &handle) != KMX_OK) {
return 1;
}
kmx_event event;
if (kmx_wait(handle, kmx_time() + KMX_TICKS_PER_SECOND, &event) != KMX_OK) {
return 2;
}
The thread submits and waits at most one second. Either step may fail, and the thread then ends with a code of its own, 1 or 2, which Python can read.
The answer is the result word, and the thread returns it to Python as its own result.
The whole program¶
"""A thread that submits: the X280 sends a command to core 0 and returns its answer."""
from pathlib import Path
import komorebi as km
HERE = Path(__file__).resolve().parent
program = km.Program("thread_submits", kernels=HERE / "kernels")
program.thread(
cluster=1,
sources=[HERE / "thread" / "submit.c"],
regions={"unused": 64},
words=[6, 7],
)
with km.Session() as session:
run = session.load(program)
result = run.thread_result(timeout=10.0)
print(f"the thread ended with code {result.code}")
print(f"core 0 answered the thread with {result.words[0]} = 6 * 7")
run.stop()
assert (result.code, result.words[0]) == (0, 42)
/* An X280 thread that sends one command to core 0 and returns the answer. */
#include "kmx.h"
int kmx_main(int core) {
(void)core;
const kmx_args* arguments = kmx_args_get();
km_command_t command;
command.sequence = command.land1 = command.land2 = command.land3 = 0;
command.hdr = KM_FLAG_RESULT << 8;
command.group = 0 | 1 << 16;
command.a0 = arguments->words[0];
command.a1 = arguments->words[1];
command.a2 = command.a3 = command.a4 = command.a5 = 0;
command.a6 = command.a7 = command.a8 = command.a9 = 0;
uint32_t handle;
if (kmx_submit(&command, &handle) != KMX_OK) {
return 1;
}
kmx_event event;
if (kmx_wait(handle, kmx_time() + KMX_TICKS_PER_SECOND, &event) != KMX_OK) {
return 2;
}
const uint32_t answer = event.ticks;
kmx_result(0, &answer, 1);
return 0;
}
// The writer kernel: it answers every command with the product of its two arguments.
#include "komorebi/device/km_worker.hpp"
void kernel_main() {
km::Writer writer;
writer.run([&](const km_command_t& command) {
const uint32_t left = command.a0;
const uint32_t right = command.a1;
writer.result(left * right);
return true;
});
}
reader.cpp and compute.cpp are the empty ones. Python sends no command: it only starts
the program and reads the thread's result.
The 42 was made by a Tensix core, passed to the thread in the event, and passed on to Python in the thread's result.
Common mistakes¶
- Forgetting
KM_FLAG_RESULT. The event is then stillKM_EVENT_DONE, butevent.ticksis a time, not the numberwriter.cppset. - Leaving an argument unset. The command has ten, and an
a5that was never written holds whatever the stack held. Set all ten. - Not checking
kmx_submitandkmx_wait. Both return a code, and a thread that ignores it goes on with an event that is not there. - Submitting from cores 1 to 3 of the cluster (lesson 34, Four cores). Only core 0 may; the other three end the run with a library error.
Next: lesson 34, Four cores uses the cluster's other three cores.