Skip to content

4-State Simulation

Celox supports IEEE 1800-compliant 4-state simulation with X (unknown) values. This page explains how to use 4-state features from TypeScript testbenches.

Enabling 4-State Mode

Pass { fourState: true } when creating a simulator:

typescript
import { Simulator } from "@celox-sim/celox";
import { MyModule } from "../src/MyModule.veryl";

const sim = Simulator.create(MyModule, { fourState: true });

The same option works with Simulation:

typescript
import { Simulation } from "@celox-sim/celox";
import { MyModule } from "../src/MyModule.veryl";

const sim = Simulation.create(MyModule, { fourState: true });

WARNING

Without fourState: true, all signals behave as 2-state. X values cannot be written or read.

Veryl Types and 4-State

Whether a signal supports 4-state depends on its Veryl type:

TypeStateNotes
logic4-statePrimary 4-state type
clock, reset4-stateControl signals
bit2-stateMask is always 0

When a 4-state value is assigned to a bit-type variable inside the design, the mask (X bits) is automatically cleared to 0. This prevents unintended X propagation through 2-state boundaries.

TIP

To use 4-state simulation, declare your ports as logic rather than bit. Ports declared as bit will silently drop X values.

Writing X and Z Values

Assigning All-X or All-Z

Use the X sentinel to set all bits to unknown, or Z to set all bits to high-impedance:

typescript
import { Simulator, X, Z } from "@celox-sim/celox";
import { MyModule } from "../src/MyModule.veryl";

const sim = Simulator.create(MyModule, { fourState: true });

sim.dut.data_in = X; // all bits unknown
sim.dut.tri_bus = Z;  // all bits high-impedance
sim.tick();

Assigning Specific Bits as X

Use FourState(value, mask) to control individual bits. Mask bits set to 1 indicate X:

typescript
import { Simulator, FourState } from "@celox-sim/celox";
import { MyModule } from "../src/MyModule.veryl";

const sim = Simulator.create(MyModule, { fourState: true });

// Bits [3:0] = 0101, bits [7:4] = X
sim.dut.data_in = FourState(0b0000_0101, 0b1111_0000);
sim.tick();

For wide signals, use bigint literals:

typescript
sim.dut.wide_data = FourState(0x1234n, 0xFF00n);

Reading 4-State Values

Standard Read

Reading a port via sim.dut.<port> returns only the value portion (mask is not included):

typescript
const val = sim.dut.result; // bigint — X bits read as 0

Reading the Full Value/Mask Pair

Use the fourState() method on Simulator or Simulation to read the value and X mask together:

typescript
const fs = sim.fourState("result");

if (fs.mask !== 0n) {
  console.log("Result contains X bits:", fs.mask.toString(2));
}

// fs.__fourState === true
// fs.value — the value portion
// fs.mask  — mask where 1 = X

The returned FourStateValue has the following semantics:

mask bitvalue bitMeaning
000
011
10Z (high-impedance)
11X (unknown)

TIP

For low-level access (e.g. custom buffer manipulation), the readFourState(buffer, layout) function is also available as an internal API.

Example: Testing X Propagation

Suppose your project has the following structure:

my-project/
├── src/
│   └── ALU.veryl          # Design with logic ports
└── test/
    └── alu.test.ts        # 4-state test

Where src/ALU.veryl uses logic type ports (4-state capable):

veryl
module ALU (
    clk: input  clock,
    rst: input  reset,
    a:   input  logic<8>,
    b:   input  logic<8>,
    op:  input  logic<2>,
    y:   output logic<8>,
) {
    // ...
}

Then test/alu.test.ts:

typescript
import { describe, test, expect } from "vitest";
import { Simulator, X, FourState } from "@celox-sim/celox";
import { ALU } from "../src/ALU.veryl";

describe("ALU", () => {
  test("X input propagates to output", () => {
    const sim = Simulator.create(ALU, { fourState: true });

    sim.dut.a = X;
    sim.dut.b = 42n;
    sim.tick();

    // Arithmetic with X produces all-X result
    // Use readFourState to verify X propagation
    // ...

    sim.dispose();
  });

  test("known-0 AND cancels X", () => {
    const sim = Simulator.create(ALU, { fourState: true });

    // a = X, but b = 0 — AND should produce known 0
    sim.dut.a = X;
    sim.dut.b = 0n;
    sim.dut.op = 0n; // AND
    sim.tick();

    sim.dispose();
  });

  test("partial X with FourState", () => {
    const sim = Simulator.create(ALU, { fourState: true });

    // Lower 4 bits known, upper 4 bits X
    sim.dut.a = FourState(0x05, 0xF0);
    sim.dut.b = 0xFFn;
    sim.tick();

    sim.dispose();
  });
});

X Propagation Rules

Celox follows IEEE 1800 X propagation semantics:

OperationBehavior
a & bKnown 0 cancels X
a | bKnown 1 cancels X
a ^ bX if either operand is X
+, -, *, /, %Any X in operands makes the entire result X
==, !=, <, >Any X in operands makes the result X
if (x_cond)X selector merges both branches conservatively
Shift by X amountEntire result becomes X

Further Reading

  • 4-State Internals -- Representation model, normalization, and JIT compilation details.