1mod layout;
2
3use fxhash::FxHashMap as HashMap;
4#[cfg(not(target_arch = "wasm32"))]
5use std::sync::{Arc, Mutex};
6
7use napi::bindgen_prelude::*;
8use napi_derive::napi;
9use veryl_analyzer::{Analyzer, Context, attribute_table, ir::Ir, symbol_table};
10use veryl_metadata::Metadata;
11use veryl_parser::Parser;
12use veryl_path::PathSet;
13
14#[cfg(not(target_arch = "wasm32"))]
15use celox::SimBackend;
16#[cfg(not(target_arch = "wasm32"))]
17use layout::{build_event_map, build_hierarchy_node, build_signal_layout};
18
19#[napi(object)]
21pub struct NapiInstanceSegment {
22 pub name: String,
23 pub index: u32,
24}
25
26#[napi(object)]
28pub struct NapiSignalPath {
29 pub instance_path: Vec<NapiInstanceSegment>,
30 pub var_path: Vec<String>,
31}
32
33#[napi(object)]
35pub struct NapiFalseLoop {
36 pub from: NapiSignalPath,
37 pub to: NapiSignalPath,
38}
39
40#[napi(object)]
42pub struct NapiTrueLoop {
43 pub from: NapiSignalPath,
44 pub to: NapiSignalPath,
45 pub max_iter: u32,
46}
47
48#[napi(object)]
50pub struct NapiSourceFile {
51 pub content: String,
52 pub path: String,
53}
54
55#[napi(object)]
57pub struct NapiParamOverride {
58 pub name: String,
59 pub value: i64,
60}
61
62#[napi(object)]
64pub struct NapiOptimizeOptions {
65 pub store_load_forwarding: Option<bool>,
66 pub hoist_common_branch_loads: Option<bool>,
67 pub bit_extract_peephole: Option<bool>,
68 pub optimize_blocks: Option<bool>,
69 pub split_wide_commits: Option<bool>,
70 pub commit_sinking: Option<bool>,
71 pub inline_commit_forwarding: Option<bool>,
72 pub eliminate_dead_working_stores: Option<bool>,
73 pub reschedule: Option<bool>,
74 pub coalesce_stores: Option<bool>,
75}
76
77#[napi(object)]
79pub struct NapiOptions {
80 pub four_state: Option<bool>,
81 pub vcd: Option<String>,
82 pub opt_level: Option<String>,
85 pub pass_overrides: Option<Vec<String>>,
88 pub optimize: Option<bool>,
91 pub optimize_options: Option<NapiOptimizeOptions>,
93 pub cranelift_opt_level: Option<String>,
95 pub regalloc_algorithm: Option<String>,
97 pub enable_alias_analysis: Option<bool>,
99 pub enable_verifier: Option<bool>,
101 pub false_loops: Option<Vec<NapiFalseLoop>>,
102 pub true_loops: Option<Vec<NapiTrueLoop>>,
103 pub clock_type: Option<String>,
105 pub reset_type: Option<String>,
107 pub extra_source: Option<String>,
109 pub parameters: Option<Vec<NapiParamOverride>>,
111 pub dead_store_policy: Option<String>,
113}
114
115#[allow(dead_code)]
117struct ParsedOptionsCommon {
118 four_state: bool,
119 optimize_options: celox::OptimizeOptions,
120 vcd: Option<String>,
121 false_loops: Vec<(
122 (Vec<(String, usize)>, Vec<String>),
123 (Vec<(String, usize)>, Vec<String>),
124 )>,
125 true_loops: Vec<(
126 (Vec<(String, usize)>, Vec<String>),
127 (Vec<(String, usize)>, Vec<String>),
128 usize,
129 )>,
130 clock_type: Option<celox::ClockType>,
131 reset_type: Option<celox::ResetType>,
132 extra_source: Option<String>,
133 parameters: Vec<(String, u64)>,
134}
135
136#[cfg(not(target_arch = "wasm32"))]
138struct ParsedOptions {
139 common: ParsedOptionsCommon,
140 cranelift_options: celox::CraneliftOptions,
141 dead_store_policy: celox::DeadStorePolicy,
142}
143
144#[cfg(not(target_arch = "wasm32"))]
145impl std::ops::Deref for ParsedOptions {
146 type Target = ParsedOptionsCommon;
147 fn deref(&self) -> &Self::Target {
148 &self.common
149 }
150}
151
152fn convert_signal_path(p: &NapiSignalPath) -> (Vec<(String, usize)>, Vec<String>) {
154 let inst: Vec<(String, usize)> = p
155 .instance_path
156 .iter()
157 .map(|seg| (seg.name.clone(), seg.index as usize))
158 .collect();
159 let var_path: Vec<String> = p.var_path.clone();
160 (inst, var_path)
161}
162
163fn parse_clock_type(s: &str) -> Result<celox::ClockType> {
165 match s {
166 "posedge" => Ok(celox::ClockType::PosEdge),
167 "negedge" => Ok(celox::ClockType::NegEdge),
168 _ => Err(Error::from_reason(format!(
169 "Invalid clock_type '{}'. Expected 'posedge' or 'negedge'.",
170 s
171 ))),
172 }
173}
174
175fn parse_reset_type(s: &str) -> Result<celox::ResetType> {
177 match s {
178 "async_high" => Ok(celox::ResetType::AsyncHigh),
179 "async_low" => Ok(celox::ResetType::AsyncLow),
180 "sync_high" => Ok(celox::ResetType::SyncHigh),
181 "sync_low" => Ok(celox::ResetType::SyncLow),
182 _ => Err(Error::from_reason(format!(
183 "Invalid reset_type '{}'. Expected 'async_high', 'async_low', 'sync_high', or 'sync_low'.",
184 s
185 ))),
186 }
187}
188
189fn convert_optimize_options(napi: &NapiOptimizeOptions) -> celox::OptimizeOptions {
192 let mut opts = celox::OptimizeOptions::all();
193 let fields: &[(Option<bool>, celox::SirPass)] = &[
194 (
195 napi.store_load_forwarding,
196 celox::SirPass::StoreLoadForwarding,
197 ),
198 (
199 napi.hoist_common_branch_loads,
200 celox::SirPass::HoistCommonBranchLoads,
201 ),
202 (
203 napi.bit_extract_peephole,
204 celox::SirPass::BitExtractPeephole,
205 ),
206 (napi.optimize_blocks, celox::SirPass::OptimizeBlocks),
207 (napi.split_wide_commits, celox::SirPass::SplitWideCommits),
208 (napi.commit_sinking, celox::SirPass::CommitSinking),
209 (
210 napi.inline_commit_forwarding,
211 celox::SirPass::InlineCommitForwarding,
212 ),
213 (
214 napi.eliminate_dead_working_stores,
215 celox::SirPass::EliminateDeadWorkingStores,
216 ),
217 (napi.reschedule, celox::SirPass::Reschedule),
218 (napi.coalesce_stores, celox::SirPass::CoalesceStores),
219 ];
220 for &(val, pass) in fields {
221 if let Some(false) = val {
222 opts = opts.disable(pass);
223 }
224 }
225 opts
226}
227
228fn apply_pass_overrides(
230 mut opts: celox::OptimizeOptions,
231 overrides: &[String],
232) -> Result<celox::OptimizeOptions> {
233 for s in overrides {
234 let (enable, rest) = if let Some(rest) = s.strip_prefix('+') {
235 (true, rest)
236 } else if let Some(rest) = s.strip_prefix('-') {
237 (false, rest)
238 } else {
239 return Err(Error::from_reason(format!(
240 "Invalid pass override '{}'. Must start with '+' or '-'.",
241 s
242 )));
243 };
244 let pass_name = rest.strip_prefix("sir:").unwrap_or(rest);
245 let pass = celox::SirPass::parse(pass_name).ok_or_else(|| {
246 Error::from_reason(format!(
247 "Unknown SIR pass '{}'. Valid passes: {}",
248 pass_name,
249 celox::SirPass::ALL
250 .iter()
251 .map(|p| p.as_str())
252 .collect::<Vec<_>>()
253 .join(", ")
254 ))
255 })?;
256 opts = if enable {
257 opts.enable(pass)
258 } else {
259 opts.disable(pass)
260 };
261 }
262 Ok(opts)
263}
264
265#[cfg(not(target_arch = "wasm32"))]
267fn parse_cranelift_opt_level(s: &str) -> Result<celox::CraneliftOptLevel> {
268 match s {
269 "none" => Ok(celox::CraneliftOptLevel::None),
270 "speed" => Ok(celox::CraneliftOptLevel::Speed),
271 "speed_and_size" => Ok(celox::CraneliftOptLevel::SpeedAndSize),
272 _ => Err(Error::from_reason(format!(
273 "Invalid cranelift_opt_level '{}'. Expected 'none', 'speed', or 'speed_and_size'.",
274 s
275 ))),
276 }
277}
278
279#[cfg(not(target_arch = "wasm32"))]
281fn parse_regalloc_algorithm(s: &str) -> Result<celox::RegallocAlgorithm> {
282 match s {
283 "backtracking" => Ok(celox::RegallocAlgorithm::Backtracking),
284 "single_pass" => Ok(celox::RegallocAlgorithm::SinglePass),
285 _ => Err(Error::from_reason(format!(
286 "Invalid regalloc_algorithm '{}'. Expected 'backtracking' or 'single_pass'.",
287 s
288 ))),
289 }
290}
291
292#[cfg(not(target_arch = "wasm32"))]
294fn parse_dead_store_policy(s: &str) -> Result<celox::DeadStorePolicy> {
295 match s {
296 "off" => Ok(celox::DeadStorePolicy::Off),
297 "preserve_top_ports" => Ok(celox::DeadStorePolicy::PreserveTopPorts),
298 "preserve_all_ports" => Ok(celox::DeadStorePolicy::PreserveAllPorts),
299 _ => Err(Error::from_reason(format!(
300 "Invalid dead_store_policy '{}'. Expected 'off', 'preserve_top_ports', or 'preserve_all_ports'.",
301 s
302 ))),
303 }
304}
305
306fn parse_options_common(options: &Option<NapiOptions>) -> Result<ParsedOptionsCommon> {
308 match options.as_ref() {
309 Some(o) => {
310 let false_loops = o
311 .false_loops
312 .as_ref()
313 .map(|loops| {
314 loops
315 .iter()
316 .map(|fl| (convert_signal_path(&fl.from), convert_signal_path(&fl.to)))
317 .collect()
318 })
319 .unwrap_or_default();
320 let true_loops = o
321 .true_loops
322 .as_ref()
323 .map(|loops| {
324 loops
325 .iter()
326 .map(|tl| {
327 (
328 convert_signal_path(&tl.from),
329 convert_signal_path(&tl.to),
330 tl.max_iter as usize,
331 )
332 })
333 .collect()
334 })
335 .unwrap_or_default();
336 let clock_type = o.clock_type.as_deref().map(parse_clock_type).transpose()?;
337 let reset_type = o.reset_type.as_deref().map(parse_reset_type).transpose()?;
338 let parameters = o
339 .parameters
340 .as_ref()
341 .map(|params| {
342 params
343 .iter()
344 .map(|p| (p.name.clone(), p.value as u64))
345 .collect()
346 })
347 .unwrap_or_default();
348 let optimize_options = if let Some(ref level_str) = o.opt_level {
354 let level = celox::OptLevel::parse(level_str).ok_or_else(|| {
355 Error::from_reason(format!(
356 "Invalid opt_level '{}'. Expected 'O0', 'O1', or 'O2'.",
357 level_str
358 ))
359 })?;
360 let opts = celox::OptimizeOptions::new(level);
361 if let Some(ref overrides) = o.pass_overrides {
362 apply_pass_overrides(opts, overrides)?
363 } else {
364 opts
365 }
366 } else if let Some(ref oo) = o.optimize_options {
367 let opts = convert_optimize_options(oo);
368 if let Some(ref overrides) = o.pass_overrides {
369 apply_pass_overrides(opts, overrides)?
370 } else {
371 opts
372 }
373 } else if let Some(false) = o.optimize {
374 celox::OptimizeOptions::none()
375 } else {
376 celox::OptimizeOptions::all()
377 };
378 Ok(ParsedOptionsCommon {
379 four_state: o.four_state.unwrap_or(false),
380 optimize_options,
381 vcd: o.vcd.clone(),
382 false_loops,
383 true_loops,
384 clock_type,
385 reset_type,
386 extra_source: o.extra_source.clone(),
387 parameters,
388 })
389 }
390 None => Ok(ParsedOptionsCommon {
391 four_state: false,
392 optimize_options: celox::OptimizeOptions::all(),
393 vcd: None,
394 false_loops: Vec::new(),
395 true_loops: Vec::new(),
396 clock_type: None,
397 reset_type: None,
398 extra_source: None,
399 parameters: Vec::new(),
400 }),
401 }
402}
403
404#[cfg(not(target_arch = "wasm32"))]
406fn parse_options(options: &Option<NapiOptions>) -> Result<ParsedOptions> {
407 let common = parse_options_common(options)?;
408 match options.as_ref() {
409 Some(o) => {
410 let dead_store_policy = o
411 .dead_store_policy
412 .as_deref()
413 .map(parse_dead_store_policy)
414 .transpose()?
415 .unwrap_or(celox::DeadStorePolicy::Off);
416 let cranelift_opt_level = o
417 .cranelift_opt_level
418 .as_deref()
419 .map(parse_cranelift_opt_level)
420 .transpose()?
421 .unwrap_or(celox::CraneliftOptLevel::Speed);
422 let regalloc_algorithm = o
423 .regalloc_algorithm
424 .as_deref()
425 .map(parse_regalloc_algorithm)
426 .transpose()?
427 .unwrap_or(celox::RegallocAlgorithm::Backtracking);
428 let cranelift_options = celox::CraneliftOptions {
429 opt_level: cranelift_opt_level,
430 regalloc_algorithm,
431 enable_alias_analysis: o.enable_alias_analysis.unwrap_or(true),
432 enable_verifier: o.enable_verifier.unwrap_or(true),
433 tail_call_split: true,
434 diagnostics: celox::CraneliftDiagnostics::default(),
435 };
436 Ok(ParsedOptions {
437 common,
438 cranelift_options,
439 dead_store_policy,
440 })
441 }
442 None => Ok(ParsedOptions {
443 common,
444 cranelift_options: celox::CraneliftOptions::default(),
445 dead_store_policy: celox::DeadStorePolicy::Off,
446 }),
447 }
448}
449
450fn append_extra_source(sources: &mut Vec<(String, std::path::PathBuf)>, extra: &Option<String>) {
452 if let Some(extra) = extra {
453 sources.push((extra.clone(), std::path::PathBuf::from("<extra>")));
454 }
455}
456
457#[derive(serde::Deserialize, Default)]
459#[allow(dead_code)]
460struct CeloxConfig {
461 #[serde(default)]
464 exclude: Vec<String>,
465 #[serde(default)]
466 test: CeloxTestConfig,
467 #[serde(default)]
468 simulation: CeloxSimulationConfig,
469}
470
471#[derive(serde::Deserialize, Default)]
472struct CeloxTestConfig {
473 #[serde(default)]
476 sources: Vec<String>,
477}
478
479#[derive(serde::Deserialize, Default)]
480#[allow(dead_code)]
481struct CeloxSimulationConfig {
482 max_steps: Option<u32>,
485}
486
487fn load_celox_config(project_root: &std::path::Path) -> Result<CeloxConfig> {
490 let path = project_root.join("celox.toml");
491 if !path.exists() {
492 return Ok(CeloxConfig::default());
493 }
494 let content = std::fs::read_to_string(&path)
495 .map_err(|e| Error::from_reason(format!("Failed to read celox.toml: {e}")))?;
496 toml::from_str(&content)
497 .map_err(|e| Error::from_reason(format!("Failed to parse celox.toml: {e}")))
498}
499
500fn build_exclude_set(config: &CeloxConfig) -> Result<Option<globset::GlobSet>> {
503 if config.exclude.is_empty() {
504 return Ok(None);
505 }
506 let mut builder = globset::GlobSetBuilder::new();
507 for pattern in &config.exclude {
508 let glob = globset::GlobBuilder::new(pattern)
509 .literal_separator(true)
510 .build()
511 .map_err(|e| Error::from_reason(format!("Invalid exclude pattern '{pattern}': {e}")))?;
512 builder.add(glob);
513 }
514 let set = builder
515 .build()
516 .map_err(|e| Error::from_reason(format!("Failed to build exclude set: {e}")))?;
517 Ok(Some(set))
518}
519
520fn is_excluded(
523 path: &std::path::Path,
524 project_root: &std::path::Path,
525 exclude_set: &globset::GlobSet,
526) -> bool {
527 let relative = path.strip_prefix(project_root).unwrap_or(path);
528 let rel_str = relative.to_string_lossy().replace('\\', "/");
530 exclude_set.is_match(&rel_str)
531}
532
533fn collect_test_sources(
536 sources: &mut Vec<(String, std::path::PathBuf)>,
537 project_root: &std::path::Path,
538 config: &CeloxConfig,
539) -> Result<()> {
540 for dir in &config.test.sources {
541 let dir_path = project_root.join(dir);
542 if !dir_path.exists() {
543 continue;
544 }
545 let entries = walkdir(&dir_path)?;
546 for entry in entries {
547 let content = std::fs::read_to_string(&entry)
548 .map_err(|e| Error::from_reason(format!("{}: {e}", entry.display())))?;
549 sources.push((content, entry));
550 }
551 }
552 Ok(())
553}
554
555fn walkdir(dir: &std::path::Path) -> Result<Vec<std::path::PathBuf>> {
557 let mut files = Vec::new();
558 let read = std::fs::read_dir(dir)
559 .map_err(|e| Error::from_reason(format!("Cannot read directory {}: {e}", dir.display())))?;
560 for entry in read {
561 let entry = entry.map_err(|e| Error::from_reason(format!("Directory entry error: {e}")))?;
562 let path = entry.path();
563 if path.is_dir() {
564 files.extend(walkdir(&path)?);
565 } else if path.extension().is_some_and(|ext| ext == "veryl") {
566 files.push(path);
567 }
568 }
569 files.sort();
570 Ok(files)
571}
572
573fn load_project_sources(
579 project_path: &str,
580) -> Result<(Vec<(String, std::path::PathBuf)>, Metadata, CeloxConfig)> {
581 let toml_path = Metadata::search_from(project_path)
582 .map_err(|e| Error::from_reason(format!("Could not find Veryl.toml: {e}")))?;
583 let mut metadata = Metadata::load(&toml_path)
584 .map_err(|e| Error::from_reason(format!("Failed to load Veryl.toml: {e}")))?;
585 let paths = metadata
586 .paths::<&str>(&[], false, false)
587 .map_err(|e| Error::from_reason(format!("Failed to gather sources: {e}")))?;
588 let mut sources = Vec::new();
589 for p in paths.iter().filter(|path| !path.example) {
590 let content = std::fs::read_to_string(&p.src)
591 .map_err(|e| Error::from_reason(format!("{}: {e}", p.src.display())))?;
592 sources.push((content, p.src.clone()));
593 }
594 let project_root = toml_path.parent().unwrap_or(&toml_path);
595 let celox_cfg = load_celox_config(project_root)?;
596 collect_test_sources(&mut sources, project_root, &celox_cfg)?;
597 if let Some(exclude_set) = build_exclude_set(&celox_cfg)? {
598 sources.retain(|(_, path)| !is_excluded(path, project_root, &exclude_set));
599 }
600 Ok((sources, metadata, celox_cfg))
601}
602
603fn format_warnings_json(warnings: &[celox::CompilationWarning]) -> String {
608 let msgs: Vec<String> = warnings
609 .iter()
610 .map(|w| celox::render_diagnostic(w))
611 .collect();
612 serde_json::to_string(&msgs).unwrap_or_else(|_| "[]".to_string())
613}
614
615#[cfg(not(target_arch = "wasm32"))]
617fn apply_options<'a, T>(
618 mut builder: celox::SimulatorBuilder<'a, T>,
619 opts: &ParsedOptions,
620) -> celox::SimulatorBuilder<'a, T> {
621 builder = builder.four_state(opts.four_state);
622 builder = builder.optimize_options(opts.optimize_options.clone());
623 builder = builder.cranelift_options(opts.cranelift_options);
624 for (from, to) in &opts.false_loops {
626 builder = builder.false_loop(from.clone(), to.clone());
627 }
628 for (from, to, max_iter) in &opts.true_loops {
629 builder = builder.true_loop(from.clone(), to.clone(), *max_iter);
630 }
631 if let Some(ct) = opts.clock_type {
632 builder = builder.clock_type(ct);
633 }
634 if let Some(rt) = opts.reset_type {
635 builder = builder.reset_type(rt);
636 }
637 for (name, value) in &opts.parameters {
638 builder = builder.param(name, *value);
639 }
640 builder = builder.dead_store_policy(opts.dead_store_policy);
641 builder
642}
643
644#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
649type SharedCode = celox::SharedNativeCode;
650#[cfg(all(
651 not(target_arch = "wasm32"),
652 not(any(target_arch = "x86_64", target_arch = "aarch64"))
653))]
654type SharedCode = celox::SharedJitCode;
655
656#[cfg(not(target_arch = "wasm32"))]
657struct CachedBuild {
659 shared_code: Arc<SharedCode>,
660 runtime_errors: HashMap<i64, (String, Vec<String>)>,
661 layout_json: String,
662 events_json: String,
663 hierarchy_json: String,
664 warnings_json: String,
665 stable_size: u32,
666 total_size: u32,
667 vcd_descs: Vec<celox::VcdSignalDesc>,
669}
670
671#[cfg(not(target_arch = "wasm32"))]
672#[derive(Clone, Debug, PartialEq, Eq, Hash)]
678struct CacheKey {
679 sources: Vec<(String, String)>,
681 top: String,
682 four_state: bool,
683 sir_optimization: SirOptimizationCacheKey,
684 cranelift_opt_level: u8,
685 regalloc_algorithm: u8,
686 enable_alias_analysis: bool,
687 enable_verifier: bool,
688 dead_store_policy: u8,
689 clock_type: Option<u8>,
690 reset_type: Option<u8>,
691 parameters: Vec<(String, u64)>,
692 false_loops: Vec<(
693 (Vec<(String, usize)>, Vec<String>),
694 (Vec<(String, usize)>, Vec<String>),
695 )>,
696 true_loops: Vec<(
697 (Vec<(String, usize)>, Vec<String>),
698 (Vec<(String, usize)>, Vec<String>),
699 usize,
700 )>,
701 metadata_clock_type: Option<u8>,
704 metadata_reset_type: Option<u8>,
705}
706
707#[cfg(not(target_arch = "wasm32"))]
709#[derive(Clone, Debug, PartialEq, Eq, Hash)]
710struct SirOptimizationCacheKey {
711 opt_level: celox::OptLevel,
712 enabled_passes: Box<[bool]>,
713 max_native_memory_width: usize,
714}
715
716#[cfg(not(target_arch = "wasm32"))]
717impl From<&celox::OptimizeOptions> for SirOptimizationCacheKey {
718 fn from(options: &celox::OptimizeOptions) -> Self {
719 Self {
720 opt_level: options.opt_level(),
721 enabled_passes: celox::SirPass::ALL
722 .iter()
723 .map(|&pass| options.is_enabled(pass))
724 .collect(),
725 max_native_memory_width: options.max_native_memory_width(),
726 }
727 }
728}
729
730#[cfg(not(target_arch = "wasm32"))]
731static JIT_CACHE: std::sync::LazyLock<Mutex<HashMap<CacheKey, Arc<CachedBuild>>>> =
732 std::sync::LazyLock::new(|| Mutex::new(HashMap::default()));
733
734#[cfg(not(target_arch = "wasm32"))]
735fn build_cache_key(
741 sources: &[(String, std::path::PathBuf)],
742 top: &str,
743 opts: &ParsedOptions,
744 metadata: Option<&Metadata>,
745) -> CacheKey {
746 let mut sorted_sources: Vec<(String, String)> = sources
747 .iter()
748 .map(|(content, path)| (path.to_string_lossy().into_owned(), content.clone()))
749 .collect();
750 sorted_sources.sort_by(|a, b| a.0.cmp(&b.0));
751
752 CacheKey {
753 sources: sorted_sources,
754 top: top.to_string(),
755 four_state: opts.four_state,
756 sir_optimization: SirOptimizationCacheKey::from(&opts.optimize_options),
757 cranelift_opt_level: opts.cranelift_options.opt_level as u8,
758 regalloc_algorithm: opts.cranelift_options.regalloc_algorithm as u8,
759 enable_alias_analysis: opts.cranelift_options.enable_alias_analysis,
760 enable_verifier: opts.cranelift_options.enable_verifier,
761 dead_store_policy: opts.dead_store_policy as u8,
762 clock_type: opts.clock_type.map(|ct| ct as u8),
763 reset_type: opts.reset_type.map(|rt| rt as u8),
764 parameters: opts.parameters.clone(),
765 false_loops: opts.false_loops.clone(),
766 true_loops: opts.true_loops.clone(),
767 metadata_clock_type: metadata.map(|m| m.build.clock_type as u8),
768 metadata_reset_type: metadata.map(|m| m.build.reset_type as u8),
769 }
770}
771
772#[cfg(not(target_arch = "wasm32"))]
773fn runtime_errors_by_name(program: &celox::RuntimeProgram) -> HashMap<i64, (String, Vec<String>)> {
774 program
775 .runtime_schema
776 .runtime_errors
777 .iter()
778 .map(|(&code, info)| {
779 (
780 code,
781 (
782 info.message.clone(),
783 info.signals
784 .iter()
785 .map(|addr| program.get_path(addr))
786 .collect(),
787 ),
788 )
789 })
790 .collect()
791}
792
793#[cfg(not(target_arch = "wasm32"))]
794fn napi_runtime_error(
795 runtime_errors: &HashMap<i64, (String, Vec<String>)>,
796 err: celox::RuntimeErrorCode,
797) -> Error {
798 match err {
799 celox::RuntimeErrorCode::DetectedTrueLoopCode(code) => {
800 if let Some((message, signals)) = runtime_errors.get(&code) {
801 if message == "Detected True Loop" {
802 Error::from_reason(format!(
803 "{}",
804 celox::RuntimeErrorCode::DetectedTrueLoopAt {
805 signals: signals.clone(),
806 }
807 ))
808 } else {
809 Error::from_reason(format!(
810 "{}",
811 celox::RuntimeErrorCode::Runtime {
812 message: message.clone(),
813 signals: signals.clone(),
814 }
815 ))
816 }
817 } else {
818 Error::from_reason(format!("{}", celox::RuntimeErrorCode::DetectedTrueLoop))
819 }
820 }
821 other => Error::from_reason(format!("{}", other)),
822 }
823}
824
825#[cfg(not(target_arch = "wasm32"))]
829#[napi]
830pub struct NativeSimulatorHandle {
831 backend: Option<celox::DefaultBackend>,
832 runtime_errors: HashMap<i64, (String, Vec<String>)>,
833 vcd_writer: Option<celox::VcdWriter>,
834 layout_json: String,
835 events_json: String,
836 hierarchy_json: String,
837 warnings_json: String,
838 stable_size: u32,
839 total_size: u32,
840}
841
842#[cfg(not(target_arch = "wasm32"))]
843impl NativeSimulatorHandle {
844 pub fn from_simulator(simulator: celox::Simulator, vcd_path: Option<&str>) -> Result<Self> {
853 Self::build_and_cache(simulator, vcd_path, None)
854 }
855
856 pub fn from_frontend(
861 artifact: celox::FrontendArtifact,
862 options: Option<NapiOptions>,
863 ) -> Result<Self> {
864 let opts = parse_options(&options)?;
865 let builder = apply_options(celox::Simulator::from_frontend(artifact), &opts);
866 let simulator = builder
867 .build()
868 .map_err(|error| Error::from_reason(error.to_string()))?;
869 Self::from_simulator(simulator, opts.vcd.as_deref())
870 }
871}
872
873#[cfg(not(target_arch = "wasm32"))]
874#[napi]
875impl NativeSimulatorHandle {
876 fn build_and_cache(
879 sim: celox::Simulator,
880 vcd_path: Option<&str>,
881 cache_key: Option<CacheKey>,
882 ) -> Result<Self> {
883 let four_state = sim.layout().four_state;
884 let warnings_json = format_warnings_json(sim.warnings());
885 let signals = sim.named_signals();
886 let events = sim.named_events();
887 let hierarchy = sim.named_hierarchy();
888 let (_, total_size) = sim.memory_as_ptr();
889 let stable_size = sim.stable_region_size();
890 let vcd_descs = sim.build_vcd_descs(four_state);
891 let runtime_errors = runtime_errors_by_name(sim.program());
892
893 let layout_map = build_signal_layout(&signals, four_state);
894 let event_map = build_event_map(&events);
895 let hierarchy_node = build_hierarchy_node(&hierarchy, four_state);
896
897 let layout_json = serde_json::to_string(&layout_map)
898 .map_err(|e| Error::from_reason(format!("Failed to serialize layout: {}", e)))?;
899 let events_json = serde_json::to_string(&event_map)
900 .map_err(|e| Error::from_reason(format!("Failed to serialize events: {}", e)))?;
901 let hierarchy_json = serde_json::to_string(&hierarchy_node)
902 .map_err(|e| Error::from_reason(format!("Failed to serialize hierarchy: {}", e)))?;
903
904 if let Some(key) = cache_key {
906 let cached = Arc::new(CachedBuild {
907 shared_code: sim.shared_code(),
908 runtime_errors: runtime_errors.clone(),
909 layout_json: layout_json.clone(),
910 events_json: events_json.clone(),
911 hierarchy_json: hierarchy_json.clone(),
912 warnings_json: warnings_json.clone(),
913 stable_size: stable_size as u32,
914 total_size: total_size as u32,
915 vcd_descs: vcd_descs.clone(),
916 });
917 let mut cache = JIT_CACHE.lock().unwrap_or_else(|e| e.into_inner());
918 cache.insert(key, cached);
919 }
920
921 let vcd_writer = if let Some(path) = vcd_path {
923 Some(
924 celox::VcdWriter::new(path, &vcd_descs)
925 .map_err(|e| Error::from_reason(format!("Failed to create VCD: {}", e)))?,
926 )
927 } else {
928 None
929 };
930
931 let backend = sim.into_backend();
933
934 Ok(Self {
935 backend: Some(backend),
936 runtime_errors,
937 vcd_writer,
938 layout_json,
939 events_json,
940 hierarchy_json,
941 warnings_json,
942 stable_size: stable_size as u32,
943 total_size: total_size as u32,
944 })
945 }
946
947 fn from_cached(cached: &CachedBuild, vcd_path: Option<&str>) -> Result<Self> {
949 #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
950 let backend = celox::NativeBackend::from_shared(Arc::clone(&cached.shared_code));
951 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
952 let backend = celox::JitBackend::from_shared(Arc::clone(&cached.shared_code));
953 let vcd_writer = if let Some(path) = vcd_path {
954 Some(
955 celox::VcdWriter::new(path, &cached.vcd_descs)
956 .map_err(|e| Error::from_reason(format!("Failed to create VCD: {}", e)))?,
957 )
958 } else {
959 None
960 };
961 Ok(Self {
962 backend: Some(backend),
963 runtime_errors: cached.runtime_errors.clone(),
964 vcd_writer,
965 layout_json: cached.layout_json.clone(),
966 events_json: cached.events_json.clone(),
967 hierarchy_json: cached.hierarchy_json.clone(),
968 warnings_json: cached.warnings_json.clone(),
969 stable_size: cached.stable_size,
970 total_size: cached.total_size,
971 })
972 }
973
974 #[napi(constructor)]
976 pub fn new(
977 sources: Vec<NapiSourceFile>,
978 top: String,
979 options: Option<NapiOptions>,
980 ) -> Result<Self> {
981 let opts = parse_options(&options)?;
982 let mut src_pairs: Vec<(String, std::path::PathBuf)> = sources
983 .into_iter()
984 .map(|s| (s.content, std::path::PathBuf::from(s.path)))
985 .collect();
986 append_extra_source(&mut src_pairs, &opts.extra_source);
987
988 let cache_key = build_cache_key(&src_pairs, &top, &opts, None);
989
990 {
991 let cache = JIT_CACHE.lock().unwrap_or_else(|e| e.into_inner());
992 if let Some(cached) = cache.get(&cache_key) {
993 return Self::from_cached(cached, opts.vcd.as_deref());
994 }
995 }
996
997 let source_refs: Vec<(&str, &std::path::Path)> = src_pairs
998 .iter()
999 .map(|(s, p)| (s.as_str(), p.as_path()))
1000 .collect();
1001 let builder = apply_options(celox::Simulator::from_sources(source_refs, &top), &opts);
1002 let sim = builder
1003 .build()
1004 .map_err(|e| Error::from_reason(format!("{}", e)))?;
1005
1006 Self::build_and_cache(sim, opts.vcd.as_deref(), Some(cache_key))
1007 }
1008
1009 #[napi(factory)]
1011 pub fn from_frontend_artifact(
1012 artifact_json: String,
1013 options: Option<NapiOptions>,
1014 ) -> Result<Self> {
1015 let artifact = celox::FrontendArtifact::from_json(&artifact_json)
1016 .map_err(|error| Error::from_reason(error.to_string()))?;
1017 Self::from_frontend(artifact, options)
1018 }
1019
1020 #[napi(factory)]
1026 pub fn from_project(
1027 project_path: String,
1028 top: String,
1029 options: Option<NapiOptions>,
1030 ) -> Result<Self> {
1031 let opts = parse_options(&options)?;
1032 let (mut sources, metadata, _celox_cfg) = load_project_sources(&project_path)?;
1033 append_extra_source(&mut sources, &opts.extra_source);
1034
1035 let cache_key = build_cache_key(&sources, &top, &opts, Some(&metadata));
1036
1037 {
1038 let cache = JIT_CACHE.lock().unwrap_or_else(|e| e.into_inner());
1039 if let Some(cached) = cache.get(&cache_key) {
1040 return Self::from_cached(cached, opts.vcd.as_deref());
1041 }
1042 }
1043
1044 let source_refs: Vec<(&str, &std::path::Path)> = sources
1045 .iter()
1046 .map(|(s, p)| (s.as_str(), p.as_path()))
1047 .collect();
1048
1049 let builder = apply_options(
1050 celox::Simulator::from_sources(source_refs, &top).with_metadata(metadata),
1051 &opts,
1052 );
1053 let sim = builder
1054 .build()
1055 .map_err(|e| Error::from_reason(format!("{}", e)))?;
1056
1057 Self::build_and_cache(sim, opts.vcd.as_deref(), Some(cache_key))
1058 }
1059
1060 #[napi(getter)]
1062 pub fn layout_json(&self) -> String {
1063 self.layout_json.clone()
1064 }
1065
1066 #[napi(getter)]
1068 pub fn events_json(&self) -> String {
1069 self.events_json.clone()
1070 }
1071
1072 #[napi(getter)]
1074 pub fn hierarchy_json(&self) -> String {
1075 self.hierarchy_json.clone()
1076 }
1077
1078 #[napi(getter)]
1080 pub fn warnings_json(&self) -> String {
1081 self.warnings_json.clone()
1082 }
1083
1084 #[napi(getter)]
1086 pub fn stable_size(&self) -> u32 {
1087 self.stable_size
1088 }
1089
1090 #[napi(getter)]
1092 pub fn total_size(&self) -> u32 {
1093 self.total_size
1094 }
1095
1096 #[napi]
1098 pub fn tick(&mut self, event_id: u32) -> Result<()> {
1099 let runtime_errors = self.runtime_errors.clone();
1100 let b = self
1101 .backend
1102 .as_mut()
1103 .ok_or_else(|| Error::from_reason("Simulator has been disposed"))?;
1104 let event = b.id_to_event_slice()[event_id as usize];
1105 b.eval_comb()
1106 .map_err(|e| napi_runtime_error(&runtime_errors, e))?;
1107 b.eval_apply_ff_at(event)
1108 .map_err(|e| napi_runtime_error(&runtime_errors, e))?;
1109 b.eval_comb()
1110 .map_err(|e| napi_runtime_error(&runtime_errors, e))
1111 }
1112
1113 #[napi]
1115 pub fn tick_n(&mut self, event_id: u32, count: u32) -> Result<()> {
1116 let runtime_errors = self.runtime_errors.clone();
1117 let b = self
1118 .backend
1119 .as_mut()
1120 .ok_or_else(|| Error::from_reason("Simulator has been disposed"))?;
1121 let event = b.id_to_event_slice()[event_id as usize];
1122 for _ in 0..count {
1123 b.eval_comb()
1124 .map_err(|e| napi_runtime_error(&runtime_errors, e))?;
1125 b.eval_apply_ff_at(event)
1126 .map_err(|e| napi_runtime_error(&runtime_errors, e))?;
1127 b.eval_comb()
1128 .map_err(|e| napi_runtime_error(&runtime_errors, e))?;
1129 }
1130 Ok(())
1131 }
1132
1133 #[napi]
1135 pub fn eval_comb(&mut self) -> Result<()> {
1136 let runtime_errors = self.runtime_errors.clone();
1137 let b = self
1138 .backend
1139 .as_mut()
1140 .ok_or_else(|| Error::from_reason("Simulator has been disposed"))?;
1141 b.eval_comb()
1142 .map_err(|e| napi_runtime_error(&runtime_errors, e))
1143 }
1144
1145 #[napi]
1147 pub fn dump(&mut self, timestamp: f64) -> Result<()> {
1148 let b = self
1149 .backend
1150 .as_ref()
1151 .ok_or_else(|| Error::from_reason("Simulator has been disposed"))?;
1152 if let Some(ref mut writer) = self.vcd_writer {
1153 let (ptr, size) = b.memory_as_ptr();
1154 let memory = unsafe { std::slice::from_raw_parts(ptr, size) };
1155 writer
1156 .dump(timestamp as u64, memory)
1157 .map_err(|e| Error::from_reason(format!("VCD write error: {}", e)))?;
1158 }
1159 Ok(())
1160 }
1161
1162 #[napi]
1165 pub fn shared_memory(&mut self) -> Result<Uint8Array> {
1166 let b = self
1167 .backend
1168 .as_mut()
1169 .ok_or_else(|| Error::from_reason("Simulator has been disposed"))?;
1170 let (ptr, _) = b.memory_as_mut_ptr();
1171 let stable_size = b.stable_region_size();
1172 Ok(unsafe { Uint8Array::with_external_data(ptr, stable_size, |_, _| {}) })
1173 }
1174
1175 #[napi]
1177 pub fn dispose(&mut self) {
1178 self.backend = None;
1179 self.vcd_writer = None;
1180 }
1181}
1182
1183#[cfg(not(target_arch = "wasm32"))]
1185#[napi]
1186pub struct NativeSimulationHandle {
1187 sim: Option<celox::Simulation>,
1188 layout_json: String,
1189 events_json: String,
1190 hierarchy_json: String,
1191 warnings_json: String,
1192 stable_size: u32,
1193 total_size: u32,
1194 default_max_steps: Option<u32>,
1197}
1198
1199#[cfg(not(target_arch = "wasm32"))]
1200#[napi]
1201impl NativeSimulationHandle {
1202 #[napi(constructor)]
1204 pub fn new(
1205 sources: Vec<NapiSourceFile>,
1206 top: String,
1207 options: Option<NapiOptions>,
1208 ) -> Result<Self> {
1209 let opts = parse_options(&options)?;
1210 let mut src_pairs: Vec<(String, std::path::PathBuf)> = sources
1211 .into_iter()
1212 .map(|s| (s.content, std::path::PathBuf::from(s.path)))
1213 .collect();
1214 append_extra_source(&mut src_pairs, &opts.extra_source);
1215 let source_refs: Vec<(&str, &std::path::Path)> = src_pairs
1216 .iter()
1217 .map(|(s, p)| (s.as_str(), p.as_path()))
1218 .collect();
1219 let mut builder = apply_options(celox::Simulation::from_sources(source_refs, &top), &opts);
1220 if let Some(path) = &opts.vcd {
1221 builder = builder.vcd(path);
1222 }
1223 let sim = builder
1224 .build()
1225 .map_err(|e| Error::from_reason(format!("{}", e)))?;
1226
1227 let warnings_json = format_warnings_json(sim.warnings());
1228 let signals = sim.named_signals();
1229 let events = sim.named_events();
1230 let hierarchy = sim.named_hierarchy();
1231 let (_, total_size) = sim.memory_as_ptr();
1232 let stable_size = sim.stable_region_size();
1233
1234 let layout_map = build_signal_layout(&signals, opts.four_state);
1235 let event_map = build_event_map(&events);
1236 let hierarchy_node = build_hierarchy_node(&hierarchy, opts.four_state);
1237
1238 let layout_json = serde_json::to_string(&layout_map)
1239 .map_err(|e| Error::from_reason(format!("Failed to serialize layout: {}", e)))?;
1240 let events_json = serde_json::to_string(&event_map)
1241 .map_err(|e| Error::from_reason(format!("Failed to serialize events: {}", e)))?;
1242 let hierarchy_json = serde_json::to_string(&hierarchy_node)
1243 .map_err(|e| Error::from_reason(format!("Failed to serialize hierarchy: {}", e)))?;
1244
1245 Ok(Self {
1246 sim: Some(sim),
1247 layout_json,
1248 events_json,
1249 hierarchy_json,
1250 warnings_json,
1251 stable_size: stable_size as u32,
1252 total_size: total_size as u32,
1253 default_max_steps: None,
1254 })
1255 }
1256
1257 #[napi(factory)]
1259 pub fn from_frontend_artifact(
1260 artifact_json: String,
1261 options: Option<NapiOptions>,
1262 ) -> Result<Self> {
1263 let opts = parse_options(&options)?;
1264 let artifact = celox::FrontendArtifact::from_json(&artifact_json)
1265 .map_err(|error| Error::from_reason(error.to_string()))?;
1266 let mut builder = apply_options(celox::Simulation::from_frontend(artifact), &opts);
1267 if let Some(path) = &opts.vcd {
1268 builder = builder.vcd(path);
1269 }
1270 let sim = builder
1271 .build()
1272 .map_err(|error| Error::from_reason(error.to_string()))?;
1273
1274 let warnings_json = format_warnings_json(sim.warnings());
1275 let signals = sim.named_signals();
1276 let events = sim.named_events();
1277 let hierarchy = sim.named_hierarchy();
1278 let (_, total_size) = sim.memory_as_ptr();
1279 let stable_size = sim.stable_region_size();
1280 let layout_map = build_signal_layout(&signals, opts.four_state);
1281 let event_map = build_event_map(&events);
1282 let hierarchy_node = build_hierarchy_node(&hierarchy, opts.four_state);
1283 let layout_json = serde_json::to_string(&layout_map)
1284 .map_err(|error| Error::from_reason(format!("Failed to serialize layout: {error}")))?;
1285 let events_json = serde_json::to_string(&event_map)
1286 .map_err(|error| Error::from_reason(format!("Failed to serialize events: {error}")))?;
1287 let hierarchy_json = serde_json::to_string(&hierarchy_node).map_err(|error| {
1288 Error::from_reason(format!("Failed to serialize hierarchy: {error}"))
1289 })?;
1290
1291 Ok(Self {
1292 sim: Some(sim),
1293 layout_json,
1294 events_json,
1295 hierarchy_json,
1296 warnings_json,
1297 stable_size: stable_size as u32,
1298 total_size: total_size as u32,
1299 default_max_steps: None,
1300 })
1301 }
1302
1303 #[napi(factory)]
1305 pub fn from_project(
1306 project_path: String,
1307 top: String,
1308 options: Option<NapiOptions>,
1309 ) -> Result<Self> {
1310 let opts = parse_options(&options)?;
1311 let (mut sources, metadata, celox_cfg) = load_project_sources(&project_path)?;
1312 append_extra_source(&mut sources, &opts.extra_source);
1313 let source_refs: Vec<(&str, &std::path::Path)> = sources
1314 .iter()
1315 .map(|(s, p)| (s.as_str(), p.as_path()))
1316 .collect();
1317
1318 let mut builder = apply_options(
1319 celox::Simulation::from_sources(source_refs, &top).with_metadata(metadata),
1320 &opts,
1321 );
1322 if let Some(path) = &opts.vcd {
1323 builder = builder.vcd(path);
1324 }
1325 let sim = builder
1326 .build()
1327 .map_err(|e| Error::from_reason(format!("{}", e)))?;
1328
1329 let warnings_json = format_warnings_json(sim.warnings());
1330 let signals = sim.named_signals();
1331 let events = sim.named_events();
1332 let hierarchy = sim.named_hierarchy();
1333 let (_, total_size) = sim.memory_as_ptr();
1334 let stable_size = sim.stable_region_size();
1335
1336 let layout_map = build_signal_layout(&signals, opts.four_state);
1337 let event_map = build_event_map(&events);
1338 let hierarchy_node = build_hierarchy_node(&hierarchy, opts.four_state);
1339
1340 let layout_json = serde_json::to_string(&layout_map)
1341 .map_err(|e| Error::from_reason(format!("Failed to serialize layout: {}", e)))?;
1342 let events_json = serde_json::to_string(&event_map)
1343 .map_err(|e| Error::from_reason(format!("Failed to serialize events: {}", e)))?;
1344 let hierarchy_json = serde_json::to_string(&hierarchy_node)
1345 .map_err(|e| Error::from_reason(format!("Failed to serialize hierarchy: {}", e)))?;
1346
1347 Ok(Self {
1348 sim: Some(sim),
1349 layout_json,
1350 events_json,
1351 hierarchy_json,
1352 warnings_json,
1353 stable_size: stable_size as u32,
1354 total_size: total_size as u32,
1355 default_max_steps: celox_cfg.simulation.max_steps,
1356 })
1357 }
1358
1359 #[napi(getter)]
1361 pub fn layout_json(&self) -> String {
1362 self.layout_json.clone()
1363 }
1364
1365 #[napi(getter)]
1367 pub fn events_json(&self) -> String {
1368 self.events_json.clone()
1369 }
1370
1371 #[napi(getter)]
1373 pub fn hierarchy_json(&self) -> String {
1374 self.hierarchy_json.clone()
1375 }
1376
1377 #[napi(getter)]
1379 pub fn warnings_json(&self) -> String {
1380 self.warnings_json.clone()
1381 }
1382
1383 #[napi(getter)]
1385 pub fn stable_size(&self) -> u32 {
1386 self.stable_size
1387 }
1388
1389 #[napi(getter)]
1391 pub fn total_size(&self) -> u32 {
1392 self.total_size
1393 }
1394
1395 #[napi(getter)]
1398 pub fn default_max_steps(&self) -> Option<u32> {
1399 self.default_max_steps
1400 }
1401
1402 #[napi]
1404 pub fn add_clock(&mut self, event_id: u32, period: f64, initial_delay: f64) -> Result<()> {
1405 let sim = self
1406 .sim
1407 .as_mut()
1408 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1409 sim.add_clock_by_id(event_id, period as u64, initial_delay as u64);
1410 Ok(())
1411 }
1412
1413 #[napi]
1415 pub fn schedule(&mut self, event_id: u32, time: f64, value: f64) -> Result<()> {
1416 let sim = self
1417 .sim
1418 .as_mut()
1419 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1420 sim.schedule_by_id(event_id, time as u64, value as u64)
1421 .map_err(|e| Error::from_reason(format!("{}", e)))
1422 }
1423
1424 #[napi]
1426 pub fn run_until(&mut self, end_time: f64) -> Result<()> {
1427 let sim = self
1428 .sim
1429 .as_mut()
1430 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1431 sim.run_until(end_time as u64)
1432 .map_err(|e| Error::from_reason(format!("{}", e)))
1433 }
1434
1435 #[napi]
1437 pub fn step(&mut self) -> Result<Option<f64>> {
1438 let sim = self
1439 .sim
1440 .as_mut()
1441 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1442 sim.step()
1443 .map(|opt| opt.map(|t| t as f64))
1444 .map_err(|e| Error::from_reason(format!("{}", e)))
1445 }
1446
1447 #[napi]
1449 pub fn time(&self) -> Result<f64> {
1450 let sim = self
1451 .sim
1452 .as_ref()
1453 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1454 Ok(sim.time() as f64)
1455 }
1456
1457 #[napi]
1459 pub fn next_event_time(&self) -> Result<Option<f64>> {
1460 let sim = self
1461 .sim
1462 .as_ref()
1463 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1464 Ok(sim.next_event_time().map(|t| t as f64))
1465 }
1466
1467 #[napi]
1469 pub fn eval_comb(&mut self) -> Result<()> {
1470 let sim = self
1471 .sim
1472 .as_mut()
1473 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1474 sim.eval_comb()
1475 .map_err(|e| Error::from_reason(format!("{}", e)))
1476 }
1477
1478 #[napi]
1480 pub fn dump(&mut self, timestamp: f64) -> Result<()> {
1481 let sim = self
1482 .sim
1483 .as_mut()
1484 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1485 sim.dump(timestamp as u64);
1486 Ok(())
1487 }
1488
1489 #[napi]
1492 pub fn shared_memory(&mut self) -> Result<Uint8Array> {
1493 let sim = self
1494 .sim
1495 .as_mut()
1496 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1497 let (ptr, _) = sim.memory_as_mut_ptr();
1498 let stable_size = sim.stable_region_size();
1499 Ok(unsafe { Uint8Array::with_external_data(ptr, stable_size, |_, _| {}) })
1500 }
1501
1502 #[napi]
1504 pub fn dispose(&mut self) {
1505 self.sim = None;
1506 }
1507}
1508
1509#[cfg(target_arch = "wasm32")]
1514#[napi]
1515pub struct NativeSimulatorHandle {
1516 program: celox::LaidOutProgram,
1517 four_state: bool,
1518 layout_json: String,
1519 events_json: String,
1520 hierarchy_json: String,
1521 warnings_json: String,
1522 stable_size: u32,
1523 total_size: u32,
1524}
1525
1526#[cfg(target_arch = "wasm32")]
1527impl NativeSimulatorHandle {
1528 pub fn from_frontend(
1535 artifact: celox::FrontendArtifact,
1536 options: Option<NapiOptions>,
1537 ) -> Result<Self> {
1538 let opts = parse_options_common(&options)?;
1539 let trace_opts = celox::TraceOptions::default();
1540 let (program, warnings) = celox::compile_frontend_to_sir(
1541 &artifact,
1542 &opts.false_loops,
1543 &opts.true_loops,
1544 opts.four_state,
1545 &trace_opts,
1546 None,
1547 &opts.optimize_options,
1548 )
1549 .map_err(|error| Error::from_reason(error.to_string()))?;
1550
1551 let laid_out = program.into_laid_out(opts.four_state);
1552 let layout = laid_out.layout();
1553 let layout_json = Self::build_layout_json(&laid_out, layout, opts.four_state);
1554 let events_json = Self::build_events_json(&laid_out);
1555 let warnings_json = format_warnings_json(&warnings);
1556 let stable_size = layout.total_size as u32;
1557 let total_size = layout.merged_total_size as u32;
1558
1559 Ok(Self {
1560 program: laid_out,
1561 four_state: opts.four_state,
1562 layout_json,
1563 events_json,
1564 hierarchy_json: "{}".to_string(),
1565 warnings_json,
1566 stable_size,
1567 total_size,
1568 })
1569 }
1570}
1571
1572#[cfg(target_arch = "wasm32")]
1573#[napi]
1574impl NativeSimulatorHandle {
1575 #[napi(constructor)]
1582 pub fn new(
1583 sources: Vec<NapiSourceFile>,
1584 top: String,
1585 options: Option<NapiOptions>,
1586 ) -> Result<Self> {
1587 let opts = parse_options_common(&options)?;
1588 let mut src_pairs: Vec<(String, std::path::PathBuf)> = sources
1589 .into_iter()
1590 .map(|s| (s.content, std::path::PathBuf::from(s.path)))
1591 .collect();
1592 append_extra_source(&mut src_pairs, &opts.extra_source);
1593
1594 let source_refs: Vec<(&str, &std::path::Path)> = src_pairs
1595 .iter()
1596 .map(|(s, p)| (s.as_str(), p.as_path()))
1597 .collect();
1598
1599 let trace_opts = celox::TraceOptions::default();
1600 let (program, warnings) = celox::compile_to_sir(
1601 &source_refs,
1602 &top,
1603 &opts
1604 .false_loops
1605 .iter()
1606 .map(|(f, t)| (f.clone(), t.clone()))
1607 .collect::<Vec<_>>(),
1608 &opts
1609 .true_loops
1610 .iter()
1611 .map(|(f, t, m)| (f.clone(), t.clone(), *m))
1612 .collect::<Vec<_>>(),
1613 opts.four_state,
1614 &trace_opts,
1615 None,
1616 None,
1617 opts.clock_type,
1618 opts.reset_type,
1619 &opts.parameters,
1620 &opts.optimize_options,
1621 )
1622 .map_err(|e| Error::from_reason(format!("{}", e)))?;
1623
1624 let laid_out = program.into_laid_out(opts.four_state);
1625 let layout = laid_out.layout();
1626
1627 let layout_json = Self::build_layout_json(&laid_out, layout, opts.four_state);
1628 let events_json = Self::build_events_json(&laid_out);
1629 let hierarchy_json = "{}".to_string(); let warnings_json = format_warnings_json(&warnings);
1631
1632 let stable_size = layout.total_size as u32;
1633 let total_size = layout.merged_total_size as u32;
1634 Ok(Self {
1635 program: laid_out,
1636 four_state: opts.four_state,
1637 layout_json,
1638 events_json,
1639 hierarchy_json,
1640 warnings_json,
1641 stable_size,
1642 total_size,
1643 })
1644 }
1645
1646 #[napi(factory)]
1648 pub fn from_frontend_artifact(
1649 artifact_json: String,
1650 options: Option<NapiOptions>,
1651 ) -> Result<Self> {
1652 let artifact = celox::FrontendArtifact::from_json(&artifact_json)
1653 .map_err(|error| Error::from_reason(error.to_string()))?;
1654 Self::from_frontend(artifact, options)
1655 }
1656
1657 #[napi(factory)]
1659 pub fn from_project(
1660 project_path: String,
1661 top: String,
1662 options: Option<NapiOptions>,
1663 ) -> Result<Self> {
1664 let opts = parse_options_common(&options)?;
1665 let (mut sources, metadata, _celox_cfg) = load_project_sources(&project_path)?;
1666 append_extra_source(&mut sources, &opts.extra_source);
1667
1668 let source_refs: Vec<(&str, &std::path::Path)> = sources
1669 .iter()
1670 .map(|(s, p)| (s.as_str(), p.as_path()))
1671 .collect();
1672
1673 let trace_opts = celox::TraceOptions::default();
1674 let (program, warnings) = celox::compile_to_sir(
1675 &source_refs,
1676 &top,
1677 &opts
1678 .false_loops
1679 .iter()
1680 .map(|(f, t)| (f.clone(), t.clone()))
1681 .collect::<Vec<_>>(),
1682 &opts
1683 .true_loops
1684 .iter()
1685 .map(|(f, t, m)| (f.clone(), t.clone(), *m))
1686 .collect::<Vec<_>>(),
1687 opts.four_state,
1688 &trace_opts,
1689 None,
1690 Some(metadata),
1691 opts.clock_type,
1692 opts.reset_type,
1693 &opts.parameters,
1694 &opts.optimize_options,
1695 )
1696 .map_err(|e| Error::from_reason(format!("{}", e)))?;
1697
1698 let laid_out = program.into_laid_out(opts.four_state);
1699 let layout = laid_out.layout();
1700
1701 let layout_json = Self::build_layout_json(&laid_out, layout, opts.four_state);
1702 let events_json = Self::build_events_json(&laid_out);
1703 let hierarchy_json = "{}".to_string();
1704 let warnings_json = format_warnings_json(&warnings);
1705
1706 let stable_size = layout.total_size as u32;
1707 let total_size = layout.merged_total_size as u32;
1708 Ok(Self {
1709 program: laid_out,
1710 four_state: opts.four_state,
1711 layout_json,
1712 events_json,
1713 hierarchy_json,
1714 warnings_json,
1715 stable_size,
1716 total_size,
1717 })
1718 }
1719
1720 #[napi(getter)]
1722 pub fn layout_json(&self) -> String {
1723 self.layout_json.clone()
1724 }
1725
1726 #[napi(getter)]
1728 pub fn events_json(&self) -> String {
1729 self.events_json.clone()
1730 }
1731
1732 #[napi(getter)]
1734 pub fn four_state_init_regions_json(&self) -> String {
1735 Self::build_four_state_init_regions_json(
1736 &self.program,
1737 self.program.layout(),
1738 self.four_state,
1739 )
1740 }
1741
1742 #[napi]
1744 pub fn initial_memory_bytes(&self) -> Vec<u8> {
1745 Self::build_initial_memory_bytes(&self.program, self.program.layout(), self.four_state)
1746 }
1747
1748 #[napi(getter)]
1750 pub fn hierarchy_json(&self) -> String {
1751 self.hierarchy_json.clone()
1752 }
1753
1754 #[napi(getter)]
1756 pub fn warnings_json(&self) -> String {
1757 self.warnings_json.clone()
1758 }
1759
1760 #[napi(getter)]
1762 pub fn stable_size(&self) -> u32 {
1763 self.stable_size
1764 }
1765
1766 #[napi(getter)]
1768 pub fn total_size(&self) -> u32 {
1769 self.total_size
1770 }
1771
1772 #[napi]
1774 pub fn comb_wasm_bytes(&self) -> Vec<u8> {
1775 let wasm = celox::wasm_codegen::compile_units(
1776 &self.program.sir.eval_comb,
1777 self.program.layout(),
1778 self.four_state,
1779 false,
1780 );
1781 wasm.bytes
1782 }
1783
1784 #[napi]
1788 pub fn event_wasm_bytes(&self, event_name: String) -> Result<Vec<u8>> {
1789 for (addr, units) in &self.program.sir.eval_apply_ffs {
1790 let event_path = self.program.get_path(addr);
1791 if event_path == event_name {
1792 let wasm = celox::wasm_codegen::compile_units(
1793 units,
1794 self.program.layout(),
1795 self.four_state,
1796 false,
1797 );
1798 return Ok(wasm.bytes);
1799 }
1800 }
1801
1802 Err(Error::from_reason(format!(
1803 "Event '{}' not found. Available events: {}",
1804 event_name,
1805 self.program
1806 .sir
1807 .eval_apply_ffs
1808 .keys()
1809 .map(|addr| self.program.get_path(addr))
1810 .collect::<Vec<_>>()
1811 .join(", ")
1812 )))
1813 }
1814
1815 #[napi]
1817 pub fn dispose(&mut self) {}
1818}
1819
1820#[cfg(target_arch = "wasm32")]
1821impl NativeSimulatorHandle {
1822 fn write_memory_bit(memory: &mut [u8], byte: usize, bit: usize, value: bool) {
1823 let mask = 1u8 << bit;
1824 if value {
1825 memory[byte] |= mask;
1826 } else {
1827 memory[byte] &= !mask;
1828 }
1829 }
1830
1831 fn byte_bit(bytes: &[u8], bit: usize) -> bool {
1832 bytes
1833 .get(bit / 8)
1834 .is_some_and(|byte| byte & (1u8 << (bit % 8)) != 0)
1835 }
1836
1837 fn build_initial_memory_bytes(
1838 program: &celox::LaidOutProgram,
1839 layout: &celox::MemoryLayout,
1840 four_state: bool,
1841 ) -> Vec<u8> {
1842 let mut memory = vec![0u8; layout.merged_total_size];
1843
1844 if four_state {
1845 for (address, &offset) in &layout.offsets {
1846 if layout.is_4states.get(address).copied().unwrap_or(false) {
1847 let plane_size = layout.plane_size(address);
1848 memory[offset..offset + plane_size * 2].fill(0xff);
1849 }
1850 }
1851 for (address, &relative_offset) in &layout.working_offsets {
1852 if layout.is_4states.get(address).copied().unwrap_or(false) {
1853 let offset = layout.working_base_offset + relative_offset;
1854 let plane_size = layout.plane_size(address);
1855 memory[offset..offset + plane_size * 2].fill(0xff);
1856 }
1857 }
1858 }
1859
1860 for initial in &program.design.initial_state {
1861 let Some(&offset) = layout.offsets.get(&initial.address) else {
1862 continue;
1863 };
1864 let width = layout.widths[&initial.address];
1865 let plane_size = layout.plane_size(&initial.address);
1866 let write_mask = four_state
1867 && layout
1868 .is_4states
1869 .get(&initial.address)
1870 .copied()
1871 .unwrap_or(false);
1872
1873 match &initial.data {
1874 celox_design::InitialStateData::Packed {
1875 value,
1876 mask,
1877 written_mask,
1878 } => {
1879 let value = value.to_bytes_le();
1880 let mask = mask.to_bytes_le();
1881 let written_mask = written_mask.to_bytes_le();
1882 for bit in 0..width {
1883 if !Self::byte_bit(&written_mask, bit) {
1884 continue;
1885 }
1886 let (byte, intra) = layout.map_static_bit_offset(&initial.address, bit);
1887 let is_unknown = Self::byte_bit(&mask, bit);
1888 Self::write_memory_bit(
1889 &mut memory,
1890 offset + byte,
1891 intra,
1892 Self::byte_bit(&value, bit) && (write_mask || !is_unknown),
1893 );
1894 if write_mask {
1895 Self::write_memory_bit(
1896 &mut memory,
1897 offset + plane_size + byte,
1898 intra,
1899 is_unknown,
1900 );
1901 }
1902 }
1903 }
1904 celox_design::InitialStateData::Writes(runs) => {
1905 for run in runs {
1906 for relative_bit in 0..run.bit_width {
1907 let bit = run.bit_offset + relative_bit;
1908 if bit >= width {
1909 break;
1910 }
1911 let (byte, intra) = layout.map_static_bit_offset(&initial.address, bit);
1912 Self::write_memory_bit(
1913 &mut memory,
1914 offset + byte,
1915 intra,
1916 Self::byte_bit(&run.value_bytes, relative_bit),
1917 );
1918 if write_mask {
1919 Self::write_memory_bit(
1920 &mut memory,
1921 offset + plane_size + byte,
1922 intra,
1923 Self::byte_bit(&run.mask_bytes, relative_bit),
1924 );
1925 }
1926 }
1927 }
1928 }
1929 }
1930 }
1931
1932 memory
1933 }
1934
1935 fn build_four_state_init_regions_json(
1936 program: &celox::LaidOutProgram,
1937 layout: &celox::MemoryLayout,
1938 four_state: bool,
1939 ) -> String {
1940 if !four_state {
1941 return "[]".to_string();
1942 }
1943
1944 let mut regions = Vec::new();
1945 for (addr, &offset) in &layout.offsets {
1946 if program
1947 .design
1948 .state_objects
1949 .get(addr)
1950 .is_some_and(|metadata| metadata.is_4state)
1951 {
1952 regions.push((offset, layout.plane_size(addr)));
1953 }
1954 }
1955 for (addr, &relative_offset) in &layout.working_offsets {
1956 if program
1957 .design
1958 .state_objects
1959 .get(addr)
1960 .is_some_and(|metadata| metadata.is_4state)
1961 {
1962 regions.push((
1963 layout.working_base_offset + relative_offset,
1964 layout.plane_size(addr),
1965 ));
1966 }
1967 }
1968 regions.sort_unstable();
1969
1970 serde_json::to_string(®ions).unwrap_or_else(|_| "[]".to_string())
1971 }
1972
1973 fn build_layout_json(
1976 program: &celox::LaidOutProgram,
1977 layout: &celox::MemoryLayout,
1978 four_state: bool,
1979 ) -> String {
1980 use std::collections::BTreeMap;
1981
1982 let mut layout_map: BTreeMap<String, serde_json::Value> = BTreeMap::new();
1983
1984 for addr in program.design.state_objects.keys() {
1985 let Some(variable) = program.design.variable(addr) else {
1986 continue;
1987 };
1988 let Some(instance) = program.design.instance(addr.instance_id) else {
1989 continue;
1990 };
1991 if !instance.resolves_path_to(&variable.path, *addr) {
1992 continue;
1993 }
1994 let metadata = &program.design.state_objects[addr];
1995 let Some(&offset) = layout.offsets.get(addr) else {
1996 continue;
1997 };
1998 let name = program.get_path(addr);
1999 let total_width = layout.widths.get(addr).copied().unwrap_or(0);
2000 let (width, array_dims) = if metadata.array_dims.is_empty() {
2001 (total_width, None)
2002 } else {
2003 let element_count = metadata.array_dims.iter().product::<usize>();
2004 (total_width / element_count, Some(&metadata.array_dims))
2005 };
2006 let byte_size = celox::get_byte_size(width);
2007 let mut entry = serde_json::json!({
2008 "offset": offset,
2009 "width": width,
2010 "byte_size": byte_size,
2011 "is_4state": four_state && metadata.is_4state,
2012 "direction": layout::direction_str(variable.var_kind),
2013 "type_kind": layout::type_kind_str(metadata.type_kind),
2014 });
2015 if let Some(array_dims) = array_dims {
2016 entry["array_dims"] = serde_json::json!(array_dims);
2017 }
2018 layout_map.insert(name, entry);
2019 }
2020
2021 serde_json::to_string(&layout_map).unwrap_or_else(|_| "{}".to_string())
2022 }
2023
2024 fn build_events_json(program: &celox::LaidOutProgram) -> String {
2026 use std::collections::BTreeMap;
2027
2028 let mut events: BTreeMap<String, usize> = BTreeMap::new();
2029
2030 for (next_id, addr) in program.sir.eval_apply_ffs.keys().enumerate() {
2031 let name = program.get_path(addr);
2032 events.insert(name, next_id);
2033 }
2034
2035 serde_json::to_string(&events).unwrap_or_else(|_| "{}".to_string())
2036 }
2037}
2038
2039fn analyzer_error_to_diagnostics(
2042 err: &veryl_analyzer::AnalyzerError,
2043 all_sources: &[(String, String)],
2044 is_error: bool,
2045) -> Vec<celox_ts_gen::JsonDiagnostic> {
2046 use miette::Diagnostic as _;
2047
2048 let severity = if is_error {
2049 celox_ts_gen::DiagnosticSeverity::Error
2050 } else {
2051 celox_ts_gen::DiagnosticSeverity::Warning
2052 };
2053
2054 let message = format!("{err}");
2055 let help = err.help().map(|h| h.to_string());
2056 let url = err.url().map(|u| u.to_string());
2057
2058 let labels: Vec<_> = err.labels().map(|l| l.collect()).unwrap_or_default();
2059
2060 if labels.is_empty() {
2061 return vec![celox_ts_gen::JsonDiagnostic {
2062 severity,
2063 message,
2064 file: String::new(),
2065 line: 1,
2066 column: 1,
2067 end_line: None,
2068 end_column: None,
2069 help,
2070 url,
2071 }];
2072 }
2073
2074 labels
2075 .into_iter()
2076 .map(|label| {
2077 let offset = label.offset();
2078 let len = label.len();
2079
2080 let mut file = String::new();
2085 let mut line = 1usize;
2086 let mut col = 1usize;
2087 let mut end_line = None;
2088 let mut end_col = None;
2089
2090 for (path, content) in all_sources {
2091 let mut cur_line = 1;
2093 let mut cur_col = 1;
2094 let mut found = false;
2095
2096 for (i, ch) in content.char_indices() {
2097 if i == offset {
2098 file = path.clone();
2099 line = cur_line;
2100 col = cur_col;
2101 found = true;
2102 }
2103 if found && i == offset + len {
2104 end_line = Some(cur_line);
2105 end_col = Some(cur_col);
2106 break;
2107 }
2108 if ch == '\n' {
2109 cur_line += 1;
2110 cur_col = 1;
2111 } else {
2112 cur_col += 1;
2113 }
2114 }
2115
2116 if found {
2117 if end_line.is_none() {
2118 end_line = Some(cur_line);
2119 end_col = Some(cur_col);
2120 }
2121 break;
2122 }
2123 }
2124
2125 celox_ts_gen::JsonDiagnostic {
2126 severity: severity.clone(),
2127 message: label.label().unwrap_or(&message).to_string(),
2128 file,
2129 line,
2130 column: col,
2131 end_line,
2132 end_column: end_col,
2133 help: help.clone(),
2134 url: url.clone(),
2135 }
2136 })
2137 .collect()
2138}
2139
2140fn format_errors_with_warnings(
2142 pass_label: &str,
2143 errors: &[&veryl_analyzer::AnalyzerError],
2144 warnings: &[veryl_analyzer::AnalyzerError],
2145) -> String {
2146 let error_msgs: Vec<String> = errors
2147 .iter()
2148 .map(|e| celox::render_diagnostic(*e))
2149 .collect();
2150 let mut msg = format!("Errors in {pass_label}: {}", error_msgs.join("; "));
2151 if !warnings.is_empty() {
2152 let warning_msgs: Vec<String> = warnings
2153 .iter()
2154 .map(|w| celox::render_diagnostic(w))
2155 .collect();
2156 msg.push_str("\n\n--- warnings ---\n\n");
2157 msg.push_str(&warning_msgs.join("\n"));
2158 }
2159 msg
2160}
2161
2162#[cfg(not(target_arch = "wasm32"))]
2166#[napi]
2167pub fn clear_jit_cache() {
2168 let mut cache = JIT_CACHE.lock().unwrap_or_else(|e| e.into_inner());
2169 cache.clear();
2170}
2171
2172#[cfg(target_arch = "wasm32")]
2174#[napi]
2175pub fn clear_jit_cache() {}
2176
2177#[cfg(not(target_arch = "wasm32"))]
2183#[napi(object)]
2184pub struct NapiAssertionResult {
2185 pub passed: bool,
2186 pub message: Option<String>,
2187 pub file: Option<String>,
2188 pub line: Option<u32>,
2189 pub column: Option<u32>,
2190}
2191
2192#[cfg(not(target_arch = "wasm32"))]
2194#[napi(object)]
2195pub struct NapiTestResult {
2196 pub passed: bool,
2197 pub assertions: Vec<NapiAssertionResult>,
2198 pub error: Option<String>,
2199}
2200
2201#[cfg(not(target_arch = "wasm32"))]
2202fn convert_test_result(r: celox::TestResultDetailed) -> NapiTestResult {
2203 NapiTestResult {
2204 passed: r.passed,
2205 error: r.error,
2206 assertions: r
2207 .assertions
2208 .into_iter()
2209 .map(|a| {
2210 let (file, line, column) = match a.location {
2211 Some(loc) => (Some(loc.file), Some(loc.line), Some(loc.column)),
2212 None => (None, None, None),
2213 };
2214 NapiAssertionResult {
2215 passed: a.passed,
2216 message: a.message,
2217 file,
2218 line,
2219 column,
2220 }
2221 })
2222 .collect(),
2223 }
2224}
2225
2226#[cfg(not(target_arch = "wasm32"))]
2227#[napi(object)]
2228pub struct NapiInjectedValue {
2229 pub name: Option<String>,
2230 pub bits: Option<BigInt>,
2231 pub mask_xz: Option<BigInt>,
2232 pub width: Option<u32>,
2233 pub string_value: Option<String>,
2234}
2235
2236#[cfg(not(target_arch = "wasm32"))]
2237#[napi(object)]
2238pub struct NapiInjectedCall {
2239 pub instance: String,
2240 pub phase: String,
2241 pub method: Option<String>,
2242 pub inputs: Vec<NapiInjectedValue>,
2243 pub params: Vec<NapiInjectedValue>,
2244 pub ports: Vec<NapiInjectedPort>,
2245 pub args: Vec<NapiInjectedValue>,
2246 pub cycle: BigInt,
2247 pub time: BigInt,
2248 pub seed: BigInt,
2249 pub fired_clock: Option<String>,
2250 pub four_state: bool,
2251}
2252
2253#[cfg(not(target_arch = "wasm32"))]
2254#[napi(object)]
2255pub struct NapiInjectedPort {
2256 pub name: String,
2257 pub direction: String,
2258 pub role: Option<String>,
2259 pub width: u32,
2260}
2261
2262#[cfg(not(target_arch = "wasm32"))]
2263#[napi(object)]
2264pub struct NapiInjectedResult {
2265 pub outputs: Option<Vec<NapiInjectedValue>>,
2266 pub return_value: Option<NapiInjectedValue>,
2267 pub failures: Option<Vec<String>>,
2268 pub logs: Option<Vec<String>>,
2269 pub finish: Option<bool>,
2270}
2271
2272#[cfg(not(target_arch = "wasm32"))]
2273#[napi(object, object_to_js = false)]
2274pub struct NapiInjectedComponent {
2275 pub name: String,
2276 pub manifest: String,
2277 pub handler: FunctionRef<NapiInjectedCall, NapiInjectedResult>,
2278}
2279
2280#[cfg(not(target_arch = "wasm32"))]
2281struct NapiInjectedHandler {
2282 env: Env,
2283 handler: FunctionRef<NapiInjectedCall, NapiInjectedResult>,
2284}
2285
2286#[cfg(not(target_arch = "wasm32"))]
2290unsafe impl Send for NapiInjectedHandler {}
2291#[cfg(not(target_arch = "wasm32"))]
2292unsafe impl Sync for NapiInjectedHandler {}
2293
2294#[cfg(not(target_arch = "wasm32"))]
2295fn napi_bigint(value: u64) -> BigInt {
2296 BigInt {
2297 sign_bit: false,
2298 words: vec![value],
2299 }
2300}
2301
2302#[cfg(not(target_arch = "wasm32"))]
2303fn to_napi_injected_value(name: Option<String>, value: celox::InjectedValue) -> NapiInjectedValue {
2304 match value {
2305 celox::InjectedValue::Bits {
2306 words,
2307 mask_xz,
2308 width,
2309 } => NapiInjectedValue {
2310 name,
2311 bits: Some(BigInt {
2312 sign_bit: false,
2313 words,
2314 }),
2315 mask_xz: Some(BigInt {
2316 sign_bit: false,
2317 words: mask_xz,
2318 }),
2319 width: Some(width),
2320 string_value: None,
2321 },
2322 celox::InjectedValue::String(value) => NapiInjectedValue {
2323 name,
2324 bits: None,
2325 mask_xz: None,
2326 width: None,
2327 string_value: Some(value),
2328 },
2329 celox::InjectedValue::Unit => NapiInjectedValue {
2330 name,
2331 bits: None,
2332 mask_xz: None,
2333 width: None,
2334 string_value: None,
2335 },
2336 }
2337}
2338
2339#[cfg(not(target_arch = "wasm32"))]
2340fn from_napi_injected_value(
2341 value: NapiInjectedValue,
2342) -> std::result::Result<celox::InjectedValue, String> {
2343 if let Some(bits) = value.bits {
2344 if bits.sign_bit || value.mask_xz.as_ref().is_some_and(|mask| mask.sign_bit) {
2345 return Err("component callback values cannot be negative".into());
2346 }
2347 let width = value
2348 .width
2349 .ok_or_else(|| "component callback bit value has no width".to_string())?;
2350 return Ok(celox::InjectedValue::Bits {
2351 words: bits.words,
2352 mask_xz: value.mask_xz.map(|mask| mask.words).unwrap_or_default(),
2353 width,
2354 });
2355 }
2356 Ok(match value.string_value {
2357 Some(value) => celox::InjectedValue::String(value),
2358 None => celox::InjectedValue::Unit,
2359 })
2360}
2361
2362#[cfg(not(target_arch = "wasm32"))]
2363impl celox::InjectedComponentHandler for NapiInjectedHandler {
2364 fn call(
2365 &self,
2366 call: celox::InjectedCall,
2367 ) -> std::result::Result<celox::InjectedResult, String> {
2368 let (phase, method, args) = match call.hook {
2369 celox::InjectedHook::Create => ("create", None, Vec::new()),
2370 celox::InjectedHook::Init => ("init", None, Vec::new()),
2371 celox::InjectedHook::Reset => ("reset", None, Vec::new()),
2372 celox::InjectedHook::Clock => ("clock", None, Vec::new()),
2373 celox::InjectedHook::Finish => ("finish", None, Vec::new()),
2374 celox::InjectedHook::Method { name, args } => ("method", Some(name), args),
2375 };
2376 let request = NapiInjectedCall {
2377 instance: call.instance,
2378 phase: phase.into(),
2379 method,
2380 inputs: call
2381 .inputs
2382 .into_iter()
2383 .map(|value| to_napi_injected_value(Some(value.name), value.value))
2384 .collect(),
2385 params: call
2386 .params
2387 .into_iter()
2388 .map(|value| to_napi_injected_value(Some(value.name), value.value))
2389 .collect(),
2390 ports: call
2391 .ports
2392 .into_iter()
2393 .map(|port| NapiInjectedPort {
2394 name: port.name,
2395 direction: port.direction,
2396 role: port.role,
2397 width: port.width,
2398 })
2399 .collect(),
2400 args: args
2401 .into_iter()
2402 .map(|value| to_napi_injected_value(None, value))
2403 .collect(),
2404 cycle: napi_bigint(call.cycle),
2405 time: napi_bigint(call.time),
2406 seed: napi_bigint(call.seed),
2407 fired_clock: call.fired_clock,
2408 four_state: call.four_state,
2409 };
2410 let result = self
2411 .handler
2412 .borrow_back(&self.env)
2413 .and_then(|handler| handler.call(request))
2414 .map_err(|error| error.to_string())?;
2415 let outputs = result
2416 .outputs
2417 .unwrap_or_default()
2418 .into_iter()
2419 .map(|value| {
2420 let name = value
2421 .name
2422 .clone()
2423 .ok_or_else(|| "component callback output has no name".to_string())?;
2424 Ok(celox::InjectedNamedValue {
2425 name,
2426 value: from_napi_injected_value(value)?,
2427 })
2428 })
2429 .collect::<std::result::Result<Vec<_>, String>>()?;
2430 Ok(celox::InjectedResult {
2431 outputs,
2432 return_value: result
2433 .return_value
2434 .map(from_napi_injected_value)
2435 .transpose()?,
2436 failures: result.failures.unwrap_or_default(),
2437 logs: result.logs.unwrap_or_default(),
2438 finish: result.finish.unwrap_or(false),
2439 })
2440 }
2441}
2442
2443#[cfg(not(target_arch = "wasm32"))]
2444fn injected_components(
2445 env: Env,
2446 definitions: Option<Vec<NapiInjectedComponent>>,
2447) -> Result<celox::InjectedComponents> {
2448 let mut components = celox::InjectedComponents::new();
2449 for definition in definitions.unwrap_or_default() {
2450 components
2451 .insert(
2452 definition.name,
2453 &definition.manifest,
2454 Arc::new(NapiInjectedHandler {
2455 env,
2456 handler: definition.handler,
2457 }),
2458 )
2459 .map_err(Error::from_reason)?;
2460 }
2461 Ok(components)
2462}
2463
2464#[cfg(not(target_arch = "wasm32"))]
2470#[napi]
2471pub fn run_test(
2472 env: Env,
2473 sources: Vec<NapiSourceFile>,
2474 top: String,
2475 options: Option<NapiOptions>,
2476 components: Option<Vec<NapiInjectedComponent>>,
2477) -> Result<NapiTestResult> {
2478 let opts = parse_options(&options)?;
2479 let mut src_pairs: Vec<(String, std::path::PathBuf)> = sources
2480 .into_iter()
2481 .map(|s| (s.content, std::path::PathBuf::from(s.path)))
2482 .collect();
2483 append_extra_source(&mut src_pairs, &opts.extra_source);
2484
2485 let source_refs: Vec<(&str, &std::path::Path)> = src_pairs
2486 .iter()
2487 .map(|(s, p)| (s.as_str(), p.as_path()))
2488 .collect();
2489 let builder = apply_options(celox::Simulator::from_sources(source_refs, &top), &opts)
2490 .with_injected_components(injected_components(env, components)?);
2491 let result = builder
2492 .run_test_detailed()
2493 .map_err(|e| Error::from_reason(format!("{e}")))?;
2494 Ok(convert_test_result(result))
2495}
2496
2497#[cfg(not(target_arch = "wasm32"))]
2499#[napi]
2500pub fn run_test_with_frontend_artifact(
2501 env: Env,
2502 artifact_json: String,
2503 sources: Vec<NapiSourceFile>,
2504 top: String,
2505 options: Option<NapiOptions>,
2506 components: Option<Vec<NapiInjectedComponent>>,
2507) -> Result<NapiTestResult> {
2508 let opts = parse_options(&options)?;
2509 let artifact = celox::FrontendArtifact::from_json(&artifact_json)
2510 .map_err(|error| Error::from_reason(error.to_string()))?;
2511 let mut src_pairs: Vec<(String, std::path::PathBuf)> = sources
2512 .into_iter()
2513 .map(|source| (source.content, std::path::PathBuf::from(source.path)))
2514 .collect();
2515 append_extra_source(&mut src_pairs, &opts.extra_source);
2516 let source_refs: Vec<(&str, &std::path::Path)> = src_pairs
2517 .iter()
2518 .map(|(source, path)| (source.as_str(), path.as_path()))
2519 .collect();
2520 let builder = apply_options(
2521 celox::Simulator::from_frontend_with_testbench(artifact, source_refs, &top),
2522 &opts,
2523 )
2524 .with_injected_components(injected_components(env, components)?);
2525 let result = builder
2526 .run_test_detailed()
2527 .map_err(|error| Error::from_reason(error.to_string()))?;
2528 Ok(convert_test_result(result))
2529}
2530
2531#[cfg(not(target_arch = "wasm32"))]
2536#[napi]
2537pub fn run_test_from_project(
2538 env: Env,
2539 project_path: String,
2540 top: String,
2541 options: Option<NapiOptions>,
2542 components: Option<Vec<NapiInjectedComponent>>,
2543) -> Result<NapiTestResult> {
2544 let opts = parse_options(&options)?;
2545 let (mut sources, metadata, _celox_cfg) = load_project_sources(&project_path)?;
2546 append_extra_source(&mut sources, &opts.extra_source);
2547
2548 let source_refs: Vec<(&str, &std::path::Path)> = sources
2549 .iter()
2550 .map(|(s, p)| (s.as_str(), p.as_path()))
2551 .collect();
2552 let builder = apply_options(
2553 celox::Simulator::from_sources(source_refs, &top).with_metadata(metadata),
2554 &opts,
2555 )
2556 .with_injected_components(injected_components(env, components)?);
2557 let result = builder
2558 .run_test_detailed()
2559 .map_err(|e| Error::from_reason(format!("{e}")))?;
2560 Ok(convert_test_result(result))
2561}
2562
2563#[napi(object)]
2568pub struct NapiInjectedManifest {
2569 pub name: String,
2570 pub manifest: String,
2571}
2572
2573fn inject_analyzer_components(definitions: Option<Vec<NapiInjectedManifest>>) -> Result<()> {
2574 let definitions = definitions.unwrap_or_default();
2575 let names: Vec<_> = definitions
2576 .iter()
2577 .map(|definition| definition.name.as_str())
2578 .collect();
2579 veryl_analyzer::tb_component::insert_external_components(&names);
2580 for definition in definitions {
2581 let manifest =
2582 veryl_metadata::ComponentManifest::parse(&definition.manifest).ok_or_else(|| {
2583 Error::from_reason(format!(
2584 "manifest of injected component `{}` cannot be parsed",
2585 definition.name
2586 ))
2587 })?;
2588 veryl_analyzer::component_manifest_table::insert(
2589 veryl_parser::resource_table::insert_str(&definition.name),
2590 manifest,
2591 );
2592 }
2593 Ok(())
2594}
2595
2596#[napi]
2600pub fn gen_ts(
2601 project_path: String,
2602 components: Option<Vec<NapiInjectedManifest>>,
2603) -> Result<String> {
2604 use celox_ts_gen::{JsonModuleEntry, JsonOutput, generate_all};
2605
2606 let toml_path = Metadata::search_from(&project_path)
2607 .map_err(|e| Error::from_reason(format!("Could not find Veryl.toml: {e}")))?;
2608 let mut metadata = Metadata::load(&toml_path)
2609 .map_err(|e| Error::from_reason(format!("Failed to load Veryl.toml: {e}")))?;
2610
2611 let base_path = toml_path
2612 .parent()
2613 .unwrap_or(&toml_path)
2614 .to_string_lossy()
2615 .to_string();
2616
2617 let mut paths = metadata
2618 .paths::<std::path::PathBuf>(&[], true, true)
2619 .map_err(|e| Error::from_reason(format!("Failed to gather sources: {e}")))?;
2620 paths.retain(|path| !path.example);
2621
2622 let project_root = toml_path.parent().unwrap_or(&toml_path).to_path_buf();
2624 let celox_cfg = load_celox_config(&project_root)?;
2625 let prj_name = metadata.project.name.clone();
2626 for dir in &celox_cfg.test.sources {
2627 let dir_path = project_root.join(dir);
2628 if !dir_path.exists() {
2629 continue;
2630 }
2631 for src in walkdir(&dir_path)? {
2632 paths.push(PathSet {
2633 prj: prj_name.clone(),
2634 src: src.clone(),
2635 dst: src.with_extension("sv"),
2636 map: src.with_extension("map"),
2637 example: false,
2638 });
2639 }
2640 }
2641
2642 if let Some(exclude_set) = build_exclude_set(&celox_cfg)? {
2643 paths.retain(|p| !is_excluded(&p.src, &project_root, &exclude_set));
2644 }
2645
2646 if paths.is_empty() {
2647 return Err(Error::from_reason("No Veryl source files found"));
2648 }
2649
2650 symbol_table::clear();
2652 attribute_table::clear();
2653
2654 let analyzer = Analyzer::new(&metadata);
2655 inject_analyzer_components(components)?;
2656 let mut parsers = Vec::new();
2657 let mut all_warnings = Vec::new();
2658
2659 for path in &paths {
2660 let input = std::fs::read_to_string(&path.src)
2661 .map_err(|e| Error::from_reason(format!("{}: {e}", path.src.display())))?;
2662 let parser = Parser::parse(&input, &path.src)
2663 .map_err(|e| Error::from_reason(format!("Parse error: {e}")))?;
2664
2665 let results = analyzer.analyze_pass1(&path.prj, &parser.veryl);
2666 let real_errors: Vec<_> = results.iter().filter(|e| e.is_error()).collect();
2667 if !real_errors.is_empty() {
2668 return Err(Error::from_reason(format_errors_with_warnings(
2669 "analysis pass 1",
2670 &real_errors,
2671 &all_warnings,
2672 )));
2673 }
2674 all_warnings.extend(results.into_iter().filter(|e| !e.is_error()));
2675
2676 parsers.push((path.clone(), parser));
2677 }
2678
2679 let results = Analyzer::analyze_post_pass1();
2680 let real_errors: Vec<_> = results.iter().filter(|e| e.is_error()).collect();
2681 if !real_errors.is_empty() {
2682 return Err(Error::from_reason(format_errors_with_warnings(
2683 "post-pass 1 analysis",
2684 &real_errors,
2685 &all_warnings,
2686 )));
2687 }
2688 all_warnings.extend(results.into_iter().filter(|e| !e.is_error()));
2689
2690 let base_normalized = base_path.replace('\\', "/");
2694 let all_source_files: Vec<String> = parsers
2695 .iter()
2696 .map(|(path, _)| {
2697 let src_normalized = path
2698 .src
2699 .to_string_lossy()
2700 .replace(r"\\?\", "")
2701 .replace('\\', "/");
2702 src_normalized
2703 .strip_prefix(&base_normalized)
2704 .unwrap_or(&src_normalized)
2705 .trim_start_matches('/')
2706 .to_string()
2707 })
2708 .collect();
2709 let source_file_refs: Vec<&str> = all_source_files.iter().map(|s| s.as_str()).collect();
2710
2711 let mut all_modules = Vec::new();
2712 let mut file_modules: HashMap<String, Vec<String>> = HashMap::default();
2713 let mut post_pass_ir = Ir::default();
2714
2715 for (i, (_path, parser)) in parsers.iter().enumerate() {
2716 let mut analyzer_context = Context::default();
2717 let mut ir = Ir::default();
2718 let results = analyzer.analyze_pass2(&parser.veryl, &mut analyzer_context, Some(&mut ir));
2719 let real_errors: Vec<_> = results.iter().filter(|e| e.is_error()).collect();
2720 if !real_errors.is_empty() {
2721 return Err(Error::from_reason(format_errors_with_warnings(
2722 "analysis pass 2",
2723 &real_errors,
2724 &all_warnings,
2725 )));
2726 }
2727 all_warnings.extend(results.into_iter().filter(|e| !e.is_error()));
2728
2729 let modules = generate_all(&ir, &source_file_refs);
2730 post_pass_ir.append(&mut ir);
2731 let source_file = all_source_files[i].clone();
2732
2733 let module_names: Vec<String> = modules.iter().map(|m| m.module_name.clone()).collect();
2734 if !module_names.is_empty() {
2735 file_modules.insert(source_file.clone(), module_names);
2736 }
2737
2738 for m in modules {
2739 all_modules.push(JsonModuleEntry {
2740 module_name: m.module_name,
2741 source_file: source_file.clone(),
2742 dts_content: m.dts_content,
2743 md_content: m.md_content,
2744 ports: m.ports,
2745 events: m.events,
2746 instances: m.instances,
2747 is_test: m.is_test,
2748 });
2749 }
2750 }
2751
2752 let results = Analyzer::analyze_post_pass2(&post_pass_ir);
2753 let real_errors: Vec<_> = results.iter().filter(|e| e.is_error()).collect();
2754 if !real_errors.is_empty() {
2755 return Err(Error::from_reason(format_errors_with_warnings(
2756 "post-pass 2 analysis",
2757 &real_errors,
2758 &all_warnings,
2759 )));
2760 }
2761 all_warnings.extend(results.into_iter().filter(|e| !e.is_error()));
2762
2763 all_modules.sort_by(|a, b| a.module_name.cmp(&b.module_name));
2765
2766 let warning_msgs: Vec<String> = all_warnings
2767 .iter()
2768 .map(|w| celox::render_diagnostic(w))
2769 .collect();
2770
2771 let all_sources: Vec<(String, String)> = parsers
2772 .iter()
2773 .map(|(p, _)| {
2774 let path_str = p.src.to_string_lossy().to_string();
2775 let content = std::fs::read_to_string(&p.src).unwrap_or_default();
2776 (path_str, content)
2777 })
2778 .collect();
2779
2780 let diagnostics: Vec<celox_ts_gen::JsonDiagnostic> = all_warnings
2781 .iter()
2782 .flat_map(|w| analyzer_error_to_diagnostics(w, &all_sources, false))
2783 .collect();
2784
2785 let output = JsonOutput {
2786 project_path: base_path,
2787 modules: all_modules,
2788 file_modules,
2789 warnings: warning_msgs,
2790 diagnostics,
2791 };
2792
2793 serde_json::to_string(&output)
2794 .map_err(|e| Error::from_reason(format!("Failed to serialize JSON: {e}")))
2795}
2796
2797#[napi]
2802pub fn gen_ts_from_source(
2803 sources: Vec<NapiSourceFile>,
2804 components: Option<Vec<NapiInjectedManifest>>,
2805) -> Result<String> {
2806 use celox_ts_gen::{JsonModuleEntry, JsonOutput, generate_all};
2807
2808 if sources.is_empty() {
2809 return Err(Error::from_reason("No source files provided"));
2810 }
2811
2812 let metadata = Metadata::create_default("playground")
2813 .map_err(|e| Error::from_reason(format!("Failed to create default metadata: {e}")))?;
2814
2815 symbol_table::clear();
2817 attribute_table::clear();
2818
2819 let analyzer = Analyzer::new(&metadata);
2820 inject_analyzer_components(components)?;
2821 let mut parsers = Vec::new();
2822 let mut all_warnings = Vec::new();
2823
2824 for src in &sources {
2825 let path = std::path::PathBuf::from(&src.path);
2826 let parser = Parser::parse(&src.content, &path)
2827 .map_err(|e| Error::from_reason(format!("Parse error: {e}")))?;
2828
2829 let prj = "playground".to_string();
2830 let results = analyzer.analyze_pass1(&prj, &parser.veryl);
2831 let real_errors: Vec<_> = results.iter().filter(|e| e.is_error()).collect();
2832 if !real_errors.is_empty() {
2833 return Err(Error::from_reason(format_errors_with_warnings(
2834 "analysis pass 1",
2835 &real_errors,
2836 &all_warnings,
2837 )));
2838 }
2839 all_warnings.extend(results.into_iter().filter(|e| !e.is_error()));
2840
2841 parsers.push((prj, path, parser));
2842 }
2843
2844 let results = Analyzer::analyze_post_pass1();
2845 let real_errors: Vec<_> = results.iter().filter(|e| e.is_error()).collect();
2846 if !real_errors.is_empty() {
2847 return Err(Error::from_reason(format_errors_with_warnings(
2848 "post-pass 1 analysis",
2849 &real_errors,
2850 &all_warnings,
2851 )));
2852 }
2853 all_warnings.extend(results.into_iter().filter(|e| !e.is_error()));
2854
2855 let all_source_files: Vec<String> = parsers
2857 .iter()
2858 .map(|(_, p, _)| p.to_string_lossy().replace('\\', "/"))
2859 .collect();
2860 let source_file_refs: Vec<&str> = all_source_files.iter().map(|s| s.as_str()).collect();
2861
2862 let mut all_modules = Vec::new();
2863 let mut file_modules: HashMap<String, Vec<String>> = HashMap::default();
2864 let mut post_pass_ir = Ir::default();
2865
2866 for (i, (_prj, _path, parser)) in parsers.iter().enumerate() {
2867 let mut analyzer_context = Context::default();
2868 let mut ir = Ir::default();
2869 let results = analyzer.analyze_pass2(&parser.veryl, &mut analyzer_context, Some(&mut ir));
2870 let real_errors: Vec<_> = results.iter().filter(|e| e.is_error()).collect();
2871 if !real_errors.is_empty() {
2872 return Err(Error::from_reason(format_errors_with_warnings(
2873 "analysis pass 2",
2874 &real_errors,
2875 &all_warnings,
2876 )));
2877 }
2878 all_warnings.extend(results.into_iter().filter(|e| !e.is_error()));
2879
2880 let modules = generate_all(&ir, &source_file_refs);
2881 post_pass_ir.append(&mut ir);
2882 let source_file = all_source_files[i].clone();
2883
2884 let module_names: Vec<String> = modules.iter().map(|m| m.module_name.clone()).collect();
2885 if !module_names.is_empty() {
2886 file_modules.insert(source_file.clone(), module_names);
2887 }
2888
2889 for m in modules {
2890 all_modules.push(JsonModuleEntry {
2891 module_name: m.module_name,
2892 source_file: source_file.clone(),
2893 dts_content: m.dts_content,
2894 md_content: m.md_content,
2895 ports: m.ports,
2896 events: m.events,
2897 instances: m.instances,
2898 is_test: m.is_test,
2899 });
2900 }
2901 }
2902
2903 let results = Analyzer::analyze_post_pass2(&post_pass_ir);
2904 let real_errors: Vec<_> = results.iter().filter(|e| e.is_error()).collect();
2905 if !real_errors.is_empty() {
2906 return Err(Error::from_reason(format_errors_with_warnings(
2907 "post-pass 2 analysis",
2908 &real_errors,
2909 &all_warnings,
2910 )));
2911 }
2912 all_warnings.extend(results.into_iter().filter(|e| !e.is_error()));
2913
2914 all_modules.sort_by(|a, b| a.module_name.cmp(&b.module_name));
2916
2917 let warning_msgs: Vec<String> = all_warnings
2918 .iter()
2919 .map(|w| celox::render_diagnostic(w))
2920 .collect();
2921
2922 let all_sources: Vec<(String, String)> = sources
2923 .iter()
2924 .map(|s| (s.path.clone(), s.content.clone()))
2925 .collect();
2926
2927 let diagnostics: Vec<celox_ts_gen::JsonDiagnostic> = all_warnings
2928 .iter()
2929 .flat_map(|w| analyzer_error_to_diagnostics(w, &all_sources, false))
2930 .collect();
2931
2932 let output = JsonOutput {
2933 project_path: String::new(),
2934 modules: all_modules,
2935 file_modules,
2936 warnings: warning_msgs,
2937 diagnostics,
2938 };
2939
2940 serde_json::to_string(&output)
2941 .map_err(|e| Error::from_reason(format!("Failed to serialize JSON: {e}")))
2942}
2943
2944#[cfg(all(test, not(target_arch = "wasm32")))]
2945mod tests {
2946 use super::*;
2947
2948 fn default_opts() -> ParsedOptions {
2949 ParsedOptions {
2950 common: ParsedOptionsCommon {
2951 four_state: false,
2952 optimize_options: celox::OptimizeOptions::all(),
2953 vcd: None,
2954 false_loops: vec![],
2955 true_loops: vec![],
2956 clock_type: None,
2957 reset_type: None,
2958 extra_source: None,
2959 parameters: vec![],
2960 },
2961 cranelift_options: celox::CraneliftOptions::default(),
2962 dead_store_policy: celox::DeadStorePolicy::Off,
2963 }
2964 }
2965
2966 fn make_sources(pairs: &[(&str, &str)]) -> Vec<(String, std::path::PathBuf)> {
2967 pairs
2968 .iter()
2969 .map(|(content, path)| (content.to_string(), std::path::PathBuf::from(path)))
2970 .collect()
2971 }
2972
2973 #[test]
2974 fn simulator_handle_derives_four_state_metadata_from_layout() {
2975 let source = "module Top (a: input logic<1>) {}";
2976 let path = std::path::Path::new("top.veryl");
2977
2978 for four_state in [false, true] {
2979 let simulator = celox::Simulator::from_sources(vec![(source, path)], "Top")
2980 .four_state(four_state)
2981 .build()
2982 .unwrap();
2983 let handle = NativeSimulatorHandle::from_simulator(simulator, None).unwrap();
2984 let layout: serde_json::Value = serde_json::from_str(&handle.layout_json()).unwrap();
2985
2986 assert_eq!(layout["a"]["is_4state"].as_bool(), Some(four_state));
2987 }
2988 }
2989
2990 #[test]
2991 fn normal_project_loading_excludes_example_sources() {
2992 let project = tempfile::tempdir().unwrap();
2993 std::fs::create_dir(project.path().join("src")).unwrap();
2994 std::fs::create_dir(project.path().join("examples")).unwrap();
2995 std::fs::write(
2996 project.path().join("Veryl.toml"),
2997 "[project]\nname = \"example_filter\"\nversion = \"0.1.0\"\n",
2998 )
2999 .unwrap();
3000 std::fs::write(project.path().join("src/top.veryl"), "module Top () {}\n").unwrap();
3001 std::fs::write(
3002 project.path().join("examples/demo.veryl"),
3003 "module Demo () {}\n",
3004 )
3005 .unwrap();
3006
3007 let (sources, _, _) = load_project_sources(project.path().to_str().unwrap()).unwrap();
3008 let source_names = sources
3009 .iter()
3010 .map(|(_, path)| path.file_name().unwrap().to_str().unwrap())
3011 .collect::<Vec<_>>();
3012 assert_eq!(source_names, ["top.veryl"]);
3013 }
3014
3015 #[test]
3016 fn same_inputs_produce_same_key() {
3017 let src = make_sources(&[("module Top {}", "a.veryl")]);
3018 let opts = default_opts();
3019 let k1 = build_cache_key(&src, "Top", &opts, None);
3020 let k2 = build_cache_key(&src, "Top", &opts, None);
3021 assert_eq!(k1, k2);
3022 }
3023
3024 #[test]
3025 fn high_index_sir_pass_changes_cache_key_without_bit_packing() {
3026 let src = make_sources(&[("module Top {}", "a.veryl")]);
3027 let enabled = default_opts();
3028 let mut disabled = default_opts();
3029 disabled.common.optimize_options = disabled
3030 .common
3031 .optimize_options
3032 .clone()
3033 .disable(celox::SirPass::IdentityStoreBypass);
3034
3035 assert_ne!(
3036 build_cache_key(&src, "Top", &enabled, None),
3037 build_cache_key(&src, "Top", &disabled, None)
3038 );
3039 }
3040
3041 #[test]
3042 fn native_memory_width_changes_cache_key() {
3043 let src = make_sources(&[("module Top {}", "a.veryl")]);
3044 let mut narrow = default_opts();
3045 narrow.common.optimize_options = narrow
3046 .common
3047 .optimize_options
3048 .clone()
3049 .with_max_native_memory_width(64);
3050 let mut wide = default_opts();
3051 wide.common.optimize_options = wide
3052 .common
3053 .optimize_options
3054 .clone()
3055 .with_max_native_memory_width(128);
3056
3057 assert_ne!(
3058 build_cache_key(&src, "Top", &narrow, None),
3059 build_cache_key(&src, "Top", &wide, None)
3060 );
3061 }
3062
3063 #[test]
3064 fn different_source_content_different_key() {
3065 let s1 = make_sources(&[("module A {}", "a.veryl")]);
3066 let s2 = make_sources(&[("module B {}", "a.veryl")]);
3067 let opts = default_opts();
3068 assert_ne!(
3069 build_cache_key(&s1, "Top", &opts, None),
3070 build_cache_key(&s2, "Top", &opts, None),
3071 );
3072 }
3073
3074 #[test]
3075 fn different_source_path_different_key() {
3076 let s1 = make_sources(&[("module A {}", "a.veryl")]);
3077 let s2 = make_sources(&[("module A {}", "b.veryl")]);
3078 let opts = default_opts();
3079 assert_ne!(
3080 build_cache_key(&s1, "Top", &opts, None),
3081 build_cache_key(&s2, "Top", &opts, None),
3082 );
3083 }
3084
3085 #[test]
3086 fn different_top_different_key() {
3087 let src = make_sources(&[("module Top {}", "a.veryl")]);
3088 let opts = default_opts();
3089 assert_ne!(
3090 build_cache_key(&src, "Top", &opts, None),
3091 build_cache_key(&src, "Other", &opts, None),
3092 );
3093 }
3094
3095 #[test]
3096 fn four_state_differs() {
3097 let src = make_sources(&[("module Top {}", "a.veryl")]);
3098 let mut o1 = default_opts();
3099 let mut o2 = default_opts();
3100 o1.common.four_state = false;
3101 o2.common.four_state = true;
3102 assert_ne!(
3103 build_cache_key(&src, "Top", &o1, None),
3104 build_cache_key(&src, "Top", &o2, None),
3105 );
3106 }
3107
3108 #[test]
3109 fn optimize_options_differs() {
3110 let src = make_sources(&[("module Top {}", "a.veryl")]);
3111 let mut o1 = default_opts();
3112 let mut o2 = default_opts();
3113 o1.common.optimize_options = celox::OptimizeOptions::all();
3114 o2.common.optimize_options = celox::OptimizeOptions::none();
3115 assert_ne!(
3116 build_cache_key(&src, "Top", &o1, None),
3117 build_cache_key(&src, "Top", &o2, None),
3118 );
3119 }
3120
3121 #[test]
3122 fn cranelift_opt_level_differs() {
3123 let src = make_sources(&[("module Top {}", "a.veryl")]);
3124 let mut o1 = default_opts();
3125 let mut o2 = default_opts();
3126 o1.cranelift_options.opt_level = celox::CraneliftOptLevel::Speed;
3127 o2.cranelift_options.opt_level = celox::CraneliftOptLevel::None;
3128 assert_ne!(
3129 build_cache_key(&src, "Top", &o1, None),
3130 build_cache_key(&src, "Top", &o2, None),
3131 );
3132 }
3133
3134 #[test]
3135 fn regalloc_algorithm_differs() {
3136 let src = make_sources(&[("module Top {}", "a.veryl")]);
3137 let mut o1 = default_opts();
3138 let mut o2 = default_opts();
3139 o1.cranelift_options.regalloc_algorithm = celox::RegallocAlgorithm::Backtracking;
3140 o2.cranelift_options.regalloc_algorithm = celox::RegallocAlgorithm::SinglePass;
3141 assert_ne!(
3142 build_cache_key(&src, "Top", &o1, None),
3143 build_cache_key(&src, "Top", &o2, None),
3144 );
3145 }
3146
3147 #[test]
3148 fn dead_store_policy_differs() {
3149 let src = make_sources(&[("module Top {}", "a.veryl")]);
3150 let mut o1 = default_opts();
3151 let mut o2 = default_opts();
3152 o1.dead_store_policy = celox::DeadStorePolicy::Off;
3153 o2.dead_store_policy = celox::DeadStorePolicy::PreserveTopPorts;
3154 assert_ne!(
3155 build_cache_key(&src, "Top", &o1, None),
3156 build_cache_key(&src, "Top", &o2, None),
3157 );
3158 }
3159
3160 #[test]
3161 fn clock_type_differs() {
3162 let src = make_sources(&[("module Top {}", "a.veryl")]);
3163 let mut o1 = default_opts();
3164 let mut o2 = default_opts();
3165 o1.common.clock_type = None;
3166 o2.common.clock_type = Some(celox::ClockType::NegEdge);
3167 assert_ne!(
3168 build_cache_key(&src, "Top", &o1, None),
3169 build_cache_key(&src, "Top", &o2, None),
3170 );
3171 }
3172
3173 #[test]
3174 fn reset_type_differs() {
3175 let src = make_sources(&[("module Top {}", "a.veryl")]);
3176 let mut o1 = default_opts();
3177 let mut o2 = default_opts();
3178 o1.common.reset_type = None;
3179 o2.common.reset_type = Some(celox::ResetType::SyncHigh);
3180 assert_ne!(
3181 build_cache_key(&src, "Top", &o1, None),
3182 build_cache_key(&src, "Top", &o2, None),
3183 );
3184 }
3185
3186 #[test]
3187 fn parameters_differ() {
3188 let src = make_sources(&[("module Top {}", "a.veryl")]);
3189 let mut o1 = default_opts();
3190 let mut o2 = default_opts();
3191 o1.common.parameters = vec![("WIDTH".into(), 8)];
3192 o2.common.parameters = vec![("WIDTH".into(), 16)];
3193 assert_ne!(
3194 build_cache_key(&src, "Top", &o1, None),
3195 build_cache_key(&src, "Top", &o2, None),
3196 );
3197 }
3198
3199 #[test]
3200 fn source_order_independent() {
3201 let s1 = make_sources(&[("aaa", "a.veryl"), ("bbb", "b.veryl")]);
3202 let s2 = make_sources(&[("bbb", "b.veryl"), ("aaa", "a.veryl")]);
3203 let opts = default_opts();
3204 assert_eq!(
3205 build_cache_key(&s1, "Top", &opts, None),
3206 build_cache_key(&s2, "Top", &opts, None),
3207 );
3208 }
3209
3210 #[test]
3211 fn non_compilation_options_ignored() {
3212 let src = make_sources(&[("module Top {}", "a.veryl")]);
3213 let mut o1 = default_opts();
3214 let mut o2 = default_opts();
3215 o1.common.vcd = None;
3217 o2.common.vcd = Some("/tmp/dump.vcd".into());
3218 assert_eq!(
3219 build_cache_key(&src, "Top", &o1, None),
3220 build_cache_key(&src, "Top", &o2, None),
3221 );
3222 }
3223
3224 #[test]
3225 fn false_loops_differ() {
3226 let src = make_sources(&[("module Top {}", "a.veryl")]);
3227 let mut o1 = default_opts();
3228 let mut o2 = default_opts();
3229 o1.common.false_loops = vec![];
3230 o2.common.false_loops = vec![((vec![], vec!["a".into()]), (vec![], vec!["b".into()]))];
3231 assert_ne!(
3232 build_cache_key(&src, "Top", &o1, None),
3233 build_cache_key(&src, "Top", &o2, None),
3234 );
3235 }
3236
3237 #[test]
3238 fn true_loops_differ() {
3239 let src = make_sources(&[("module Top {}", "a.veryl")]);
3240 let mut o1 = default_opts();
3241 let mut o2 = default_opts();
3242 o1.common.true_loops = vec![];
3243 o2.common.true_loops = vec![((vec![], vec!["x".into()]), (vec![], vec!["y".into()]), 4)];
3244 assert_ne!(
3245 build_cache_key(&src, "Top", &o1, None),
3246 build_cache_key(&src, "Top", &o2, None),
3247 );
3248 }
3249
3250 #[test]
3251 fn metadata_clock_reset_differs() {
3252 let src = make_sources(&[("module Top {}", "a.veryl")]);
3253 let opts = default_opts();
3254
3255 let mut m1 = Metadata::create_default("prj").unwrap();
3256 let mut m2 = Metadata::create_default("prj").unwrap();
3257 m1.build.clock_type = celox::ClockType::PosEdge;
3258 m2.build.clock_type = celox::ClockType::NegEdge;
3259 assert_ne!(
3260 build_cache_key(&src, "Top", &opts, Some(&m1)),
3261 build_cache_key(&src, "Top", &opts, Some(&m2)),
3262 );
3263
3264 let mut m3 = Metadata::create_default("prj").unwrap();
3265 let mut m4 = Metadata::create_default("prj").unwrap();
3266 m3.build.reset_type = celox::ResetType::AsyncLow;
3267 m4.build.reset_type = celox::ResetType::SyncHigh;
3268 assert_ne!(
3269 build_cache_key(&src, "Top", &opts, Some(&m3)),
3270 build_cache_key(&src, "Top", &opts, Some(&m4)),
3271 );
3272 }
3273
3274 #[test]
3275 fn no_metadata_vs_metadata_differs() {
3276 let src = make_sources(&[("module Top {}", "a.veryl")]);
3277 let opts = default_opts();
3278 let m = Metadata::create_default("prj").unwrap();
3279 assert_ne!(
3281 build_cache_key(&src, "Top", &opts, None),
3282 build_cache_key(&src, "Top", &opts, Some(&m)),
3283 );
3284 }
3285}