C⏚ v2.0.0Updated 2026-05-31·Language

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 == 4

An 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 == 0x123

Nested 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 1

Struct-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 v1Do 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 portRead 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 v1Do this instead
Enum as a port typeCarry the underlying integer across the port; compare against a const literal on the far side
Two enums sharing a bare literal nameQualify 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