@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.
- package/dist/dut.d.ts +35 -0
- package/dist/dut.d.ts.map +1 -0
- package/dist/dut.js +342 -0
- package/dist/dut.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/napi-bridge.d.ts +7 -0
- package/dist/napi-bridge.d.ts.map +1 -0
- package/dist/napi-bridge.js +7 -0
- package/dist/napi-bridge.js.map +1 -0
- package/dist/napi-helpers.d.ts +104 -0
- package/dist/napi-helpers.d.ts.map +1 -0
- package/dist/napi-helpers.js +207 -0
- package/dist/napi-helpers.js.map +1 -0
- package/dist/simulation.d.ts +111 -0
- package/dist/simulation.d.ts.map +1 -0
- package/dist/simulation.js +187 -0
- package/dist/simulation.js.map +1 -0
- package/dist/simulator.d.ts +92 -0
- package/dist/simulator.d.ts.map +1 -0
- package/dist/simulator.js +171 -0
- package/dist/simulator.js.map +1 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +23 -0
- package/dist/types.js.map +1 -0
- package/package.json +50 -7
- package/src/dut.test.ts +534 -0
- package/src/dut.ts +449 -0
- package/src/e2e.bench.ts +240 -0
- package/src/e2e.test.ts +965 -0
- package/src/index.ts +57 -0
- package/src/matchers.ts +154 -0
- package/src/napi-bridge.ts +13 -0
- package/src/napi-helpers.ts +326 -0
- package/src/simulation.test.ts +185 -0
- package/src/simulation.ts +273 -0
- package/src/simulator.test.ts +191 -0
- package/src/simulator.ts +266 -0
- package/src/types.ts +164 -0
- package/README.md +0 -45
package/src/index.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @celox-sim/celox
|
|
3
|
+
*
|
|
4
|
+
* TypeScript runtime for Celox HDL simulation.
|
|
5
|
+
* Provides zero-FFI signal I/O via SharedArrayBuffer + DataView,
|
|
6
|
+
* with NAPI calls only for control operations (tick, runUntil, etc.).
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Core types
|
|
12
|
+
export type {
|
|
13
|
+
ModuleDefinition,
|
|
14
|
+
PortInfo,
|
|
15
|
+
SimulatorOptions,
|
|
16
|
+
EventHandle,
|
|
17
|
+
FourStateValue,
|
|
18
|
+
} from "./types.js";
|
|
19
|
+
|
|
20
|
+
/** @internal */
|
|
21
|
+
export type {
|
|
22
|
+
SignalLayout,
|
|
23
|
+
CreateResult,
|
|
24
|
+
NativeHandle,
|
|
25
|
+
NativeSimulatorHandle,
|
|
26
|
+
NativeSimulationHandle,
|
|
27
|
+
} from "./types.js";
|
|
28
|
+
|
|
29
|
+
// 4-state helpers
|
|
30
|
+
export { X, FourState, isFourStateValue } from "./types.js";
|
|
31
|
+
|
|
32
|
+
// Simulator (event-based)
|
|
33
|
+
export { Simulator } from "./simulator.js";
|
|
34
|
+
|
|
35
|
+
// Simulation (time-based)
|
|
36
|
+
export { Simulation } from "./simulation.js";
|
|
37
|
+
|
|
38
|
+
/** @internal */
|
|
39
|
+
export { createDut, readFourState } from "./dut.js";
|
|
40
|
+
/** @internal */
|
|
41
|
+
export type { DirtyState } from "./dut.js";
|
|
42
|
+
|
|
43
|
+
/** @internal */
|
|
44
|
+
export {
|
|
45
|
+
loadNativeAddon,
|
|
46
|
+
parseNapiLayout,
|
|
47
|
+
buildPortsFromLayout,
|
|
48
|
+
wrapDirectSimulatorHandle,
|
|
49
|
+
wrapDirectSimulationHandle,
|
|
50
|
+
createSimulatorBridge,
|
|
51
|
+
createSimulationBridge,
|
|
52
|
+
} from "./napi-helpers.js";
|
|
53
|
+
/** @internal */
|
|
54
|
+
export type { RawNapiAddon, RawNapiSimulatorHandle, RawNapiSimulationHandle } from "./napi-helpers.js";
|
|
55
|
+
|
|
56
|
+
// NAPI bridge (backward compat — re-exports from napi-helpers)
|
|
57
|
+
// Consumers that import from "./napi-bridge.js" still work.
|
package/src/matchers.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vitest custom matchers for Veryl simulation.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import { setupMatchers } from "@celox-sim/celox/matchers";
|
|
6
|
+
* setupMatchers();
|
|
7
|
+
*
|
|
8
|
+
* expect(dut.y).toBeX();
|
|
9
|
+
* expect(dut.y).not.toBeZ();
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { SignalLayout } from "./types.js";
|
|
13
|
+
import { readFourState } from "./dut.js";
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Matcher declarations (augment vitest's Assertion interface)
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
declare module "vitest" {
|
|
20
|
+
interface Assertion {
|
|
21
|
+
/** Assert that the value has any X bits (mask !== 0). */
|
|
22
|
+
toBeX(): void;
|
|
23
|
+
/** Assert that the value is all-X (mask === all-ones). */
|
|
24
|
+
toBeAllX(): void;
|
|
25
|
+
/** Assert that the value has no X bits (mask === 0). */
|
|
26
|
+
toBeNotX(): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface AsymmetricMatchersContaining {
|
|
30
|
+
toBeX(): void;
|
|
31
|
+
toBeAllX(): void;
|
|
32
|
+
toBeNotX(): void;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// 4-state inspection context
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A wrapper that carries the SharedArrayBuffer + SignalLayout alongside
|
|
42
|
+
* the value, so matchers can inspect the raw 4-state representation.
|
|
43
|
+
*
|
|
44
|
+
* Usage:
|
|
45
|
+
* const ref = sim.fourStateRef("y");
|
|
46
|
+
* expect(ref).toBeX();
|
|
47
|
+
*/
|
|
48
|
+
export interface FourStateRef {
|
|
49
|
+
readonly __fourStateRef: true;
|
|
50
|
+
readonly buffer: SharedArrayBuffer;
|
|
51
|
+
readonly layout: SignalLayout;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function fourStateRef(
|
|
55
|
+
buffer: SharedArrayBuffer,
|
|
56
|
+
layout: SignalLayout,
|
|
57
|
+
): FourStateRef {
|
|
58
|
+
return { __fourStateRef: true, buffer, layout };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isFourStateRef(v: unknown): v is FourStateRef {
|
|
62
|
+
return (
|
|
63
|
+
typeof v === "object" &&
|
|
64
|
+
v !== null &&
|
|
65
|
+
(v as FourStateRef).__fourStateRef === true
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Matcher implementations
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
function getMask(received: unknown): number | bigint {
|
|
74
|
+
if (!isFourStateRef(received)) {
|
|
75
|
+
throw new TypeError(
|
|
76
|
+
"toBeX/toBeAllX/toBeNotX matchers require a FourStateRef. " +
|
|
77
|
+
"Use sim.fourStateRef(name) to get one.",
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
const [, mask] = readFourState(received.buffer, received.layout);
|
|
81
|
+
return mask;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const customMatchers = {
|
|
85
|
+
toBeX(received: unknown) {
|
|
86
|
+
const mask = getMask(received);
|
|
87
|
+
const pass = mask !== 0 && mask !== 0n;
|
|
88
|
+
return {
|
|
89
|
+
pass,
|
|
90
|
+
message: () =>
|
|
91
|
+
pass
|
|
92
|
+
? `expected signal NOT to have X bits, but mask = ${mask}`
|
|
93
|
+
: `expected signal to have X bits, but mask = 0`,
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
toBeAllX(received: unknown) {
|
|
98
|
+
if (!isFourStateRef(received)) {
|
|
99
|
+
throw new TypeError("toBeAllX requires a FourStateRef");
|
|
100
|
+
}
|
|
101
|
+
const [, mask] = readFourState(received.buffer, received.layout);
|
|
102
|
+
const width = received.layout.width;
|
|
103
|
+
const allOnes =
|
|
104
|
+
width <= 53
|
|
105
|
+
? (width === 53 ? Number.MAX_SAFE_INTEGER : (1 << width) - 1)
|
|
106
|
+
: (1n << BigInt(width)) - 1n;
|
|
107
|
+
const pass = mask === allOnes;
|
|
108
|
+
return {
|
|
109
|
+
pass,
|
|
110
|
+
message: () =>
|
|
111
|
+
pass
|
|
112
|
+
? `expected signal NOT to be all-X`
|
|
113
|
+
: `expected signal to be all-X, but mask = ${mask}`,
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
toBeNotX(received: unknown) {
|
|
118
|
+
const mask = getMask(received);
|
|
119
|
+
const pass = mask === 0 || mask === 0n;
|
|
120
|
+
return {
|
|
121
|
+
pass,
|
|
122
|
+
message: () =>
|
|
123
|
+
pass
|
|
124
|
+
? `expected signal to have X bits, but mask = 0`
|
|
125
|
+
: `expected signal NOT to have X bits, but mask = ${mask}`,
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Setup
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Register custom matchers with vitest.
|
|
136
|
+
* Call once in a setup file or at the top of your test:
|
|
137
|
+
*
|
|
138
|
+
* ```ts
|
|
139
|
+
* import { setupMatchers } from "@celox-sim/celox/matchers";
|
|
140
|
+
* setupMatchers();
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export function setupMatchers(): void {
|
|
144
|
+
// Dynamic import to keep vitest as an optional peer dependency
|
|
145
|
+
try {
|
|
146
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
147
|
+
const { expect } = require("vitest");
|
|
148
|
+
expect.extend(customMatchers);
|
|
149
|
+
} catch {
|
|
150
|
+
throw new Error(
|
|
151
|
+
"vitest is required for setupMatchers(). Install it as a dev dependency.",
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backward-compatibility re-exports from napi-helpers.ts.
|
|
3
|
+
*
|
|
4
|
+
* New code should import from "./napi-helpers.js" directly.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
createSimulatorBridge,
|
|
9
|
+
createSimulationBridge,
|
|
10
|
+
type RawNapiAddon,
|
|
11
|
+
type RawNapiSimulatorHandle,
|
|
12
|
+
type RawNapiSimulationHandle,
|
|
13
|
+
} from "./napi-helpers.js";
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NAPI helper utilities for the @celox-sim/celox TypeScript runtime.
|
|
3
|
+
*
|
|
4
|
+
* Provides reusable functions for:
|
|
5
|
+
* - Loading the native addon
|
|
6
|
+
* - Parsing NAPI layout JSON into SignalLayout
|
|
7
|
+
* - Building PortInfo from NAPI layout (auto-detect ports)
|
|
8
|
+
* - Wrapping NAPI handles with zero-copy direct operations
|
|
9
|
+
* - Creating bridge functions for Simulator.create() / Simulation.create()
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
CreateResult,
|
|
14
|
+
NativeSimulatorHandle,
|
|
15
|
+
NativeSimulationHandle,
|
|
16
|
+
PortInfo,
|
|
17
|
+
SignalLayout,
|
|
18
|
+
SimulatorOptions,
|
|
19
|
+
} from "./types.js";
|
|
20
|
+
import type { NativeCreateFn } from "./simulator.js";
|
|
21
|
+
import type { NativeCreateSimulationFn } from "./simulation.js";
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Raw NAPI handle shapes (what the .node addon actually exports)
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
export interface RawNapiSimulatorHandle {
|
|
28
|
+
readonly layoutJson: string;
|
|
29
|
+
readonly eventsJson: string;
|
|
30
|
+
readonly stableSize: number;
|
|
31
|
+
readonly totalSize: number;
|
|
32
|
+
tick(eventId: number): void;
|
|
33
|
+
tickN(eventId: number, count: number): void;
|
|
34
|
+
evalComb(): void;
|
|
35
|
+
dump(timestamp: number): void;
|
|
36
|
+
sharedMemory(): Uint8Array;
|
|
37
|
+
dispose(): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface RawNapiSimulationHandle {
|
|
41
|
+
readonly layoutJson: string;
|
|
42
|
+
readonly eventsJson: string;
|
|
43
|
+
readonly stableSize: number;
|
|
44
|
+
readonly totalSize: number;
|
|
45
|
+
addClock(eventId: number, period: number, initialDelay: number): void;
|
|
46
|
+
schedule(eventId: number, time: number, value: number): void;
|
|
47
|
+
runUntil(endTime: number): void;
|
|
48
|
+
step(): number | null;
|
|
49
|
+
time(): number;
|
|
50
|
+
evalComb(): void;
|
|
51
|
+
dump(timestamp: number): void;
|
|
52
|
+
sharedMemory(): Uint8Array;
|
|
53
|
+
dispose(): void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface NapiOptions {
|
|
57
|
+
fourState?: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface RawNapiAddon {
|
|
61
|
+
NativeSimulatorHandle: {
|
|
62
|
+
new (code: string, top: string, options?: NapiOptions): RawNapiSimulatorHandle;
|
|
63
|
+
fromProject(projectPath: string, top: string, options?: NapiOptions): RawNapiSimulatorHandle;
|
|
64
|
+
};
|
|
65
|
+
NativeSimulationHandle: {
|
|
66
|
+
new (code: string, top: string, options?: NapiOptions): RawNapiSimulationHandle;
|
|
67
|
+
fromProject(projectPath: string, top: string, options?: NapiOptions): RawNapiSimulationHandle;
|
|
68
|
+
};
|
|
69
|
+
genTs(projectPath: string): string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Native addon loading
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
import { createRequire } from "node:module";
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Load the native NAPI addon.
|
|
80
|
+
*
|
|
81
|
+
* Resolution: `@celox-sim/celox-napi` package (works both in workspace dev
|
|
82
|
+
* and when installed from npm — napi-rs generated index.js handles platform
|
|
83
|
+
* detection). An explicit path can override this.
|
|
84
|
+
*
|
|
85
|
+
* @param addonPath Explicit path to the `.node` file (overrides auto-detection).
|
|
86
|
+
*/
|
|
87
|
+
export function loadNativeAddon(addonPath?: string): RawNapiAddon {
|
|
88
|
+
const require = createRequire(import.meta.url);
|
|
89
|
+
|
|
90
|
+
if (addonPath) {
|
|
91
|
+
return require(addonPath) as RawNapiAddon;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
return require("@celox-sim/celox-napi") as RawNapiAddon;
|
|
96
|
+
} catch (e) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Failed to load NAPI addon from @celox-sim/celox-napi. ` +
|
|
99
|
+
`Build it with: pnpm run build:napi`,
|
|
100
|
+
{ cause: e },
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// Layout parsing helpers
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
interface RawSignalLayout {
|
|
110
|
+
offset: number;
|
|
111
|
+
width: number;
|
|
112
|
+
byte_size: number;
|
|
113
|
+
is_4state: boolean;
|
|
114
|
+
direction: string;
|
|
115
|
+
type_kind: string;
|
|
116
|
+
array_dims?: number[];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Parse the NAPI layout JSON into SignalLayout records.
|
|
121
|
+
* Returns both the full layout (with type_kind for port detection) and
|
|
122
|
+
* the DUT-compatible layout (without type_kind).
|
|
123
|
+
*/
|
|
124
|
+
export function parseNapiLayout(json: string): {
|
|
125
|
+
signals: Record<string, SignalLayout & { typeKind: string; arrayDims?: number[] }>;
|
|
126
|
+
forDut: Record<string, SignalLayout>;
|
|
127
|
+
} {
|
|
128
|
+
const raw: Record<string, RawSignalLayout> = JSON.parse(json);
|
|
129
|
+
const signals: Record<string, SignalLayout & { typeKind: string; arrayDims?: number[] }> = {};
|
|
130
|
+
const forDut: Record<string, SignalLayout> = {};
|
|
131
|
+
|
|
132
|
+
for (const [name, r] of Object.entries(raw)) {
|
|
133
|
+
const sl: SignalLayout = {
|
|
134
|
+
offset: r.offset,
|
|
135
|
+
width: r.width,
|
|
136
|
+
byteSize: r.byte_size > 0 ? r.byte_size : Math.ceil(r.width / 8),
|
|
137
|
+
is4state: r.is_4state,
|
|
138
|
+
direction: r.direction as "input" | "output" | "inout",
|
|
139
|
+
};
|
|
140
|
+
const entry: SignalLayout & { typeKind: string; arrayDims?: number[] } = {
|
|
141
|
+
...sl,
|
|
142
|
+
typeKind: r.type_kind,
|
|
143
|
+
};
|
|
144
|
+
if (r.array_dims && r.array_dims.length > 0) {
|
|
145
|
+
entry.arrayDims = r.array_dims;
|
|
146
|
+
}
|
|
147
|
+
signals[name] = entry;
|
|
148
|
+
forDut[name] = sl;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { signals, forDut };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Build PortInfo records from the NAPI layout signals.
|
|
156
|
+
* This auto-detects port metadata so users don't need to hand-write ModuleDefinition.
|
|
157
|
+
*/
|
|
158
|
+
export function buildPortsFromLayout(
|
|
159
|
+
signals: Record<string, SignalLayout & { typeKind: string; arrayDims?: number[] }>,
|
|
160
|
+
_events: Record<string, number>,
|
|
161
|
+
): Record<string, PortInfo> {
|
|
162
|
+
const ports: Record<string, PortInfo> = {};
|
|
163
|
+
|
|
164
|
+
for (const [name, sig] of Object.entries(signals)) {
|
|
165
|
+
const typeKind = sig.typeKind;
|
|
166
|
+
let portType: "clock" | "reset" | "logic" | "bit";
|
|
167
|
+
switch (typeKind) {
|
|
168
|
+
case "clock":
|
|
169
|
+
portType = "clock";
|
|
170
|
+
break;
|
|
171
|
+
case "reset":
|
|
172
|
+
portType = "reset";
|
|
173
|
+
break;
|
|
174
|
+
case "bit":
|
|
175
|
+
portType = "bit";
|
|
176
|
+
break;
|
|
177
|
+
default:
|
|
178
|
+
portType = "logic";
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const port: PortInfo = {
|
|
183
|
+
direction: sig.direction,
|
|
184
|
+
type: portType,
|
|
185
|
+
width: sig.width,
|
|
186
|
+
is4state: sig.is4state,
|
|
187
|
+
};
|
|
188
|
+
if (sig.arrayDims && sig.arrayDims.length > 0) {
|
|
189
|
+
(port as { arrayDims: readonly number[] }).arrayDims = sig.arrayDims;
|
|
190
|
+
}
|
|
191
|
+
ports[name] = port;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return ports;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// Handle wrapping — zero-copy direct operations
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Wrap a raw NAPI simulator handle with direct (zero-copy) operations.
|
|
203
|
+
* The buffer is shared between JS and Rust — no copies per tick.
|
|
204
|
+
*/
|
|
205
|
+
export function wrapDirectSimulatorHandle(
|
|
206
|
+
raw: RawNapiSimulatorHandle,
|
|
207
|
+
): NativeSimulatorHandle {
|
|
208
|
+
return {
|
|
209
|
+
tick(eventId: number): void {
|
|
210
|
+
raw.tick(eventId);
|
|
211
|
+
},
|
|
212
|
+
tickN(eventId: number, count: number): void {
|
|
213
|
+
raw.tickN(eventId, count);
|
|
214
|
+
},
|
|
215
|
+
evalComb(): void {
|
|
216
|
+
raw.evalComb();
|
|
217
|
+
},
|
|
218
|
+
dump(timestamp: number): void {
|
|
219
|
+
raw.dump(timestamp);
|
|
220
|
+
},
|
|
221
|
+
dispose(): void {
|
|
222
|
+
raw.dispose();
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Wrap a raw NAPI simulation handle with direct (zero-copy) operations.
|
|
229
|
+
*/
|
|
230
|
+
export function wrapDirectSimulationHandle(
|
|
231
|
+
raw: RawNapiSimulationHandle,
|
|
232
|
+
): NativeSimulationHandle {
|
|
233
|
+
return {
|
|
234
|
+
addClock(eventId: number, period: number, initialDelay: number): void {
|
|
235
|
+
raw.addClock(eventId, period, initialDelay);
|
|
236
|
+
},
|
|
237
|
+
schedule(eventId: number, time: number, value: number): void {
|
|
238
|
+
raw.schedule(eventId, time, value);
|
|
239
|
+
},
|
|
240
|
+
runUntil(endTime: number): void {
|
|
241
|
+
raw.runUntil(endTime);
|
|
242
|
+
},
|
|
243
|
+
step(): number | null {
|
|
244
|
+
return raw.step();
|
|
245
|
+
},
|
|
246
|
+
time(): number {
|
|
247
|
+
return raw.time();
|
|
248
|
+
},
|
|
249
|
+
evalComb(): void {
|
|
250
|
+
raw.evalComb();
|
|
251
|
+
},
|
|
252
|
+
dump(timestamp: number): void {
|
|
253
|
+
raw.dump(timestamp);
|
|
254
|
+
},
|
|
255
|
+
dispose(): void {
|
|
256
|
+
raw.dispose();
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
// Legacy layout parser (used by bridge helpers)
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
function parseLegacyLayout(json: string): Record<string, SignalLayout> {
|
|
266
|
+
const raw: Record<string, RawSignalLayout> = JSON.parse(json);
|
|
267
|
+
const result: Record<string, SignalLayout> = {};
|
|
268
|
+
for (const [name, r] of Object.entries(raw)) {
|
|
269
|
+
result[name] = {
|
|
270
|
+
offset: r.offset,
|
|
271
|
+
width: r.width,
|
|
272
|
+
byteSize: r.byte_size > 0 ? r.byte_size : Math.ceil(r.width / 8),
|
|
273
|
+
is4state: r.is_4state,
|
|
274
|
+
direction: r.direction as "input" | "output" | "inout",
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
// Simulator bridge (used by Simulator.create())
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Create a `NativeCreateFn` from a raw NAPI addon, suitable for
|
|
286
|
+
* `Simulator.create(module, { __nativeCreate: ... })`.
|
|
287
|
+
*/
|
|
288
|
+
export function createSimulatorBridge(addon: RawNapiAddon): NativeCreateFn {
|
|
289
|
+
return (
|
|
290
|
+
source: string,
|
|
291
|
+
moduleName: string,
|
|
292
|
+
_options: SimulatorOptions,
|
|
293
|
+
): CreateResult<NativeSimulatorHandle> => {
|
|
294
|
+
const raw = new addon.NativeSimulatorHandle(source, moduleName);
|
|
295
|
+
|
|
296
|
+
const layout = parseLegacyLayout(raw.layoutJson);
|
|
297
|
+
const events: Record<string, number> = JSON.parse(raw.eventsJson);
|
|
298
|
+
|
|
299
|
+
const buf = raw.sharedMemory().buffer;
|
|
300
|
+
const handle = wrapDirectSimulatorHandle(raw);
|
|
301
|
+
|
|
302
|
+
return { buffer: buf, layout, events, handle };
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Create a `NativeCreateSimulationFn` from a raw NAPI addon, suitable for
|
|
308
|
+
* `Simulation.create(module, { __nativeCreate: ... })`.
|
|
309
|
+
*/
|
|
310
|
+
export function createSimulationBridge(addon: RawNapiAddon): NativeCreateSimulationFn {
|
|
311
|
+
return (
|
|
312
|
+
source: string,
|
|
313
|
+
moduleName: string,
|
|
314
|
+
_options: SimulatorOptions,
|
|
315
|
+
): CreateResult<NativeSimulationHandle> => {
|
|
316
|
+
const raw = new addon.NativeSimulationHandle(source, moduleName);
|
|
317
|
+
|
|
318
|
+
const layout = parseLegacyLayout(raw.layoutJson);
|
|
319
|
+
const events: Record<string, number> = JSON.parse(raw.eventsJson);
|
|
320
|
+
|
|
321
|
+
const buf = raw.sharedMemory().buffer;
|
|
322
|
+
const handle = wrapDirectSimulationHandle(raw);
|
|
323
|
+
|
|
324
|
+
return { buffer: buf, layout, events, handle };
|
|
325
|
+
};
|
|
326
|
+
}
|