use std::collections::HashMap;
use vj_codegen::process_lower::SignalSlot;
use vj_sim::netlist::InstanceId;
use vj_sim::symid::SymbolId;
pub(crate) use vj_exec::timing::negtiming::Explicit;
use crate::primitives::Loc;
pub(crate) const MARGIN: i64 = 1;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Limit {
DataFirst,
DataSecond,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Constraint {
pub(crate) reference: usize,
pub(crate) data: usize,
pub(crate) data_first: i64,
pub(crate) data_second: i64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Zeroed {
pub(crate) check: usize,
pub(crate) limit: Limit,
pub(crate) was: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct Solution {
pub(crate) delays: Vec<i64>,
pub(crate) effective: Vec<(i64, i64)>,
pub(crate) zeroed: Vec<Zeroed>,
}
fn least_delays(nodes: usize, checks: &[Constraint], limits: &[(i64, i64)]) -> Option<Vec<i64>> {
let mut d = vec![0i64; nodes];
for _ in 0..=nodes {
let mut changed = false;
for (c, &(first, second)) in checks.iter().zip(limits) {
if first >= 0 && second >= 0 {
continue;
}
for (from, to, w) in [
(c.reference, c.data, MARGIN - second),
(c.data, c.reference, MARGIN - first),
] {
if d[to] < d[from] + w {
d[to] = d[from] + w;
changed = true;
}
}
}
if !changed {
return Some(d);
}
}
None
}
pub(crate) fn solve(nodes: usize, checks: &[Constraint]) -> Solution {
let mut effective: Vec<(i64, i64)> = checks
.iter()
.map(|c| (c.data_first, c.data_second))
.collect();
let mut zeroed = Vec::new();
loop {
if let Some(delays) = least_delays(nodes, checks, &effective) {
return Solution {
delays,
effective,
zeroed,
};
}
let mut best: Option<(i64, usize, Limit)> = None;
for (i, &(first, second)) in effective.iter().enumerate() {
for (v, limit) in [(first, Limit::DataFirst), (second, Limit::DataSecond)] {
if v < 0 && best.is_none_or(|(magnitude, ..)| -v < magnitude) {
best = Some((-v, i, limit));
}
}
}
let (magnitude, check, limit) =
best.expect("an inconsistent system has a negative limit: only those add constraints");
zeroed.push(Zeroed {
check,
limit,
was: -magnitude,
});
match limit {
Limit::DataFirst => effective[check].0 = 0,
Limit::DataSecond => effective[check].1 = 0,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Role {
Reference,
Data,
}
#[derive(Clone, Debug)]
pub(crate) struct DelayedSignal {
pub(crate) instance: InstanceId,
pub(crate) slot: SignalSlot,
pub(crate) delay: i64,
pub(crate) explicit: Vec<Explicit>,
pub(crate) implicit: Option<Loc>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct NegativeCheck {
pub(crate) entry: usize,
pub(crate) check: SymbolId,
pub(crate) reference: usize,
pub(crate) data: usize,
pub(crate) declared: (i64, i64),
pub(crate) effective: (i64, i64),
}
#[derive(Clone, Debug, Default)]
pub(crate) struct DelayTable {
pub(crate) signals: Vec<DelayedSignal>,
index: HashMap<(InstanceId, SymbolId), usize>,
pub(crate) checks: Vec<NegativeCheck>,
pub(crate) warnings: Vec<String>,
pub(crate) enabled: bool,
}
impl DelayTable {
pub(crate) fn new(enabled: bool) -> DelayTable {
DelayTable {
enabled,
..DelayTable::default()
}
}
pub(crate) fn node(
&mut self,
instance: InstanceId,
owner: InstanceId,
signal: &SymbolId,
slot: SignalSlot,
) -> usize {
*self
.index
.entry((instance, signal.clone()))
.or_insert_with(|| {
self.signals.push(DelayedSignal {
instance: owner,
slot,
delay: 0,
explicit: Vec::new(),
implicit: None,
});
self.signals.len() - 1
})
}
pub(crate) fn add_explicit(&mut self, node: usize, e: Explicit) {
let list = &mut self.signals[node].explicit;
let known = list.iter().any(|x| {
x.loc.inst == e.loc.inst
&& x.loc.slot.slot == e.loc.slot.slot
&& x.dst_lo == e.dst_lo
&& x.source_lo == e.source_lo
&& x.width == e.width
});
if !known {
list.push(e);
}
}
pub(crate) fn find(&self, instance: InstanceId, signal: &SymbolId) -> Option<usize> {
self.index.get(&(instance, signal.clone())).copied()
}
pub(crate) fn delay_of(&self, instance: InstanceId, signal: &SymbolId) -> i64 {
self.find(instance, signal)
.map_or(0, |n| self.signals[n].delay)
}
pub(crate) fn shadow_of(
&self,
instance: InstanceId,
signal: &SymbolId,
lo: u32,
width: u32,
) -> Option<(Loc, u32)> {
let s = &self.signals[self.find(instance, signal)?];
if s.delay == 0 {
return None;
}
s.explicit
.iter()
.find(|e| e.source_lo <= lo && lo + width <= e.source_lo + e.width)
.map(|e| (e.loc, lo - e.source_lo + e.dst_lo))
.or_else(|| s.implicit.map(|loc| (loc, lo)))
}
pub(crate) fn first_of(&self, check: &SymbolId) -> Option<Role> {
let c = self.checks.iter().find(|c| &c.check == check)?;
match c.effective {
(first, _) if first < 0 => Some(Role::Reference),
(_, second) if second < 0 => Some(Role::Data),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_lrm_31_9_2_delay_table_is_reproduced() {
const CP: usize = 0;
const D: usize = 1;
const TI: usize = 2;
const TE: usize = 3;
let checks = [
Constraint {
reference: CP,
data: D,
data_first: -1000,
data_second: 2000,
},
Constraint {
reference: CP,
data: TI,
data_first: 2000,
data_second: -1000,
},
Constraint {
reference: CP,
data: TE,
data_first: -400,
data_second: 800,
},
];
let s = solve(4, &checks);
assert_eq!(s.zeroed, vec![], "the set is consistent");
assert_eq!(s.delays, vec![1001, 0, 2002, 202]);
}
#[test]
fn example_1_delays_the_reference_by_the_negative_setup() {
let s = solve(
2,
&[Constraint {
reference: 0,
data: 1,
data_first: -10,
data_second: 20,
}],
);
assert_eq!(s.delays, vec![10 + MARGIN, 0]);
assert_eq!(s.effective, vec![(-10, 20)]);
}
#[test]
fn example_2_gives_one_delay_to_the_shared_reference() {
let s = solve(
3,
&[
Constraint {
reference: 0,
data: 1,
data_first: -10,
data_second: 20,
},
Constraint {
reference: 0,
data: 2,
data_first: -15,
data_second: 18,
},
],
);
assert_eq!(s.delays, vec![15 + MARGIN, 0, 0]);
}
#[test]
fn a_negative_hold_delays_the_data() {
let s = solve(
2,
&[Constraint {
reference: 0,
data: 1,
data_first: 20,
data_second: -10,
}],
);
assert_eq!(s.delays, vec![0, 10 + MARGIN]);
}
#[test]
fn the_larger_negative_setup_of_two_checks_on_one_pair_wins() {
let s = solve(
2,
&[
Constraint {
reference: 0,
data: 1,
data_first: -3,
data_second: 8,
},
Constraint {
reference: 0,
data: 1,
data_first: -7,
data_second: 13,
},
],
);
assert_eq!(s.delays, vec![7 + MARGIN, 0]);
}
#[test]
fn an_empty_window_zeroes_its_negative_limit() {
let s = solve(
2,
&[Constraint {
reference: 0,
data: 1,
data_first: -10,
data_second: 5,
}],
);
assert_eq!(s.delays, vec![0, 0]);
assert_eq!(s.effective, vec![(0, 5)]);
assert_eq!(
s.zeroed,
vec![Zeroed {
check: 0,
limit: Limit::DataFirst,
was: -10
}]
);
}
#[test]
fn the_smallest_magnitude_negative_limit_is_zeroed_first() {
let s = solve(
2,
&[
Constraint {
reference: 0,
data: 1,
data_first: -9,
data_second: 12,
},
Constraint {
reference: 1,
data: 0,
data_first: -1,
data_second: 0,
},
],
);
assert_eq!(
s.zeroed,
vec![Zeroed {
check: 1,
limit: Limit::DataFirst,
was: -1
}]
);
assert_eq!(s.effective, vec![(-9, 12), (0, 0)]);
assert_eq!(s.delays, vec![9 + MARGIN, 0]);
}
#[test]
fn a_tie_zeroes_the_first_limit_in_order() {
let s = solve(
2,
&[Constraint {
reference: 0,
data: 1,
data_first: -3,
data_second: -3,
}],
);
assert_eq!(s.zeroed[0].limit, Limit::DataFirst);
assert_eq!(s.effective, vec![(0, 0)]);
assert_eq!(s.zeroed.len(), 2);
assert_eq!(s.delays, vec![0, 0]);
}
#[test]
fn non_negative_checks_do_not_constrain() {
let s = solve(
2,
&[Constraint {
reference: 0,
data: 1,
data_first: 0,
data_second: 1,
}],
);
assert_eq!(s.delays, vec![0, 0]);
assert_eq!(s.zeroed, vec![]);
}
#[test]
fn a_check_of_a_signal_against_itself_is_zeroed() {
let s = solve(
1,
&[Constraint {
reference: 0,
data: 0,
data_first: -3,
data_second: 8,
}],
);
assert_eq!(s.delays, vec![0]);
assert_eq!(s.effective, vec![(0, 8)]);
}
#[test]
fn solutions_are_feasible_and_minimal() {
let checks = [
Constraint {
reference: 0,
data: 1,
data_first: -6,
data_second: 20,
},
Constraint {
reference: 0,
data: 2,
data_first: -4,
data_second: 20,
},
Constraint {
reference: 3,
data: 1,
data_first: 5,
data_second: -2,
},
];
let s = solve(4, &checks);
assert!(s.zeroed.is_empty());
for (c, &(f, h)) in checks.iter().zip(&s.effective) {
let delta = s.delays[c.data] - s.delays[c.reference];
assert!(f - delta >= MARGIN, "{c:?}: setup' {}", f - delta);
assert!(h + delta >= MARGIN, "{c:?}: hold' {}", h + delta);
}
for node in 0..4 {
if s.delays[node] == 0 {
continue;
}
let mut lower = s.delays.clone();
lower[node] -= 1;
let ok = checks.iter().zip(&s.effective).all(|(c, &(f, h))| {
let delta = lower[c.data] - lower[c.reference];
f - delta >= MARGIN && h + delta >= MARGIN
});
assert!(!ok, "node {node} could have been delayed less");
}
}
}