Scripting Language Reference

Overview

Gate Lab's scripting language lets you define arbitrary digital logic in custom components. The syntax is C-like: imperative, expression-based, and procedural. It is not a general-purpose language — there is no heap allocation, no floating-point, no recursion, and no I/O beyond reading input pins and writing output pins. Everything is a 64-bit signed integer.

A script has two parts, in order:

  1. Header section — keyword declarations that define the component's pin interface and internal memory. These lines have no semicolons and must all appear before any logic code.
  2. Logic body — statements that compute output values from input values. Every statement ends with a semicolon.
minimal script structure
inputs: A, B
outputs: Y

Y = A & B;    # AND gate

Open the editor: In Gate Lab, drag a Custom Component or Custom Sequential Component from the Programmable category onto the canvas, then right-click it and select Edit Script to open the script editor.

LAUNCH GATE LAB

Component types

There are two programmable component types in the Programmable category of the component panel. Both use the same scripting language; they differ in execution model.

Type When the body runs Persistent state clock: header
Custom Component Every time any input pin changes value No Not allowed
Custom Sequential Component On the rising edge of the clock pin only Yes — via state: Required
Rule of thumb: if the component needs to remember anything between clock ticks (a counter value, a register, a RAM array), use Custom Sequential Component with state: variables. For pure combinational logic — gates, MUXes, ALU operations — use Custom Component.

Header declarations

Header declarations define the component's pins and internal storage. Each keyword may appear at most once and must come before the logic body. No semicolons in the header.

inputs: and outputs:

Define input and output pins. Names are comma-separated. Append [N] to a name for an N-bit bus pin.

Declaration Result
inputs: A, B Two 1-bit input pins named A and B
inputs: Data[8], Select[2] An 8-bit bus pin and a 2-bit bus pin
outputs: Result[4], Carry A 4-bit bus output and a 1-bit output

Accessing individual bits of a bus in the logic body uses zero-based indexing: Data[0] is the least-significant bit; Data[7] is the most-significant bit of an 8-bit bus. Using the name without brackets reads or writes the full integer value of the bus.

clock: and state:

clock: names the clock input pin for a Custom Sequential Component. The logic body executes only on the rising edge of this pin (LOW-to-HIGH transition). It takes a single name:

clock: CLK

state: declares variables that persist between clock cycles. Their values survive from one rising edge to the next. Valid only when a clock: is also declared.

state: count[8], ram[16][8]   # 8-bit counter, 16-row × 8-bit memory

State variables support single values, 1D arrays, and 2D arrays. All state variables are initialised to 0 when the simulation starts and when the simulation is reset.

vars:

vars: declares scratch variables. Unlike state variables, vars: values are reset to 0 before each script execution. They are useful for intermediate calculations within a single clock cycle.

vars: temp, carry

Variables can also be declared inline in the logic body using the var keyword:

var sum = A + B;    # declare and initialise
var buf[4];         # declare a 4-element array
var arr[4] = 0xFF;  # declare array with assignment (truncated to 4 bits)
Scope: inline var declarations are effectively hoisted to script scope — they are accessible throughout the entire logic body. However, the initialisation runs at the point the statement appears.

Syntax and data types

Integers and buses

All values are 64-bit signed integers. There is no boolean type: 0 is LOW/false and any non-zero value is HIGH/true. The keywords true and false are aliases for 1 and 0.

Number literals:

  • Decimal: 123, -5
  • Hexadecimal: 0xFF, 0x1A

When an integer value is assigned to a declared array or bus (e.g., Out[4] = 255), the value is truncated to fit — only the least-significant N bits are retained.

Comments

Comments start with # and run to the end of the line. They can appear on a line of their own or after a statement.

# This entire line is a comment
Y = A & B;  # inline comment

Reserved keywords

The following identifiers are reserved and cannot be used as variable or pin names:

if, else, for, while, break, continue, true, false, var, inputs, outputs, state, clock, vars, random, abs, min, max, popcount

Operators

Standard operator precedence applies: unary operators bind tightest, then multiplication/division, then addition/subtraction, then shifts, then bitwise AND/XOR/OR, then comparison, then logical AND/OR.

Arithmetic operators

Operator Description Notes
+ Addition
- Subtraction Also unary negation
* Multiplication
/ Integer division Division by zero returns 0
% Modulo Modulo by zero returns 0
val = (A + B) * 2;
remainder = count % 16;

Bitwise operators

Operator Description
& Bitwise AND
| Bitwise OR
^ Bitwise XOR
~ Bitwise NOT (ones complement)
<< Left shift
>> Right shift
masked  = Data & 0x0F;   # keep bottom 4 bits
inverted = ~Data;
bit3    = 1 << 3;        # result is 8

Comparison operators

All return 1 (true) or 0 (false).

Operator Description
== Equal
!= Not equal
< Less than
> Greater than
<= Less than or equal
>= Greater than or equal

Logical operators

Operate on the boolean interpretation of values (non-zero = true). Short-circuit evaluation is applied.

Operator Description
&& Logical AND
|| Logical OR
! Logical NOT
if (Enable && !Reset) {
  count = count + 1;
}
Bitwise vs logical: use & and | when you want to operate on individual bits of a bus. Use && and || when you want true/false logic on whole values. 1 & 2 is 0; 1 && 2 is 1.

Ternary operator

Inline if-else expression: condition ? value_if_true : value_if_false

larger = (A > B) ? A : B;
IsZero = (Result == 0) ? 1 : 0;

Control flow

if / else

Curly braces are required around the block, even for a single statement.

if (condition) {
  # statements
} else if (another_condition) {
  # statements
} else {
  # statements
}

for loop

All three parts of the for header are optional.

for (var i = 0; i < 8; i = i + 1) {
  sum = sum + data[i];
}

# Infinite loop — exit with break
for (;;) {
  if (done) { break; }
}

while loop

var bit = 0;
while ((value & 1) == 0 && bit < 8) {
  value = value >> 1;
  bit = bit + 1;
}

break and continue

break exits the innermost loop immediately. continue skips the rest of the current iteration and evaluates the loop condition again. Both affect only the innermost enclosing loop.

# Sum odd numbers 1..9
var sum = 0;
for (var i = 0; i < 10; i = i + 1) {
  if ((i & 1) == 0) { continue; }  # skip even
  sum = sum + i;
}

Built-in functions

abs(x)

Returns the absolute value of x.

distance = abs(A - B);   # always non-negative

min(a, b) and max(a, b)

Return the smaller or larger of two values.

clamped = min(value, 255);  # cap at 255
floored = max(value, 0);    # floor at 0

random()

random() with no argument returns a random bit (0 or 1). random(max) returns a random integer in the range 0 to max - 1 inclusive. The generator is automatically seeded each simulation run.

bit    = random();      # 0 or 1
nibble = random(16);    # 0 to 15
byte   = random(256);   # 0 to 255

popcount(x)

Returns the number of set bits (1s) in the integer x.

ones   = popcount(0x0B);         # returns 3  (binary 1011)
parity = popcount(data) & 1;     # 1 if odd number of 1s
weight = popcount(value);

Examples

Each example below is a complete, ready-to-paste script. Copy it into the script editor of the matching component type.

Example 1 — Basic logic gates (AND, OR, NOT)

One component that exposes an AND output, an OR output, and a NOT output for inputs A and B. Use a Custom Component (combinational).

Custom Component
inputs: A, B
outputs: OutAND, OutOR, OutNOT

OutAND = A && B;
OutOR  = A || B;
OutNOT = !A;

Example 2 — XOR gate

XOR using the built-in bitwise XOR operator. The commented-out line shows the equivalent construction from basic operators.

Custom Component
inputs: A, B
outputs: OutXOR

OutXOR = A ^ B;

# Equivalent: OutXOR = (A && !B) || (!A && B);

Example 3 — Half adder

Adds two single bits. Produces a Sum and a Carry out.

Custom Component
inputs: A, B
outputs: Sum, Carry

Sum   = A ^ B;
Carry = A && B;

Example 4 — 4-to-1 multiplexer

Selects one of four 1-bit inputs using a 2-bit Select bus.

Custom Component
inputs: Select[2], In0, In1, In2, In3
outputs: OutMUX

if (Select == 0) {
  OutMUX = In0;
} else if (Select == 1) {
  OutMUX = In1;
} else if (Select == 2) {
  OutMUX = In2;
} else {
  OutMUX = In3;
}

Example 5 — Simple ALU

A 4-bit ALU supporting addition, subtraction, bitwise AND, and bitwise OR. OpCode selects the operation. Outputs the result and a zero flag. Combinational — no clock needed.

Custom Component
inputs: PacketA[4], PacketB[4], OpCode[2]
outputs: Result[4], IsZero

var val = 0;

if (OpCode == 0) {
  val = PacketA + PacketB;   # add
} else if (OpCode == 1) {
  val = PacketA - PacketB;   # subtract
} else if (OpCode == 2) {
  val = PacketA & PacketB;   # bitwise AND
} else {
  val = PacketA | PacketB;   # bitwise OR
}

Result = val;
IsZero = (Result == 0) ? 1 : 0;

Example 6 — 8-bit counter

Counts up on each rising clock edge when Enable is HIGH. Resets to 0 when Reset is HIGH. Uses Custom Sequential Component because the count must persist between clock edges.

Custom Sequential Component
clock: CLK
inputs: Reset, Enable
outputs: Count[8]
state: internal_count[8]

if (Reset) {
  internal_count = 0;
} else if (Enable) {
  internal_count = internal_count + 1;
}

Count = internal_count;

Example 7 — 4×4-bit RAM

A 4-address, 4-bit-wide RAM. Writes DataIn to Address when WriteEnable is HIGH on the rising clock edge. Reads the stored value when ReadEnable is HIGH.

Custom Sequential Component
clock: CLK
inputs: Address[2], DataIn[4], WriteEnable, ReadEnable
outputs: DataOut[4]
state: memory[4][4]   # 4 rows × 4 bits

if (WriteEnable) {
  memory[Address] = DataIn;
}

if (ReadEnable) {
  DataOut = memory[Address];
} else {
  DataOut = 0;
}

Example 8 — Random number generator

Outputs a random bit and a random N-bit value each time Enable is HIGH. Range controls the upper bound; if Range is 0 it defaults to 256. Combinational — values update whenever Enable or Range change.

Custom Component
inputs: Enable, Range[4]
outputs: RandomValue[8], RandomBit

if (Enable) {
  RandomBit = random();
  var bound = Range;
  if (bound == 0) { bound = 256; }
  RandomValue = random(bound);
} else {
  RandomBit  = 0;
  RandomValue = 0;
}

Example 9 — ALU using built-in functions

Demonstrates abs, min, max, and popcount. Operation selects between absolute difference, minimum, maximum, and bitwise AND. Outputs a 4-bit Flags bus: bit 0 is parity, bit 1 is overflow, bit 2 is zero, bit 3 signals that more than half the result bits are set.

Custom Component
inputs: A[8], B[8], Operation[2]
outputs: Result[8], Flags[4]

var output = 0;

if (Operation == 0) {
  output = abs(A - B);
} else if (Operation == 1) {
  output = min(A, B);
} else if (Operation == 2) {
  output = max(A, B);
} else {
  output = A & B;
}

var ones = popcount(output);
Result   = output;
Flags[0] = ones & 1;          # parity
Flags[1] = (output > 255) ? 1 : 0;   # overflow
Flags[2] = (output == 0) ? 1 : 0;    # zero
Flags[3] = (ones > 4) ? 1 : 0;       # majority set

Ready to try? Open Gate Lab, add a Custom Component or Custom Sequential Component to the canvas, and paste one of the scripts above into the editor.

LAUNCH GATE LAB