Skip to main content

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

TypeWidthDescription
xp16-bitSigned integer (default for indices, control)
hp16-bitHalf-precision float (IEEE E5)
sp32-bitIEEE 754 single-precision float
ep32-bitExtended-half float (8-bit exponent, 10-bit mantissa)
u1616-bitUnsigned 16-bit integer
s1616-bitSigned 16-bit integer; it computes as xp does
u3232-bitUnsigned 32-bit integer
s3232-bitSigned 32-bit integer
u64, s6464-bitOn 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
u1predicateBoolean/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:

AddressRegister
0x7C46Column ID (fabric X coordinate)
0x7C45Row 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

OperatorSpellingExample
assign<-, or = in a statementa <- 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. ∀i applies a declared range, and ∀k in [0, N) declares one inline.
  • [0, N) — half-open range (excludes N)
  • (N, 0] — decreasing range: N-1 down to 0. The bracket shape sets the direction (unlike math notation).
  • End a let with ;. Without it the program still compiles, but the let repeats 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

ConstructDescription
∀i in [a, b), or let i in [a, b); then ∀iIterator loop (half-open range)
if/elseConditional
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.

CodeGivesRule
p >> 132764A run-time >> shifts in zeros, even on a signed type
-7 >> 1-4A shift of literals or param values, which the compiler folds, is arithmetic
p x>> 1-4x>> is the arithmetic shift
p / 2, p % 2-4, 1Signed division of run-time values rounds toward minus infinity, as in Python
-7 / 2, -7 % 2-3, -1A 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;-1A comparison used as a value is the hardware compare mask, all ones when true
(a < b) + 1011As an operand of + or -, a comparison reads as 1
hp z[n=4] = {1.5};1.5, 1.5, 1.5, 1.5A list of one value fills every element; C gives 1.5, 0, 0, 0
i in (0, 4] o[i] <- i;runs 0 timesThe 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 xp value 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 a u32, built with a union or with the u32 shifts 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.