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(target_arch = "x86_64")]
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(target_arch = "x86_64")]
649type SharedCode = celox::SharedNativeCode;
650#[cfg(all(not(target_arch = "wasm32"), not(target_arch = "x86_64")))]
651type SharedCode = celox::SharedJitCode;
652
653#[cfg(not(target_arch = "wasm32"))]
654struct CachedBuild {
656 shared_code: Arc<SharedCode>,
657 runtime_errors: HashMap<i64, (String, Vec<String>)>,
658 layout_json: String,
659 events_json: String,
660 hierarchy_json: String,
661 warnings_json: String,
662 stable_size: u32,
663 total_size: u32,
664 vcd_descs: Vec<celox::VcdSignalDesc>,
666}
667
668#[cfg(not(target_arch = "wasm32"))]
669#[derive(Clone, Debug, PartialEq, Eq, Hash)]
675struct CacheKey {
676 sources: Vec<(String, String)>,
678 top: String,
679 four_state: bool,
680 sir_optimization: SirOptimizationCacheKey,
681 cranelift_opt_level: u8,
682 regalloc_algorithm: u8,
683 enable_alias_analysis: bool,
684 enable_verifier: bool,
685 dead_store_policy: u8,
686 clock_type: Option<u8>,
687 reset_type: Option<u8>,
688 parameters: Vec<(String, u64)>,
689 false_loops: Vec<(
690 (Vec<(String, usize)>, Vec<String>),
691 (Vec<(String, usize)>, Vec<String>),
692 )>,
693 true_loops: Vec<(
694 (Vec<(String, usize)>, Vec<String>),
695 (Vec<(String, usize)>, Vec<String>),
696 usize,
697 )>,
698 metadata_clock_type: Option<u8>,
701 metadata_reset_type: Option<u8>,
702}
703
704#[cfg(not(target_arch = "wasm32"))]
706#[derive(Clone, Debug, PartialEq, Eq, Hash)]
707struct SirOptimizationCacheKey {
708 opt_level: celox::OptLevel,
709 enabled_passes: Box<[bool]>,
710 max_native_memory_width: usize,
711}
712
713#[cfg(not(target_arch = "wasm32"))]
714impl From<&celox::OptimizeOptions> for SirOptimizationCacheKey {
715 fn from(options: &celox::OptimizeOptions) -> Self {
716 Self {
717 opt_level: options.opt_level(),
718 enabled_passes: celox::SirPass::ALL
719 .iter()
720 .map(|&pass| options.is_enabled(pass))
721 .collect(),
722 max_native_memory_width: options.max_native_memory_width(),
723 }
724 }
725}
726
727#[cfg(not(target_arch = "wasm32"))]
728static JIT_CACHE: std::sync::LazyLock<Mutex<HashMap<CacheKey, Arc<CachedBuild>>>> =
729 std::sync::LazyLock::new(|| Mutex::new(HashMap::default()));
730
731#[cfg(not(target_arch = "wasm32"))]
732fn build_cache_key(
738 sources: &[(String, std::path::PathBuf)],
739 top: &str,
740 opts: &ParsedOptions,
741 metadata: Option<&Metadata>,
742) -> CacheKey {
743 let mut sorted_sources: Vec<(String, String)> = sources
744 .iter()
745 .map(|(content, path)| (path.to_string_lossy().into_owned(), content.clone()))
746 .collect();
747 sorted_sources.sort_by(|a, b| a.0.cmp(&b.0));
748
749 CacheKey {
750 sources: sorted_sources,
751 top: top.to_string(),
752 four_state: opts.four_state,
753 sir_optimization: SirOptimizationCacheKey::from(&opts.optimize_options),
754 cranelift_opt_level: opts.cranelift_options.opt_level as u8,
755 regalloc_algorithm: opts.cranelift_options.regalloc_algorithm as u8,
756 enable_alias_analysis: opts.cranelift_options.enable_alias_analysis,
757 enable_verifier: opts.cranelift_options.enable_verifier,
758 dead_store_policy: opts.dead_store_policy as u8,
759 clock_type: opts.clock_type.map(|ct| ct as u8),
760 reset_type: opts.reset_type.map(|rt| rt as u8),
761 parameters: opts.parameters.clone(),
762 false_loops: opts.false_loops.clone(),
763 true_loops: opts.true_loops.clone(),
764 metadata_clock_type: metadata.map(|m| m.build.clock_type as u8),
765 metadata_reset_type: metadata.map(|m| m.build.reset_type as u8),
766 }
767}
768
769#[cfg(not(target_arch = "wasm32"))]
770fn runtime_errors_by_name(program: &celox::RuntimeProgram) -> HashMap<i64, (String, Vec<String>)> {
771 program
772 .runtime_schema
773 .runtime_errors
774 .iter()
775 .map(|(&code, info)| {
776 (
777 code,
778 (
779 info.message.clone(),
780 info.signals
781 .iter()
782 .map(|addr| program.get_path(addr))
783 .collect(),
784 ),
785 )
786 })
787 .collect()
788}
789
790#[cfg(not(target_arch = "wasm32"))]
791fn napi_runtime_error(
792 runtime_errors: &HashMap<i64, (String, Vec<String>)>,
793 err: celox::RuntimeErrorCode,
794) -> Error {
795 match err {
796 celox::RuntimeErrorCode::DetectedTrueLoopCode(code) => {
797 if let Some((message, signals)) = runtime_errors.get(&code) {
798 if message == "Detected True Loop" {
799 Error::from_reason(format!(
800 "{}",
801 celox::RuntimeErrorCode::DetectedTrueLoopAt {
802 signals: signals.clone(),
803 }
804 ))
805 } else {
806 Error::from_reason(format!(
807 "{}",
808 celox::RuntimeErrorCode::Runtime {
809 message: message.clone(),
810 signals: signals.clone(),
811 }
812 ))
813 }
814 } else {
815 Error::from_reason(format!("{}", celox::RuntimeErrorCode::DetectedTrueLoop))
816 }
817 }
818 other => Error::from_reason(format!("{}", other)),
819 }
820}
821
822#[cfg(not(target_arch = "wasm32"))]
826#[napi]
827pub struct NativeSimulatorHandle {
828 backend: Option<celox::DefaultBackend>,
829 runtime_errors: HashMap<i64, (String, Vec<String>)>,
830 vcd_writer: Option<celox::VcdWriter>,
831 layout_json: String,
832 events_json: String,
833 hierarchy_json: String,
834 warnings_json: String,
835 stable_size: u32,
836 total_size: u32,
837}
838
839#[cfg(not(target_arch = "wasm32"))]
840impl NativeSimulatorHandle {
841 pub fn from_simulator(simulator: celox::Simulator, vcd_path: Option<&str>) -> Result<Self> {
850 Self::build_and_cache(simulator, vcd_path, None)
851 }
852
853 pub fn from_frontend(
858 artifact: celox::FrontendArtifact,
859 options: Option<NapiOptions>,
860 ) -> Result<Self> {
861 let opts = parse_options(&options)?;
862 let builder = apply_options(celox::Simulator::from_frontend(artifact), &opts);
863 let simulator = builder
864 .build()
865 .map_err(|error| Error::from_reason(error.to_string()))?;
866 Self::from_simulator(simulator, opts.vcd.as_deref())
867 }
868}
869
870#[cfg(not(target_arch = "wasm32"))]
871#[napi]
872impl NativeSimulatorHandle {
873 fn build_and_cache(
876 sim: celox::Simulator,
877 vcd_path: Option<&str>,
878 cache_key: Option<CacheKey>,
879 ) -> Result<Self> {
880 let four_state = sim.layout().four_state;
881 let warnings_json = format_warnings_json(sim.warnings());
882 let signals = sim.named_signals();
883 let events = sim.named_events();
884 let hierarchy = sim.named_hierarchy();
885 let (_, total_size) = sim.memory_as_ptr();
886 let stable_size = sim.stable_region_size();
887 let vcd_descs = sim.build_vcd_descs(four_state);
888 let runtime_errors = runtime_errors_by_name(sim.program());
889
890 let layout_map = build_signal_layout(&signals, four_state);
891 let event_map = build_event_map(&events);
892 let hierarchy_node = build_hierarchy_node(&hierarchy, four_state);
893
894 let layout_json = serde_json::to_string(&layout_map)
895 .map_err(|e| Error::from_reason(format!("Failed to serialize layout: {}", e)))?;
896 let events_json = serde_json::to_string(&event_map)
897 .map_err(|e| Error::from_reason(format!("Failed to serialize events: {}", e)))?;
898 let hierarchy_json = serde_json::to_string(&hierarchy_node)
899 .map_err(|e| Error::from_reason(format!("Failed to serialize hierarchy: {}", e)))?;
900
901 if let Some(key) = cache_key {
903 let cached = Arc::new(CachedBuild {
904 shared_code: sim.shared_code(),
905 runtime_errors: runtime_errors.clone(),
906 layout_json: layout_json.clone(),
907 events_json: events_json.clone(),
908 hierarchy_json: hierarchy_json.clone(),
909 warnings_json: warnings_json.clone(),
910 stable_size: stable_size as u32,
911 total_size: total_size as u32,
912 vcd_descs: vcd_descs.clone(),
913 });
914 let mut cache = JIT_CACHE.lock().unwrap_or_else(|e| e.into_inner());
915 cache.insert(key, cached);
916 }
917
918 let vcd_writer = if let Some(path) = vcd_path {
920 Some(
921 celox::VcdWriter::new(path, &vcd_descs)
922 .map_err(|e| Error::from_reason(format!("Failed to create VCD: {}", e)))?,
923 )
924 } else {
925 None
926 };
927
928 let backend = sim.into_backend();
930
931 Ok(Self {
932 backend: Some(backend),
933 runtime_errors,
934 vcd_writer,
935 layout_json,
936 events_json,
937 hierarchy_json,
938 warnings_json,
939 stable_size: stable_size as u32,
940 total_size: total_size as u32,
941 })
942 }
943
944 fn from_cached(cached: &CachedBuild, vcd_path: Option<&str>) -> Result<Self> {
946 #[cfg(target_arch = "x86_64")]
947 let backend = celox::NativeBackend::from_shared(Arc::clone(&cached.shared_code));
948 #[cfg(not(target_arch = "x86_64"))]
949 let backend = celox::JitBackend::from_shared(Arc::clone(&cached.shared_code));
950 let vcd_writer = if let Some(path) = vcd_path {
951 Some(
952 celox::VcdWriter::new(path, &cached.vcd_descs)
953 .map_err(|e| Error::from_reason(format!("Failed to create VCD: {}", e)))?,
954 )
955 } else {
956 None
957 };
958 Ok(Self {
959 backend: Some(backend),
960 runtime_errors: cached.runtime_errors.clone(),
961 vcd_writer,
962 layout_json: cached.layout_json.clone(),
963 events_json: cached.events_json.clone(),
964 hierarchy_json: cached.hierarchy_json.clone(),
965 warnings_json: cached.warnings_json.clone(),
966 stable_size: cached.stable_size,
967 total_size: cached.total_size,
968 })
969 }
970
971 #[napi(constructor)]
973 pub fn new(
974 sources: Vec<NapiSourceFile>,
975 top: String,
976 options: Option<NapiOptions>,
977 ) -> Result<Self> {
978 let opts = parse_options(&options)?;
979 let mut src_pairs: Vec<(String, std::path::PathBuf)> = sources
980 .into_iter()
981 .map(|s| (s.content, std::path::PathBuf::from(s.path)))
982 .collect();
983 append_extra_source(&mut src_pairs, &opts.extra_source);
984
985 let cache_key = build_cache_key(&src_pairs, &top, &opts, None);
986
987 {
988 let cache = JIT_CACHE.lock().unwrap_or_else(|e| e.into_inner());
989 if let Some(cached) = cache.get(&cache_key) {
990 return Self::from_cached(cached, opts.vcd.as_deref());
991 }
992 }
993
994 let source_refs: Vec<(&str, &std::path::Path)> = src_pairs
995 .iter()
996 .map(|(s, p)| (s.as_str(), p.as_path()))
997 .collect();
998 let builder = apply_options(celox::Simulator::from_sources(source_refs, &top), &opts);
999 let sim = builder
1000 .build()
1001 .map_err(|e| Error::from_reason(format!("{}", e)))?;
1002
1003 Self::build_and_cache(sim, opts.vcd.as_deref(), Some(cache_key))
1004 }
1005
1006 #[napi(factory)]
1008 pub fn from_frontend_artifact(
1009 artifact_json: String,
1010 options: Option<NapiOptions>,
1011 ) -> Result<Self> {
1012 let artifact = celox::FrontendArtifact::from_json(&artifact_json)
1013 .map_err(|error| Error::from_reason(error.to_string()))?;
1014 Self::from_frontend(artifact, options)
1015 }
1016
1017 #[napi(factory)]
1023 pub fn from_project(
1024 project_path: String,
1025 top: String,
1026 options: Option<NapiOptions>,
1027 ) -> Result<Self> {
1028 let opts = parse_options(&options)?;
1029 let (mut sources, metadata, _celox_cfg) = load_project_sources(&project_path)?;
1030 append_extra_source(&mut sources, &opts.extra_source);
1031
1032 let cache_key = build_cache_key(&sources, &top, &opts, Some(&metadata));
1033
1034 {
1035 let cache = JIT_CACHE.lock().unwrap_or_else(|e| e.into_inner());
1036 if let Some(cached) = cache.get(&cache_key) {
1037 return Self::from_cached(cached, opts.vcd.as_deref());
1038 }
1039 }
1040
1041 let source_refs: Vec<(&str, &std::path::Path)> = sources
1042 .iter()
1043 .map(|(s, p)| (s.as_str(), p.as_path()))
1044 .collect();
1045
1046 let builder = apply_options(
1047 celox::Simulator::from_sources(source_refs, &top).with_metadata(metadata),
1048 &opts,
1049 );
1050 let sim = builder
1051 .build()
1052 .map_err(|e| Error::from_reason(format!("{}", e)))?;
1053
1054 Self::build_and_cache(sim, opts.vcd.as_deref(), Some(cache_key))
1055 }
1056
1057 #[napi(getter)]
1059 pub fn layout_json(&self) -> String {
1060 self.layout_json.clone()
1061 }
1062
1063 #[napi(getter)]
1065 pub fn events_json(&self) -> String {
1066 self.events_json.clone()
1067 }
1068
1069 #[napi(getter)]
1071 pub fn hierarchy_json(&self) -> String {
1072 self.hierarchy_json.clone()
1073 }
1074
1075 #[napi(getter)]
1077 pub fn warnings_json(&self) -> String {
1078 self.warnings_json.clone()
1079 }
1080
1081 #[napi(getter)]
1083 pub fn stable_size(&self) -> u32 {
1084 self.stable_size
1085 }
1086
1087 #[napi(getter)]
1089 pub fn total_size(&self) -> u32 {
1090 self.total_size
1091 }
1092
1093 #[napi]
1095 pub fn tick(&mut self, event_id: u32) -> Result<()> {
1096 let runtime_errors = self.runtime_errors.clone();
1097 let b = self
1098 .backend
1099 .as_mut()
1100 .ok_or_else(|| Error::from_reason("Simulator has been disposed"))?;
1101 let event = b.id_to_event_slice()[event_id as usize];
1102 b.eval_comb()
1103 .map_err(|e| napi_runtime_error(&runtime_errors, e))?;
1104 b.eval_apply_ff_at(event)
1105 .map_err(|e| napi_runtime_error(&runtime_errors, e))?;
1106 b.eval_comb()
1107 .map_err(|e| napi_runtime_error(&runtime_errors, e))
1108 }
1109
1110 #[napi]
1112 pub fn tick_n(&mut self, event_id: u32, count: u32) -> Result<()> {
1113 let runtime_errors = self.runtime_errors.clone();
1114 let b = self
1115 .backend
1116 .as_mut()
1117 .ok_or_else(|| Error::from_reason("Simulator has been disposed"))?;
1118 let event = b.id_to_event_slice()[event_id as usize];
1119 for _ in 0..count {
1120 b.eval_comb()
1121 .map_err(|e| napi_runtime_error(&runtime_errors, e))?;
1122 b.eval_apply_ff_at(event)
1123 .map_err(|e| napi_runtime_error(&runtime_errors, e))?;
1124 b.eval_comb()
1125 .map_err(|e| napi_runtime_error(&runtime_errors, e))?;
1126 }
1127 Ok(())
1128 }
1129
1130 #[napi]
1132 pub fn eval_comb(&mut self) -> Result<()> {
1133 let runtime_errors = self.runtime_errors.clone();
1134 let b = self
1135 .backend
1136 .as_mut()
1137 .ok_or_else(|| Error::from_reason("Simulator has been disposed"))?;
1138 b.eval_comb()
1139 .map_err(|e| napi_runtime_error(&runtime_errors, e))
1140 }
1141
1142 #[napi]
1144 pub fn dump(&mut self, timestamp: f64) -> Result<()> {
1145 let b = self
1146 .backend
1147 .as_ref()
1148 .ok_or_else(|| Error::from_reason("Simulator has been disposed"))?;
1149 if let Some(ref mut writer) = self.vcd_writer {
1150 let (ptr, size) = b.memory_as_ptr();
1151 let memory = unsafe { std::slice::from_raw_parts(ptr, size) };
1152 writer
1153 .dump(timestamp as u64, memory)
1154 .map_err(|e| Error::from_reason(format!("VCD write error: {}", e)))?;
1155 }
1156 Ok(())
1157 }
1158
1159 #[napi]
1162 pub fn shared_memory(&mut self) -> Result<Uint8Array> {
1163 let b = self
1164 .backend
1165 .as_mut()
1166 .ok_or_else(|| Error::from_reason("Simulator has been disposed"))?;
1167 let (ptr, _) = b.memory_as_mut_ptr();
1168 let stable_size = b.stable_region_size();
1169 Ok(unsafe { Uint8Array::with_external_data(ptr, stable_size, |_, _| {}) })
1170 }
1171
1172 #[napi]
1174 pub fn dispose(&mut self) {
1175 self.backend = None;
1176 self.vcd_writer = None;
1177 }
1178}
1179
1180#[cfg(not(target_arch = "wasm32"))]
1182#[napi]
1183pub struct NativeSimulationHandle {
1184 sim: Option<celox::Simulation>,
1185 layout_json: String,
1186 events_json: String,
1187 hierarchy_json: String,
1188 warnings_json: String,
1189 stable_size: u32,
1190 total_size: u32,
1191 default_max_steps: Option<u32>,
1194}
1195
1196#[cfg(not(target_arch = "wasm32"))]
1197#[napi]
1198impl NativeSimulationHandle {
1199 #[napi(constructor)]
1201 pub fn new(
1202 sources: Vec<NapiSourceFile>,
1203 top: String,
1204 options: Option<NapiOptions>,
1205 ) -> Result<Self> {
1206 let opts = parse_options(&options)?;
1207 let mut src_pairs: Vec<(String, std::path::PathBuf)> = sources
1208 .into_iter()
1209 .map(|s| (s.content, std::path::PathBuf::from(s.path)))
1210 .collect();
1211 append_extra_source(&mut src_pairs, &opts.extra_source);
1212 let source_refs: Vec<(&str, &std::path::Path)> = src_pairs
1213 .iter()
1214 .map(|(s, p)| (s.as_str(), p.as_path()))
1215 .collect();
1216 let mut builder = apply_options(celox::Simulation::from_sources(source_refs, &top), &opts);
1217 if let Some(path) = &opts.vcd {
1218 builder = builder.vcd(path);
1219 }
1220 let sim = builder
1221 .build()
1222 .map_err(|e| Error::from_reason(format!("{}", e)))?;
1223
1224 let warnings_json = format_warnings_json(sim.warnings());
1225 let signals = sim.named_signals();
1226 let events = sim.named_events();
1227 let hierarchy = sim.named_hierarchy();
1228 let (_, total_size) = sim.memory_as_ptr();
1229 let stable_size = sim.stable_region_size();
1230
1231 let layout_map = build_signal_layout(&signals, opts.four_state);
1232 let event_map = build_event_map(&events);
1233 let hierarchy_node = build_hierarchy_node(&hierarchy, opts.four_state);
1234
1235 let layout_json = serde_json::to_string(&layout_map)
1236 .map_err(|e| Error::from_reason(format!("Failed to serialize layout: {}", e)))?;
1237 let events_json = serde_json::to_string(&event_map)
1238 .map_err(|e| Error::from_reason(format!("Failed to serialize events: {}", e)))?;
1239 let hierarchy_json = serde_json::to_string(&hierarchy_node)
1240 .map_err(|e| Error::from_reason(format!("Failed to serialize hierarchy: {}", e)))?;
1241
1242 Ok(Self {
1243 sim: Some(sim),
1244 layout_json,
1245 events_json,
1246 hierarchy_json,
1247 warnings_json,
1248 stable_size: stable_size as u32,
1249 total_size: total_size as u32,
1250 default_max_steps: None,
1251 })
1252 }
1253
1254 #[napi(factory)]
1256 pub fn from_frontend_artifact(
1257 artifact_json: String,
1258 options: Option<NapiOptions>,
1259 ) -> Result<Self> {
1260 let opts = parse_options(&options)?;
1261 let artifact = celox::FrontendArtifact::from_json(&artifact_json)
1262 .map_err(|error| Error::from_reason(error.to_string()))?;
1263 let mut builder = apply_options(celox::Simulation::from_frontend(artifact), &opts);
1264 if let Some(path) = &opts.vcd {
1265 builder = builder.vcd(path);
1266 }
1267 let sim = builder
1268 .build()
1269 .map_err(|error| Error::from_reason(error.to_string()))?;
1270
1271 let warnings_json = format_warnings_json(sim.warnings());
1272 let signals = sim.named_signals();
1273 let events = sim.named_events();
1274 let hierarchy = sim.named_hierarchy();
1275 let (_, total_size) = sim.memory_as_ptr();
1276 let stable_size = sim.stable_region_size();
1277 let layout_map = build_signal_layout(&signals, opts.four_state);
1278 let event_map = build_event_map(&events);
1279 let hierarchy_node = build_hierarchy_node(&hierarchy, opts.four_state);
1280 let layout_json = serde_json::to_string(&layout_map)
1281 .map_err(|error| Error::from_reason(format!("Failed to serialize layout: {error}")))?;
1282 let events_json = serde_json::to_string(&event_map)
1283 .map_err(|error| Error::from_reason(format!("Failed to serialize events: {error}")))?;
1284 let hierarchy_json = serde_json::to_string(&hierarchy_node).map_err(|error| {
1285 Error::from_reason(format!("Failed to serialize hierarchy: {error}"))
1286 })?;
1287
1288 Ok(Self {
1289 sim: Some(sim),
1290 layout_json,
1291 events_json,
1292 hierarchy_json,
1293 warnings_json,
1294 stable_size: stable_size as u32,
1295 total_size: total_size as u32,
1296 default_max_steps: None,
1297 })
1298 }
1299
1300 #[napi(factory)]
1302 pub fn from_project(
1303 project_path: String,
1304 top: String,
1305 options: Option<NapiOptions>,
1306 ) -> Result<Self> {
1307 let opts = parse_options(&options)?;
1308 let (mut sources, metadata, celox_cfg) = load_project_sources(&project_path)?;
1309 append_extra_source(&mut sources, &opts.extra_source);
1310 let source_refs: Vec<(&str, &std::path::Path)> = sources
1311 .iter()
1312 .map(|(s, p)| (s.as_str(), p.as_path()))
1313 .collect();
1314
1315 let mut builder = apply_options(
1316 celox::Simulation::from_sources(source_refs, &top).with_metadata(metadata),
1317 &opts,
1318 );
1319 if let Some(path) = &opts.vcd {
1320 builder = builder.vcd(path);
1321 }
1322 let sim = builder
1323 .build()
1324 .map_err(|e| Error::from_reason(format!("{}", e)))?;
1325
1326 let warnings_json = format_warnings_json(sim.warnings());
1327 let signals = sim.named_signals();
1328 let events = sim.named_events();
1329 let hierarchy = sim.named_hierarchy();
1330 let (_, total_size) = sim.memory_as_ptr();
1331 let stable_size = sim.stable_region_size();
1332
1333 let layout_map = build_signal_layout(&signals, opts.four_state);
1334 let event_map = build_event_map(&events);
1335 let hierarchy_node = build_hierarchy_node(&hierarchy, opts.four_state);
1336
1337 let layout_json = serde_json::to_string(&layout_map)
1338 .map_err(|e| Error::from_reason(format!("Failed to serialize layout: {}", e)))?;
1339 let events_json = serde_json::to_string(&event_map)
1340 .map_err(|e| Error::from_reason(format!("Failed to serialize events: {}", e)))?;
1341 let hierarchy_json = serde_json::to_string(&hierarchy_node)
1342 .map_err(|e| Error::from_reason(format!("Failed to serialize hierarchy: {}", e)))?;
1343
1344 Ok(Self {
1345 sim: Some(sim),
1346 layout_json,
1347 events_json,
1348 hierarchy_json,
1349 warnings_json,
1350 stable_size: stable_size as u32,
1351 total_size: total_size as u32,
1352 default_max_steps: celox_cfg.simulation.max_steps,
1353 })
1354 }
1355
1356 #[napi(getter)]
1358 pub fn layout_json(&self) -> String {
1359 self.layout_json.clone()
1360 }
1361
1362 #[napi(getter)]
1364 pub fn events_json(&self) -> String {
1365 self.events_json.clone()
1366 }
1367
1368 #[napi(getter)]
1370 pub fn hierarchy_json(&self) -> String {
1371 self.hierarchy_json.clone()
1372 }
1373
1374 #[napi(getter)]
1376 pub fn warnings_json(&self) -> String {
1377 self.warnings_json.clone()
1378 }
1379
1380 #[napi(getter)]
1382 pub fn stable_size(&self) -> u32 {
1383 self.stable_size
1384 }
1385
1386 #[napi(getter)]
1388 pub fn total_size(&self) -> u32 {
1389 self.total_size
1390 }
1391
1392 #[napi(getter)]
1395 pub fn default_max_steps(&self) -> Option<u32> {
1396 self.default_max_steps
1397 }
1398
1399 #[napi]
1401 pub fn add_clock(&mut self, event_id: u32, period: f64, initial_delay: f64) -> Result<()> {
1402 let sim = self
1403 .sim
1404 .as_mut()
1405 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1406 sim.add_clock_by_id(event_id, period as u64, initial_delay as u64);
1407 Ok(())
1408 }
1409
1410 #[napi]
1412 pub fn schedule(&mut self, event_id: u32, time: f64, value: f64) -> Result<()> {
1413 let sim = self
1414 .sim
1415 .as_mut()
1416 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1417 sim.schedule_by_id(event_id, time as u64, value as u64)
1418 .map_err(|e| Error::from_reason(format!("{}", e)))
1419 }
1420
1421 #[napi]
1423 pub fn run_until(&mut self, end_time: f64) -> Result<()> {
1424 let sim = self
1425 .sim
1426 .as_mut()
1427 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1428 sim.run_until(end_time as u64)
1429 .map_err(|e| Error::from_reason(format!("{}", e)))
1430 }
1431
1432 #[napi]
1434 pub fn step(&mut self) -> Result<Option<f64>> {
1435 let sim = self
1436 .sim
1437 .as_mut()
1438 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1439 sim.step()
1440 .map(|opt| opt.map(|t| t as f64))
1441 .map_err(|e| Error::from_reason(format!("{}", e)))
1442 }
1443
1444 #[napi]
1446 pub fn time(&self) -> Result<f64> {
1447 let sim = self
1448 .sim
1449 .as_ref()
1450 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1451 Ok(sim.time() as f64)
1452 }
1453
1454 #[napi]
1456 pub fn next_event_time(&self) -> Result<Option<f64>> {
1457 let sim = self
1458 .sim
1459 .as_ref()
1460 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1461 Ok(sim.next_event_time().map(|t| t as f64))
1462 }
1463
1464 #[napi]
1466 pub fn eval_comb(&mut self) -> Result<()> {
1467 let sim = self
1468 .sim
1469 .as_mut()
1470 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1471 sim.eval_comb()
1472 .map_err(|e| Error::from_reason(format!("{}", e)))
1473 }
1474
1475 #[napi]
1477 pub fn dump(&mut self, timestamp: f64) -> Result<()> {
1478 let sim = self
1479 .sim
1480 .as_mut()
1481 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1482 sim.dump(timestamp as u64);
1483 Ok(())
1484 }
1485
1486 #[napi]
1489 pub fn shared_memory(&mut self) -> Result<Uint8Array> {
1490 let sim = self
1491 .sim
1492 .as_mut()
1493 .ok_or_else(|| Error::from_reason("Simulation has been disposed"))?;
1494 let (ptr, _) = sim.memory_as_mut_ptr();
1495 let stable_size = sim.stable_region_size();
1496 Ok(unsafe { Uint8Array::with_external_data(ptr, stable_size, |_, _| {}) })
1497 }
1498
1499 #[napi]
1501 pub fn dispose(&mut self) {
1502 self.sim = None;
1503 }
1504}
1505
1506#[cfg(target_arch = "wasm32")]
1511#[napi]
1512pub struct NativeSimulatorHandle {
1513 program: celox::LaidOutProgram,
1514 four_state: bool,
1515 layout_json: String,
1516 events_json: String,
1517 hierarchy_json: String,
1518 warnings_json: String,
1519 stable_size: u32,
1520 total_size: u32,
1521}
1522
1523#[cfg(target_arch = "wasm32")]
1524impl NativeSimulatorHandle {
1525 pub fn from_frontend(
1532 artifact: celox::FrontendArtifact,
1533 options: Option<NapiOptions>,
1534 ) -> Result<Self> {
1535 let opts = parse_options_common(&options)?;
1536 let trace_opts = celox::TraceOptions::default();
1537 let (program, warnings) = celox::compile_frontend_to_sir(
1538 &artifact,
1539 &opts.false_loops,
1540 &opts.true_loops,
1541 opts.four_state,
1542 &trace_opts,
1543 None,
1544 &opts.optimize_options,
1545 )
1546 .map_err(|error| Error::from_reason(error.to_string()))?;
1547
1548 let laid_out = program.into_laid_out(opts.four_state);
1549 let layout = laid_out.layout();
1550 let layout_json = Self::build_layout_json(&laid_out, layout, opts.four_state);
1551 let events_json = Self::build_events_json(&laid_out);
1552 let warnings_json = format_warnings_json(&warnings);
1553 let stable_size = layout.total_size as u32;
1554 let total_size = layout.merged_total_size as u32;
1555
1556 Ok(Self {
1557 program: laid_out,
1558 four_state: opts.four_state,
1559 layout_json,
1560 events_json,
1561 hierarchy_json: "{}".to_string(),
1562 warnings_json,
1563 stable_size,
1564 total_size,
1565 })
1566 }
1567}
1568
1569#[cfg(target_arch = "wasm32")]
1570#[napi]
1571impl NativeSimulatorHandle {
1572 #[napi(constructor)]
1579 pub fn new(
1580 sources: Vec<NapiSourceFile>,
1581 top: String,
1582 options: Option<NapiOptions>,
1583 ) -> Result<Self> {
1584 let opts = parse_options_common(&options)?;
1585 let mut src_pairs: Vec<(String, std::path::PathBuf)> = sources
1586 .into_iter()
1587 .map(|s| (s.content, std::path::PathBuf::from(s.path)))
1588 .collect();
1589 append_extra_source(&mut src_pairs, &opts.extra_source);
1590
1591 let source_refs: Vec<(&str, &std::path::Path)> = src_pairs
1592 .iter()
1593 .map(|(s, p)| (s.as_str(), p.as_path()))
1594 .collect();
1595
1596 let trace_opts = celox::TraceOptions::default();
1597 let (program, warnings) = celox::compile_to_sir(
1598 &source_refs,
1599 &top,
1600 &opts
1601 .false_loops
1602 .iter()
1603 .map(|(f, t)| (f.clone(), t.clone()))
1604 .collect::<Vec<_>>(),
1605 &opts
1606 .true_loops
1607 .iter()
1608 .map(|(f, t, m)| (f.clone(), t.clone(), *m))
1609 .collect::<Vec<_>>(),
1610 opts.four_state,
1611 &trace_opts,
1612 None,
1613 None,
1614 opts.clock_type,
1615 opts.reset_type,
1616 &opts.parameters,
1617 &opts.optimize_options,
1618 )
1619 .map_err(|e| Error::from_reason(format!("{}", e)))?;
1620
1621 let laid_out = program.into_laid_out(opts.four_state);
1622 let layout = laid_out.layout();
1623
1624 let layout_json = Self::build_layout_json(&laid_out, layout, opts.four_state);
1625 let events_json = Self::build_events_json(&laid_out);
1626 let hierarchy_json = "{}".to_string(); let warnings_json = format_warnings_json(&warnings);
1628
1629 let stable_size = layout.total_size as u32;
1630 let total_size = layout.merged_total_size as u32;
1631 Ok(Self {
1632 program: laid_out,
1633 four_state: opts.four_state,
1634 layout_json,
1635 events_json,
1636 hierarchy_json,
1637 warnings_json,
1638 stable_size,
1639 total_size,
1640 })
1641 }
1642
1643 #[napi(factory)]
1645 pub fn from_frontend_artifact(
1646 artifact_json: String,
1647 options: Option<NapiOptions>,
1648 ) -> Result<Self> {
1649 let artifact = celox::FrontendArtifact::from_json(&artifact_json)
1650 .map_err(|error| Error::from_reason(error.to_string()))?;
1651 Self::from_frontend(artifact, options)
1652 }
1653
1654 #[napi(factory)]
1656 pub fn from_project(
1657 project_path: String,
1658 top: String,
1659 options: Option<NapiOptions>,
1660 ) -> Result<Self> {
1661 let opts = parse_options_common(&options)?;
1662 let (mut sources, metadata, _celox_cfg) = load_project_sources(&project_path)?;
1663 append_extra_source(&mut sources, &opts.extra_source);
1664
1665 let source_refs: Vec<(&str, &std::path::Path)> = sources
1666 .iter()
1667 .map(|(s, p)| (s.as_str(), p.as_path()))
1668 .collect();
1669
1670 let trace_opts = celox::TraceOptions::default();
1671 let (program, warnings) = celox::compile_to_sir(
1672 &source_refs,
1673 &top,
1674 &opts
1675 .false_loops
1676 .iter()
1677 .map(|(f, t)| (f.clone(), t.clone()))
1678 .collect::<Vec<_>>(),
1679 &opts
1680 .true_loops
1681 .iter()
1682 .map(|(f, t, m)| (f.clone(), t.clone(), *m))
1683 .collect::<Vec<_>>(),
1684 opts.four_state,
1685 &trace_opts,
1686 None,
1687 Some(metadata),
1688 opts.clock_type,
1689 opts.reset_type,
1690 &opts.parameters,
1691 &opts.optimize_options,
1692 )
1693 .map_err(|e| Error::from_reason(format!("{}", e)))?;
1694
1695 let laid_out = program.into_laid_out(opts.four_state);
1696 let layout = laid_out.layout();
1697
1698 let layout_json = Self::build_layout_json(&laid_out, layout, opts.four_state);
1699 let events_json = Self::build_events_json(&laid_out);
1700 let hierarchy_json = "{}".to_string();
1701 let warnings_json = format_warnings_json(&warnings);
1702
1703 let stable_size = layout.total_size as u32;
1704 let total_size = layout.merged_total_size as u32;
1705 Ok(Self {
1706 program: laid_out,
1707 four_state: opts.four_state,
1708 layout_json,
1709 events_json,
1710 hierarchy_json,
1711 warnings_json,
1712 stable_size,
1713 total_size,
1714 })
1715 }
1716
1717 #[napi(getter)]
1719 pub fn layout_json(&self) -> String {
1720 self.layout_json.clone()
1721 }
1722
1723 #[napi(getter)]
1725 pub fn events_json(&self) -> String {
1726 self.events_json.clone()
1727 }
1728
1729 #[napi(getter)]
1731 pub fn four_state_init_regions_json(&self) -> String {
1732 Self::build_four_state_init_regions_json(
1733 &self.program,
1734 self.program.layout(),
1735 self.four_state,
1736 )
1737 }
1738
1739 #[napi]
1741 pub fn initial_memory_bytes(&self) -> Vec<u8> {
1742 Self::build_initial_memory_bytes(&self.program, self.program.layout(), self.four_state)
1743 }
1744
1745 #[napi(getter)]
1747 pub fn hierarchy_json(&self) -> String {
1748 self.hierarchy_json.clone()
1749 }
1750
1751 #[napi(getter)]
1753 pub fn warnings_json(&self) -> String {
1754 self.warnings_json.clone()
1755 }
1756
1757 #[napi(getter)]
1759 pub fn stable_size(&self) -> u32 {
1760 self.stable_size
1761 }
1762
1763 #[napi(getter)]
1765 pub fn total_size(&self) -> u32 {
1766 self.total_size
1767 }
1768
1769 #[napi]
1771 pub fn comb_wasm_bytes(&self) -> Vec<u8> {
1772 let wasm = celox::wasm_codegen::compile_units(
1773 &self.program.sir.eval_comb,
1774 self.program.layout(),
1775 self.four_state,
1776 false,
1777 );
1778 wasm.bytes
1779 }
1780
1781 #[napi]
1785 pub fn event_wasm_bytes(&self, event_name: String) -> Result<Vec<u8>> {
1786 for (addr, units) in &self.program.sir.eval_apply_ffs {
1787 let event_path = self.program.get_path(addr);
1788 if event_path == event_name {
1789 let wasm = celox::wasm_codegen::compile_units(
1790 units,
1791 self.program.layout(),
1792 self.four_state,
1793 false,
1794 );
1795 return Ok(wasm.bytes);
1796 }
1797 }
1798
1799 Err(Error::from_reason(format!(
1800 "Event '{}' not found. Available events: {}",
1801 event_name,
1802 self.program
1803 .sir
1804 .eval_apply_ffs
1805 .keys()
1806 .map(|addr| self.program.get_path(addr))
1807 .collect::<Vec<_>>()
1808 .join(", ")
1809 )))
1810 }
1811
1812 #[napi]
1814 pub fn dispose(&mut self) {}
1815}
1816
1817#[cfg(target_arch = "wasm32")]
1818impl NativeSimulatorHandle {
1819 fn write_memory_bit(memory: &mut [u8], byte: usize, bit: usize, value: bool) {
1820 let mask = 1u8 << bit;
1821 if value {
1822 memory[byte] |= mask;
1823 } else {
1824 memory[byte] &= !mask;
1825 }
1826 }
1827
1828 fn byte_bit(bytes: &[u8], bit: usize) -> bool {
1829 bytes
1830 .get(bit / 8)
1831 .is_some_and(|byte| byte & (1u8 << (bit % 8)) != 0)
1832 }
1833
1834 fn build_initial_memory_bytes(
1835 program: &celox::LaidOutProgram,
1836 layout: &celox::MemoryLayout,
1837 four_state: bool,
1838 ) -> Vec<u8> {
1839 let mut memory = vec![0u8; layout.merged_total_size];
1840
1841 if four_state {
1842 for (address, &offset) in &layout.offsets {
1843 if layout.is_4states.get(address).copied().unwrap_or(false) {
1844 let plane_size = layout.plane_size(address);
1845 memory[offset..offset + plane_size * 2].fill(0xff);
1846 }
1847 }
1848 for (address, &relative_offset) in &layout.working_offsets {
1849 if layout.is_4states.get(address).copied().unwrap_or(false) {
1850 let offset = layout.working_base_offset + relative_offset;
1851 let plane_size = layout.plane_size(address);
1852 memory[offset..offset + plane_size * 2].fill(0xff);
1853 }
1854 }
1855 }
1856
1857 for initial in &program.design.initial_state {
1858 let Some(&offset) = layout.offsets.get(&initial.address) else {
1859 continue;
1860 };
1861 let width = layout.widths[&initial.address];
1862 let plane_size = layout.plane_size(&initial.address);
1863 let write_mask = four_state
1864 && layout
1865 .is_4states
1866 .get(&initial.address)
1867 .copied()
1868 .unwrap_or(false);
1869
1870 match &initial.data {
1871 celox_design::InitialStateData::Packed {
1872 value,
1873 mask,
1874 written_mask,
1875 } => {
1876 let value = value.to_bytes_le();
1877 let mask = mask.to_bytes_le();
1878 let written_mask = written_mask.to_bytes_le();
1879 for bit in 0..width {
1880 if !Self::byte_bit(&written_mask, bit) {
1881 continue;
1882 }
1883 let (byte, intra) = layout.map_static_bit_offset(&initial.address, bit);
1884 let is_unknown = Self::byte_bit(&mask, bit);
1885 Self::write_memory_bit(
1886 &mut memory,
1887 offset + byte,
1888 intra,
1889 Self::byte_bit(&value, bit) && (write_mask || !is_unknown),
1890 );
1891 if write_mask {
1892 Self::write_memory_bit(
1893 &mut memory,
1894 offset + plane_size + byte,
1895 intra,
1896 is_unknown,
1897 );
1898 }
1899 }
1900 }
1901 celox_design::InitialStateData::Writes(runs) => {
1902 for run in runs {
1903 for relative_bit in 0..run.bit_width {
1904 let bit = run.bit_offset + relative_bit;
1905 if bit >= width {
1906 break;
1907 }
1908 let (byte, intra) = layout.map_static_bit_offset(&initial.address, bit);
1909 Self::write_memory_bit(
1910 &mut memory,
1911 offset + byte,
1912 intra,
1913 Self::byte_bit(&run.value_bytes, relative_bit),
1914 );
1915 if write_mask {
1916 Self::write_memory_bit(
1917 &mut memory,
1918 offset + plane_size + byte,
1919 intra,
1920 Self::byte_bit(&run.mask_bytes, relative_bit),
1921 );
1922 }
1923 }
1924 }
1925 }
1926 }
1927 }
1928
1929 memory
1930 }
1931
1932 fn build_four_state_init_regions_json(
1933 program: &celox::LaidOutProgram,
1934 layout: &celox::MemoryLayout,
1935 four_state: bool,
1936 ) -> String {
1937 if !four_state {
1938 return "[]".to_string();
1939 }
1940
1941 let mut regions = Vec::new();
1942 for (addr, &offset) in &layout.offsets {
1943 if program
1944 .design
1945 .state_objects
1946 .get(addr)
1947 .is_some_and(|metadata| metadata.is_4state)
1948 {
1949 regions.push((offset, layout.plane_size(addr)));
1950 }
1951 }
1952 for (addr, &relative_offset) in &layout.working_offsets {
1953 if program
1954 .design
1955 .state_objects
1956 .get(addr)
1957 .is_some_and(|metadata| metadata.is_4state)
1958 {
1959 regions.push((
1960 layout.working_base_offset + relative_offset,
1961 layout.plane_size(addr),
1962 ));
1963 }
1964 }
1965 regions.sort_unstable();
1966
1967 serde_json::to_string(®ions).unwrap_or_else(|_| "[]".to_string())
1968 }
1969
1970 fn build_layout_json(
1973 program: &celox::LaidOutProgram,
1974 layout: &celox::MemoryLayout,
1975 four_state: bool,
1976 ) -> String {
1977 use std::collections::BTreeMap;
1978
1979 let mut layout_map: BTreeMap<String, serde_json::Value> = BTreeMap::new();
1980
1981 for addr in program.design.state_objects.keys() {
1982 let Some(source) = program.frontend.source_address(addr) else {
1983 continue;
1984 };
1985 let module_id = program.frontend.instance_module[&source.instance_id];
1986 let variables = &program.frontend.module_variables[&module_id];
1987 let Some(info) = variables.get(&source.var_id) else {
1988 continue;
1989 };
1990 if program.frontend.module_var_path_index[&module_id].get(&info.path) == Some(&None) {
1991 continue;
1992 }
1993 let Some(&offset) = layout.offsets.get(addr) else {
1994 continue;
1995 };
1996 let name = program.get_path(addr);
1997 let total_width = layout.widths.get(addr).copied().unwrap_or(0);
1998 let (width, array_dims) = if info.array_dims.is_empty() {
1999 (total_width, None)
2000 } else {
2001 let element_count = info.array_dims.iter().product::<usize>();
2002 (total_width / element_count, Some(&info.array_dims))
2003 };
2004 let byte_size = celox::get_byte_size(width);
2005 let mut entry = serde_json::json!({
2006 "offset": offset,
2007 "width": width,
2008 "byte_size": byte_size,
2009 "is_4state": four_state && info.is_4state,
2010 "direction": layout::direction_str(info.var_kind),
2011 "type_kind": layout::type_kind_str(info.type_kind),
2012 });
2013 if let Some(array_dims) = array_dims {
2014 entry["array_dims"] = serde_json::json!(array_dims);
2015 }
2016 layout_map.insert(name, entry);
2017 }
2018
2019 serde_json::to_string(&layout_map).unwrap_or_else(|_| "{}".to_string())
2020 }
2021
2022 fn build_events_json(program: &celox::LaidOutProgram) -> String {
2024 use std::collections::BTreeMap;
2025
2026 let mut events: BTreeMap<String, usize> = BTreeMap::new();
2027
2028 for (next_id, addr) in program.sir.eval_apply_ffs.keys().enumerate() {
2029 let name = program.get_path(addr);
2030 events.insert(name, next_id);
2031 }
2032
2033 serde_json::to_string(&events).unwrap_or_else(|_| "{}".to_string())
2034 }
2035}
2036
2037fn analyzer_error_to_diagnostics(
2040 err: &veryl_analyzer::AnalyzerError,
2041 all_sources: &[(String, String)],
2042 is_error: bool,
2043) -> Vec<celox_ts_gen::JsonDiagnostic> {
2044 use miette::Diagnostic as _;
2045
2046 let severity = if is_error {
2047 celox_ts_gen::DiagnosticSeverity::Error
2048 } else {
2049 celox_ts_gen::DiagnosticSeverity::Warning
2050 };
2051
2052 let message = format!("{err}");
2053 let help = err.help().map(|h| h.to_string());
2054 let url = err.url().map(|u| u.to_string());
2055
2056 let labels: Vec<_> = err.labels().map(|l| l.collect()).unwrap_or_default();
2057
2058 if labels.is_empty() {
2059 return vec![celox_ts_gen::JsonDiagnostic {
2060 severity,
2061 message,
2062 file: String::new(),
2063 line: 1,
2064 column: 1,
2065 end_line: None,
2066 end_column: None,
2067 help,
2068 url,
2069 }];
2070 }
2071
2072 labels
2073 .into_iter()
2074 .map(|label| {
2075 let offset = label.offset();
2076 let len = label.len();
2077
2078 let mut file = String::new();
2083 let mut line = 1usize;
2084 let mut col = 1usize;
2085 let mut end_line = None;
2086 let mut end_col = None;
2087
2088 for (path, content) in all_sources {
2089 let mut cur_line = 1;
2091 let mut cur_col = 1;
2092 let mut found = false;
2093
2094 for (i, ch) in content.char_indices() {
2095 if i == offset {
2096 file = path.clone();
2097 line = cur_line;
2098 col = cur_col;
2099 found = true;
2100 }
2101 if found && i == offset + len {
2102 end_line = Some(cur_line);
2103 end_col = Some(cur_col);
2104 break;
2105 }
2106 if ch == '\n' {
2107 cur_line += 1;
2108 cur_col = 1;
2109 } else {
2110 cur_col += 1;
2111 }
2112 }
2113
2114 if found {
2115 if end_line.is_none() {
2116 end_line = Some(cur_line);
2117 end_col = Some(cur_col);
2118 }
2119 break;
2120 }
2121 }
2122
2123 celox_ts_gen::JsonDiagnostic {
2124 severity: severity.clone(),
2125 message: label.label().unwrap_or(&message).to_string(),
2126 file,
2127 line,
2128 column: col,
2129 end_line,
2130 end_column: end_col,
2131 help: help.clone(),
2132 url: url.clone(),
2133 }
2134 })
2135 .collect()
2136}
2137
2138fn format_errors_with_warnings(
2140 pass_label: &str,
2141 errors: &[&veryl_analyzer::AnalyzerError],
2142 warnings: &[veryl_analyzer::AnalyzerError],
2143) -> String {
2144 let error_msgs: Vec<String> = errors
2145 .iter()
2146 .map(|e| celox::render_diagnostic(*e))
2147 .collect();
2148 let mut msg = format!("Errors in {pass_label}: {}", error_msgs.join("; "));
2149 if !warnings.is_empty() {
2150 let warning_msgs: Vec<String> = warnings
2151 .iter()
2152 .map(|w| celox::render_diagnostic(w))
2153 .collect();
2154 msg.push_str("\n\n--- warnings ---\n\n");
2155 msg.push_str(&warning_msgs.join("\n"));
2156 }
2157 msg
2158}
2159
2160#[cfg(not(target_arch = "wasm32"))]
2164#[napi]
2165pub fn clear_jit_cache() {
2166 let mut cache = JIT_CACHE.lock().unwrap_or_else(|e| e.into_inner());
2167 cache.clear();
2168}
2169
2170#[cfg(target_arch = "wasm32")]
2172#[napi]
2173pub fn clear_jit_cache() {}
2174
2175#[cfg(not(target_arch = "wasm32"))]
2181#[napi(object)]
2182pub struct NapiAssertionResult {
2183 pub passed: bool,
2184 pub message: Option<String>,
2185 pub file: Option<String>,
2186 pub line: Option<u32>,
2187 pub column: Option<u32>,
2188}
2189
2190#[cfg(not(target_arch = "wasm32"))]
2192#[napi(object)]
2193pub struct NapiTestResult {
2194 pub passed: bool,
2195 pub assertions: Vec<NapiAssertionResult>,
2196 pub error: Option<String>,
2197}
2198
2199#[cfg(not(target_arch = "wasm32"))]
2200fn convert_test_result(r: celox::TestResultDetailed) -> NapiTestResult {
2201 NapiTestResult {
2202 passed: r.passed,
2203 error: r.error,
2204 assertions: r
2205 .assertions
2206 .into_iter()
2207 .map(|a| {
2208 let (file, line, column) = match a.location {
2209 Some(loc) => (Some(loc.file), Some(loc.line), Some(loc.column)),
2210 None => (None, None, None),
2211 };
2212 NapiAssertionResult {
2213 passed: a.passed,
2214 message: a.message,
2215 file,
2216 line,
2217 column,
2218 }
2219 })
2220 .collect(),
2221 }
2222}
2223
2224#[cfg(not(target_arch = "wasm32"))]
2225#[napi(object)]
2226pub struct NapiInjectedValue {
2227 pub name: Option<String>,
2228 pub bits: Option<BigInt>,
2229 pub mask_xz: Option<BigInt>,
2230 pub width: Option<u32>,
2231 pub string_value: Option<String>,
2232}
2233
2234#[cfg(not(target_arch = "wasm32"))]
2235#[napi(object)]
2236pub struct NapiInjectedCall {
2237 pub instance: String,
2238 pub phase: String,
2239 pub method: Option<String>,
2240 pub inputs: Vec<NapiInjectedValue>,
2241 pub params: Vec<NapiInjectedValue>,
2242 pub ports: Vec<NapiInjectedPort>,
2243 pub args: Vec<NapiInjectedValue>,
2244 pub cycle: BigInt,
2245 pub time: BigInt,
2246 pub seed: BigInt,
2247 pub fired_clock: Option<String>,
2248 pub four_state: bool,
2249}
2250
2251#[cfg(not(target_arch = "wasm32"))]
2252#[napi(object)]
2253pub struct NapiInjectedPort {
2254 pub name: String,
2255 pub direction: String,
2256 pub role: Option<String>,
2257 pub width: u32,
2258}
2259
2260#[cfg(not(target_arch = "wasm32"))]
2261#[napi(object)]
2262pub struct NapiInjectedResult {
2263 pub outputs: Option<Vec<NapiInjectedValue>>,
2264 pub return_value: Option<NapiInjectedValue>,
2265 pub failures: Option<Vec<String>>,
2266 pub logs: Option<Vec<String>>,
2267 pub finish: Option<bool>,
2268}
2269
2270#[cfg(not(target_arch = "wasm32"))]
2271#[napi(object, object_to_js = false)]
2272pub struct NapiInjectedComponent {
2273 pub name: String,
2274 pub manifest: String,
2275 pub handler: FunctionRef<NapiInjectedCall, NapiInjectedResult>,
2276}
2277
2278#[cfg(not(target_arch = "wasm32"))]
2279struct NapiInjectedHandler {
2280 env: Env,
2281 handler: FunctionRef<NapiInjectedCall, NapiInjectedResult>,
2282}
2283
2284#[cfg(not(target_arch = "wasm32"))]
2288unsafe impl Send for NapiInjectedHandler {}
2289#[cfg(not(target_arch = "wasm32"))]
2290unsafe impl Sync for NapiInjectedHandler {}
2291
2292#[cfg(not(target_arch = "wasm32"))]
2293fn napi_bigint(value: u64) -> BigInt {
2294 BigInt {
2295 sign_bit: false,
2296 words: vec![value],
2297 }
2298}
2299
2300#[cfg(not(target_arch = "wasm32"))]
2301fn to_napi_injected_value(name: Option<String>, value: celox::InjectedValue) -> NapiInjectedValue {
2302 match value {
2303 celox::InjectedValue::Bits {
2304 words,
2305 mask_xz,
2306 width,
2307 } => NapiInjectedValue {
2308 name,
2309 bits: Some(BigInt {
2310 sign_bit: false,
2311 words,
2312 }),
2313 mask_xz: Some(BigInt {
2314 sign_bit: false,
2315 words: mask_xz,
2316 }),
2317 width: Some(width),
2318 string_value: None,
2319 },
2320 celox::InjectedValue::String(value) => NapiInjectedValue {
2321 name,
2322 bits: None,
2323 mask_xz: None,
2324 width: None,
2325 string_value: Some(value),
2326 },
2327 celox::InjectedValue::Unit => NapiInjectedValue {
2328 name,
2329 bits: None,
2330 mask_xz: None,
2331 width: None,
2332 string_value: None,
2333 },
2334 }
2335}
2336
2337#[cfg(not(target_arch = "wasm32"))]
2338fn from_napi_injected_value(
2339 value: NapiInjectedValue,
2340) -> std::result::Result<celox::InjectedValue, String> {
2341 if let Some(bits) = value.bits {
2342 if bits.sign_bit || value.mask_xz.as_ref().is_some_and(|mask| mask.sign_bit) {
2343 return Err("component callback values cannot be negative".into());
2344 }
2345 let width = value
2346 .width
2347 .ok_or_else(|| "component callback bit value has no width".to_string())?;
2348 return Ok(celox::InjectedValue::Bits {
2349 words: bits.words,
2350 mask_xz: value.mask_xz.map(|mask| mask.words).unwrap_or_default(),
2351 width,
2352 });
2353 }
2354 Ok(match value.string_value {
2355 Some(value) => celox::InjectedValue::String(value),
2356 None => celox::InjectedValue::Unit,
2357 })
2358}
2359
2360#[cfg(not(target_arch = "wasm32"))]
2361impl celox::InjectedComponentHandler for NapiInjectedHandler {
2362 fn call(
2363 &self,
2364 call: celox::InjectedCall,
2365 ) -> std::result::Result<celox::InjectedResult, String> {
2366 let (phase, method, args) = match call.hook {
2367 celox::InjectedHook::Create => ("create", None, Vec::new()),
2368 celox::InjectedHook::Init => ("init", None, Vec::new()),
2369 celox::InjectedHook::Reset => ("reset", None, Vec::new()),
2370 celox::InjectedHook::Clock => ("clock", None, Vec::new()),
2371 celox::InjectedHook::Finish => ("finish", None, Vec::new()),
2372 celox::InjectedHook::Method { name, args } => ("method", Some(name), args),
2373 };
2374 let request = NapiInjectedCall {
2375 instance: call.instance,
2376 phase: phase.into(),
2377 method,
2378 inputs: call
2379 .inputs
2380 .into_iter()
2381 .map(|value| to_napi_injected_value(Some(value.name), value.value))
2382 .collect(),
2383 params: call
2384 .params
2385 .into_iter()
2386 .map(|value| to_napi_injected_value(Some(value.name), value.value))
2387 .collect(),
2388 ports: call
2389 .ports
2390 .into_iter()
2391 .map(|port| NapiInjectedPort {
2392 name: port.name,
2393 direction: port.direction,
2394 role: port.role,
2395 width: port.width,
2396 })
2397 .collect(),
2398 args: args
2399 .into_iter()
2400 .map(|value| to_napi_injected_value(None, value))
2401 .collect(),
2402 cycle: napi_bigint(call.cycle),
2403 time: napi_bigint(call.time),
2404 seed: napi_bigint(call.seed),
2405 fired_clock: call.fired_clock,
2406 four_state: call.four_state,
2407 };
2408 let result = self
2409 .handler
2410 .borrow_back(&self.env)
2411 .and_then(|handler| handler.call(request))
2412 .map_err(|error| error.to_string())?;
2413 let outputs = result
2414 .outputs
2415 .unwrap_or_default()
2416 .into_iter()
2417 .map(|value| {
2418 let name = value
2419 .name
2420 .clone()
2421 .ok_or_else(|| "component callback output has no name".to_string())?;
2422 Ok(celox::InjectedNamedValue {
2423 name,
2424 value: from_napi_injected_value(value)?,
2425 })
2426 })
2427 .collect::<std::result::Result<Vec<_>, String>>()?;
2428 Ok(celox::InjectedResult {
2429 outputs,
2430 return_value: result
2431 .return_value
2432 .map(from_napi_injected_value)
2433 .transpose()?,
2434 failures: result.failures.unwrap_or_default(),
2435 logs: result.logs.unwrap_or_default(),
2436 finish: result.finish.unwrap_or(false),
2437 })
2438 }
2439}
2440
2441#[cfg(not(target_arch = "wasm32"))]
2442fn injected_components(
2443 env: Env,
2444 definitions: Option<Vec<NapiInjectedComponent>>,
2445) -> Result<celox::InjectedComponents> {
2446 let mut components = celox::InjectedComponents::new();
2447 for definition in definitions.unwrap_or_default() {
2448 components
2449 .insert(
2450 definition.name,
2451 &definition.manifest,
2452 Arc::new(NapiInjectedHandler {
2453 env,
2454 handler: definition.handler,
2455 }),
2456 )
2457 .map_err(Error::from_reason)?;
2458 }
2459 Ok(components)
2460}
2461
2462#[cfg(not(target_arch = "wasm32"))]
2468#[napi]
2469pub fn run_test(
2470 env: Env,
2471 sources: Vec<NapiSourceFile>,
2472 top: String,
2473 options: Option<NapiOptions>,
2474 components: Option<Vec<NapiInjectedComponent>>,
2475) -> Result<NapiTestResult> {
2476 let opts = parse_options(&options)?;
2477 let mut src_pairs: Vec<(String, std::path::PathBuf)> = sources
2478 .into_iter()
2479 .map(|s| (s.content, std::path::PathBuf::from(s.path)))
2480 .collect();
2481 append_extra_source(&mut src_pairs, &opts.extra_source);
2482
2483 let source_refs: Vec<(&str, &std::path::Path)> = src_pairs
2484 .iter()
2485 .map(|(s, p)| (s.as_str(), p.as_path()))
2486 .collect();
2487 let builder = apply_options(celox::Simulator::from_sources(source_refs, &top), &opts)
2488 .with_injected_components(injected_components(env, components)?);
2489 let result = builder
2490 .run_test_detailed()
2491 .map_err(|e| Error::from_reason(format!("{e}")))?;
2492 Ok(convert_test_result(result))
2493}
2494
2495#[cfg(not(target_arch = "wasm32"))]
2497#[napi]
2498pub fn run_test_with_frontend_artifact(
2499 env: Env,
2500 artifact_json: String,
2501 sources: Vec<NapiSourceFile>,
2502 top: String,
2503 options: Option<NapiOptions>,
2504 components: Option<Vec<NapiInjectedComponent>>,
2505) -> Result<NapiTestResult> {
2506 let opts = parse_options(&options)?;
2507 let artifact = celox::FrontendArtifact::from_json(&artifact_json)
2508 .map_err(|error| Error::from_reason(error.to_string()))?;
2509 let mut src_pairs: Vec<(String, std::path::PathBuf)> = sources
2510 .into_iter()
2511 .map(|source| (source.content, std::path::PathBuf::from(source.path)))
2512 .collect();
2513 append_extra_source(&mut src_pairs, &opts.extra_source);
2514 let source_refs: Vec<(&str, &std::path::Path)> = src_pairs
2515 .iter()
2516 .map(|(source, path)| (source.as_str(), path.as_path()))
2517 .collect();
2518 let builder = apply_options(
2519 celox::Simulator::from_frontend_with_testbench(artifact, source_refs, &top),
2520 &opts,
2521 )
2522 .with_injected_components(injected_components(env, components)?);
2523 let result = builder
2524 .run_test_detailed()
2525 .map_err(|error| Error::from_reason(error.to_string()))?;
2526 Ok(convert_test_result(result))
2527}
2528
2529#[cfg(not(target_arch = "wasm32"))]
2534#[napi]
2535pub fn run_test_from_project(
2536 env: Env,
2537 project_path: String,
2538 top: String,
2539 options: Option<NapiOptions>,
2540 components: Option<Vec<NapiInjectedComponent>>,
2541) -> Result<NapiTestResult> {
2542 let opts = parse_options(&options)?;
2543 let (mut sources, metadata, _celox_cfg) = load_project_sources(&project_path)?;
2544 append_extra_source(&mut sources, &opts.extra_source);
2545
2546 let source_refs: Vec<(&str, &std::path::Path)> = sources
2547 .iter()
2548 .map(|(s, p)| (s.as_str(), p.as_path()))
2549 .collect();
2550 let builder = apply_options(
2551 celox::Simulator::from_sources(source_refs, &top).with_metadata(metadata),
2552 &opts,
2553 )
2554 .with_injected_components(injected_components(env, components)?);
2555 let result = builder
2556 .run_test_detailed()
2557 .map_err(|e| Error::from_reason(format!("{e}")))?;
2558 Ok(convert_test_result(result))
2559}
2560
2561#[napi(object)]
2566pub struct NapiInjectedManifest {
2567 pub name: String,
2568 pub manifest: String,
2569}
2570
2571fn inject_analyzer_components(definitions: Option<Vec<NapiInjectedManifest>>) -> Result<()> {
2572 let definitions = definitions.unwrap_or_default();
2573 let names: Vec<_> = definitions
2574 .iter()
2575 .map(|definition| definition.name.as_str())
2576 .collect();
2577 veryl_analyzer::tb_component::insert_external_components(&names);
2578 for definition in definitions {
2579 let manifest =
2580 veryl_metadata::ComponentManifest::parse(&definition.manifest).ok_or_else(|| {
2581 Error::from_reason(format!(
2582 "manifest of injected component `{}` cannot be parsed",
2583 definition.name
2584 ))
2585 })?;
2586 veryl_analyzer::component_manifest_table::insert(
2587 veryl_parser::resource_table::insert_str(&definition.name),
2588 manifest,
2589 );
2590 }
2591 Ok(())
2592}
2593
2594#[napi]
2598pub fn gen_ts(
2599 project_path: String,
2600 components: Option<Vec<NapiInjectedManifest>>,
2601) -> Result<String> {
2602 use celox_ts_gen::{JsonModuleEntry, JsonOutput, generate_all};
2603
2604 let toml_path = Metadata::search_from(&project_path)
2605 .map_err(|e| Error::from_reason(format!("Could not find Veryl.toml: {e}")))?;
2606 let mut metadata = Metadata::load(&toml_path)
2607 .map_err(|e| Error::from_reason(format!("Failed to load Veryl.toml: {e}")))?;
2608
2609 let base_path = toml_path
2610 .parent()
2611 .unwrap_or(&toml_path)
2612 .to_string_lossy()
2613 .to_string();
2614
2615 let mut paths = metadata
2616 .paths::<std::path::PathBuf>(&[], true, true)
2617 .map_err(|e| Error::from_reason(format!("Failed to gather sources: {e}")))?;
2618 paths.retain(|path| !path.example);
2619
2620 let project_root = toml_path.parent().unwrap_or(&toml_path).to_path_buf();
2622 let celox_cfg = load_celox_config(&project_root)?;
2623 let prj_name = metadata.project.name.clone();
2624 for dir in &celox_cfg.test.sources {
2625 let dir_path = project_root.join(dir);
2626 if !dir_path.exists() {
2627 continue;
2628 }
2629 for src in walkdir(&dir_path)? {
2630 paths.push(PathSet {
2631 prj: prj_name.clone(),
2632 src: src.clone(),
2633 dst: src.with_extension("sv"),
2634 map: src.with_extension("map"),
2635 example: false,
2636 });
2637 }
2638 }
2639
2640 if let Some(exclude_set) = build_exclude_set(&celox_cfg)? {
2641 paths.retain(|p| !is_excluded(&p.src, &project_root, &exclude_set));
2642 }
2643
2644 if paths.is_empty() {
2645 return Err(Error::from_reason("No Veryl source files found"));
2646 }
2647
2648 symbol_table::clear();
2650 attribute_table::clear();
2651
2652 let analyzer = Analyzer::new(&metadata);
2653 inject_analyzer_components(components)?;
2654 let mut parsers = Vec::new();
2655 let mut all_warnings = Vec::new();
2656
2657 for path in &paths {
2658 let input = std::fs::read_to_string(&path.src)
2659 .map_err(|e| Error::from_reason(format!("{}: {e}", path.src.display())))?;
2660 let parser = Parser::parse(&input, &path.src)
2661 .map_err(|e| Error::from_reason(format!("Parse error: {e}")))?;
2662
2663 let results = analyzer.analyze_pass1(&path.prj, &parser.veryl);
2664 let real_errors: Vec<_> = results.iter().filter(|e| e.is_error()).collect();
2665 if !real_errors.is_empty() {
2666 return Err(Error::from_reason(format_errors_with_warnings(
2667 "analysis pass 1",
2668 &real_errors,
2669 &all_warnings,
2670 )));
2671 }
2672 all_warnings.extend(results.into_iter().filter(|e| !e.is_error()));
2673
2674 parsers.push((path.clone(), parser));
2675 }
2676
2677 let results = Analyzer::analyze_post_pass1();
2678 let real_errors: Vec<_> = results.iter().filter(|e| e.is_error()).collect();
2679 if !real_errors.is_empty() {
2680 return Err(Error::from_reason(format_errors_with_warnings(
2681 "post-pass 1 analysis",
2682 &real_errors,
2683 &all_warnings,
2684 )));
2685 }
2686 all_warnings.extend(results.into_iter().filter(|e| !e.is_error()));
2687
2688 let base_normalized = base_path.replace('\\', "/");
2692 let all_source_files: Vec<String> = parsers
2693 .iter()
2694 .map(|(path, _)| {
2695 let src_normalized = path
2696 .src
2697 .to_string_lossy()
2698 .replace(r"\\?\", "")
2699 .replace('\\', "/");
2700 src_normalized
2701 .strip_prefix(&base_normalized)
2702 .unwrap_or(&src_normalized)
2703 .trim_start_matches('/')
2704 .to_string()
2705 })
2706 .collect();
2707 let source_file_refs: Vec<&str> = all_source_files.iter().map(|s| s.as_str()).collect();
2708
2709 let mut all_modules = Vec::new();
2710 let mut file_modules: HashMap<String, Vec<String>> = HashMap::default();
2711 let mut post_pass_ir = Ir::default();
2712
2713 for (i, (_path, parser)) in parsers.iter().enumerate() {
2714 let mut analyzer_context = Context::default();
2715 let mut ir = Ir::default();
2716 let results = analyzer.analyze_pass2(&parser.veryl, &mut analyzer_context, Some(&mut ir));
2717 let real_errors: Vec<_> = results.iter().filter(|e| e.is_error()).collect();
2718 if !real_errors.is_empty() {
2719 return Err(Error::from_reason(format_errors_with_warnings(
2720 "analysis pass 2",
2721 &real_errors,
2722 &all_warnings,
2723 )));
2724 }
2725 all_warnings.extend(results.into_iter().filter(|e| !e.is_error()));
2726
2727 let modules = generate_all(&ir, &source_file_refs);
2728 post_pass_ir.append(&mut ir);
2729 let source_file = all_source_files[i].clone();
2730
2731 let module_names: Vec<String> = modules.iter().map(|m| m.module_name.clone()).collect();
2732 if !module_names.is_empty() {
2733 file_modules.insert(source_file.clone(), module_names);
2734 }
2735
2736 for m in modules {
2737 all_modules.push(JsonModuleEntry {
2738 module_name: m.module_name,
2739 source_file: source_file.clone(),
2740 dts_content: m.dts_content,
2741 md_content: m.md_content,
2742 ports: m.ports,
2743 events: m.events,
2744 instances: m.instances,
2745 is_test: m.is_test,
2746 });
2747 }
2748 }
2749
2750 let results = Analyzer::analyze_post_pass2(&post_pass_ir);
2751 let real_errors: Vec<_> = results.iter().filter(|e| e.is_error()).collect();
2752 if !real_errors.is_empty() {
2753 return Err(Error::from_reason(format_errors_with_warnings(
2754 "post-pass 2 analysis",
2755 &real_errors,
2756 &all_warnings,
2757 )));
2758 }
2759 all_warnings.extend(results.into_iter().filter(|e| !e.is_error()));
2760
2761 all_modules.sort_by(|a, b| a.module_name.cmp(&b.module_name));
2763
2764 let warning_msgs: Vec<String> = all_warnings
2765 .iter()
2766 .map(|w| celox::render_diagnostic(w))
2767 .collect();
2768
2769 let all_sources: Vec<(String, String)> = parsers
2770 .iter()
2771 .map(|(p, _)| {
2772 let path_str = p.src.to_string_lossy().to_string();
2773 let content = std::fs::read_to_string(&p.src).unwrap_or_default();
2774 (path_str, content)
2775 })
2776 .collect();
2777
2778 let diagnostics: Vec<celox_ts_gen::JsonDiagnostic> = all_warnings
2779 .iter()
2780 .flat_map(|w| analyzer_error_to_diagnostics(w, &all_sources, false))
2781 .collect();
2782
2783 let output = JsonOutput {
2784 project_path: base_path,
2785 modules: all_modules,
2786 file_modules,
2787 warnings: warning_msgs,
2788 diagnostics,
2789 };
2790
2791 serde_json::to_string(&output)
2792 .map_err(|e| Error::from_reason(format!("Failed to serialize JSON: {e}")))
2793}
2794
2795#[napi]
2800pub fn gen_ts_from_source(
2801 sources: Vec<NapiSourceFile>,
2802 components: Option<Vec<NapiInjectedManifest>>,
2803) -> Result<String> {
2804 use celox_ts_gen::{JsonModuleEntry, JsonOutput, generate_all};
2805
2806 if sources.is_empty() {
2807 return Err(Error::from_reason("No source files provided"));
2808 }
2809
2810 let metadata = Metadata::create_default("playground")
2811 .map_err(|e| Error::from_reason(format!("Failed to create default metadata: {e}")))?;
2812
2813 symbol_table::clear();
2815 attribute_table::clear();
2816
2817 let analyzer = Analyzer::new(&metadata);
2818 inject_analyzer_components(components)?;
2819 let mut parsers = Vec::new();
2820 let mut all_warnings = Vec::new();
2821
2822 for src in &sources {
2823 let path = std::path::PathBuf::from(&src.path);
2824 let parser = Parser::parse(&src.content, &path)
2825 .map_err(|e| Error::from_reason(format!("Parse error: {e}")))?;
2826
2827 let prj = "playground".to_string();
2828 let results = analyzer.analyze_pass1(&prj, &parser.veryl);
2829 let real_errors: Vec<_> = results.iter().filter(|e| e.is_error()).collect();
2830 if !real_errors.is_empty() {
2831 return Err(Error::from_reason(format_errors_with_warnings(
2832 "analysis pass 1",
2833 &real_errors,
2834 &all_warnings,
2835 )));
2836 }
2837 all_warnings.extend(results.into_iter().filter(|e| !e.is_error()));
2838
2839 parsers.push((prj, path, parser));
2840 }
2841
2842 let results = Analyzer::analyze_post_pass1();
2843 let real_errors: Vec<_> = results.iter().filter(|e| e.is_error()).collect();
2844 if !real_errors.is_empty() {
2845 return Err(Error::from_reason(format_errors_with_warnings(
2846 "post-pass 1 analysis",
2847 &real_errors,
2848 &all_warnings,
2849 )));
2850 }
2851 all_warnings.extend(results.into_iter().filter(|e| !e.is_error()));
2852
2853 let all_source_files: Vec<String> = parsers
2855 .iter()
2856 .map(|(_, p, _)| p.to_string_lossy().replace('\\', "/"))
2857 .collect();
2858 let source_file_refs: Vec<&str> = all_source_files.iter().map(|s| s.as_str()).collect();
2859
2860 let mut all_modules = Vec::new();
2861 let mut file_modules: HashMap<String, Vec<String>> = HashMap::default();
2862 let mut post_pass_ir = Ir::default();
2863
2864 for (i, (_prj, _path, parser)) in parsers.iter().enumerate() {
2865 let mut analyzer_context = Context::default();
2866 let mut ir = Ir::default();
2867 let results = analyzer.analyze_pass2(&parser.veryl, &mut analyzer_context, Some(&mut ir));
2868 let real_errors: Vec<_> = results.iter().filter(|e| e.is_error()).collect();
2869 if !real_errors.is_empty() {
2870 return Err(Error::from_reason(format_errors_with_warnings(
2871 "analysis pass 2",
2872 &real_errors,
2873 &all_warnings,
2874 )));
2875 }
2876 all_warnings.extend(results.into_iter().filter(|e| !e.is_error()));
2877
2878 let modules = generate_all(&ir, &source_file_refs);
2879 post_pass_ir.append(&mut ir);
2880 let source_file = all_source_files[i].clone();
2881
2882 let module_names: Vec<String> = modules.iter().map(|m| m.module_name.clone()).collect();
2883 if !module_names.is_empty() {
2884 file_modules.insert(source_file.clone(), module_names);
2885 }
2886
2887 for m in modules {
2888 all_modules.push(JsonModuleEntry {
2889 module_name: m.module_name,
2890 source_file: source_file.clone(),
2891 dts_content: m.dts_content,
2892 md_content: m.md_content,
2893 ports: m.ports,
2894 events: m.events,
2895 instances: m.instances,
2896 is_test: m.is_test,
2897 });
2898 }
2899 }
2900
2901 let results = Analyzer::analyze_post_pass2(&post_pass_ir);
2902 let real_errors: Vec<_> = results.iter().filter(|e| e.is_error()).collect();
2903 if !real_errors.is_empty() {
2904 return Err(Error::from_reason(format_errors_with_warnings(
2905 "post-pass 2 analysis",
2906 &real_errors,
2907 &all_warnings,
2908 )));
2909 }
2910 all_warnings.extend(results.into_iter().filter(|e| !e.is_error()));
2911
2912 all_modules.sort_by(|a, b| a.module_name.cmp(&b.module_name));
2914
2915 let warning_msgs: Vec<String> = all_warnings
2916 .iter()
2917 .map(|w| celox::render_diagnostic(w))
2918 .collect();
2919
2920 let all_sources: Vec<(String, String)> = sources
2921 .iter()
2922 .map(|s| (s.path.clone(), s.content.clone()))
2923 .collect();
2924
2925 let diagnostics: Vec<celox_ts_gen::JsonDiagnostic> = all_warnings
2926 .iter()
2927 .flat_map(|w| analyzer_error_to_diagnostics(w, &all_sources, false))
2928 .collect();
2929
2930 let output = JsonOutput {
2931 project_path: String::new(),
2932 modules: all_modules,
2933 file_modules,
2934 warnings: warning_msgs,
2935 diagnostics,
2936 };
2937
2938 serde_json::to_string(&output)
2939 .map_err(|e| Error::from_reason(format!("Failed to serialize JSON: {e}")))
2940}
2941
2942#[cfg(all(test, not(target_arch = "wasm32")))]
2943mod tests {
2944 use super::*;
2945
2946 fn default_opts() -> ParsedOptions {
2947 ParsedOptions {
2948 common: ParsedOptionsCommon {
2949 four_state: false,
2950 optimize_options: celox::OptimizeOptions::all(),
2951 vcd: None,
2952 false_loops: vec![],
2953 true_loops: vec![],
2954 clock_type: None,
2955 reset_type: None,
2956 extra_source: None,
2957 parameters: vec![],
2958 },
2959 cranelift_options: celox::CraneliftOptions::default(),
2960 dead_store_policy: celox::DeadStorePolicy::Off,
2961 }
2962 }
2963
2964 fn make_sources(pairs: &[(&str, &str)]) -> Vec<(String, std::path::PathBuf)> {
2965 pairs
2966 .iter()
2967 .map(|(content, path)| (content.to_string(), std::path::PathBuf::from(path)))
2968 .collect()
2969 }
2970
2971 #[test]
2972 fn simulator_handle_derives_four_state_metadata_from_layout() {
2973 let source = "module Top (a: input logic<1>) {}";
2974 let path = std::path::Path::new("top.veryl");
2975
2976 for four_state in [false, true] {
2977 let simulator = celox::Simulator::from_sources(vec![(source, path)], "Top")
2978 .four_state(four_state)
2979 .build()
2980 .unwrap();
2981 let handle = NativeSimulatorHandle::from_simulator(simulator, None).unwrap();
2982 let layout: serde_json::Value = serde_json::from_str(&handle.layout_json()).unwrap();
2983
2984 assert_eq!(layout["a"]["is_4state"].as_bool(), Some(four_state));
2985 }
2986 }
2987
2988 #[test]
2989 fn normal_project_loading_excludes_example_sources() {
2990 let project = tempfile::tempdir().unwrap();
2991 std::fs::create_dir(project.path().join("src")).unwrap();
2992 std::fs::create_dir(project.path().join("examples")).unwrap();
2993 std::fs::write(
2994 project.path().join("Veryl.toml"),
2995 "[project]\nname = \"example_filter\"\nversion = \"0.1.0\"\n",
2996 )
2997 .unwrap();
2998 std::fs::write(project.path().join("src/top.veryl"), "module Top () {}\n").unwrap();
2999 std::fs::write(
3000 project.path().join("examples/demo.veryl"),
3001 "module Demo () {}\n",
3002 )
3003 .unwrap();
3004
3005 let (sources, _, _) = load_project_sources(project.path().to_str().unwrap()).unwrap();
3006 let source_names = sources
3007 .iter()
3008 .map(|(_, path)| path.file_name().unwrap().to_str().unwrap())
3009 .collect::<Vec<_>>();
3010 assert_eq!(source_names, ["top.veryl"]);
3011 }
3012
3013 #[test]
3014 fn same_inputs_produce_same_key() {
3015 let src = make_sources(&[("module Top {}", "a.veryl")]);
3016 let opts = default_opts();
3017 let k1 = build_cache_key(&src, "Top", &opts, None);
3018 let k2 = build_cache_key(&src, "Top", &opts, None);
3019 assert_eq!(k1, k2);
3020 }
3021
3022 #[test]
3023 fn high_index_sir_pass_changes_cache_key_without_bit_packing() {
3024 let src = make_sources(&[("module Top {}", "a.veryl")]);
3025 let enabled = default_opts();
3026 let mut disabled = default_opts();
3027 disabled.common.optimize_options = disabled
3028 .common
3029 .optimize_options
3030 .clone()
3031 .disable(celox::SirPass::IdentityStoreBypass);
3032
3033 assert_ne!(
3034 build_cache_key(&src, "Top", &enabled, None),
3035 build_cache_key(&src, "Top", &disabled, None)
3036 );
3037 }
3038
3039 #[test]
3040 fn native_memory_width_changes_cache_key() {
3041 let src = make_sources(&[("module Top {}", "a.veryl")]);
3042 let mut narrow = default_opts();
3043 narrow.common.optimize_options = narrow
3044 .common
3045 .optimize_options
3046 .clone()
3047 .with_max_native_memory_width(64);
3048 let mut wide = default_opts();
3049 wide.common.optimize_options = wide
3050 .common
3051 .optimize_options
3052 .clone()
3053 .with_max_native_memory_width(128);
3054
3055 assert_ne!(
3056 build_cache_key(&src, "Top", &narrow, None),
3057 build_cache_key(&src, "Top", &wide, None)
3058 );
3059 }
3060
3061 #[test]
3062 fn different_source_content_different_key() {
3063 let s1 = make_sources(&[("module A {}", "a.veryl")]);
3064 let s2 = make_sources(&[("module B {}", "a.veryl")]);
3065 let opts = default_opts();
3066 assert_ne!(
3067 build_cache_key(&s1, "Top", &opts, None),
3068 build_cache_key(&s2, "Top", &opts, None),
3069 );
3070 }
3071
3072 #[test]
3073 fn different_source_path_different_key() {
3074 let s1 = make_sources(&[("module A {}", "a.veryl")]);
3075 let s2 = make_sources(&[("module A {}", "b.veryl")]);
3076 let opts = default_opts();
3077 assert_ne!(
3078 build_cache_key(&s1, "Top", &opts, None),
3079 build_cache_key(&s2, "Top", &opts, None),
3080 );
3081 }
3082
3083 #[test]
3084 fn different_top_different_key() {
3085 let src = make_sources(&[("module Top {}", "a.veryl")]);
3086 let opts = default_opts();
3087 assert_ne!(
3088 build_cache_key(&src, "Top", &opts, None),
3089 build_cache_key(&src, "Other", &opts, None),
3090 );
3091 }
3092
3093 #[test]
3094 fn four_state_differs() {
3095 let src = make_sources(&[("module Top {}", "a.veryl")]);
3096 let mut o1 = default_opts();
3097 let mut o2 = default_opts();
3098 o1.common.four_state = false;
3099 o2.common.four_state = true;
3100 assert_ne!(
3101 build_cache_key(&src, "Top", &o1, None),
3102 build_cache_key(&src, "Top", &o2, None),
3103 );
3104 }
3105
3106 #[test]
3107 fn optimize_options_differs() {
3108 let src = make_sources(&[("module Top {}", "a.veryl")]);
3109 let mut o1 = default_opts();
3110 let mut o2 = default_opts();
3111 o1.common.optimize_options = celox::OptimizeOptions::all();
3112 o2.common.optimize_options = celox::OptimizeOptions::none();
3113 assert_ne!(
3114 build_cache_key(&src, "Top", &o1, None),
3115 build_cache_key(&src, "Top", &o2, None),
3116 );
3117 }
3118
3119 #[test]
3120 fn cranelift_opt_level_differs() {
3121 let src = make_sources(&[("module Top {}", "a.veryl")]);
3122 let mut o1 = default_opts();
3123 let mut o2 = default_opts();
3124 o1.cranelift_options.opt_level = celox::CraneliftOptLevel::Speed;
3125 o2.cranelift_options.opt_level = celox::CraneliftOptLevel::None;
3126 assert_ne!(
3127 build_cache_key(&src, "Top", &o1, None),
3128 build_cache_key(&src, "Top", &o2, None),
3129 );
3130 }
3131
3132 #[test]
3133 fn regalloc_algorithm_differs() {
3134 let src = make_sources(&[("module Top {}", "a.veryl")]);
3135 let mut o1 = default_opts();
3136 let mut o2 = default_opts();
3137 o1.cranelift_options.regalloc_algorithm = celox::RegallocAlgorithm::Backtracking;
3138 o2.cranelift_options.regalloc_algorithm = celox::RegallocAlgorithm::SinglePass;
3139 assert_ne!(
3140 build_cache_key(&src, "Top", &o1, None),
3141 build_cache_key(&src, "Top", &o2, None),
3142 );
3143 }
3144
3145 #[test]
3146 fn dead_store_policy_differs() {
3147 let src = make_sources(&[("module Top {}", "a.veryl")]);
3148 let mut o1 = default_opts();
3149 let mut o2 = default_opts();
3150 o1.dead_store_policy = celox::DeadStorePolicy::Off;
3151 o2.dead_store_policy = celox::DeadStorePolicy::PreserveTopPorts;
3152 assert_ne!(
3153 build_cache_key(&src, "Top", &o1, None),
3154 build_cache_key(&src, "Top", &o2, None),
3155 );
3156 }
3157
3158 #[test]
3159 fn clock_type_differs() {
3160 let src = make_sources(&[("module Top {}", "a.veryl")]);
3161 let mut o1 = default_opts();
3162 let mut o2 = default_opts();
3163 o1.common.clock_type = None;
3164 o2.common.clock_type = Some(celox::ClockType::NegEdge);
3165 assert_ne!(
3166 build_cache_key(&src, "Top", &o1, None),
3167 build_cache_key(&src, "Top", &o2, None),
3168 );
3169 }
3170
3171 #[test]
3172 fn reset_type_differs() {
3173 let src = make_sources(&[("module Top {}", "a.veryl")]);
3174 let mut o1 = default_opts();
3175 let mut o2 = default_opts();
3176 o1.common.reset_type = None;
3177 o2.common.reset_type = Some(celox::ResetType::SyncHigh);
3178 assert_ne!(
3179 build_cache_key(&src, "Top", &o1, None),
3180 build_cache_key(&src, "Top", &o2, None),
3181 );
3182 }
3183
3184 #[test]
3185 fn parameters_differ() {
3186 let src = make_sources(&[("module Top {}", "a.veryl")]);
3187 let mut o1 = default_opts();
3188 let mut o2 = default_opts();
3189 o1.common.parameters = vec![("WIDTH".into(), 8)];
3190 o2.common.parameters = vec![("WIDTH".into(), 16)];
3191 assert_ne!(
3192 build_cache_key(&src, "Top", &o1, None),
3193 build_cache_key(&src, "Top", &o2, None),
3194 );
3195 }
3196
3197 #[test]
3198 fn source_order_independent() {
3199 let s1 = make_sources(&[("aaa", "a.veryl"), ("bbb", "b.veryl")]);
3200 let s2 = make_sources(&[("bbb", "b.veryl"), ("aaa", "a.veryl")]);
3201 let opts = default_opts();
3202 assert_eq!(
3203 build_cache_key(&s1, "Top", &opts, None),
3204 build_cache_key(&s2, "Top", &opts, None),
3205 );
3206 }
3207
3208 #[test]
3209 fn non_compilation_options_ignored() {
3210 let src = make_sources(&[("module Top {}", "a.veryl")]);
3211 let mut o1 = default_opts();
3212 let mut o2 = default_opts();
3213 o1.common.vcd = None;
3215 o2.common.vcd = Some("/tmp/dump.vcd".into());
3216 assert_eq!(
3217 build_cache_key(&src, "Top", &o1, None),
3218 build_cache_key(&src, "Top", &o2, None),
3219 );
3220 }
3221
3222 #[test]
3223 fn false_loops_differ() {
3224 let src = make_sources(&[("module Top {}", "a.veryl")]);
3225 let mut o1 = default_opts();
3226 let mut o2 = default_opts();
3227 o1.common.false_loops = vec![];
3228 o2.common.false_loops = vec![((vec![], vec!["a".into()]), (vec![], vec!["b".into()]))];
3229 assert_ne!(
3230 build_cache_key(&src, "Top", &o1, None),
3231 build_cache_key(&src, "Top", &o2, None),
3232 );
3233 }
3234
3235 #[test]
3236 fn true_loops_differ() {
3237 let src = make_sources(&[("module Top {}", "a.veryl")]);
3238 let mut o1 = default_opts();
3239 let mut o2 = default_opts();
3240 o1.common.true_loops = vec![];
3241 o2.common.true_loops = vec![((vec![], vec!["x".into()]), (vec![], vec!["y".into()]), 4)];
3242 assert_ne!(
3243 build_cache_key(&src, "Top", &o1, None),
3244 build_cache_key(&src, "Top", &o2, None),
3245 );
3246 }
3247
3248 #[test]
3249 fn metadata_clock_reset_differs() {
3250 let src = make_sources(&[("module Top {}", "a.veryl")]);
3251 let opts = default_opts();
3252
3253 let mut m1 = Metadata::create_default("prj").unwrap();
3254 let mut m2 = Metadata::create_default("prj").unwrap();
3255 m1.build.clock_type = celox::ClockType::PosEdge;
3256 m2.build.clock_type = celox::ClockType::NegEdge;
3257 assert_ne!(
3258 build_cache_key(&src, "Top", &opts, Some(&m1)),
3259 build_cache_key(&src, "Top", &opts, Some(&m2)),
3260 );
3261
3262 let mut m3 = Metadata::create_default("prj").unwrap();
3263 let mut m4 = Metadata::create_default("prj").unwrap();
3264 m3.build.reset_type = celox::ResetType::AsyncLow;
3265 m4.build.reset_type = celox::ResetType::SyncHigh;
3266 assert_ne!(
3267 build_cache_key(&src, "Top", &opts, Some(&m3)),
3268 build_cache_key(&src, "Top", &opts, Some(&m4)),
3269 );
3270 }
3271
3272 #[test]
3273 fn no_metadata_vs_metadata_differs() {
3274 let src = make_sources(&[("module Top {}", "a.veryl")]);
3275 let opts = default_opts();
3276 let m = Metadata::create_default("prj").unwrap();
3277 assert_ne!(
3279 build_cache_key(&src, "Top", &opts, None),
3280 build_cache_key(&src, "Top", &opts, Some(&m)),
3281 );
3282 }
3283}