Quick tutorial
This tutorial walks through the canonical C⏚ workflow end to end:
- Write a 4-bit counter task in
.cg. - Wrap it in a test network with an asserting monitor.
- Run the bytecode simulator and watch assertions pass.
- Generate synthesizable Verilog from the same source.
The steps below assume a current toolchain release and the standard
cg-language-server.jar / VS Code extension workflow.
Prerequisites
You need one of:
- VS Code with the Neosyn C⏚ Language extension installed.
- A standalone
cg-language-server.jar(Java 17+) - built fromreleng/lsp-serveror downloaded from a release.
The VS Code path is faster for iteration; the standalone JAR is useful for CI and headless setups.
You also need a project layout the compiler can resolve. For the example below, create this folder structure first:
my-project/
src/
com/
example/Because the files declare package com.example;, they should live
under com/example/.
See Project layout for the general rules behind this example.
Step 1 - Write the counter
Create src/com/example/Counter.cg:
package com.example;
task Counter {
out push u4 count;
u4 value = 0;
void loop() {
count.write(value);
value = value + 1;
}
}Things to notice:
- The task has a single output port
countof typeu4(4-bit unsigned). Thepushqualifier means writes carry a one-cyclevalidpulse - consumers can block waiting for fresh data. valueis a state variable at the task level - it persists across cycles. Initialized to0.loop()runs once per cycle: write the current value, then increment.
Step 2 - Add a test network with a monitor
The bytecode simulator runs whichever network you point it at, not the task directly. The canonical pattern is a thin test network that instantiates the design under test (DUT) plus a monitor inner task that asserts what we expect cycle by cycle.
Create src/com/example/Counter_test.cg:
package com.example;
network Counter_test {
import com.example.Counter;
properties {
test: { terminate: "monitor.finished" }
}
dut = new Counter();
monitor = new task {
bool finished;
void setup() {
for (u4 expected = 0; expected < 8; expected++) {
u4 got = dut.count.read();
print("cycle ", expected, " count = ", got);
assert(got == expected);
}
finished = true;
}
};
}The key bits:
properties { test: { terminate: "monitor.finished" } }tells the simulator to keep running untilmonitor.finishedbecomestrue.- The monitor is an inner task - anonymous, defined inline. It
has a single state variable
finished(abool). - In
setup(), the monitor readsdut.countrepeatedly, prints each value, and asserts it equals what we expect. After the loop, it setsfinished = true, which terminates the simulation.
Step 3 - Run the bytecode simulator
From VS Code
Open Counter_test.cg in the editor, then either:
- Run the Neosyn: Fast Simulation (Bytecode) command from the command palette, or
- Click the play-icon Fast Simulation (Bytecode) action in the editor title bar.
The console pane shows the cycle-by-cycle output. Integers print as
hex (0x...) by default - see
Bytecode simulator → Print and assertions:
cycle 0x0 count = 0x0
cycle 0x1 count = 0x1
cycle 0x2 count = 0x2
cycle 0x3 count = 0x3
cycle 0x4 count = 0x4
cycle 0x5 count = 0x5
cycle 0x6 count = 0x6
cycle 0x7 count = 0x7
Simulation completed successfully.If any assert fails, the simulation stops with a diagnostic at the
offending cycle.
From the command line
java -jar cg-language-server.jar simulate path/to/Counter_test.cgIf your .cg file declares multiple top-level entities and the
simulator can't pick one automatically, name it explicitly:
java -jar cg-language-server.jar simulate path/to/Counter_test.cg \
--entity Counter_testThe short alias sim also works:
java -jar cg-language-server.jar sim path/to/Counter_test.cgStep 4 - Inspect the waveform
The simulation drops a VCD file at the project root
(Counter_test.vcd by default - named after the top-level entity,
placed alongside src/ and verilog-gen/), capturing every port
and state variable at every cycle.
- VS Code: run the Neosyn: Open Waveform command, or open the
.vcddirectly with the WaveTrace extension. - GTKWave / Surfer: open the file with any standalone VCD viewer.
For our counter you'll see count.data step through 0..7 and
count.valid pulse high every cycle.
Step 5 - Generate Verilog
The same .cg source compiles to synthesizable HDL.
From VS Code
Run Neosyn: Generate HDL from the command palette. Output lands
in verilog-gen/ at the project root (next to src/).
From the command line
java -jar cg-language-server.jar generate path/to/Counter.cgBy default this produces Verilog. To target VHDL instead:
java -jar cg-language-server.jar generate path/to/Counter.cg --target vhdlTo choose a specific output directory:
java -jar cg-language-server.jar generate path/to/project --output build/hdlThe generated HDL is plain text intended for normal downstream FPGA toolchains. Exact synthesis results still depend on the backend, target device, and the constructs used in your design.
Step 6 - What the generated Verilog gives you
The output of step 5 is synthesizable RTL for the DUT itself -
nothing more. The monitor's print() and assert() calls live only
in the bytecode simulator; they have no synthesizable equivalent
and don't appear in the generated .v files.
What you can do with the generated Verilog:
- Hand it to your FPGA toolchain (Vivado, Quartus, Yosys, etc.) for synthesis and place-and-route - see FPGA integration.
- Drive the DUT from a hand-written Verilog testbench if you need an HDL-level cross-check beyond bytecode coverage - see Cross-check against HDL for when and how.
What you cannot do is replay the inline-monitor pattern through iverilog and expect the same printouts. The monitor is a bytecode-side construct. The bytecode simulator is the canonical correctness check; the Verilog backend exists to feed downstream synthesis tools.
What you just exercised
| Layer | Tool | Output |
|---|---|---|
| Source | text editor / VS Code | Counter.cg, Counter_test.cg |
| Bytecode sim | cg-language-server.jar simulate | console output + Counter_test.vcd |
| HDL gen | cg-language-server.jar generate | verilog-gen/com/example/Counter.v (DUT only) |
The same source flows through both layers, but they serve different purposes: bytecode for fast correctness loops, Verilog for synthesis.
Where next
- Bytecode simulator - full reference on
monitors, the
testproperty, VCD output. - Concepts - bit-accurate types, cycle-by-cycle execution, determinism. The next layer of mental model.
- Tasks - full task semantics including
cycle breaks,
setup/loop, external tasks. - Networks - wiring instances together for non-trivial designs.
- Standard library - FIFOs, RAMs, clock-domain synchronizers.