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
}Enums as port types
An enum is a legal port type. Declare the port with the enum and read it back as the enum:
enum Mode { IDLE, RUN, HALT }
task Decode {
sync { in Mode m; out u8 y; }
void loop() {
Mode cur = m.read();
if (cur == Mode.IDLE) { y.write(10); }
else if (cur == Mode.RUN) { y.write(20); }
else { y.write(30); }
}
}Stimulus for such a port is written as the underlying integer
(m: [0, 1, 2]), and the generated HDL carries the enum's
underlying width.
Earlier releases did not support this, and the workaround was to
carry a plain integer across the port and compare it against
const literals on the far side. That is no longer necessary from
v2.9.0.
What enums cannot do yet
| Not supported | Do this instead |
|---|---|
| Two enums sharing a bare literal name | Qualify the ambiguous literal: state_t.DONE |
Literals declared in a bundle always need enum-name qualification
across an import, even under a wildcard import. See
Bundles.
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.