add formal proof for unit_free_regs_tracker
Some checks failed
/ test (push) Failing after 3h11m43s

This commit is contained in:
Jacob Lifshay 2024-10-15 02:25:35 -07:00
parent cb5855589f
commit aaa2cb193e
Signed by: programmerjake
SSH key fingerprint: SHA256:B1iRVvUJkvd7upMIiMqn6OyxvD2SgJkAH3ZnUOj6z+c
2 changed files with 342 additions and 17 deletions

8
Cargo.lock generated
View file

@ -279,7 +279,7 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
[[package]] [[package]]
name = "fayalite" name = "fayalite"
version = "0.2.0" version = "0.2.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#3939ce2360138c75c53972368069c8f882111571" source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#3d0f95cfe5a910f79177babf8fe12483f83e96f9"
dependencies = [ dependencies = [
"bitvec", "bitvec",
"blake3", "blake3",
@ -300,7 +300,7 @@ dependencies = [
[[package]] [[package]]
name = "fayalite-proc-macros" name = "fayalite-proc-macros"
version = "0.2.0" version = "0.2.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#3939ce2360138c75c53972368069c8f882111571" source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#3d0f95cfe5a910f79177babf8fe12483f83e96f9"
dependencies = [ dependencies = [
"fayalite-proc-macros-impl", "fayalite-proc-macros-impl",
] ]
@ -308,7 +308,7 @@ dependencies = [
[[package]] [[package]]
name = "fayalite-proc-macros-impl" name = "fayalite-proc-macros-impl"
version = "0.2.0" version = "0.2.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#3939ce2360138c75c53972368069c8f882111571" source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#3d0f95cfe5a910f79177babf8fe12483f83e96f9"
dependencies = [ dependencies = [
"base16ct", "base16ct",
"num-bigint", "num-bigint",
@ -323,7 +323,7 @@ dependencies = [
[[package]] [[package]]
name = "fayalite-visit-gen" name = "fayalite-visit-gen"
version = "0.2.0" version = "0.2.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#3939ce2360138c75c53972368069c8f882111571" source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#3d0f95cfe5a910f79177babf8fe12483f83e96f9"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"prettyplease", "prettyplease",

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: LGPL-3.0-or-later // SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information // See Notices.txt for copyright information
use crate::util::tree_reduce::tree_reduce; use crate::util::tree_reduce::tree_reduce;
use fayalite::{prelude::*, util::ready_valid::ReadyValid}; use fayalite::{module::wire_with_loc, prelude::*, util::ready_valid::ReadyValid};
use std::{num::NonZeroUsize, ops::Range}; use std::{num::NonZeroUsize, ops::Range};
#[hdl_module] #[hdl_module]
@ -17,7 +17,7 @@ pub fn unit_free_regs_tracker(
m.input(Array[ReadyValid[UInt[reg_num_width]]][free_at_once.get()]); m.input(Array[ReadyValid[UInt[reg_num_width]]][free_at_once.get()]);
#[hdl] #[hdl]
let alloc_out: Array<ReadyValid<UInt>> = let alloc_out: Array<ReadyValid<UInt>> =
m.input(Array[ReadyValid[UInt[reg_num_width]]][alloc_at_once.get()]); m.output(Array[ReadyValid[UInt[reg_num_width]]][alloc_at_once.get()]);
assert!( assert!(
reg_num_width <= isize::MAX.ilog2() as usize, reg_num_width <= isize::MAX.ilog2() as usize,
"too many registers" "too many registers"
@ -47,7 +47,7 @@ pub fn unit_free_regs_tracker(
}) = tree_reduce( }) = tree_reduce(
(0..reg_count).map(|index| Summary { (0..reg_count).map(|index| Summary {
range: index..index + 1, range: index..index + 1,
count: allocated_reg[index] count: (!allocated_reg[index])
.cast_to_static::<UInt<1>>() .cast_to_static::<UInt<1>>()
.cast_to(count_ty), .cast_to(count_ty),
count_overflowed: false.to_expr(), count_overflowed: false.to_expr(),
@ -55,25 +55,35 @@ pub fn unit_free_regs_tracker(
}), }),
|l, r| { |l, r| {
let range = l.range.start..r.range.end; let range = l.range.start..r.range.end;
#[hdl] let reduced_count = wire_with_loc(
let reduced_count = wire(count_ty); &format!("reduced_count_{}_{}", range.start, range.end),
SourceLocation::caller(),
count_ty,
);
connect_any(reduced_count, l.count + r.count); connect_any(reduced_count, l.count + r.count);
#[hdl] let reduced_count_overflowed = wire_with_loc(
let reduced_count_overflowed = wire(); &format!("reduced_count_overflowed_{}_{}", range.start, range.end),
SourceLocation::caller(),
Bool,
);
connect( connect(
reduced_count_overflowed, reduced_count_overflowed,
(l.count + r.count).cmp_ne(reduced_count) | l.count_overflowed | r.count_overflowed, (l.count + r.count).cmp_ne(reduced_count) | l.count_overflowed | r.count_overflowed,
); );
#[hdl] let reduced_alloc_nums = wire_with_loc(
let reduced_alloc_nums = wire( &format!("reduced_alloc_nums_{}_{}", range.start, range.end),
SourceLocation::caller(),
Array[UInt[Expr::ty(l.alloc_nums).element().width() + 1]][alloc_at_once.get()], Array[UInt[Expr::ty(l.alloc_nums).element().width() + 1]][alloc_at_once.get()],
); );
for alloc_index in 0..alloc_at_once.get() { for alloc_index in 0..alloc_at_once.get() {
#[hdl] #[hdl]
if l.count_overflowed | l.count.cmp_ge(alloc_at_once) { if l.count_overflowed | l.count.cmp_gt(alloc_index) {
connect(reduced_alloc_nums[alloc_index], l.alloc_nums[alloc_index]); connect(reduced_alloc_nums[alloc_index], l.alloc_nums[alloc_index]);
} else { } else {
connect(reduced_alloc_nums[alloc_index], r.alloc_nums[alloc_index]); connect(
reduced_alloc_nums[alloc_index],
r.alloc_nums[alloc_index.to_expr() - l.count] + l.range.len(),
);
} }
} }
Summary { Summary {
@ -93,7 +103,7 @@ pub fn unit_free_regs_tracker(
connect(allocated_reg[alloc_num], true); connect(allocated_reg[alloc_num], true);
} }
#[hdl] #[hdl]
if count_overflowed | count.cmp_ge(alloc_index) { if count_overflowed | count.cmp_gt(alloc_index) {
connect( connect(
alloc_out[alloc_index].data, alloc_out[alloc_index].data,
HdlSome(alloc_nums[alloc_index]), HdlSome(alloc_nums[alloc_index]),
@ -107,4 +117,319 @@ pub fn unit_free_regs_tracker(
} }
} }
// TODO: add formal proof of unit_free_regs_tracker #[cfg(test)]
mod tests {
use super::*;
use fayalite::{
cli::FormalMode, firrtl::ExportOptions,
module::transform::simplify_enums::SimplifyEnumsKind, testing::assert_formal,
};
use std::num::NonZero;
fn test_unit_free_regs_tracker(
free_at_once: NonZeroUsize,
alloc_at_once: NonZeroUsize,
reg_num_width: usize,
) {
// enough time to completely allocate all regs, free whatever we please,
// allocate one, then completely free the rest
let history_len = (3 + 2 * (1 << reg_num_width)).max(5);
assert_formal(
format_args!(
"test_unit_free_regs_tracker_{free_at_once}_{alloc_at_once}_{reg_num_width}"
),
unit_free_regs_tracker_test(free_at_once, alloc_at_once, reg_num_width, history_len),
FormalMode::Prove,
history_len as u64 + 2,
None,
ExportOptions {
simplify_enums: Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts),
..ExportOptions::default()
},
);
#[hdl_module]
fn unit_free_regs_tracker_test(
free_at_once: NonZeroUsize,
alloc_at_once: NonZeroUsize,
reg_num_width: usize,
history_len: usize,
) {
#[hdl]
let clk: Clock = m.input();
#[hdl]
let cd = wire();
connect(
cd,
#[hdl]
ClockDomain {
clk,
rst: formal_reset().to_reset(),
},
);
#[hdl]
let dut = instance(unit_free_regs_tracker(
free_at_once,
alloc_at_once,
reg_num_width,
));
connect(dut.cd, cd);
let reg_count = 1 << reg_num_width;
let reg_num_ty = UInt[reg_num_width];
#[hdl]
let free_reg = reg_builder().clock_domain(cd).reset(vec![true; reg_count]);
for free_index in 0..free_at_once.get() {
#[hdl]
if any_seq(Bool) {
let data = any_seq(reg_num_ty);
connect(dut.free_in[free_index].data, HdlSome(data));
} else {
connect(
dut.free_in[free_index].data,
HdlOption[reg_num_ty].HdlNone(),
);
}
#[hdl]
if let HdlSome(free) = ReadyValid::firing_data(dut.free_in[free_index]) {
hdl_assume(clk, !free_reg[free], "");
connect(free_reg[free], true);
}
}
#[hdl]
let free_before_alloc_array = wire(Array[Expr::ty(free_reg)][alloc_at_once.get() + 1]);
connect(free_before_alloc_array[0], free_reg);
#[hdl]
let expected_alloc = wire(Array[HdlOption[reg_num_ty]][alloc_at_once.get()]);
for alloc_index in 0..alloc_at_once.get() {
let free_before_alloc = free_before_alloc_array[alloc_index];
let free_after_alloc = free_before_alloc_array[alloc_index + 1];
connect(free_after_alloc, free_before_alloc);
connect(dut.alloc_out[alloc_index].ready, any_seq(Bool));
connect(expected_alloc[alloc_index], HdlOption[reg_num_ty].HdlNone());
for reg_index in (0..reg_count).rev() {
#[hdl]
if free_before_alloc[reg_index] {
connect(
expected_alloc[alloc_index],
HdlSome(reg_index.cast_to(reg_num_ty)),
);
}
#[hdl]
if let HdlSome(alloc) = expected_alloc[alloc_index] {
#[hdl]
if alloc.cmp_eq(reg_index) {
connect(free_after_alloc[reg_index], false);
}
}
}
#[hdl]
if let HdlSome(expected_alloc) = expected_alloc[alloc_index] {
#[hdl]
if let HdlSome(alloc) = dut.alloc_out[alloc_index].data {
hdl_assert(clk, expected_alloc.cmp_eq(alloc), "");
} else {
hdl_assert(clk, false.to_expr(), "");
}
} else {
hdl_assert(clk, HdlOption::is_none(dut.alloc_out[alloc_index].data), "");
}
#[hdl]
if let HdlSome(alloc) = ReadyValid::firing_data(dut.alloc_out[alloc_index]) {
connect(free_reg[alloc], false);
}
}
#[hdl]
let free_fired_history_reg = reg_builder()
.clock_domain(cd)
.reset(repeat(!UInt[history_len].zero(), free_at_once.get()));
for free_index in 0..free_at_once.get() {
connect_any(
free_fired_history_reg[free_index],
(free_fired_history_reg[free_index] << 1)
| ReadyValid::firing(dut.free_in[free_index]).cast_to(UInt[1]),
);
hdl_assume(clk, free_fired_history_reg[free_index].any_one_bits(), "");
}
#[hdl]
let alloc_fired_history_reg = reg_builder()
.clock_domain(cd)
.reset(repeat(!UInt[history_len].zero(), alloc_at_once.get()));
for alloc_index in 0..alloc_at_once.get() {
connect_any(
alloc_fired_history_reg[alloc_index],
(alloc_fired_history_reg[alloc_index] << 1)
| ReadyValid::firing(dut.alloc_out[alloc_index]).cast_to(UInt[1]),
);
if alloc_index < reg_count {
hdl_assume(clk, alloc_fired_history_reg[alloc_index].any_one_bits(), "");
}
}
#[hdl]
let any_free_history_reg = reg_builder()
.clock_domain(cd)
.reset(UInt[history_len].zero());
#[hdl]
let any_allocated_history_reg = reg_builder()
.clock_domain(cd)
.reset(UInt[history_len].zero());
#[hdl]
let any_free = wire();
connect(any_free, false);
#[hdl]
let any_allocated = wire();
connect(any_allocated, false);
for reg_index in 0..reg_count {
#[hdl]
if free_reg[reg_index] {
connect(any_free, true);
} else {
connect(any_allocated, true);
}
}
connect_any(
any_free_history_reg,
(any_free_history_reg << 1) | any_free.cast_to(UInt[1]),
);
connect_any(
any_allocated_history_reg,
(any_allocated_history_reg << 1) | any_allocated.cast_to(UInt[1]),
);
hdl_assume(clk, any_free_history_reg.any_zero_bits(), "");
hdl_assume(clk, any_allocated_history_reg.any_zero_bits(), "");
}
}
#[test]
fn test_unit_free_regs_tracker_1_1_0() {
test_unit_free_regs_tracker(NonZero::new(1).unwrap(), NonZero::new(1).unwrap(), 0);
}
#[test]
fn test_unit_free_regs_tracker_1_1_1() {
test_unit_free_regs_tracker(NonZero::new(1).unwrap(), NonZero::new(1).unwrap(), 1);
}
#[test]
fn test_unit_free_regs_tracker_1_1_2() {
test_unit_free_regs_tracker(NonZero::new(1).unwrap(), NonZero::new(1).unwrap(), 2);
}
#[test]
fn test_unit_free_regs_tracker_1_1_3() {
test_unit_free_regs_tracker(NonZero::new(1).unwrap(), NonZero::new(1).unwrap(), 3);
}
#[test]
fn test_unit_free_regs_tracker_1_1_4() {
test_unit_free_regs_tracker(NonZero::new(1).unwrap(), NonZero::new(1).unwrap(), 4);
}
#[test]
fn test_unit_free_regs_tracker_1_2_0() {
test_unit_free_regs_tracker(NonZero::new(1).unwrap(), NonZero::new(2).unwrap(), 0);
}
#[test]
fn test_unit_free_regs_tracker_1_2_4() {
test_unit_free_regs_tracker(NonZero::new(1).unwrap(), NonZero::new(2).unwrap(), 4);
}
#[test]
fn test_unit_free_regs_tracker_1_3_0() {
test_unit_free_regs_tracker(NonZero::new(1).unwrap(), NonZero::new(3).unwrap(), 0);
}
#[test]
fn test_unit_free_regs_tracker_1_3_1() {
test_unit_free_regs_tracker(NonZero::new(1).unwrap(), NonZero::new(3).unwrap(), 1);
}
#[test]
fn test_unit_free_regs_tracker_1_3_4() {
test_unit_free_regs_tracker(NonZero::new(1).unwrap(), NonZero::new(3).unwrap(), 4);
}
#[test]
fn test_unit_free_regs_tracker_1_4_0() {
test_unit_free_regs_tracker(NonZero::new(1).unwrap(), NonZero::new(4).unwrap(), 0);
}
#[test]
fn test_unit_free_regs_tracker_1_4_1() {
test_unit_free_regs_tracker(NonZero::new(1).unwrap(), NonZero::new(4).unwrap(), 1);
}
#[test]
fn test_unit_free_regs_tracker_1_4_2() {
test_unit_free_regs_tracker(NonZero::new(1).unwrap(), NonZero::new(4).unwrap(), 2);
}
#[test]
fn test_unit_free_regs_tracker_1_4_4() {
test_unit_free_regs_tracker(NonZero::new(1).unwrap(), NonZero::new(4).unwrap(), 4);
}
#[test]
fn test_unit_free_regs_tracker_2_1_4() {
test_unit_free_regs_tracker(NonZero::new(2).unwrap(), NonZero::new(1).unwrap(), 4);
}
#[test]
fn test_unit_free_regs_tracker_2_2_0() {
test_unit_free_regs_tracker(NonZero::new(2).unwrap(), NonZero::new(2).unwrap(), 0);
}
#[test]
fn test_unit_free_regs_tracker_2_2_4() {
test_unit_free_regs_tracker(NonZero::new(2).unwrap(), NonZero::new(2).unwrap(), 4);
}
#[test]
fn test_unit_free_regs_tracker_3_1_4() {
test_unit_free_regs_tracker(NonZero::new(3).unwrap(), NonZero::new(1).unwrap(), 4);
}
#[test]
fn test_unit_free_regs_tracker_3_3_0() {
test_unit_free_regs_tracker(NonZero::new(3).unwrap(), NonZero::new(3).unwrap(), 0);
}
#[test]
fn test_unit_free_regs_tracker_3_3_1() {
test_unit_free_regs_tracker(NonZero::new(3).unwrap(), NonZero::new(3).unwrap(), 1);
}
#[test]
fn test_unit_free_regs_tracker_3_3_2() {
test_unit_free_regs_tracker(NonZero::new(3).unwrap(), NonZero::new(3).unwrap(), 2);
}
#[test]
fn test_unit_free_regs_tracker_3_3_4() {
test_unit_free_regs_tracker(NonZero::new(3).unwrap(), NonZero::new(3).unwrap(), 4);
}
#[test]
fn test_unit_free_regs_tracker_4_1_4() {
test_unit_free_regs_tracker(NonZero::new(4).unwrap(), NonZero::new(1).unwrap(), 4);
}
#[test]
fn test_unit_free_regs_tracker_4_4_0() {
test_unit_free_regs_tracker(NonZero::new(4).unwrap(), NonZero::new(4).unwrap(), 0);
}
#[test]
fn test_unit_free_regs_tracker_4_4_1() {
test_unit_free_regs_tracker(NonZero::new(4).unwrap(), NonZero::new(4).unwrap(), 1);
}
#[test]
fn test_unit_free_regs_tracker_4_4_2() {
test_unit_free_regs_tracker(NonZero::new(4).unwrap(), NonZero::new(4).unwrap(), 2);
}
#[test]
fn test_unit_free_regs_tracker_4_4_4() {
test_unit_free_regs_tracker(NonZero::new(4).unwrap(), NonZero::new(4).unwrap(), 4);
}
}