Part 5. X280 threads · lesson 37 of 42
37. A core asks the X280¶
An X280 port joins one Tensix core and one X280 core, and lets each send the other short messages while a command runs. This lesson relies on lesson 33, A thread that submits and lesson 13, The leader.
Why a port¶
Until now a thread and the Tensix cores only met at the edges: a command from the thread, an event back, a region at the end. Some work needs a talk in the middle. A Tensix core that meets a decision it cannot make cheaply, such as which piece of work to take next, can ask a bigger core on the card and wait for the answer. An X280 port is that line. It is a different thing from the port of lesson 25, A port, which is a counter on a Tensix core: this one connects a Tensix core to an X280 core. It has no dispatcher in its path, and it is fast: a message is a number that identifies it and three 32-bit words.
There are two kinds of message, and a program can use both, in both directions:
- A tell is a message that the receiver looks at when it likes. If two tells arrive before the receiver looks, only the newest is seen.
- An ask is a message that expects an answer. The asker waits for the answer to that ask, and the other side answers it by the number that came with it.
The general form¶
In Python, next to program.thread(...):
On the Tensix core, in reader.cpp, with #include "komorebi/device/km_x280.hpp":
km::X280Port port(reader);
...
km::X280Msg reply;
bool got = port.call(A, B, C, reply);
port.tell(A, B, C);
In the thread, the function that serves, and the call that starts a command and serves it:
static void serve(int cluster, int cpu, uint64_t argument) {
while (kmx_port_open()) {
kmx_message message;
if (kmx_port_asked(CORE, &message) == KMX_OK) {
kmx_port_answer(CORE, message.sequence, A, B, C);
}
if (kmx_port_told(CORE, &message) == KMX_OK) {
...
}
}
}
kmx_event event;
int status = kmx_port_run(&command, serve, ARGUMENT, &event);
ports({CORE: (CLUSTER, CPU)})says which X280 core serves which Tensix core: Tensix core numberCOREis served by coreCPU, 0 to 3, of clusterCLUSTER. Every served core has exactly one. The thread must run on the card, and on that cluster.km::X280Port port(reader)makes the core's end of the port.reader.cppowns it; the other kernels do not use it.port.call(A, B, C, reply)asks, with three numbers, and waits for the answer. It returnstruewhen the answer came. The answer's three numbers arereply.w[0],reply.w[1],reply.w[2].port.tell(A, B, C)tells the X280 core three numbers and does not wait.kmx_port_owner(CORE)is the X280 core that serves Tensix coreCORE; the functionserveruns on every X280 core, and the one that is not the owner has nothing to do.kmx_port_asked(CORE, &message)looks for an ask from that core that is not answered yet. It returnsKMX_OKand the message, orKMX_NONE, and never waits. The ask's numbers aremessage.w[0]tomessage.w[2], andmessage.sequenceis the number of the ask.kmx_port_answer(CORE, message.sequence, A, B, C)answers that ask.kmx_port_told(CORE, &message)looks for a tell that is newer than the last one it saw. It returnsKMX_OKand the message, orKMX_NONE, and never waits.kmx_port_open()is 1 while the command is running and 0 once its event has come, or the run was stopped.kmx_port_run(&command, serve, ARGUMENT, &event)submits the command likekmx_submitdoes (lesson 33, A thread that submits), runsserveon every X280 core of every cluster that runs the thread until the event comes, and returns it inevent.
One line at a time¶
Tensix core 0 is served by core 0 of cluster 1. That is the one X280 core that the program uses for it.
reader.run([&](const km_command_t& command) {
const uint32_t number = command.a0;
km::X280Msg answer;
if (!port.call(number, 0, 0, answer)) {
return;
}
port.tell(answer.w[0] + 1, 0, 0);
});
reader.cpp of core 0 asks for the number a0 of its command, waits for the answer, and
tells the X280 core the answer plus one. If the ask gets no answer it gives up and tells
nothing. The tell is the proof, for the thread, that the answer arrived.
static void look(void) {
kmx_message message;
if (kmx_port_asked(0, &message) == KMX_OK) {
kmx_port_answer(0, message.sequence, message.w[0] * message.w[0], 0, 0);
}
if (kmx_port_told(0, &message) == KMX_OK) {
proof = message.w[0];
}
}
One look at the port of core 0: answer an ask with the square of its number, and keep what a tell says.
static void serve(int cluster, int cpu, uint64_t argument) {
(void)cluster;
(void)argument;
if (kmx_port_owner(0) != cpu) {
return;
}
while (kmx_port_open()) {
look();
}
look();
}
The serving function. Only the X280 core that owns Tensix core 0 does anything. It looks again and again while the command runs, and once more after it ended, because the tell can come just before the end.
The thread sends the command and serves it until its event comes. The thread then returns the tell that it saw as its result.
The whole program¶
"""A core asks the X280: core 0 of the grid asks a thread for a square."""
from pathlib import Path
import komorebi as km
HERE = Path(__file__).resolve().parent
NUMBER = 12
program = km.Program("core_asks_x280", kernels=HERE / "kernels")
program.thread(
cluster=1,
sources=[HERE / "thread" / "serve.c"],
regions={"unused": 64},
words=[NUMBER],
)
program.ports({0: (1, 0)})
with km.Session() as session:
run = session.load(program)
result = run.thread_result(timeout=10.0)
run.stop()
print(f"core 0 asked for the square of {NUMBER}")
print(f"it told the X280 {result.words[0]}, which is the answer plus one")
assert result.code == 0 and result.words[0] == NUMBER * NUMBER + 1
/* An X280 thread that serves core 0 of the grid: it answers core 0's ask with the
* square of the number, and returns what core 0 tells it afterwards. */
#include "kmx.h"
__attribute__((section(".data"))) static volatile uint32_t proof = 1;
static void look(void) {
kmx_message message;
if (kmx_port_asked(0, &message) == KMX_OK) {
kmx_port_answer(0, message.sequence, message.w[0] * message.w[0], 0, 0);
}
if (kmx_port_told(0, &message) == KMX_OK) {
proof = message.w[0];
}
}
static void serve(int cluster, int cpu, uint64_t argument) {
(void)cluster;
(void)argument;
if (kmx_port_owner(0) != cpu) {
return;
}
while (kmx_port_open()) {
look();
}
look();
}
int kmx_main(int core) {
(void)core;
km_command_t command;
command.sequence = command.land1 = command.land2 = command.land3 = 0;
command.hdr = 0;
command.group = 0 | 1 << 16;
command.a0 = kmx_args_get()->words[0];
command.a1 = command.a2 = command.a3 = command.a4 = command.a5 = 0;
command.a6 = command.a7 = command.a8 = command.a9 = 0;
kmx_event event;
if (kmx_port_run(&command, serve, 0, &event) != KMX_OK) {
return 1;
}
const uint32_t told = proof;
kmx_result(event.kind == KM_EVENT_DONE ? 0 : 2, &told, 1);
return 0;
}
// The reader kernel: it asks the X280 for the square of its argument, and tells the
// X280 the answer plus one, as proof that the answer arrived.
#include <stdint.h>
#include "api/dataflow/dataflow_api.h"
#include "komorebi/device/km_worker.hpp"
#include "komorebi/device/km_x280.hpp"
void kernel_main() {
km::Reader reader;
km::X280Port port(reader);
reader.run([&](const km_command_t& command) {
const uint32_t number = command.a0;
km::X280Msg answer;
if (!port.call(number, 0, 0, answer)) {
return;
}
port.tell(answer.w[0] + 1, 0, 0);
});
}
compute.cpp and writer.cpp are the empty ones of lesson 4, Hello, core. Run it from the folder
tutorial/37_core_asks_x280:
The 145 is the square of 12, plus the one that the Tensix core added. It could only reach Python by going from the thread to the core in an answer, and back from the core to the thread in a tell.
Common mistakes¶
- Declaring the ports twice. A second
program.ports(...)stops withValueError: ... already declares its X280 ports. - Looking only while
kmx_port_open()is true. A message that comes just before the command's end is missed. Look once more after the loop, asservedoes. - Expecting every tell to arrive. A tell is the newest message: two tells in a row, with no look between, leave only the second.
- An ask that nobody answers.
port.callwaits, with a limit, and returnsfalse.
Next: lesson 38, Measuring starts Part 6 with how long things take.