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:
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:
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:
| Type | State | Notes |
|---|---|---|
logic | 4-state | Primary 4-state type |
clock, reset | 4-state | Control signals |
bit | 2-state | Mask 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:
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:
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:
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):
const val = sim.dut.result; // bigint — X bits read as 0Reading the Full Value/Mask Pair
Use the fourState() method on Simulator or Simulation to read the value and X mask together:
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 = XThe returned FourStateValue has the following semantics:
| mask bit | value bit | Meaning |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | Z (high-impedance) |
| 1 | 1 | X (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 testWhere src/ALU.veryl uses logic type ports (4-state capable):
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:
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:
| Operation | Behavior |
|---|---|
a & b | Known 0 cancels X |
a | b | Known 1 cancels X |
a ^ b | X 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 amount | Entire result becomes X |
Further Reading
- 4-State Internals -- Representation model, normalization, and JIT compilation details.