Structs & enums
Structs and enums are compile-time sugar. A struct groups fixed-width fields under one name; an enum names a set of integer constants. Neither survives to hardware.
The compiler flattens every struct to its leaf fields and lowers every enum literal to an integer. They cost no extra gates and add no cycles. You get readable source; synthesis sees plain bits.
Structs
Declaration
A struct groups fixed-width fields under one name. Declare it in a network, a task, or a bundle.
struct Pair {
u8 lo;
u16 hi;
}A struct declared in a network is shared by every task inside it. A
struct declared inside a task is private to that task. Fields must be
integers, bool, or other structs.
Field access
Read and write fields with dot notation. Each field is an independent lvalue, so read-modify-write works in place.
Pair p;
p.lo = 3;
p.hi = 0x100;
u16 sum = (u16)(p.hi + p.lo); // 0x103
p.lo = (u8)(p.lo + 1); // read-modify-write: p.lo == 4An uninitialized struct local is zero in every field.
Whole-struct copy
Assigning one struct to another of the same type copies every field.
Pair a;
a.lo = 7; a.hi = 0x123;
Pair b = a; // b.lo == 7, b.hi == 0x123Nested structs
A struct field may itself be a struct. Chain the access through the dot.
struct Header { u8 src; u8 dst; }
struct Packet { Header hdr; u16 payload; }
Packet pk;
pk.hdr.src = 1;
pk.payload = 0x200;Nesting flattens recursively to leaf fields. A Packet is just src,
dst, and payload once the compiler is done.
Arrays of struct
Declare an array of struct, then reach a field of one element with
arr[i].field. The index is a literal or a variable, and nested chains
work the same way.
Packet batch[3];
batch[0].hdr.src = 0;
batch[n].payload = 0x300; // variable index
u8 s = batch[1].hdr.src; // nested field of element 1Struct-typed ports
A port can carry a whole struct. The bare form and the handshaked forms
(push, stream, confirm) all work, and the transfer is atomic. Every
field moves together in one cycle behind a single shared handshake.
processor = new task {
in stream Packet in_pkt;
out stream Packet out_pkt;
void loop() {
Packet p = in_pkt.read(); // whole-struct read, gated by the handshake
out_pkt.write(p); // whole-struct write
}
};A bare struct port drops the qualifier: in Packet in_pkt;. Read and
write it whole with .read() and .write(...). For what push,
stream, and confirm mean, see
Declarations → Ports.
What structs cannot do yet
| Not supported in v1 | Do this instead |
|---|---|
| Struct as task state (persists across cycles) | Keep persistent state in scalar state variables; use structs for locals, ports, and values |
Whole array-element value: Packet p = batch[i]; | Copy field by field: p.hdr.src = batch[i].hdr.src; ... |
Whole sub-struct value: Header h = pk.hdr; | Copy each leaf field of the sub-struct |
| Read-modify-write a nested field directly on a struct just read from a port | Read into a local first, then modify the local |
Enums
Declaration
An enum names a set of integer constants. Declare it in a network, a task, or a bundle.
enum state_t { IDLE, RUN, DONE }Values start at 0 and increment, so IDLE is 0, RUN is 1, DONE is
2. The underlying width is the smallest unsigned integer that holds the
largest value. Three literals fit in u2.
Using literals
Refer to a literal bare or qualified by its enum name. The two forms are identical.
state_t s = IDLE;
s = RUN;
s = state_t.DONE; // same value as bare DONE
if (s == DONE) { /* ... */ }An enum value also compares against its integer, so RUN == 1 holds.
Explicit width and values
Annotate the underlying type with : u<N> and assign explicit values.
An unassigned literal continues from the previous value plus one, C-style.
enum opcode_t : u4 {
ADD = 0,
SUB, // 1
MUL, // 2
JMP = 8,
NOP // 9
}What enums cannot do yet
| Not supported in v1 | Do this instead |
|---|---|
| Enum as a port type | Carry the underlying integer across the port; compare against a const literal on the far side |
| Two enums sharing a bare literal name | Qualify the ambiguous literal: state_t.DONE |
A worked example
This network sends nested-struct packets through a stream port, reads
each one back, copies it, and prints its fields. It combines nested
structs, an array of struct, a non-bare struct port, and a whole-struct
copy. Open it in VS Code and run Fast Sim.
package com.neosyn.structdemo;
network StructDemo {
properties { test: { terminate: "driver.finished" } }
struct Header { u8 src; u8 dst; }
struct Packet { Header hdr; u16 payload; }
processor = new task {
in stream Packet in_pkt;
out stream Packet out_pkt;
void loop() {
Packet p = in_pkt.read();
out_pkt.write(p);
}
};
driver = new task {
u8 n;
bool finished;
void setup() { n = 0; finished = false; }
void loop() {
if (!finished) {
Packet batch[3];
batch[0].hdr.src = 0; batch[0].hdr.dst = 0xA0; batch[0].payload = 0x100;
batch[1].hdr.src = 1; batch[1].hdr.dst = 0xA1; batch[1].payload = 0x200;
batch[2].hdr.src = 2; batch[2].hdr.dst = 0xA2; batch[2].payload = 0x300;
Packet p;
p.hdr.src = batch[n].hdr.src;
p.hdr.dst = batch[n].hdr.dst;
p.payload = batch[n].payload;
processor.in_pkt.write(p);
idle(2);
Packet r = processor.out_pkt.read();
Packet copy = r; // whole-struct copy
print("packet ", n, ": src=", copy.hdr.src,
" dst=", copy.hdr.dst, " payload=", copy.payload, "\n");
n = n + 1;
if (n == 3) { print("=== StructDemo done ===\n"); finished = true; }
}
}
};
}The batch array is a local inside loop(), not task state, because a
struct cannot be persistent state in v1. The driver reaches into the
processor directly with processor.in_pkt.write(...) rather than a
network-level connection.
See also
- Declarations → Types - the fixed-width scalar types that struct fields are built from.
- Declarations → Ports -
push,stream, andconfirmhandshake semantics. - Concepts - why every type has an exact bit width.