Tungsten Syntax Quick Reference
This is a condensed reference for the Tungsten (.w) language. For the complete
specification, see the
Tungsten Language Reference;
each section below links to the part it condenses.
Program Structure
Reference: 1. Program Structure
A Tungsten file (.w) describes a single PE's (Processing Element) behavior.
There is no required main function: top-level statements define behavior, and
an optional named block such as main { ... } can group them. The name is only
a label: main is not special, and blocks run in source order.
// This is a line comment
/* This is a block comment */
xp param N; // parameter from Paint
xp a[n=N]; // array declaration
let i in [0, N); // named range
∀i a[i] <- i * 2; // loop over it; <- assigns
Data Types
Reference: 3. Data Types
| Type | Width | Description |
|---|---|---|
xp | 16-bit | Signed integer (default for indices, control) |
hp | 16-bit | Half-precision float (IEEE E5) |
sp | 32-bit | IEEE 754 single-precision float |
ep | 32-bit | Extended-half float (8-bit exponent, 10-bit mantissa) |
u16 | 16-bit | Unsigned 16-bit integer |
s16 | 16-bit | Signed 16-bit integer; it computes as xp does |
u32 | 32-bit | Unsigned 32-bit integer |
s32 | 32-bit | Signed 32-bit integer |
u64, s64 | 64-bit | On SDR, storage only: an array initialiser (each literal keeps its full 64-bit value), a sparse __builtin_merge result, or a stored stream descriptor. Every other 64-bit operation is rejected at compile time on SDR, including any use of a scalar variable, copies between 64-bit arrays, arithmetic, comparisons, bitwise operators and conversions. Use a u16/u32 address(&array) alias to access the words |
u1 | predicate | Boolean/predicate value |
Variables
Reference: 4. Variables
xp a = 5; // scalar with initial value
xp param N; // parameter, value from Paint
xp param N = 4; // parameter with default value
hp x; // uninitialized scalar
Array sizes and vectorize lengths are compile-time values: write them with
literals and param values. A variable holds a runtime value, so it is not
accepted as an array size. A param is a constant; assigning it fails to
compile. In memory a 16-bit
type takes one word and a 32-bit type two, and the compiler may give a scalar
no memory word at all.
Arrays
Reference: 5. Arrays
xp a[n=4]; // 1D array, 4 elements
xp b[n=4] = {1, 2, 3, 4}; // explicit initializer
hp d[r=2][c=3]; // 2D array
hp e[r=2][c=3] = { // 2D explicit init
{1.0, 2.0, 3.0},
{4.0, 5.0, 6.0}
};
An initializer lists each element's value, or gives one value that fills
every element (unlike C). A longer list that is
still short leaves the rest 0: xp c[n=4] = {5, 6}; holds 5, 6, 0, 0. There
is no generator form that computes values from the index.
Named dimensions
In [n=4], the name before the = (n) names the dimension and the number
after it (4) is its size. Dimension names appear in bench.map entries such
as a [ n<4 ], and miget/miset take them to select and order elements
(miget ... symbol=a "[n=0:3]", miset ... order="n").
Hardware Register Access — address()
Reference: Hardware Register Access
Map a variable to a specific hardware memory address using address():
// Memory-mapped hardware registers
xp col_id[1] address(0x7C46); // Column ID register
xp row_id[1] address(0x7C45); // Row ID register
{
xp my_col = col_id[0]; // read own fabric column coordinate
xp my_row = row_id[0]; // read own fabric row coordinate
}
This is the direct source mechanism for mapping hardware registers. The address
is an integer literal, hex or decimal (address(20) is address(0x14)). Common
addresses:
| Address | Register |
|---|---|
0x7C46 | Column ID (fabric X coordinate) |
0x7C45 | Row ID (fabric Y coordinate) |
Lesson 12 and the topic Message passing: routing by address use them.
Unions
Reference: Union Declarations
Unions let you reinterpret the same memory as different types. They are one way to build the 32-bit words that message passing needs from 16-bit halves:
// 32-bit value as two 16-bit halves
union mp_header {
u32 u; // 32-bit view (for socket send)
xp w[2]; // two 16-bit halves: w[0]=lower, w[1]=upper
};
// Usage
union mp_header hdr;
hdr.w[1] <- dst_col << 4; // destination column, row 0
hdr.w[0] <- 1 | 0x80; // north of a sender on another row; 1 wavelet
sock[] <- hdr.u; // send as one 32-bit wavelet
A union also packs two xp values into one u32 for message passing, which
requires full 32-bit wavelets.
Shifts on u32 values are the other way:
((u32)(u16) hi << 16) | (u32)(u16) lo. The (u16) step matters, because
(u32) of a negative xp sign-extends and fills the upper half with ones.
Common union patterns
// Pack two xp values into one u32 wavelet
union coeff_pair {
u32 u;
xp w[2]; // w[0] = first value, w[1] = second value
};
union coeff_pair pair;
pair.w[0] <- a;
pair.w[1] <- b;
sock[] <- pair.u; // send as one 32-bit wavelet
See the topics Message passing: routing by address and Collectives over messages: tree reduction and prefix scan for complete examples.
Assignment Operators
Reference: 16. Assignment
| Operator | Spelling | Example |
|---|---|---|
| assign | <-, or = in a statement | a <- expr |
| accumulate | <+- or += | a <+- expr |
A declaration's initializer keeps = (xp a = 5;). Each operator also has a
Unicode spelling; see
30. Unicode Support.
Iterator Loops
Reference: 12. Iterators and Loops
let i in [0, N); // half-open: i = 0, 1, ..., N-1
let j in [0, M);
∀i a[i] <- a[i] + 1;
∀i ∀j { // j runs in full for each i
c[i][j] <- a[i] + b[j];
}
∀k in (N, 0] d[k] <- 0; // inline range: k = N-1 down to 0
let i in [0, N);declares a named range.∀iapplies a declared range, and∀k in [0, N)declares one inline.[0, N)— half-open range (excludes N)(N, 0]— decreasing range:N-1down to0. The bracket shape sets the direction (unlike math notation).- End a
letwith;. Without it the program still compiles, but theletrepeats the next statement over its range:let i in [0, 4)followed by∀i a[i] <- a[i] + 10;adds 40 to each element.
Conditionals
Reference: If / Else If / Else
if (a > 5)
b <- 1;
else
b <- 0;
if (x != 0) {
y <- 100 / x;
z <- y + 1;
}
While Loops
Reference: While Loops
xp count = 0;
while (count < 10) {
a[count] <- count;
count <- count + 1;
}
Sockets (Inter-PE Communication)
Reference: 9. Sockets
socket tx;
socket rx;
// Send
tx[] <- value; // send a wavelet
// Receive
a[i] <- rx[]; // receive a wavelet
Socket names are bound to physical colors in the Paint file (Code Attachment):
let c_out = color()
let c_in = color()
let t = tile()
t : code('kernel [tx:c_out rx:c_in])
Every socket the kernel declares must be bound in code(). An unbound socket,
even one the kernel never uses, fails the compile with
socket [rx] is not initialized.
Input Queues
Declare sockets without a type: Carbide infers each socket's type from its uses. Each socket the kernel receives on takes an input queue, and a socket used only to send takes none. For example, the Game of Life uses 9 colors (8 neighbors + self) but SDR has only 8 input queues. Its transmit socket only sends, which leaves room for 8 input sockets:
socket tx; // 0 input queues
socket rx0, rx1, rx2, rx3, rx4, rx5, rx6, rx7; // 8 input queues
See the topic Eight neighbours: spiral routing on a budget for the full kernel.
Flip (Switch Position Advance)
flip tx[]; marks the last word of the send just before it on tx. When that
word leaves, the PE's own input switch advances to its next position. So
flip must follow a send on the same socket; written first, it fails to
compile. The serial drain pattern uses it:
{
let i in [0, N);
∀i tx[] <- data[i];
flip tx[]; // final data word advances the local input switch
}
With routing [C>L ; R>L]: position 0 sends own data left, position 1
passes through data from the right. The flip switches from position 0
to position 1.
See lesson 10, Switches for the full serial drain pattern.
Control Wavelets
// Send a control wavelet that advances switches on tx's color
u32 ADV = 0x09409249; // command 0 = advance, commands 1-7 = NOP
tx[] <- control(ADV);
All eight commands of 0x09409249 carry the CE-filter bit, so no PE's
compute engine receives the wavelet. Every router that sees command 0 acts on
it, so without pop=adv on the routes it advances the input switch of every
PE it crosses on that color. With pop=adv, each route removes command 0,
and the wavelet advances only the next switch. The bit layout is under
Control Wavelets.
See the topic Switch protocols for control(ADV) usage.
FIFO Buffers
Reference: 10. FIFOs
xp a[n=N];
xp fifo buffer = {.mem=a};
buffer[] <- value; // enqueue
x <- buffer[]; // dequeue
FIFOs provide hardware-backed producer-consumer queues on a single PE.
Wavelets
Reference: Sending Structured Wavelets
// Construct a wavelet from two values
tx[] <- wavelet(hp_value, xp_index);
A dispatcher receives it in its wavelet(hp x, xp i) handler (see
Dispatch Blocks).
Type Casting
Reference: 27. Type Casting and Coercion
// Cast xp to sp (single precision float)
sp y = (sp) x;
// Cast in expressions
tx[] <- (sp) acc;
// Cast hp to sp for accumulation
sp sum = (sp) hp_val + (sp) hp_val2;
Lesson 2 casts xp to sp, and
lesson 3 casts hp to sp.
Dispatch Blocks
Reference: 18. Dispatch Blocks
let j in [0, N);
forever dispatch(socket_name) {
wavelet(hp x, xp i) {
// runs for each data wavelet
acc[i] <+- x;
}
flush() {
// runs for each flush() message
∀j tx[] <- acc[j];
}
}
wavelet is a reserved name. Plain data wavelets start the wavelet handler,
and the sender's tx[] <- wavelet(x, i); sends data wavelets, not a control
wavelet. Any other name, such as flush, is a named message: the sender's
tx[] <- flush(); sends a control wavelet that names the handler's task. Plain
sends never start a named handler, and nothing reports it.
Lesson 7 uses flush(). A range filter (with f(i)) works only on the handler
named wavelet.
Forever Loops
Reference: Forever Blocks
let i in [0, N);
forever {
∀i tx[] <- rx[];
}
A forever block repeats indefinitely. Use parallel when independent bodies
must execute concurrently.
Blocks and Parallelism
Reference: 20. Parallel and Serial Blocks
// Serial block — statements run in sequence
{
a <- 1;
b <- 2;
}
// Parallel block — statements run concurrently
parallel {
a <- rx1[];
b <- rx2[];
}
Functions
Reference: 17. Functions
function add(xp a, xp b) -> xp {
return a + b;
}
result <- add(x, y);
Structs
Reference: Struct Declarations
struct Point {
xp x;
xp y;
};
struct Point p;
p.x <- 1;
p.y <- 2;
p.x <- p.x + 1;
Control Flow Summary
Reference: 14. Control Flow
| Construct | Description |
|---|---|
∀i in [a, b), or let i in [a, b); then ∀i | Iterator loop (half-open range) |
if/else | Conditional |
while (cond) | While loop |
forever { } | Indefinitely repeated execution block |
dispatch(s) { } | Event-driven wavelet handler |
{ } | Serial block |
parallel { } | Parallel block |
Expressions and Operators
Reference: 15. Expressions and Operators
Arithmetic
+, -, *, /, % (modulo)
Comparison
==, !=, <, >, <=, >=
Logical
&& (and), || (or), ! (not)
Bitwise
&, |, ^ (xor), ~ (not), << (shift left), >> (logical shift right),
x>> (arithmetic shift right)
Rules That Differ from C
Reference: 15. Expressions and Operators
In this table p is an xp that holds -7 at run time, a < b is true, and
f() is a function call.
| Code | Gives | Rule |
|---|---|---|
p >> 1 | 32764 | A run-time >> shifts in zeros, even on a signed type |
-7 >> 1 | -4 | A shift of literals or param values, which the compiler folds, is arithmetic |
p x>> 1 | -4 | x>> is the arithmetic shift |
p / 2, p % 2 | -4, 1 | Signed division of run-time values rounds toward minus infinity, as in Python |
-7 / 2, -7 % 2 | -3, -1 | A division of literals or param values truncates, as in C |
(p > 100) && f() | 0, and f() runs | && and || always evaluate both operands |
xp u = (a < b) && 1; | 1 | &&, || and ! give 0 or 1 |
xp t = a < b; | -1 | A comparison used as a value is the hardware compare mask, all ones when true |
(a < b) + 10 | 11 | As an operand of + or -, a comparison reads as 1 |
hp z[n=4] = {1.5}; | 1.5, 1.5, 1.5, 1.5 | A list of one value fills every element; C gives 1.5, 0, 0, 0 |
i in (0, 4] o[i] <- i; | runs 0 times | The bracket shape sets the direction: [lo, hi) walks up and (hi, lo] walks down. (0, 4] walks down from -1 to 4, so it is empty; write [1, 5) for 1 to 4 |
Comments
Reference: 2. Comments
// Line comment (C-style)
/* Block comment — can span
multiple lines */
Both styles are supported. Line comments (//) are preferred for inline
annotations; block comments use C syntax (/* ... */).
Message Passing (MP)
Reference: Message Passing
Hardware message passing allows a PE to send data to any other PE
using fabric coordinates, without routes of its own. In Paint, allocate a message-passing
bank with let b0 = bank(0), bind the socket to color(b0), and enable mp(b0)
on every PE the message crosses, including relay PEs.
MP Header Construction
// Union for constructing the 32-bit MP header
union mp_header {
u32 u; // 32-bit view (for socket send)
xp w[2]; // w[0]=lower 16 bits, w[1]=upper 16 bits
};
// Header format (bits of the u32):
// 31:20 destination column ID (w[1] bits 15:4)
// 19:8 destination row ID (w[1] bits 3:0, w[0] bits 15:8)
// 7 destination is north: its row ID is smaller than the source's
// 6 destination is south: its row ID is larger than the source's
// 5 0: bits 4:0 give the length (1: software ends the message)
// 4:0 number of data wavelets that follow, at most 31
union mp_header hdr;
hdr.w[1] <- dst_col << 4; // destination column, row 0
hdr.w[0] <- N | 0x80; // north of a sender on another row; N <= 31 wavelets
Row 0 is the northernmost row. Between PEs on the same row, leave both
direction bits clear. N must be at most 31: a larger N spills into bits
5-7, the end-of-message and direction bits. Send a longer payload as several
messages.
Sending MP Messages
socket mp_sock; // bound to color(b0) in Paint
union payload_word {
u32 u;
xp w[2];
};
// Send header + payload
mp_sock[] <- hdr.u; // header (32-bit)
union payload_word word;
let i in [0, N);
∀i {
word.w[0] <- payload[i];
word.w[1] <- 0;
mp_sock[] <- word.u;
}
Receiving MP Messages
// Receive header + payload
union mp_header hdr;
hdr.u <- mp_sock[]; // receive header
union payload_word {
u32 u;
xp w[2];
};
union payload_word word;
word.u <- mp_sock[]; // receive one full data wavelet
xp value = word.w[0]; // extract value
Critical MP Constraint
All wavelets on MP colors must be 32 bits (full wavelets). A 16-bit
xpvalue sent directly on an MP socket can leave as a half-wavelet: the output queue packs two 16-bit sends into one wavelet only when it can. Half-wavelets may not be sent on message-passing colors, and sending one is undefined behavior. Always send au32, built with a union or with theu32shifts under Unions.
Packing xp into u32
union payload {
u32 u;
xp w[2]; // w[0] = value, w[1] = tag or padding
};
union payload data;
data.w[0] <- my_value;
data.w[1] <- 0; // or a tag like sender's column ID
mp_sock[] <- data.u; // sends full 32-bit wavelet
Reading Own Coordinates
xp col_id[1] address(0x7C46); // Column ID register
xp row_id[1] address(0x7C45); // Row ID register
xp my_col = col_id[0];
See the topic Message passing: routing by address for basic MP and sends to a destination chosen at run time, and Collectives over messages: tree reduction and prefix scan for a tree reduction and a two-phase tree algorithm.