1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
//! vj CLI: `vj run <file.sv>` -- Task S7's end-to-end pipeline
//! (`docs/superpowers/plans/2026-09-26-wave-A2-slice.md`; ledger CG-125).
//! `vj build-aot <file.sv>... -o <out.so>` / `vj run-aot <out.so>` --
//! the AOT path (see `vj::aot`'s own module doc comment).
//! Any other/no subcommand prints usage to stderr and exits `2`, per Task
//! S7's own "Public names/types introduced" spec.
use std::io::Write;
use std::path::{Path, PathBuf};
use vj::aot::{RUNTIME_LIB_NAME, build_aot_design, build_aot_exe, find_runtime_lib};
use vj::aot_loader::run_aot;
use vj::cli::{RunArgs, parse_build_aot_args, parse_run_args};
use vj::dpi;
use vj::run::run_sources_with_options;
use vj::vpi;
/// Task 11 (AB-006): a manual/diagnostic smoke path proving the AOT-static
/// build embeds the fixture kernel directly (ordinary linked-symbol calls,
/// no `libloading`, no `dlopen`). Only compiled when this crate's own
/// `static-test-kernel` Cargo feature is enabled -- the default `vj`
/// build never references this module at all. Not exercised by any
/// `#[test]` in this crate (the automated coverage lives in
/// `tests/static_link.rs`, which does its own independent `include!` of the
/// same `build.rs`-generated shim); this exists so a human (`cargo run -p
/// vj --features static-test-kernel -- __static_test_kernel_smoke__`)
/// can observe the static path work from the real CLI binary too.
#[cfg(feature = "static-test-kernel")]
mod static_test_kernel_smoke {
include!(concat!(env!("OUT_DIR"), "/kernel_shim.rs"));
pub fn run() {
// SAFETY: sim_abi_version/sim_init are the statically-linked
// fixture kernel's real ABI-conformant exports -- build.rs compiled
// and archived them, and wired the archive into this very binary's
// own link line (`cargo:rustc-link-arg-bins`, see build.rs's own
// doc comment for why). Calling them with their declared signatures
// (design_id: u32) -> *mut c_void, matching
// tests/fixtures/test_kernel.rs's own real signatures, is exactly
// what they're for.
unsafe {
let version = sim_abi_version();
let handle = sim_init(0);
println!(
"static-test-kernel smoke: sim_abi_version={version} sim_init_handle={handle:p}"
);
}
}
}
/// A `--vpi`/`+vpi=` entry as a library path: a C/C++ source is compiled
/// against the bundled headers first (exiting with status 1 on failure), a
/// library path is used as given, and a bare `NAME` means `NAME.so`.
fn vpi_library(arg: &str) -> PathBuf {
let path = Path::new(arg);
if dpi::is_c_source(path) {
match dpi::compile_sources(&[path.to_path_buf()]) {
Ok(lib) => lib,
Err(e) => {
eprintln!("error: {e}");
std::process::exit(1);
}
}
} else {
dpi::sv_lib_path(arg)
}
}
/// `vj run --aot` (AB-012): elaborate -> codegen -> AOT compile+link into a
/// private temporary library -> load and run it, returning the exit code.
/// The temporary directory is removed before returning. A timing option is
/// refused by name (it is a build-time choice of `build-aot`; pass the same
/// flags to `build-aot` and `run-aot`).
#[allow(clippy::too_many_arguments)]
fn run_aot_in_one_invocation(
sources: &vj::sources::SourceSet,
timing: vj::timing_options::TimingOptions,
driver_options: Vec<String>,
x_initial_zero: bool,
coverage: bool,
seed: Option<u64>,
plusargs: &[String],
libs: &[PathBuf],
vpi_libs: &[PathBuf],
) -> i32 {
if timing != Default::default() {
eprintln!("error: `run --aot` does not take timing options; use `build-aot` and `run-aot`");
return 2;
}
let Some(runtime_lib) = find_runtime_lib() else {
eprintln!(
"error: cannot find {RUNTIME_LIB_NAME}; build it with `cargo xtask \
build-aot-runtime` or set VJ_AOT_RT"
);
return 1;
};
// `design.so` is loaded from here: an owner-only directory with an
// unpredictable name, never a pre-existing one (see `private_dir`).
let dir = match vj::private_dir::create("vj-run-aot") {
Ok(d) => d,
Err(e) => {
eprintln!("error: cannot create a private scratch directory: {e}");
return 1;
}
};
let so = dir.join("design.so");
vj_runtime::codecov::set_enabled(coverage);
vj_codegen::frontend::set_driver_options(driver_options);
vj::run::set_x_initial_zero(x_initial_zero);
dpi::set_pending_libraries(libs.to_vec());
vpi::loader::set_pending_libraries(vpi_libs.to_vec());
let code = match build_aot_design(sources, timing, &runtime_lib, &so) {
Err(e) => {
eprintln!("error: {e}");
1
}
Ok(_) => {
let mut run_args: Vec<String> = plusargs.to_vec();
for l in libs {
run_args.push("--sv-lib".into());
run_args.push(l.display().to_string());
}
for l in vpi_libs {
run_args.push("--vpi".into());
run_args.push(l.display().to_string());
}
if x_initial_zero {
run_args.extend(["--x-initial".into(), "zero".into()]);
}
if coverage {
run_args.push("--coverage".into());
}
if let Some(s) = seed {
run_args.extend(["--seed".into(), s.to_string()]);
}
match run_aot(&so, &run_args) {
Ok(code) => code,
Err(e) => {
eprintln!("error: {e}");
1
}
}
}
};
let _ = std::fs::remove_dir_all(&dir);
code
}
fn main() {
let args: Vec<String> = std::env::args().collect();
#[cfg(feature = "static-test-kernel")]
if args.len() == 2 && args[1] == "__static_test_kernel_smoke__" {
static_test_kernel_smoke::run();
return;
}
match args.get(1).map(String::as_str) {
// RT-077: `vj run <file.sv> [+PLUSARG]... [DPI...]` -- every
// trailing token that starts with `+` is a plusarg, handed, in
// order, to `run_design_with_options` (which reaches
// `vj_runtime::plusargs_sink::set_plusargs_for_run` before
// simulation starts). DPI-C code: a C/C++ source is compiled into a
// shared library, and `--sv-lib LIB` / `-sv_lib LIB` / a `.so`
// path is loaded, before the design is compiled (`vj::dpi`).
// Several files, `-I`/`+incdir+`, `-D`/`+define+`, `--top` and `-f`
// list files are read by `vj::cli`.
Some("run") if args.len() >= 3 => {
// AB-012: `--aot` selects the other half of the pipeline
// (elaborate -> codegen -> AOT compile+link -> run) in a single
// invocation: `build-aot` into a private temporary library, then
// `run-aot` it, then delete it.
let aot = args[2..].iter().any(|a| a == "--aot");
// `--parse-only`: preprocess and parse the sources, no
// elaboration and no simulation (exit 0 when they parse).
let parse_only = args[2..].iter().any(|a| a == "--parse-only");
let run_tokens: Vec<String> = args[2..]
.iter()
.filter(|a| *a != "--aot" && *a != "--parse-only")
.cloned()
.collect();
let parsed = match parse_run_args(&run_tokens) {
Ok(p) => p,
Err(e) => {
eprintln!("error: {e}");
std::process::exit(2);
}
};
let RunArgs {
sources: source_set,
plusargs,
mut libs,
c_sources,
timing,
vpi_libs,
driver_options,
x_initial_zero,
coverage,
seed,
} = parsed;
if parse_only {
match vj::sources::parse_only(&source_set) {
Ok(()) => std::process::exit(0),
Err(e) => {
eprintln!("error: {e}");
std::process::exit(1);
}
}
}
let vpi_libs: Vec<PathBuf> = vpi_libs.iter().map(|l| vpi_library(l)).collect();
if !c_sources.is_empty() {
match dpi::compile_sources(&c_sources) {
Ok(lib) => libs.push(lib),
Err(e) => {
eprintln!("error: {e}");
std::process::exit(1);
}
}
}
if aot {
std::process::exit(run_aot_in_one_invocation(
&source_set,
timing,
driver_options,
x_initial_zero,
coverage,
seed,
&plusargs,
&libs,
&vpi_libs,
));
}
vj_codegen::frontend::set_driver_options(driver_options);
vj::run::set_x_initial_zero(x_initial_zero);
vj_runtime::codecov::set_enabled(coverage);
vj::run::set_seed(seed);
dpi::set_pending_libraries(libs);
vpi::loader::set_pending_libraries(vpi_libs);
vpi::api::set_pending_command_line(args.clone());
let mut stdout = std::io::stdout();
match run_sources_with_options(&source_set, &plusargs, timing, &mut stdout) {
Ok(code) => {
let _ = stdout.flush();
std::process::exit(code);
}
Err(e) => {
eprintln!("error: {e}");
std::process::exit(1);
}
}
}
// `cc -I "$(vj --vpi-include-dir)" -shared -fPIC my_vpi.c`.
Some("--vpi-include-dir") if args.len() == 2 => match vpi::include_dir() {
Ok(dir) => println!("{}", dir.display()),
Err(e) => {
eprintln!("error: {e}");
std::process::exit(1);
}
},
// `vj build-aot FILE... [source options] -o OUT.so [--runtime-lib LIB]`:
// the design of `run`, compiled into a shared library (see
// `vj::aot`). A construct the library cannot carry is refused
// by name.
Some("build-aot") if args.len() >= 3 => {
let parsed = match parse_build_aot_args(&args[2..]) {
Ok(p) => p,
Err(e) => {
eprintln!("error: {e}");
std::process::exit(2);
}
};
let RunArgs {
sources: source_set,
mut libs,
c_sources,
timing,
vpi_libs,
driver_options,
x_initial_zero,
coverage,
..
} = parsed.run;
// Statement probes are code, so they are a build choice
// (`build-aot --coverage`); toggle and assertion coverage are not.
vj_runtime::codecov::set_enabled(coverage);
let runtime_lib = match parsed.runtime_lib.or_else(find_runtime_lib) {
Some(l) => l,
None => {
eprintln!(
"error: cannot find {RUNTIME_LIB_NAME}; build it with `cargo xtask \
build-aot-runtime` and pass --runtime-lib or set VJ_AOT_RT"
);
std::process::exit(1);
}
};
// Libraries only matter to be refused: DPI and VPI run host code.
let vpi_libs: Vec<PathBuf> = vpi_libs.iter().map(|l| vpi_library(l)).collect();
if !c_sources.is_empty() {
match dpi::compile_sources(&c_sources) {
Ok(lib) => libs.push(lib),
Err(e) => {
eprintln!("error: {e}");
std::process::exit(1);
}
}
}
vj_codegen::frontend::set_driver_options(driver_options);
vj::run::set_x_initial_zero(x_initial_zero);
dpi::set_pending_libraries(libs);
vpi::loader::set_pending_libraries(vpi_libs);
if parsed.exe {
match build_aot_exe(&source_set, timing, &runtime_lib, &parsed.out) {
Ok(()) => println!("linked {} (standalone executable)", parsed.out.display()),
Err(e) => {
eprintln!("error: {e}");
std::process::exit(1);
}
}
} else {
match build_aot_design(&source_set, timing, &runtime_lib, &parsed.out) {
Ok(handle) => {
println!(
"linked {} (abi_version={}, process_count={})",
parsed.out.display(),
handle.abi_version,
handle.process_count
);
}
Err(e) => {
eprintln!("error: {e}");
std::process::exit(1);
}
}
}
}
// `vj run-aot OUT.so [+plusarg]... [--seed N] [--x-initial zero|x]
// [timing options]`: a timing option (same flags as `run`/`build-aot`,
// M5) is checked against what the library was built with, never
// forwarded to the design -- a disagreement is refused by name.
Some("run-aot") if args.len() >= 3 => {
let code = match run_aot(Path::new(&args[2]), &args[3..]) {
Ok(code) => code,
Err(e) => {
eprintln!("error: {e}");
1
}
};
std::process::exit(code);
}
_ => {
eprintln!(
"usage: vj run <file.sv>... [source options] [+plusarg]... [dpi.c | lib.so | --sv-lib LIB]... \
[--vpi LIB | +vpi=LIB[,LIB]]... [-GNAME=VALUE]... [compat options] [timing options]"
);
eprintln!(" source options:");
eprintln!(
" -I DIR | +incdir+DIR[+DIR]... `include search directories (after the including file's own)"
);
eprintln!(
" -D NAME[=VALUE] | +define+NAME[=VALUE]... predefined macros (NAME alone is 1)"
);
eprintln!(
" --top MODULE | -top MODULE elaborate MODULE as a top (repeatable; default: every uninstantiated module)"
);
eprintln!(
" -f LIST | -F LIST read arguments from a list file (-F: paths relative to the list file)"
);
eprintln!(
" --timescale UNIT/PRECISION time scale of design elements that declare none (e.g. 1ns/1ps)"
);
eprintln!(" compat options (non-LRM; each relaxes or overrides an IEEE 1800 rule):");
eprintln!(
" --allow-use-before-declare a name used before its declaration resolves to it"
);
eprintln!(
" --relax-enum-conversions implicit integral-to-enum conversions (LRM 6.19.3)"
);
eprintln!(
" --allow-hierarchical-const hierarchical references in constant expressions"
);
eprintln!(
" --compat vcs|all slang's bundles of the relaxations above"
);
eprintln!(
" --x-initial zero uninitialized variables and memories start at 0, not X"
);
eprintln!(
" --parse-only (run) preprocess and parse the sources, then stop"
);
eprintln!(
" --seed N base seed of the $random/$urandom generators (default: a fixed constant)"
);
eprintln!(" timing options:");
eprintln!(
" --timing-check-per-bit per-bit expansion of vector timing checks (LRM 31.8)"
);
eprintln!(
" --enable-negative-timing-checks negative $setuphold/$recrem limits (LRM 31.9.4)"
);
eprintln!(
" --timing-reject-limit-pct=N default pulse reject limit, 0..100 percent of the delay (LRM 30.7.2, 32.7)"
);
eprintln!(
" --timing-error-limit-pct=N default pulse error limit, 0..100 percent of the delay (LRM 30.7.2, 32.7)"
);
eprintln!(
" --pulse-style=on-event|on-detect pulse style, overrides declarations (LRM 30.7.4.1)"
);
eprintln!(
" --showcancelled | --noshowcancelled showcancelled mode, overrides declarations (LRM 30.7.4.2)"
);
eprintln!(
" --sdf-mtm=minimum|typical|maximum what $sdf_annotate's TOOL_CONTROL selects (LRM Table 32-5)"
);
eprintln!(
" vj build-aot <file.sv>... [source options] -o <out.so> [--runtime-lib libvj_aot.a] [--exe]"
);
eprintln!(
" --exe link <out.so> as a standalone executable (no vj run-aot needed)"
);
eprintln!(
" vj run-aot <out.so> [+plusarg]... [--seed N] [--x-initial zero|x] [--vpi LIB]... [timing options]"
);
eprintln!(" vj --vpi-include-dir");
std::process::exit(2);
}
}
}