Compare commits

..

12 commits

Author SHA1 Message Date
Jacob Lifshay 16ea6850c8
WIP adding VCD output
All checks were successful
/ deps (push) Successful in 15s
/ test (push) Successful in 4m50s
/ deps (pull_request) Successful in 14s
/ test (pull_request) Successful in 4m49s
2024-11-18 03:04:10 -08:00
Jacob Lifshay 904752fa0c
wire up simulator trace writing interface
All checks were successful
/ deps (push) Successful in 17s
/ test (push) Successful in 4m48s
2024-11-17 21:02:44 -08:00
Jacob Lifshay 6eef3c23b5
WIP adding VCD output
All checks were successful
/ deps (push) Successful in 18s
/ test (push) Successful in 4m48s
2024-11-17 01:02:35 -08:00
Jacob Lifshay 414a2d74f1
simple combinatorial simulation works!
All checks were successful
/ deps (push) Successful in 21s
/ test (push) Successful in 4m49s
2024-11-14 20:28:43 -08:00
Jacob Lifshay 2e9d5c1835
Simulation::settle_step() works for simple modules
All checks were successful
/ deps (push) Successful in 18s
/ test (push) Successful in 4m49s
2024-11-13 21:20:15 -08:00
Jacob Lifshay d7d8e2e7ce
simulator WIP: use petgraph for topological sort over assignments
All checks were successful
/ deps (push) Successful in 18s
/ test (push) Successful in 4m50s
2024-11-13 04:14:43 -08:00
Jacob Lifshay f780e31622
working on simulator...
All checks were successful
/ deps (push) Successful in 20s
/ test (push) Successful in 4m58s
2024-11-12 22:11:12 -08:00
Jacob Lifshay 5b2abd3fca
working on simulator
All checks were successful
/ deps (push) Successful in 18s
/ test (push) Successful in 4m49s
2024-11-12 01:08:32 -08:00
Jacob Lifshay 96b3f1fee4
working on simulator
All checks were successful
/ deps (push) Successful in 17s
/ test (push) Successful in 4m50s
2024-11-10 22:12:57 -08:00
Jacob Lifshay 41ce9b3474
add missing copyright headers
All checks were successful
/ deps (push) Successful in 11m33s
/ test (push) Successful in 4m51s
2024-11-07 21:50:31 -08:00
Jacob Lifshay 32253bc3f4
WIP implementing simulator
Some checks failed
/ test (push) Blocked by required conditions
/ deps (push) Has been cancelled
2024-11-07 21:46:34 -08:00
Jacob Lifshay 56ff69ba52
WIP adding simulator
Some checks failed
/ deps (push) Successful in 17s
/ test (push) Failing after 16s
2024-11-07 01:19:07 -08:00
3 changed files with 20 additions and 225 deletions

View file

@ -3620,7 +3620,6 @@ struct SimulationImpl {
uninitialized_inputs: HashSet<Target>,
io_targets: HashMap<Target, CompiledValue<CanonicalType>>,
made_initial_step: bool,
needs_settle: bool,
trace_decls: TraceModule,
traces: Box<[SimTrace<BitVec>]>,
trace_writers: Vec<TraceWriterState<DynTraceWriterDecls>>,
@ -3670,7 +3669,6 @@ impl SimulationImpl {
uninitialized_inputs: HashSet::new(),
io_targets: HashMap::new(),
made_initial_step: false,
needs_settle: true,
trace_decls: compiled.base_module.trace_decls,
traces: Box::from_iter(compiled.traces.iter().map(
|&SimTrace {
@ -3806,7 +3804,9 @@ impl SimulationImpl {
}
#[track_caller]
fn advance_time(&mut self, duration: SimDuration) {
self.settle_step();
if !self.made_initial_step {
self.settle_step();
}
self.instant += duration;
self.for_each_trace_writer_storing_error(|this, mut trace_writer_state| {
match &mut trace_writer_state {
@ -3825,9 +3825,6 @@ impl SimulationImpl {
self.uninitialized_inputs.is_empty(),
"didn't initialize all inputs",
);
if !self.needs_settle {
return;
}
self.state.setup_call(0);
self.state.run();
if self.made_initial_step {
@ -3836,7 +3833,6 @@ impl SimulationImpl {
self.read_traces::<true>();
}
self.made_initial_step = true;
self.needs_settle = false;
self.for_each_trace_writer_storing_error(|this, trace_writer_state| {
Ok(match trace_writer_state {
TraceWriterState::Decls(trace_writer_decls) => TraceWriterState::Running(
@ -3865,14 +3861,12 @@ impl SimulationImpl {
panic!("simulator read/write expression must not have dynamic array indexes");
}
#[track_caller]
fn read_bool_or_int<I: BoolOrIntType>(&mut self, io: Expr<I>) -> I::Value {
fn read_bool_or_int<I: BoolOrIntType>(&self, io: Expr<I>) -> I::Value {
let Some(target) = io.target() else {
panic!("can't read from expression that's not a field/element of `Simulation::io()`");
};
let compiled_value = self.get_io(*target);
if self.made_initial_step {
self.settle_step();
} else {
if !self.made_initial_step {
match target.flow() {
Flow::Source => {
panic!("can't read from an output before the simulation has made any steps");
@ -3912,7 +3906,6 @@ impl SimulationImpl {
if !self.made_initial_step {
self.uninitialized_inputs.remove(&*target);
}
self.needs_settle = true;
match compiled_value.range.len() {
TypeLen::A_SMALL_SLOT => {
self.state.small_slots[compiled_value.range.small_slots.start] =
@ -3988,15 +3981,9 @@ impl SimulationImpl {
retval
}
fn close(mut self) -> std::io::Result<()> {
if self.made_initial_step {
self.settle_step();
}
self.close_all_trace_writers()
}
fn flush_traces(&mut self) -> std::io::Result<()> {
if self.made_initial_step {
self.settle_step();
}
self.for_each_trace_writer_getting_error(
|this, trace_writer: TraceWriterState<DynTraceWriterDecls>| match trace_writer {
TraceWriterState::Decls(v) => {
@ -4081,7 +4068,6 @@ impl<T: BundleType> fmt::Debug for Simulation<T> {
uninitialized_inputs,
io_targets,
made_initial_step,
needs_settle,
trace_decls,
traces,
trace_writers,
@ -4098,7 +4084,6 @@ impl<T: BundleType> fmt::Debug for Simulation<T> {
)
.field("io_targets", &SortedMapDebug(io_targets))
.field("made_initial_step", made_initial_step)
.field("needs_settle", needs_settle)
.field("trace_decls", trace_decls)
.field("traces", traces)
.field("trace_writers", trace_writers)
@ -4155,7 +4140,7 @@ impl<T: BundleType> Simulation<T> {
self.sim_impl.advance_time(duration);
}
#[track_caller]
pub fn read_bool_or_int<I: BoolOrIntType>(&mut self, io: Expr<I>) -> I::Value {
pub fn read_bool_or_int<I: BoolOrIntType>(&self, io: Expr<I>) -> I::Value {
self.sim_impl.read_bool_or_int(io)
}
#[track_caller]

View file

@ -1,133 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! Formal tests in Fayalite
use fayalite::{
cli::FormalMode,
clock::{Clock, ClockDomain},
expr::{CastTo, HdlPartialEq},
firrtl::ExportOptions,
formal::{any_seq, formal_reset, hdl_assert, hdl_assume},
hdl_module,
int::{Bool, UInt},
module::{connect, connect_any, reg_builder, wire},
reset::ToReset,
testing::assert_formal,
};
/// Test hidden state
///
/// Hidden state can cause problems for induction, since the formal engine
/// can assign invalid values to the state registers, making it traverse
/// valid but unreachable states.
///
/// One solution is to go sufficiently in the past so the engine is forced
/// to eventually take a reachable state. This may be hampered by
/// existence of loops, then assumptions may be added to break them.
///
/// Another solution is to "open the black box" and add additional
/// assertions involving the hidden state, so that the unreachable states
/// become invalid as well.
///
/// Both approaches are taken here.
///
/// See [Claire Wolf's presentation] and [Zipcpu blog article].
///
/// [Claire Wolf's presentation]: https://web.archive.org/web/20200115081517fw_/http://www.clifford.at/papers/2017/smtbmc-sby/
/// [Zipcpu blog article]: https://zipcpu.com/blog/2018/03/10/induction-exercise.html
mod hidden_state {
use super::*;
/// Test hidden state by shift registers
///
/// The code implement the ideas from an article in the [Zipcpu blog]. Two
/// shift registers are fed from the same input, so they should always have
/// the same value. However the only observable is a comparison of their
/// last bit, all the others are hidden. To complicate matters, an enable
/// signal causes a loop in state space.
///
/// [Zipcpu blog]: https://zipcpu.com/blog/2018/03/10/induction-exercise.html
#[test]
fn shift_register() {
enum ConstraintMode {
WithExtraAssertions,
WithExtraAssumptions,
}
use ConstraintMode::*;
#[hdl_module]
fn test_module(constraint_mode: ConstraintMode) {
#[hdl]
let clk: Clock = m.input();
#[hdl]
let cd = wire();
connect(
cd,
#[hdl]
ClockDomain {
clk,
rst: formal_reset().to_reset(),
},
);
// input signal for the shift registers
#[hdl]
let i: Bool = wire();
connect(i, any_seq(Bool));
// shift enable signal
#[hdl]
let en: Bool = wire();
connect(en, any_seq(Bool));
// comparison output
#[hdl]
let o: Bool = wire();
// shift registers, with enable
#[hdl]
let r1 = reg_builder().clock_domain(cd).reset(0u8);
#[hdl]
let r2 = reg_builder().clock_domain(cd).reset(0u8);
#[hdl]
if en {
connect_any(r1, (r1 << 1) | i.cast_to(UInt[1]));
connect_any(r2, (r2 << 1) | i.cast_to(UInt[1]));
}
// compare last bits of both shift registers
connect(o, r1[7].cmp_eq(r2[7]));
// what we want to prove: last bits are always equal
hdl_assert(clk, o, "");
// additional terms below are only needed to assist with the induction proof
match constraint_mode {
WithExtraAssertions => {
// "Open the box": add assertions about hidden state.
// In this case, the hidden bits are also always equal.
hdl_assert(clk, r1.cmp_eq(r2), "");
}
WithExtraAssumptions => {
// Break the loop, do not allow "en" to remain low forever
#[hdl]
let past_en_reg = reg_builder().clock_domain(cd).reset(false);
connect(past_en_reg, en);
hdl_assume(clk, past_en_reg | en, "");
}
}
}
// we need a minimum of 16 steps so we can constrain all eight shift register bits,
// given that we are allowed to disable the shift once every two cycles.
assert_formal(
"shift_register_with_assumptions",
test_module(WithExtraAssumptions),
FormalMode::Prove,
16,
None,
ExportOptions::default(),
);
// here a couple of cycles is enough
assert_formal(
"shift_register_with_assertions",
test_module(WithExtraAssertions),
FormalMode::Prove,
2,
None,
ExportOptions::default(),
);
}
}

View file

@ -120,7 +120,6 @@ fn test_connect_const() {
},
},
made_initial_step: true,
needs_settle: false,
trace_decls: TraceModule {
name: "connect_const",
children: [
@ -183,6 +182,7 @@ pub fn mod1() {
connect(o, child);
}
#[cfg(todo)]
#[hdl]
#[test]
fn test_mod1() {
@ -195,58 +195,10 @@ fn test_mod1() {
sim.advance_time(SimDuration::from_micros(1));
sim.write_bool_or_int(sim.io().o.i, 0xA_hdl_u4);
sim.advance_time(SimDuration::from_micros(1));
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd
!= r#"$timescale 1 ps $end
$scope module mod1 $end
$scope struct o $end
$var wire 4 ! i $end
$var wire 2 " o $end
$var wire 2 # i2 $end
$var wire 4 $ o2 $end
$upscope $end
$scope struct child $end
$var wire 4 ) i $end
$var wire 2 * o $end
$var wire 2 + i2 $end
$var wire 4 , o2 $end
$upscope $end
$scope module mod1_child $end
$var wire 4 % i $end
$var wire 2 & o $end
$var wire 2 ' i2 $end
$var wire 4 ( o2 $end
$upscope $end
$upscope $end
$enddefinitions $end
$dumpvars
b11 !
b11 "
b10 #
b1110 $
b11 %
b11 &
b10 '
b1110 (
b11 )
b11 *
b10 +
b1110 ,
$end
#1000000
b1010 !
b10 "
b1111 $
b1010 %
b10 &
b1111 (
b1010 )
b10 *
b1111 ,
#2000000
"# {
todo!("generated vcd is incorrect");
if vcd != r#""# {
panic!();
}
let sim_debug = format!("{sim:#?}");
@ -766,7 +718,6 @@ b1111 ,
},
},
made_initial_step: true,
needs_settle: false,
trace_decls: TraceModule {
name: "mod1",
children: [
@ -936,7 +887,7 @@ b1111 ,
ty: UInt<4>,
},
state: 0xa,
last_state: 0x3,
last_state: 0xa,
},
SimTrace {
id: TraceScalarId(1),
@ -945,7 +896,7 @@ b1111 ,
ty: SInt<2>,
},
state: 0x2,
last_state: 0x3,
last_state: 0x2,
},
SimTrace {
id: TraceScalarId(2),
@ -963,7 +914,7 @@ b1111 ,
ty: UInt<4>,
},
state: 0xf,
last_state: 0xe,
last_state: 0xf,
},
SimTrace {
id: TraceScalarId(4),
@ -972,7 +923,7 @@ b1111 ,
ty: UInt<4>,
},
state: 0xa,
last_state: 0x3,
last_state: 0xa,
},
SimTrace {
id: TraceScalarId(5),
@ -981,7 +932,7 @@ b1111 ,
ty: SInt<2>,
},
state: 0x2,
last_state: 0x3,
last_state: 0x2,
},
SimTrace {
id: TraceScalarId(6),
@ -999,7 +950,7 @@ b1111 ,
ty: UInt<4>,
},
state: 0xf,
last_state: 0xe,
last_state: 0xf,
},
SimTrace {
id: TraceScalarId(8),
@ -1008,7 +959,7 @@ b1111 ,
ty: UInt<4>,
},
state: 0xa,
last_state: 0x3,
last_state: 0xa,
},
SimTrace {
id: TraceScalarId(9),
@ -1017,7 +968,7 @@ b1111 ,
ty: SInt<2>,
},
state: 0x2,
last_state: 0x3,
last_state: 0x2,
},
SimTrace {
id: TraceScalarId(10),
@ -1035,19 +986,11 @@ b1111 ,
ty: UInt<4>,
},
state: 0xf,
last_state: 0xe,
last_state: 0xf,
},
],
trace_writers: [
Running(
VcdWriter {
finished_init: true,
timescale: 1 ps,
..
},
),
],
instant: 2 μs,
trace_writers: [],
instant: 0 s,
}"# {
panic!();
}