@celox-sim/celox 0.0.1 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Event-based Simulator.
3
+ *
4
+ * Wraps a NativeSimulatorHandle and provides a high-level TypeScript API
5
+ * for manually controlling clock edges via `tick()`.
6
+ */
7
+
8
+ import type {
9
+ CreateResult,
10
+ EventHandle,
11
+ ModuleDefinition,
12
+ NativeSimulatorHandle,
13
+ SimulatorOptions,
14
+ } from "./types.js";
15
+ import { createDut, type DirtyState } from "./dut.js";
16
+ import {
17
+ loadNativeAddon,
18
+ parseNapiLayout,
19
+ buildPortsFromLayout,
20
+ wrapDirectSimulatorHandle,
21
+ } from "./napi-helpers.js";
22
+
23
+ /**
24
+ * Placeholder for the NAPI binding's `createSimulator()`.
25
+ * Stream B will provide the real implementation; until then tests can
26
+ * inject a mock via `Simulator.create()` options or by replacing this
27
+ * module.
28
+ * @internal
29
+ */
30
+ export type NativeCreateFn = (
31
+ source: string,
32
+ moduleName: string,
33
+ options: SimulatorOptions,
34
+ ) => CreateResult<NativeSimulatorHandle>;
35
+
36
+ let _nativeCreate: NativeCreateFn | undefined;
37
+
38
+ /**
39
+ * Register the NAPI binding at module load time.
40
+ * Called once by the package entry point after loading the native addon.
41
+ * @internal
42
+ */
43
+ export function setNativeSimulatorCreate(fn: NativeCreateFn): void {
44
+ _nativeCreate = fn;
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Simulator
49
+ // ---------------------------------------------------------------------------
50
+
51
+ export class Simulator<P = Record<string, unknown>> {
52
+ private readonly _handle: NativeSimulatorHandle;
53
+ private readonly _dut: P;
54
+ private readonly _events: Record<string, number>;
55
+ private readonly _defaultEventId: number;
56
+ private readonly _state: DirtyState;
57
+ private _disposed = false;
58
+
59
+ private constructor(
60
+ handle: NativeSimulatorHandle,
61
+ dut: P,
62
+ events: Record<string, number>,
63
+ state: DirtyState,
64
+ ) {
65
+ this._handle = handle;
66
+ this._dut = dut;
67
+ this._events = events;
68
+ this._state = state;
69
+ const keys = Object.keys(events);
70
+ this._defaultEventId = keys.length > 0 ? events[keys[0]!]! : -1;
71
+ }
72
+
73
+ /**
74
+ * Create a Simulator for the given module.
75
+ *
76
+ * ```ts
77
+ * import { Adder } from "./generated/Adder.js";
78
+ * const sim = Simulator.create(Adder);
79
+ * ```
80
+ */
81
+ static create<P>(
82
+ module: ModuleDefinition<P>,
83
+ options?: SimulatorOptions & {
84
+ /** Override for testing — inject a mock NAPI create function. */
85
+ __nativeCreate?: NativeCreateFn;
86
+ },
87
+ ): Simulator<P> {
88
+ // When the module was produced by the Vite plugin, delegate to fromProject()
89
+ if (module.projectPath && !options?.__nativeCreate) {
90
+ return Simulator.fromProject<P>(module.projectPath, module.name, options);
91
+ }
92
+
93
+ const createFn = options?.__nativeCreate ?? _nativeCreate;
94
+ if (!createFn) {
95
+ throw new Error(
96
+ "Native simulator binding not loaded. " +
97
+ "Ensure @celox-sim/celox-napi is installed.",
98
+ );
99
+ }
100
+
101
+ const { fourState, vcd } = options ?? {};
102
+ const result = createFn(module.source, module.name, { fourState, vcd });
103
+ const state: DirtyState = { dirty: false };
104
+
105
+ const dut = createDut<P>(
106
+ result.buffer,
107
+ result.layout,
108
+ module.ports,
109
+ result.handle,
110
+ state,
111
+ );
112
+
113
+ return new Simulator<P>(result.handle, dut, result.events, state);
114
+ }
115
+
116
+ /**
117
+ * Create a Simulator directly from Veryl source code.
118
+ *
119
+ * Automatically discovers ports from the NAPI layout — no
120
+ * `ModuleDefinition` needed.
121
+ *
122
+ * ```ts
123
+ * const sim = Simulator.fromSource<AdderPorts>(ADDER_SOURCE, "Adder");
124
+ * sim.dut.a = 100;
125
+ * sim.dut.b = 200;
126
+ * sim.tick();
127
+ * expect(sim.dut.sum).toBe(300);
128
+ * ```
129
+ */
130
+ static fromSource<P = Record<string, unknown>>(
131
+ source: string,
132
+ top: string,
133
+ options?: SimulatorOptions & { nativeAddonPath?: string },
134
+ ): Simulator<P> {
135
+ const addon = loadNativeAddon(options?.nativeAddonPath);
136
+ const napiOpts = options?.fourState ? { fourState: options.fourState } : undefined;
137
+ const raw = new addon.NativeSimulatorHandle(source, top, napiOpts);
138
+
139
+ const layout = parseNapiLayout(raw.layoutJson);
140
+ const events: Record<string, number> = JSON.parse(raw.eventsJson);
141
+
142
+ const ports = buildPortsFromLayout(layout.signals, events);
143
+
144
+ const buf = raw.sharedMemory().buffer;
145
+
146
+ const state: DirtyState = { dirty: false };
147
+ const handle = wrapDirectSimulatorHandle(raw);
148
+ const dut = createDut<P>(buf, layout.forDut, ports, handle, state);
149
+
150
+ return new Simulator<P>(handle, dut, events, state);
151
+ }
152
+
153
+ /**
154
+ * Create a Simulator from a Veryl project directory.
155
+ *
156
+ * Searches upward from `projectPath` for `Veryl.toml`, gathers all
157
+ * `.veryl` source files, and builds the simulator using the project's
158
+ * clock/reset settings.
159
+ *
160
+ * ```ts
161
+ * const sim = Simulator.fromProject<MyPorts>("./my-project", "Top");
162
+ * ```
163
+ */
164
+ static fromProject<P = Record<string, unknown>>(
165
+ projectPath: string,
166
+ top: string,
167
+ options?: SimulatorOptions & { nativeAddonPath?: string },
168
+ ): Simulator<P> {
169
+ const addon = loadNativeAddon(options?.nativeAddonPath);
170
+ const napiOpts = options?.fourState ? { fourState: options.fourState } : undefined;
171
+ const raw = addon.NativeSimulatorHandle.fromProject(projectPath, top, napiOpts);
172
+
173
+ const layout = parseNapiLayout(raw.layoutJson);
174
+ const events: Record<string, number> = JSON.parse(raw.eventsJson);
175
+
176
+ const ports = buildPortsFromLayout(layout.signals, events);
177
+
178
+ const buf = raw.sharedMemory().buffer;
179
+
180
+ const state: DirtyState = { dirty: false };
181
+ const handle = wrapDirectSimulatorHandle(raw);
182
+ const dut = createDut<P>(buf, layout.forDut, ports, handle, state);
183
+
184
+ return new Simulator<P>(handle, dut, events, state);
185
+ }
186
+
187
+ /** The DUT accessor object — read/write ports as plain properties. */
188
+ get dut(): P {
189
+ return this._dut;
190
+ }
191
+
192
+ /**
193
+ * Trigger a clock edge.
194
+ *
195
+ * @param event Optional event handle from `this.event()`.
196
+ * If omitted, ticks the first (default) event.
197
+ * @param count Number of ticks. Default: 1.
198
+ */
199
+ tick(event?: EventHandle | number, count?: number): void;
200
+ tick(count?: number): void;
201
+ tick(
202
+ eventOrCount?: EventHandle | number,
203
+ count?: number,
204
+ ): void {
205
+ this.ensureAlive();
206
+
207
+ let eventId: number;
208
+ let ticks: number;
209
+
210
+ if (typeof eventOrCount === "object" && eventOrCount !== null) {
211
+ // tick(eventHandle, count?)
212
+ eventId = (eventOrCount as EventHandle).id;
213
+ ticks = count ?? 1;
214
+ } else if (typeof eventOrCount === "number") {
215
+ // tick(count) — default event
216
+ eventId = this._defaultEventId;
217
+ ticks = eventOrCount;
218
+ } else {
219
+ // tick() — default event, 1 tick
220
+ eventId = this._defaultEventId;
221
+ ticks = 1;
222
+ }
223
+
224
+ if (ticks === 1) {
225
+ this._handle.tick(eventId);
226
+ } else if (ticks > 1) {
227
+ this._handle.tickN(eventId, ticks);
228
+ }
229
+ this._state.dirty = false;
230
+ }
231
+
232
+ /** Resolve an event name to a handle for use with `tick()`. */
233
+ event(name: string): EventHandle {
234
+ const id = this._events[name];
235
+ if (id === undefined) {
236
+ throw new Error(
237
+ `Unknown event '${name}'. Available: ${Object.keys(this._events).join(", ")}`,
238
+ );
239
+ }
240
+ return { name, id };
241
+ }
242
+
243
+ /** Write current signal values to VCD at the given timestamp. */
244
+ dump(timestamp: number): void {
245
+ this.ensureAlive();
246
+ this._handle.dump(timestamp);
247
+ }
248
+
249
+ /** Release native resources. */
250
+ dispose(): void {
251
+ if (!this._disposed) {
252
+ this._disposed = true;
253
+ this._handle.dispose();
254
+ }
255
+ }
256
+
257
+ // -----------------------------------------------------------------------
258
+ // Internal
259
+ // -----------------------------------------------------------------------
260
+
261
+ private ensureAlive(): void {
262
+ if (this._disposed) {
263
+ throw new Error("Simulator has been disposed");
264
+ }
265
+ }
266
+ }
package/src/types.ts ADDED
@@ -0,0 +1,164 @@
1
+ /**
2
+ * @celox-sim/celox — Core type definitions
3
+ *
4
+ * These types define the contract between:
5
+ * - Stream A (type generator): produces ModuleDefinition
6
+ * - Stream B (NAPI bindings): produces CreateResult / NativeHandles
7
+ * - Stream C (this package): consumes both
8
+ */
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Module definition (produced by Stream A's `celox-gen-ts`)
12
+ // ---------------------------------------------------------------------------
13
+
14
+ /**
15
+ * A compiled module descriptor emitted by `celox-gen-ts`.
16
+ * The type parameter `Ports` carries the generated port interface
17
+ * (e.g. `AdderPorts`) so that `Simulator.create(Adder)` returns
18
+ * a correctly-typed DUT accessor.
19
+ */
20
+ export interface ModuleDefinition<Ports = Record<string, unknown>> {
21
+ readonly __celox_module: true;
22
+ readonly name: string;
23
+ readonly source: string;
24
+ readonly ports: Record<string, PortInfo>;
25
+ readonly events: string[];
26
+ /** Absolute path to the Veryl project directory (set by Vite plugin). */
27
+ readonly projectPath?: string;
28
+ /** Phantom field — never set at runtime. Carries the `Ports` type. */
29
+ readonly __ports?: Ports;
30
+ }
31
+
32
+ /** Metadata for a single port / interface member. */
33
+ export interface PortInfo {
34
+ readonly direction: "input" | "output" | "inout";
35
+ readonly type: "clock" | "reset" | "logic" | "bit";
36
+ readonly width: number;
37
+ readonly arrayDims?: readonly number[];
38
+ readonly is4state?: boolean;
39
+ /** Nested interface members (recursive). */
40
+ readonly interface?: Record<string, PortInfo>;
41
+ }
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // Signal layout (produced by Stream B's NAPI `create()`)
45
+ // ---------------------------------------------------------------------------
46
+
47
+ /**
48
+ * Byte-level location of a signal inside the SharedArrayBuffer.
49
+ * @internal
50
+ */
51
+ export interface SignalLayout {
52
+ /** Byte offset within the STABLE region. */
53
+ readonly offset: number;
54
+ /** Bit width of the signal. */
55
+ readonly width: number;
56
+ /** Number of bytes occupied (ceil(width/8)). */
57
+ readonly byteSize: number;
58
+ /** If true, an equal-sized mask follows immediately after the value. */
59
+ readonly is4state: boolean;
60
+ readonly direction: "input" | "output" | "inout";
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // NAPI handles (produced by Stream B)
65
+ // ---------------------------------------------------------------------------
66
+
67
+ /**
68
+ * Opaque handle returned by NAPI for event-based simulation.
69
+ * @internal
70
+ */
71
+ export interface NativeSimulatorHandle {
72
+ tick(eventId: number): void;
73
+ tickN(eventId: number, count: number): void;
74
+ evalComb(): void;
75
+ dump(timestamp: number): void;
76
+ dispose(): void;
77
+ }
78
+
79
+ /**
80
+ * Opaque handle returned by NAPI for time-based simulation.
81
+ * @internal
82
+ */
83
+ export interface NativeSimulationHandle {
84
+ addClock(eventId: number, period: number, initialDelay: number): void;
85
+ schedule(eventId: number, time: number, value: number): void;
86
+ runUntil(endTime: number): void;
87
+ step(): number | null;
88
+ time(): number;
89
+ evalComb(): void;
90
+ dump(timestamp: number): void;
91
+ dispose(): void;
92
+ }
93
+
94
+ /**
95
+ * Union of both handle types for code that is handle-agnostic.
96
+ * @internal
97
+ */
98
+ export type NativeHandle = NativeSimulatorHandle | NativeSimulationHandle;
99
+
100
+ /**
101
+ * Result returned by NAPI `create()`.
102
+ * @internal
103
+ */
104
+ export interface CreateResult<H extends NativeHandle = NativeHandle> {
105
+ /** The simulator's internal memory buffer shared zero-copy. */
106
+ readonly buffer: ArrayBuffer | SharedArrayBuffer;
107
+ /** Per-signal byte layout within `buffer`. */
108
+ readonly layout: Record<string, SignalLayout>;
109
+ /** Event name → internal event ID mapping. */
110
+ readonly events: Record<string, number>;
111
+ /** Native control handle. */
112
+ readonly handle: H;
113
+ }
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // User-facing options
117
+ // ---------------------------------------------------------------------------
118
+
119
+ export interface SimulatorOptions {
120
+ /** Enable 4-state (X/Z) simulation. Default: false. */
121
+ fourState?: boolean;
122
+ /** Path to write VCD waveform output. */
123
+ vcd?: string;
124
+ }
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // Event handle (returned by Simulator.event())
128
+ // ---------------------------------------------------------------------------
129
+
130
+ /** A resolved event reference for use with `tick()`. */
131
+ export interface EventHandle {
132
+ readonly name: string;
133
+ readonly id: number;
134
+ }
135
+
136
+ // ---------------------------------------------------------------------------
137
+ // 4-state helpers
138
+ // ---------------------------------------------------------------------------
139
+
140
+ /** Sentinel representing all-X. */
141
+ export const X = Symbol.for("veryl:X");
142
+
143
+ /** A 4-state value with explicit bit-level mask. */
144
+ export interface FourStateValue {
145
+ readonly __fourState: true;
146
+ readonly value: number | bigint;
147
+ readonly mask: number | bigint;
148
+ }
149
+
150
+ /** Construct a 4-state value. Mask bits set to 1 indicate X. */
151
+ export function FourState(
152
+ value: number | bigint,
153
+ mask: number | bigint,
154
+ ): FourStateValue {
155
+ return { __fourState: true, value, mask };
156
+ }
157
+
158
+ export function isFourStateValue(v: unknown): v is FourStateValue {
159
+ return (
160
+ typeof v === "object" &&
161
+ v !== null &&
162
+ (v as FourStateValue).__fourState === true
163
+ );
164
+ }
package/README.md DELETED
@@ -1,45 +0,0 @@
1
- # @celox-sim/celox
2
-
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
4
-
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
6
-
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
8
-
9
- ## Purpose
10
-
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@celox-sim/celox`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
15
-
16
- ## What is OIDC Trusted Publishing?
17
-
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
19
-
20
- ## Setup Instructions
21
-
22
- To properly configure OIDC trusted publishing for this package:
23
-
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
28
-
29
- ## DO NOT USE THIS PACKAGE
30
-
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
36
-
37
- ## More Information
38
-
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
42
-
43
- ---
44
-
45
- **Maintained for OIDC setup purposes only**