Part 1. One core · lesson 4 of 42
4. Hello, core¶
A Komorebi program is a folder with three C++ files, one for each of three roles, and a Python file that loads them onto the cores and sends the cores a command. This lesson takes the smallest such program apart, line by line. It relies on lesson 1, What Komorebi is, lesson 2, Setup and the first run and lesson 3, The card.
Why there are three files¶
Every Tensix core has five CPUs (lesson 3, The card). Komorebi runs one small C++ function on each of three sets of them:
reader.cppruns on the first of the two CPUs that move data,writer.cppruns on the second of them,compute.cppruns on the three that do arithmetic.
Such a function is called a kernel. It is the code that runs on a core, as opposed to the Python that runs on your computer. Each is a file of its own, all in one folder. Komorebi always loads all three onto the cores, so a program always has all three files, even when a role has nothing to do. In this lesson none has: the program does nothing on the core except to be asked.
The two data-movement CPUs are alike. Either can read from DRAM, write to DRAM, and send to and receive from other cores, and a kernel on one of them may do all of these. "Reader" and "writer" are only the traditional names of the two. A program often has the first bring data in and the second send it out, and the tutorial does the same from lesson 16, Reading from DRAM on, but that is the program's choice. Three things are not a choice:
- the two CPUs use different NoCs, one each, fixed for the program: by default the first uses NoC 0 and the second NoC 1;
- a queue of data in L1 has exactly one CPU that fills it and one that empties it;
- in Komorebi the second CPU is the one that reports the command to Python, and the one that can attach a result to it (lesson 6, A result).
The general form of a kernel file¶
Each of the three files has the same shape. Words in capitals are the parts you choose.
#include "komorebi/device/km_worker.hpp"
void kernel_main() {
km::ROLE name;
name.run([&](const km_command_t& command) {
BODY
});
}
#include "komorebi/device/km_worker.hpp"brings in everything a kernel needs from Komorebi.void kernel_main()is the function the card calls. The name is fixed.km::ROLE name;makes the object that stands for the role:km::Reader,km::Computeorkm::Writer, the one that belongs to the file.nameis yours; the tutorial uses the role's own word:reader,compute,writer.name.run(...)is a loop that you do not write. It waits until a command arrives for this core, calls the function you gave it once, and waits again. It goes on until the program is stopped.[&](const km_command_t& command) { BODY }is that function: a C++ lambda that takes the command that arrived.BODYis the work. The command is the only parameter, and you do not use it yet.
In writer.cpp, BODY must be return true;. That true is writer.cpp saying "I am
done with this command, report it". The functions of reader.cpp and compute.cpp return nothing.
This is the whole writer.cpp of the program in this lesson:
The function has one line. A command arrives, the function returns true, the core
reports.
The general form of the Python file¶
program = km.Program(NAME, kernels=FOLDER)
with km.Session() as session:
run = session.load(program)
handle = run.submit(KIND, group=(FIRST, COUNT))
event = run.wait(handle, timeout=SECONDS)
run.stop()
NAMEis a name for the program, a string. It labels messages.FOLDERis the folder with the three kernel files. The programs of this tutorial write itHERE / "kernels", whereHEREis the folder that holds the Python file itself, so that the program runs from any directory.KINDis a number that says what kind of work the command is. It is always0in this lesson; lesson 8, Handlers gives it a meaning.FIRSTandCOUNTsay which cores the command is for:COUNTcores, starting at core numberFIRST. Together they are the command's group.SECONDSis how longwaitmay wait before it gives up with an error.
One line at a time¶
A Program is a description of what the cores will run: a name and the folder with the
three files. Nothing is compiled and the card is not touched yet.
A Session is the open card. with opens it at the start of the block and closes it at
the end, also when an error ends the block early. Everything that follows is inside the
block.
load compiles the three files, copies the kernels onto all 120 cores, and starts them.
The kernels now run and wait for commands. What load returns, run, is the program
loaded and running: the handle on it for the rest of the program.
submit sends one command to the cores. A command is a message from Python to cores
that says: run your kernels once, now. submit does not wait for the cores; it returns at
once with a handle, a ticket that names this command, so that you can ask about it
later.
Only core 0 runs the command. Every core sees every command, but a core that is not in the group lets it pass.
wait blocks until the command is finished, then returns an event: the card's report
about that command. If the command takes longer than timeout seconds, wait raises an
error instead.
stop tells the kernels on every core to finish, and waits until they have. After
stop no command can be sent.
event.kind says how the command ended. done means that every core of the group
reported that it finished.
What happens, in order¶
The whole program¶
"""Hello, core: the smallest program. One command to one core, and its answer."""
from pathlib import Path
import komorebi as km
HERE = Path(__file__).resolve().parent
program = km.Program("hello", kernels=HERE / "kernels")
with km.Session() as session:
run = session.load(program)
handle = run.submit(0, group=(0, 1))
event = run.wait(handle, timeout=10.0)
run.stop()
print(f"the command is {event.kind}")
Run it from the folder tutorial/04_hello_core:
It prints one line. The program did nothing on the core, and that is the point: the command arrived, three empty functions ran, the core reported, and Python heard it. Everything the later lessons do is a way to make those three functions do something.
Common mistakes¶
- A missing file. If the folder lacks one of the three,
loadstops withFileNotFoundError: a: no kernel source [.../compute.cpp, .../writer.cpp], naming the missing files. - A group outside the card.
group=(0, 500)stops atsubmitwithValueError: group (0, 500) is not inside 120 cores. The cores are numbered 0 to 119. - A writer that returns
false, or does not returntrue. The core never reports, and after about a second the card gives up on the command andwaitraisesCommandTimeout. lesson 10, When it goes wrong is about this.
Next: lesson 5, Commands looks at what a command is, and at what it costs.