1use crate::backend::CompiledWhole;
2use crate::ir::write_log::{
3 WriteLogBuffer, clear_event_write_log, ff_commit_from_log, set_event_write_log,
4};
5use crate::ir::{
6 Event, Ir, ModuleVariables, Statement, Value, VarId, VarPath, dispatch_stmt_fast,
7 read_native_value, write_native_value,
8};
9use crate::wave_dumper::{DumpVar, WaveDumper};
10use std::str::FromStr;
11use veryl_analyzer::value::MaskCache;
12
13#[cfg(feature = "profile")]
14#[derive(Default, Debug)]
15pub struct SimProfile {
16 pub step_count: u64,
17 pub settle_comb_count: u64,
18 pub comb_eval_count: u64,
19 pub extra_pass_count: u64,
20 pub converged_first_try: u64,
21 pub settle_comb_ns: u64,
22 pub event_eval_ns: u64,
23 pub ff_swap_ns: u64,
24 pub eval_comb_full_ns: u64,
25}
26
27#[cfg(not(feature = "profile"))]
28#[derive(Default, Debug)]
29pub struct SimProfile;
30
31pub struct Simulator {
32 pub ir: Ir,
33 pub time: u64,
34 pub dump: Option<WaveDumper>,
35 dump_vars: Vec<DumpVar>,
36 pub mask_cache: MaskCache,
37 comb_dirty: bool,
38 pub profile: SimProfile,
39 last_event: Option<Event>,
40 last_event_stmts: *const Vec<Statement>,
41 pub write_log_diag: WriteLogDiag,
46}
47
48#[derive(Default)]
49pub struct WriteLogDiag {
50 pub enabled: bool,
51 pub total_cycles: u64,
52 pub total_entries: u64,
53 pub max_entries_per_cycle: u32,
54 pub cycles_with_entries: u64,
55 next_print_cycle: u64,
56}
57
58impl WriteLogDiag {
59 fn maybe_print(&mut self) {
60 if !self.enabled {
61 return;
62 }
63 if self.total_cycles >= self.next_print_cycle {
64 self.next_print_cycle = self.next_print_cycle.saturating_mul(2).max(1_000_000);
65 self.dump();
66 }
67 }
68
69 pub fn dump(&self) {
70 let avg = if self.cycles_with_entries > 0 {
71 self.total_entries as f64 / self.cycles_with_entries as f64
72 } else {
73 0.0
74 };
75 eprintln!(
76 "[write_log_diag] cycles={} cycles_with_entries={} total_entries={} max_per_cycle={} avg_per_active_cycle={:.2}",
77 self.total_cycles,
78 self.cycles_with_entries,
79 self.total_entries,
80 self.max_entries_per_cycle,
81 avg,
82 );
83 }
84}
85
86impl Simulator {
87 pub fn new(ir: Ir, dump: Option<WaveDumper>) -> Self {
88 let mut ret = Self {
89 ir,
90 time: 0,
91 dump: None,
92 dump_vars: Vec::new(),
93 mask_cache: MaskCache::default(),
94 comb_dirty: true,
95 profile: Default::default(),
96 last_event: None,
97 last_event_stmts: std::ptr::null(),
98 write_log_diag: WriteLogDiag {
99 enabled: std::env::var("VERYL_WRITE_LOG_DIAG").as_deref() == Ok("1"),
100 next_print_cycle: 1_000_000,
101 ..Default::default()
102 },
103 };
104
105 if let Some(dumper) = dump {
106 ret.setup_dump(dumper);
107 }
108
109 ret
110 }
111
112 fn do_settle_comb(&mut self) {
113 self.ir.settle_comb(&mut self.mask_cache, &mut self.profile);
114 }
115
116 pub fn set(&mut self, port: &str, value: Value) {
117 let port = VarPath::from_str(port).unwrap();
118
119 if let Some(id) = self.ir.ports.get(&port)
120 && let Some(x) = self.ir.module_variables.variables.get_mut(id)
121 {
122 let mut value = value;
123 value.trunc(x.width);
124 unsafe {
125 write_native_value(
126 x.current_values[0],
127 x.native_bytes,
128 self.ir.use_4state,
129 &value,
130 );
131 }
132 self.comb_dirty = true;
133 }
134 }
135
136 pub fn get(&mut self, port: &str) -> Option<Value> {
137 self.ensure_comb_updated();
138
139 let port = VarPath::from_str(port).unwrap();
140
141 if let Some(id) = self.ir.ports.get(&port)
142 && let Some(x) = self.ir.module_variables.variables.get(id)
143 {
144 let value = unsafe {
145 read_native_value(
146 x.current_values[0],
147 x.native_bytes,
148 self.ir.use_4state,
149 x.width as u32,
150 false,
151 )
152 };
153 Some(value)
154 } else {
155 None
156 }
157 }
158
159 pub fn get_var(&mut self, path: &str) -> Option<Value> {
162 self.ensure_comb_updated();
163
164 let target = VarPath::from_str(path).unwrap();
165 Self::find_var_in_module(&self.ir.module_variables, &target, self.ir.use_4state)
166 }
167
168 fn find_var_in_module(
169 module: &ModuleVariables,
170 target: &VarPath,
171 use_4state: bool,
172 ) -> Option<Value> {
173 if target.0.len() > 1 {
175 for child in &module.children {
176 if child.name == target.0[0] {
177 let sub = VarPath::from_slice(&target.0[1..]);
178 if let Some(v) = Self::find_var_in_module(child, &sub, use_4state) {
179 return Some(v);
180 }
181 }
182 }
183 }
184
185 for var in module.variables.values() {
187 if var.path == *target {
188 let value = unsafe {
189 read_native_value(
190 var.current_values[0],
191 var.native_bytes,
192 use_4state,
193 var.width as u32,
194 false,
195 )
196 };
197 return Some(value);
198 }
199 }
200 None
201 }
202
203 pub fn ensure_comb_updated(&mut self) {
204 if self.comb_dirty {
205 #[cfg(feature = "profile")]
206 let start = std::time::Instant::now();
207
208 self.do_settle_comb();
209 self.comb_dirty = false;
210
211 #[cfg(feature = "profile")]
212 {
213 self.profile.settle_comb_ns += start.elapsed().as_nanos() as u64;
214 }
215 }
216 }
217
218 pub fn mark_comb_dirty(&mut self) {
219 self.comb_dirty = true;
220 }
221
222 pub fn get_clock(&self, port: &str) -> Option<Event> {
223 let port = VarPath::from_str(port).unwrap();
224 self.ir.ports.get(&port).map(|id| Event::Clock(*id))
225 }
226
227 pub fn get_reset(&self, port: &str) -> Option<Event> {
228 let port = VarPath::from_str(port).unwrap();
229 self.ir.ports.get(&port).map(|id| Event::Reset(*id))
230 }
231
232 pub fn step(&mut self, event: &Event) {
233 #[cfg(feature = "profile")]
234 {
235 self.profile.step_count += 1;
236 }
237
238 unsafe {
248 set_event_write_log(&mut self.ir.write_log_buffer);
249 }
250
251 if self.comb_dirty {
252 #[cfg(feature = "profile")]
253 let start = std::time::Instant::now();
254
255 self.do_settle_comb();
256 self.comb_dirty = false;
257
258 #[cfg(feature = "profile")]
259 {
260 self.profile.settle_comb_ns += start.elapsed().as_nanos() as u64;
261 }
262 }
263
264 #[cfg(feature = "profile")]
265 let event_start = std::time::Instant::now();
266
267 let stmts_ptr = if self.last_event.as_ref() == Some(event) {
268 self.last_event_stmts
269 } else {
270 let ptr: *const Vec<Statement> = match self.ir.event_statements.get(event) {
271 Some(v) => v as *const _,
272 None => std::ptr::null(),
273 };
274 self.last_event = Some(event.clone());
275 self.last_event_stmts = ptr;
276 ptr
277 };
278
279 use crate::backend::DispatchOutcome;
286 let whole_event = self.ir.whole_events.get(event).cloned();
287 let dispatched = if let Some(whole) = whole_event {
288 let ff_ptr = self.ir.ff_values.as_ptr();
289 let comb_ptr = self.ir.comb_values.as_ptr() as *mut u8;
290 let log_ptr = (&*self.ir.write_log_buffer) as *const _ as *mut u8;
291
292 let validate = self.ir.aot_c_validate;
294
295 if !validate {
296 matches!(
297 whole.try_dispatch(ff_ptr, comb_ptr, log_ptr),
298 DispatchOutcome::Done,
299 )
300 } else {
301 self.validate_event_aot(whole.as_ref(), stmts_ptr);
307 true
308 }
309 } else {
310 false
311 };
312
313 if !dispatched && !stmts_ptr.is_null() {
314 let statements: &Vec<Statement> = unsafe { &*stmts_ptr };
316 for x in statements {
317 dispatch_stmt_fast(x, &mut self.mask_cache);
318 }
319 }
320
321 #[cfg(feature = "profile")]
322 {
323 self.profile.event_eval_ns += event_start.elapsed().as_nanos() as u64;
324 }
325
326 #[cfg(feature = "profile")]
327 let ff_start = std::time::Instant::now();
328
329 ff_commit_from_log(&mut self.ir.ff_values, &self.ir.write_log_buffer);
330
331 clear_event_write_log();
332 if self.write_log_diag.enabled {
333 let n = self.ir.write_log_buffer.count();
334 self.write_log_diag.total_cycles += 1;
335 if n > 0 {
336 self.write_log_diag.total_entries += n as u64;
337 self.write_log_diag.cycles_with_entries += 1;
338 if n > self.write_log_diag.max_entries_per_cycle {
339 self.write_log_diag.max_entries_per_cycle = n;
340 }
341 }
342 self.write_log_diag.maybe_print();
343 }
344 self.ir.write_log_buffer.reset();
345
346 #[cfg(feature = "profile")]
347 {
348 self.profile.ff_swap_ns += ff_start.elapsed().as_nanos() as u64;
349 }
350
351 self.comb_dirty = true;
352
353 self.dump_variables();
354 }
355
356 fn validate_event_aot(&mut self, whole: &dyn CompiledWhole, stmts_ptr: *const Vec<Statement>) {
363 let ff_ptr = self.ir.ff_values.as_ptr();
364 let comb_ptr = self.ir.comb_values.as_ptr() as *mut u8;
365 let log_ptr = (&*self.ir.write_log_buffer) as *const _ as *mut u8;
366
367 let ff_snap = self.ir.ff_values.to_vec();
368 let comb_snap = self.ir.comb_values.to_vec();
369 let count_before = self.ir.write_log_buffer.narrow_count as usize;
370
371 let _ = whole.try_dispatch(ff_ptr, comb_ptr, log_ptr);
373 let lww_map = |buf: &WriteLogBuffer, lo: usize, hi: usize| {
379 let mut m: std::collections::HashMap<u32, (u16, u64)> = Default::default();
380 for e in &buf.narrow_entries_slice()[lo..hi] {
381 m.insert(e.offset, (e.width_class, e.payload));
382 }
383 m
384 };
385 let aot_count = self.ir.write_log_buffer.narrow_count as usize;
386 let aot_map = lww_map(&self.ir.write_log_buffer, count_before, aot_count);
387
388 unsafe {
390 std::ptr::copy_nonoverlapping(
391 ff_snap.as_ptr(),
392 self.ir.ff_values.as_ptr() as *mut u8,
393 ff_snap.len(),
394 );
395 std::ptr::copy_nonoverlapping(
396 comb_snap.as_ptr(),
397 self.ir.comb_values.as_ptr() as *mut u8,
398 comb_snap.len(),
399 );
400 }
401 self.ir.write_log_buffer.narrow_count = count_before as u32;
402 if !stmts_ptr.is_null() {
403 let statements: &Vec<Statement> = unsafe { &*stmts_ptr };
404 for x in statements {
405 dispatch_stmt_fast(x, &mut self.mask_cache);
406 }
407 }
408 let cr_count = self.ir.write_log_buffer.narrow_count as usize;
409 let cr_map = lww_map(&self.ir.write_log_buffer, count_before, cr_count);
410
411 if aot_map != cr_map {
412 eprintln!(
413 "[aot_event_validate] DIVERGENCE event={:?}: committed-FF maps differ (aot {} offsets, cranelift {} offsets)",
414 self.last_event,
415 aot_map.len(),
416 cr_map.len(),
417 );
418 for (off, av) in &aot_map {
420 match cr_map.get(off) {
421 None => eprintln!(" off={off:#x}: aot={av:?} cranelift=<absent>"),
422 Some(cv) if cv != av => {
423 eprintln!(" off={off:#x}: aot={av:?} cranelift={cv:?}")
424 }
425 _ => {}
426 }
427 }
428 for off in cr_map.keys() {
429 if !aot_map.contains_key(off) {
430 eprintln!(" off={off:#x}: aot=<absent> cranelift={:?}", cr_map[off]);
431 }
432 }
433 panic!("AOT-C event validate divergence (see above)");
434 }
435 }
436
437 pub fn set_var_by_id(&mut self, var_id: &VarId, val: Value) {
440 if let Some(x) = self.ir.module_variables.variables.get_mut(var_id) {
441 let mut val = val;
442 val.trunc(x.width);
443 unsafe {
444 write_native_value(
445 x.current_values[0],
446 x.native_bytes,
447 self.ir.use_4state,
448 &val,
449 );
450 }
451 self.comb_dirty = true;
452 }
453 }
454
455 pub fn dump_start(&mut self) {
456 if let Some(dump) = &mut self.dump {
457 dump.begin_dumpvars();
458 dump.dump_all_vars(&self.dump_vars, self.ir.use_4state);
459 dump.end_dumpvars();
460 }
461 }
462
463 pub fn dump_variables(&mut self) {
464 if self.dump.is_some() {
465 if self.comb_dirty {
466 self.do_settle_comb();
467 self.comb_dirty = false;
468 }
469 let dump = self.dump.as_mut().unwrap();
470 dump.timestamp(self.time);
471 dump.dump_all_vars(&self.dump_vars, self.ir.use_4state);
472 }
473 }
474
475 fn setup_dump(&mut self, mut dumper: WaveDumper) {
476 dumper.timescale();
477 dumper.setup_module(&self.ir.module_variables, &mut self.dump_vars);
478 dumper.finish_header();
479 self.dump = Some(dumper);
480 }
481}