C⏚ v2.0.0Updated 2026-05-12·Platform

Properties

Properties are JSON-like data attached to a task or network. They configure clocks, reset, simulation tests, external implementations, and a few other knobs.

Two forms, same content

Properties show up in two places, with identical keys, values, and structure:

// 1. In a task or network declaration: properties { … } block.
task T {
  properties {
    clocks: ["clk_a"]
  }
}
 
// 2. At an instance site: an object literal passed to new.
ram = new DualPortRAM({
  clocks: ["clk_recv", "clk_send"]
});

The declaration form attaches properties to the entity itself. The instance form overrides or supplies them for one instance. The rules below apply to both; the rest of this chapter uses whichever form fits the example.

Syntax

A property value is one of three things:

  • Primitive: string (single or double quoted), number (decimal, 0b…, 0x…, with _ separators), boolean, or null.
  • Array: square brackets, comma-separated.
  • Object: curly braces, identifier keys, comma-separated.
{
  clocks: ["din", "dout"],
  reset: { type: "synchronous", active: "high" }
}

clocks

An array of clock names. Clock names must be valid identifiers.

properties {
  clocks: ["din", "dout"]
}

clock: "clk" is shorthand for clocks: ["clk"]. To declare no clocks, write clock: null or clocks: [], or set type: "combinational" instead.

Default: clocks: ["clock"].

Clock association

When a network instantiates a sub-entity, clocks pair up implicitly in the most common cases.

Network clocksEntity clocksPairing
any0 (combinational)nothing to associate
NNpositional, first-to-first
1Nthe single clock is associated N times
NM, N≠M, M>1must be explicit

Explicit association uses a clocks property at the instantiation site. Either map by name…

ram = new DualPortRAM({
  clocks: { rd_clock: "clk_recv", wr_clock: "clk_send" }
});

…or by position:

ram = new DualPortRAM({
  clocks: ["clk_recv", "clk_send"]
});

Inner tasks

An inner task accepts no instantiation properties. Its properties block does double duty as both declaration and instantiation:

network N {
  properties { clocks: ["clk_a", "clk_b"] }
 
  inner_a = new task {
    properties { clock: "clk_a" }
    void loop() { /* … */ }
  };
 
  inner_b = new task {
    properties { clock: "clk_b" }
    void loop() { /* … */ }
  };
}

reset

Either null (no reset) or an object with type, active, and name:

properties {
  reset: { type: "synchronous", active: "high", name: "reset_p" }
}
KeyValuesDefault
type"asynchronous" | "synchronous""asynchronous"
active"high" | "low""low"
nameidentifier"reset_n" (active-low) or "reset" (active-high)

At an instance site, reset: "ready" repurposes the named signal as the instance's reset:

new SimpleTask({ reset: "ready" })

type

Set type: "combinational" to mark an entity as having no clock or reset:

properties { type: "combinational" }

Equivalent to { clocks: [], reset: null }.

test

test drives a task or network through one or more cycles of stimulus and termination checks. There are two forms.

Termination by expression

properties {
  test: { terminate: "monitor.finished" }
}

terminate names an expression (typically a state variable on a monitor task) that ends the simulation when it becomes true. This is the canonical pattern for tests with a monitor - see Bytecode simulator.

Cycle-by-cycle stimulus

Each port name maps to an array of one value per cycle. null means "no value on this cycle":

properties {
  test: {
    data:  [    6, 5,    5, 4,    4,    4, 3,    3,    3,    3, 2 ],
    value: [ null, 6, null, 5, null, null, 4, null, null, null, 3 ],
    count: [ null, 1, null, 2, null, null, 3, null, null, null, 4 ]
  }
}

The simulation runs for as many cycles as the longest array.

Stimulus is interpreted as a black-box test: the testbench writes inputs into the DUT and checks outputs after the DUT has written them. Because output ports are registered in the generated RTL, the check happens one cycle after the corresponding input.

Cycle-by-cycle test waveform

true/false for bool ports, integer literals for integer ports. A push port that receives null on a given cycle must actually be quiet on that cycle; writing when not expected is a test failure.

The two forms can coexist. When both terminate and per-port stimulus arrays are present, the simulator drives the stimulus and checks terminate each cycle.

implementation

Tells the code generator what to emit (or not) for an entity.

properties {
  implementation: {
    type: "external",
    file: "../../top.v",
    dependencies: ["../../entity_a.v", "../../entity_b.v"]
  }
}
typeMeaning
"external"Body is provided by the named HDL file. Used for wrapping Verilog / VHDL.
"builtin"Reserved for entities defined by the compiler itself (std.mem.*, std.fifo.*).

file is the implementation file path. dependencies lists other HDL files that must appear before it in generated build scripts. Paths are absolute or relative to the folder containing the .cg file.

Embedded inline implementations in HDL are not yet supported. See the Tasks chapter for the external-task workflow, and FPGA integration for how these files enter a downstream toolchain.