How to Create a New Example
This guide walks you through creating a new Tungsten + Paint example from scratch, step by step.
Step 1: Create the Directory
mkdir my-example
cd my-example
Create the directory anywhere; the build needs only the setup in the
tutorial's prerequisites, with FLUTEMK and
TESTMK set.
Step 2: Write the Tungsten Kernel(s)
Create one .w file per PE role. For a single-PE example, you need just one
file. For several PEs, create a file for each distinct role (e.g., send.w,
compute.w, recv.w).
Single-PE template: my_kernel.w
/* my_kernel.w — describe what this kernel does */
xp param N; /* parameters from Paint */
xp a[n=N] = {0, 1, 2, 3}; /* input data, for N=4 */
xp b[n=N]; /* output data */
let i in [0, N);
∀i b[i] <- a[i] * 2; /* your computation */
Multi-PE template
send.w — sends data to the next PE:
xp param N;
socket tx;
xp a[n=N] = {0, 1, 2, 3}; /* for N=4 */
let i in [0, N);
∀i tx[] <- a[i];
recv.w — receives data from the previous PE and doubles it, as the
single-PE template does:
xp param N;
socket rx;
xp b[n=N];
let i in [0, N);
∀i b[i] <- rx[] * 2;
Message-Passing Template
For MP-based communication (no static color routes):
sender.w — sends via coordinate-based message passing:
xp param dst_col; // destination column (row 0), from Paint
xp param my_value; // value to send, from Paint
socket mp_sock; // bound to color(bank) in Paint
xp row_id[1] address(0x7C45); // own row coordinate
union mp_header {
u32 u;
xp w[2];
};
union payload {
u32 u;
xp w[2];
};
{
// north (0x80) only if the destination has a smaller row ID, south
// (0x40) only if it has a larger one; neither on the same row
xp dir = 0;
if (row_id[0] > 0) dir <- 0x80;
union mp_header hdr;
hdr.w[1] <- dst_col << 4; // destination column; row 0
hdr.w[0] <- 1 | dir; // direction, len=1
union payload data;
data.w[0] <- my_value;
data.w[1] <- 0;
mp_sock[] <- hdr.u;
mp_sock[] <- data.u;
}
receiver.w — receives MP message:
socket mp_sock;
xp result[n=1];
union mp_header { u32 u; xp w[2]; };
union payload { u32 u; xp w[2]; };
{
union mp_header hdr;
hdr.u <- mp_sock[];
union payload data;
data.u <- mp_sock[];
result[0] <- data.w[0];
}
Critical: All wavelets on MP colors must be 32 bits. Never send raw
xpon an MP socket — always send au32. A union is one way to build it, andu32shifts or a(u32)cast work too.
Design Principle: One Kernel Per Role
Never duplicate a Tungsten kernel just to change initialisation data. If several PEs run the same computation but with different data (e.g., different rows of a matrix), write one kernel with uninitialised arrays and use
misetto load per-PE data after compilation.
Bad practice — duplicating kernels:
mul0.w -> hp row[k=K] = { 1.0, 2.0 }; # DON'T
mul1.w -> hp row[k=K] = { 3.0, 4.0 }; # DON'T
Good practice — single kernel + miset:
/* compute.w — one file for all compute PEs */
hp row[k=K]; /* uninitialised — filled by miset */
# Makefile — load different data per PE into the linked image
MISET = miset image=$@ out=$@ map=bench.map
# miset reads bench.map, so the rule lists it
bench.img: bench.obj row0.bin row1.bin bench.map
cp $< $@
$(MISET) symbol=mul0.row order="k" data=row0.bin
$(MISET) symbol=mul1.row order="k" data=row1.bin
The topic Matrix multiplication across PEs is a complete working example of this pattern.
An array of a 32-bit type (sp, u32) has a second dimension in the map,
_word, for the two 16-bit words of each element, low word first. List it in
order, as in order="k _word"; with order="k", miset stops with
row0.bin: too long.
Step 3: Write the Paint Layout
Create bench.paint to describe PE placement and routing. Paint's tile()
makes one PE.
Single-PE
let t = tile()
t : code('my_kernel [N:N])
Two PEs with communication
let c = color()
let send = tile()
send : code('send [tx:c N:N])
send : paint(c [C>R])
send : name('send)
let recv = tile()
recv : code('recv [rx:c N:N])
recv : paint(c [L>C])
recv : name('recv)
hstack(send recv)
Message-Passing Layout
let b0 = bank(0)
let b0color = color(b0)
let sender = tile()
sender : code('sender [mp_sock:b0color dst_col:0 my_value:42])
sender : mp(b0)
sender : name('sender)
let receiver = tile()
receiver : code('receiver [mp_sock:b0color])
receiver : mp(b0)
receiver : name('receiver)
hstack(receiver sender)
Key: every PE on a message's path needs mp(b0), including the PEs that only
pass it on.
Checklist for Paint
- One
color()per independent data stream -
code()binds all sockets and parameters from the.wfile -
paint()rules for each color on each PE - For MP:
bank(),color(bank), andmp(bank)on every PE involved -
name()on every region of a multi-PE layout (migetsymbols are<region>.<variable>) - Layout combinator (
hstack,vstack,union) to position the PEs
Step 4: Write the Makefile
Standard template (with input extraction)
include $(FLUTEMK)
include $(TESTMK)
BENCH_NAME=my_kernel
# The cycle cap; a test.rc row or the command line can set CYCLES
CYCLES ?= 1000
SIMFLAGS=cycles=$(CYCLES) $(SIMOPTS)
# Paint variables taken from the test.rc row
PAINTVARS=N
PAINTFLAGS=lib=.. $(PAINTOPTS) verbose arch=${ARCH}
PIPELINEFLAGS=arch=${ARCH}
MODE ?= sim
all: test
clean: cleanup
test: test/$(MODE)
test/$(MODE): out-ref.bin out-$(MODE).bin
cmp $^
# Extract input data from compiled image
in.bin: bench.img bench.map
miget image=$< map=bench.map symbol=a data=$@
# Extract output from simulation core dump
out-%.bin: bench-%.core bench.map
miget image=$< map=bench.map symbol=b data=$@
# Generate reference output
out-ref.bin: in.bin
python3 ../ref.py $< $@
For the two-PE layout, each symbol starts with the name that name() gives
its region in Step 3. Replace the template's two miget rules with these:
in.bin: bench.img bench.map
miget image=$< map=bench.map symbol=send.a data=$@
out-%.bin: bench-%.core bench.map
miget image=$< map=bench.map symbol=recv.b data=$@
Self-contained template (ref.py generates its own output)
For examples whose inputs are fixed at build time, in the kernel or the layout (common with MP):
include $(FLUTEMK)
include $(TESTMK)
BENCH_NAME=receiver
CYCLES ?= 2000
SIMFLAGS=cycles=$(CYCLES) $(SIMOPTS)
PAINTFLAGS=lib=.. $(PAINTOPTS) verbose arch=${ARCH}
PIPELINEFLAGS=arch=${ARCH}
MODE ?= sim
all: test
clean: cleanup
test: test/$(MODE)
test/$(MODE): out-ref.bin out-$(MODE).bin
cmp out-ref.bin out-$(MODE).bin
# Generate reference output (self-contained, no input files)
out-ref.bin:
python3 ../ref.py
# Extract simulation output
out-%.bin: bench-%.core bench.map
miget image=$< map=bench.map symbol=receiver.result data=$@
Note:
lib=..inPAINTFLAGSlets Paint find.wfiles in the parent directory. This is needed because the build runs in a subdirectory (test-tutorial/).
Makefile Variables
| Variable | Required | Description |
|---|---|---|
FLUTEMK | Yes | Provides build rules for bench.paint → image (Paint compiles each .w with Carbide) → simulation core dump |
TESTMK | Yes | Provides test harness (TEST= row selection, cleanup, etc.); test/$(MODE) is defined in your Makefile |
CYCLES | No | The cycle cap the templates pass in SIMFLAGS; a test.rc row can set it |
SIMFLAGS | No (defaults to $(SIMOPTS)) | Simulation options: cycles=N sets max cycles |
PAINTVARS | If needed | Space-separated list of variables to pass to Paint from test.rc |
PAINTFLAGS | Yes | Flags for the paint tool: lib=, arch=, etc. |
PIPELINEFLAGS | Only for the debug bundle | Pipeline flags: arch=${ARCH} |
MODE | Optional | sim (the default) or another execution mode |
Symbol Paths in miget
| PEs | Symbol syntax | Example |
|---|---|---|
| One, unnamed | symbol=array_name | symbol=a |
| Named regions | symbol=region_name.array_name | symbol=send.a |
| Nested | symbol=parent.child.array_name | symbol=outer.inner.A |
Step 5: Write the Test Configuration
Create test.rc with one or more test instances. The templates that list
N in PAINTVARS take it from the row:
tutorial N=4 ARCH=sdr
A layout with no row variables, such as the message-passing one, needs only the architecture:
tutorial ARCH=sdr
Format
LABEL PARAM1=VALUE1 PARAM2=VALUE2 ARCH=arch
| Field | Description |
|---|---|
LABEL | Test name: tutorial, smoke, full, etc. |
PARAM=VALUE | Sets a make variable (and Paint variable if in PAINTVARS) |
ARCH=sdr | Target architecture: sdr (WSE-3) |
A label is any name. Every lesson labels its first row tutorial, and the run
instructions use make TEST=tutorial:
| Label | Description |
|---|---|
tutorial | The first row; make TEST=tutorial runs it |
n8, full, … | Other rows, named for what they test |
# at line start | Row disabled (reported SKIP under TEST='*') |
Multiple test lines create multiple test instances:
tutorial N=4 ARCH=sdr
n8 N=8 ARCH=sdr
full N=16 ARCH=sdr
Give every row its own label. Each row runs in its own directory,
test-<label>/, here test-tutorial/, test-n8/ and test-full/. A label
used on two rows names neither of them: their directories become test-1/ and
test-2/, after their line numbers, and make TEST=<label> runs both.
With CYCLES ?= in the Makefile, a row can set its own cycle cap:
full N=16 CYCLES=4000 ARCH=sdr.
With N=8 or N=16, the template kernel's four-value list leaves the other
elements of a at 0. The standard template's ref.py reads the initial data
from the image, so the test still checks every element.
Run a specific test: make TEST=n8 runs the row labelled n8, make TEST=2
the second line, and make TEST='*' every row. Under TEST='*', the
first failing row stops the run; make -k TEST='*' runs the rest too.
Step 6: Write the Reference Implementation
Create ref.py — a Python script that computes the expected output.
Pattern A: CLI-arg based (reads extracted input data)
import sys
import numpy as np
in_name = sys.argv[1]
outfile = sys.argv[2]
# Read input (must match the dtype in your .w file)
a = np.fromfile(in_name, dtype=np.int16) # xp → int16
# For hp: dtype=np.float16
# For sp: dtype=np.float32
# Compute expected result (must match your kernel logic)
b = a * 2
# Write output
b.astype('int16').tofile(outfile)
The multi-PE template of Step 2 uses the same ref.py: recv doubles the
values that send sends.
Pattern B: Self-contained (hardcoded input, writes directly)
For examples whose inputs are fixed at build time, in the kernel or the layout (typical for MP):
import numpy as np
# Mirror the value the layout passes to the sender (my_value:42)
values = [42]
# The receiver stores the one value it receives (xp result[n=1])
ref = np.array(values, dtype=np.int16)
ref.tofile("out-ref.bin")
print(f"Expected: {list(ref)}")
This pattern is simpler. The topic
Collectives over messages uses it,
with the values that its test.rc row gives the layout.
Type Mapping
| Tungsten type | NumPy dtype |
|---|---|
xp | np.int16 |
hp | np.float16 |
sp | np.float32 |
u16 | np.uint16 |
Step 7: Run Your Test
cd my-example
make TEST=tutorial
MODE=sim, the simulator, is the default.
What Happens
- Paint lays out
bench.paintand has Carbide compile each.wthat acode()names →bench.obj(image) +bench.map(symbol table) - milink applies the layout's link annotations to
bench.obj;bench.imgis a copy, into which the Makefile maymisetinputs - miget extracts initial data from
bench.img→in.bin - ref.py computes the expected output →
out-ref.bin - mielf converts
bench.imgto an ELF, the simulator runs it, and elfmi turns its core back into an image →bench-sim.core - miget extracts results from the core →
out-sim.bin - cmp compares
out-sim.binwithout-ref.bin
A passing test prints one PASS line, such as
01-hello-wafer : 0:03.73 PASS tutorial [ N=4 ARCH=sdr ]. A failing
test prints FAIL; cmp's report of the byte offset where the files differ
is in test-tutorial/make-log. A kernel that is still waiting when the
simulation reaches its cycles= limit fails the same way, with its output
only partly written.
After you edit a .w file, bench.paint, ref.py, the Makefile or
test.rc, run make clean before make: make does not rebuild an existing
test-<label>/ directory, so it would report the old result.
Step 8: Debug if Needed
If the test fails:
# Run in debug mode; clean first so the simulation reruns with tracing
make clean
make TEST=tutorial GOAL=debugx
# Launch the Portrait visualizer
cd test-tutorial/
portrait
# Open the printed URL in your browser
See the Debugging Guide for more details.
Quick Checklist
-
.wfile(s) — one per PE role -
bench.paint— PE layout and routing -
Makefile— build and test rules -
test.rc— test parameters -
ref.py— reference implementation -
make TEST=tutorial— verify it works