Queue formal proof based on one-entry FIFO equivalence #11

Merged
programmerjake merged 3 commits from cesar/fayalite:fifo-proof into master 2024-12-29 21:05:26 +00:00
Showing only changes of commit dcf865caec - Show all commits

View file

@ -178,6 +178,30 @@ pub fn queue<T: Type>(
}
}
}
// These debug ports expose some internal state during the Induction phase
// of Formal Verification. They are not present in normal use.
//
// TODO: gather the new debug ports in a bundle
#[cfg(test)]
{
// read the memory word currently stored at some fixed index
let debug_port = mem.new_read_port();
#[hdl]
let dbg_index_to_check: UInt = m.input(index_ty);
#[hdl]
let dbg_stored: T = m.output(ty);
connect(debug_port.addr, dbg_index_to_check);
connect(debug_port.en, true);
connect(debug_port.clk, cd.clk);
connect(dbg_stored, debug_port.data);
// also expose the current read and write indices
#[hdl]
programmerjake marked this conversation as resolved Outdated

imo this should be changed this back to Prove before merging. ah, you already said that...

imo this should be changed this back to Prove before merging. ah, you already said that...
let dbg_inp_index: UInt = m.output(index_ty);
#[hdl]
let dbg_out_index: UInt = m.output(index_ty);
connect(dbg_inp_index, inp_index_reg);
connect(dbg_out_index, out_index_reg);
}
}
#[cfg(test)]
@ -195,8 +219,8 @@ mod tests {
assert_formal(
format_args!("test_queue_{capacity}_{inp_ready_is_comb}_{out_valid_is_comb}"),
queue_test(capacity, inp_ready_is_comb, out_valid_is_comb),
FormalMode::BMC,
14,
FormalMode::Prove,
2,
None,
ExportOptions {
simplify_enums: Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts),
@ -254,6 +278,7 @@ mod tests {
#[hdl]
let index_to_check = wire(index_ty);
connect(index_to_check, any_const(index_ty));
hdl_assume(clk, index_to_check.cmp_lt(capacity.get()), "");
// instantiate and connect the queue
#[hdl]
@ -382,6 +407,56 @@ mod tests {
}
}
}
// from now on, some extra assertions in order to pass induction
// sync the holding register, when it's occupied, to the
// corresponding entry in the FIFO's circular buffer
connect(dut.dbg_index_to_check, index_to_check);
#[hdl]
if let HdlSome(stored) = stored_reg {
hdl_assert(clk, stored.cmp_eq(dut.dbg_stored), "");
}
// sync the read and write indices
hdl_assert(clk, inp_index_reg.cmp_eq(dut.dbg_inp_index), "");
hdl_assert(clk, out_index_reg.cmp_eq(dut.dbg_out_index), "");
// the indices should never go past the capacity, but induction
// doesn't know that...
hdl_assert(clk, inp_index_reg.cmp_lt(capacity.get()), "");
hdl_assert(clk, out_index_reg.cmp_lt(capacity.get()), "");
// strongly constrain the state of the holding register
//
// The holding register is full if and only if the corresponding
// FIFO entry was written to and not yet read. In other words, if
// the number of pending reads until the chosen entry is read out
// is greater than the current FIFO count, then the entry couldn't
// be in the FIFO in the first place.
#[hdl]
let pending_reads: UInt = wire(index_ty);
// take care of wrap-around when subtracting indices, add the
// capacity amount to keep the result positive if necessary
#[hdl]
if index_to_check.cmp_ge(out_index_reg) {
connect(pending_reads, index_to_check - out_index_reg);
} else {
connect(
pending_reads,
index_to_check + capacity.get() - out_index_reg,
);
}
// check whether the chosen entry is in the FIFO
#[hdl]
let expected_stored: Bool = wire();
connect(expected_stored, pending_reads.cmp_lt(dut.count));
// sync with the state of the holding register
hdl_assert(
clk,
expected_stored.cmp_eq(HdlOption::is_some(stored_reg)),
"",
);
}
}
@ -464,4 +539,24 @@ mod tests {
fn test_4_true_true() {
test_queue(NonZero::new(4).unwrap(), true, true);
}
#[test]
fn test_many_false_false() {
test_queue(NonZero::new((2 << 16) - 5).unwrap(), false, false);
}
#[test]
fn test_many_false_true() {
test_queue(NonZero::new((2 << 16) - 5).unwrap(), false, true);
}
#[test]
fn test_many_true_false() {
test_queue(NonZero::new((2 << 16) - 5).unwrap(), true, false);
}
#[test]
fn test_many_true_true() {
test_queue(NonZero::new((2 << 16) - 5).unwrap(), true, true);
}
}