1use std::collections::HashSet;
16use std::fmt;
17
18use crate::aig::{Aig, AigLit, Init, Leaf};
19use crate::fraig;
20use crate::lrat::{self, Lrat};
21use crate::sweep::{MiterError, Verdict};
22use crate::units::{self, Binding, Claim, Unit, Unrolled};
23
24pub struct Ports<'a> {
28 pub in_valid: String,
29 pub in_ready: String,
30 pub in_data: Vec<String>,
31 pub out_valid: String,
32 pub out_ready: Option<String>,
33 pub out_data: Vec<String>,
34 pub token: &'a dyn Fn(usize, &str) -> Binding,
35}
36
37pub struct Environment<'a> {
42 pub frames: usize,
43 pub offer: &'a dyn Fn(usize) -> bool,
44 pub accept: &'a dyn Fn(usize) -> bool,
45 pub other: &'a dyn Fn(&str, usize) -> Binding,
46}
47
48pub struct StreamSide<'a> {
50 pub aig: &'a Aig,
51 pub units: &'a [Unit],
52 pub ports: Ports<'a>,
53}
54
55#[derive(Clone, Debug)]
58pub struct Schedule {
59 pub took: Vec<usize>,
60 pub gave: Vec<usize>,
61 pub proof: Lrat,
62}
63
64#[derive(Clone, Debug, PartialEq, Eq)]
66pub enum StreamError {
67 Miter(MiterError),
68 Port(String),
70 DataDependent { port: String, frame: usize },
72 Undecided { port: String, frame: usize },
74}
75
76impl fmt::Display for StreamError {
77 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78 match self {
79 StreamError::Miter(e) => write!(f, "{e:?}"),
80 StreamError::Port(p) => write!(f, "no port {p}"),
81 StreamError::DataDependent { port, frame } => write!(f, "{port} at clock {frame} depends on the data: the side has no schedule"),
82 StreamError::Undecided { port, frame } => write!(f, "{port} at clock {frame} was not proven constant"),
83 }
84 }
85}
86
87struct Alone {
91 aig: Aig,
92 ready: Vec<AigLit>,
93 valid: Vec<AigLit>,
94}
95
96fn alone(side: &StreamSide, env: &Environment) -> Result<Alone, StreamError> {
97 let p = &side.ports;
98 for port in [&p.in_valid].into_iter().chain(&p.in_data).chain(p.out_ready.iter()) {
99 side.aig.input_index(port).ok_or_else(|| StreamError::Port(port.clone()))?;
100 }
101 for port in [&p.in_ready, &p.out_valid].into_iter().chain(&p.out_data) {
102 side.aig.output_index(port).ok_or_else(|| StreamError::Port(port.clone()))?;
103 }
104 let handshakes = [side.aig.output_index(&p.in_ready).expect("checked"), side.aig.output_index(&p.out_valid).expect("checked")];
105 let cone = side.aig.sequential_cone(&handshakes);
106 let g = &cone;
107 let results: HashSet<&str> = side.units.iter().flat_map(|u| u.results.iter().map(String::as_str)).collect();
108 let data: HashSet<&str> = p.in_data.iter().map(String::as_str).collect();
109 let mut m = Aig::new();
110 let mut shared: std::collections::HashMap<String, AigLit> = std::collections::HashMap::new();
111 let mut state: Vec<AigLit> = g
112 .latches()
113 .iter()
114 .map(|l| match l.init {
115 Init::Zero => AigLit::FALSE,
116 Init::One => AigLit::TRUE,
117 Init::Free => m.add_input(None),
118 })
119 .collect();
120 let (mut ready, mut valid) = (Vec::new(), Vec::new());
121 let (ri, vi) = (0, 1);
122 for k in 0..env.frames {
123 let frame_inputs: Vec<AigLit> = g
124 .inputs()
125 .iter()
126 .map(|port| {
127 let Some(name) = port.name.as_deref() else { return m.add_input(None) };
128 let constant = |b: bool| if b { AigLit::TRUE } else { AigLit::FALSE };
129 if name == p.in_valid {
130 constant((env.offer)(k))
131 } else if p.out_ready.as_deref() == Some(name) {
132 constant((env.accept)(k))
133 } else if data.contains(name) || results.contains(name) {
134 m.add_input(None)
135 } else {
136 match (env.other)(name, k) {
137 Binding::Const(b) => constant(b),
138 Binding::Shared(symbol) => *shared.entry(symbol.clone()).or_insert_with(|| m.add_input(Some(symbol))),
139 Binding::Fresh => m.add_input(None),
140 }
141 }
142 })
143 .collect();
144 let map = m.import(g, &mut |leaf| match leaf {
145 Leaf::Input(i) => frame_inputs[i],
146 Leaf::Latch(j) => state[j],
147 });
148 let at = |l: AigLit| map[l.node() as usize].with_phase(l.is_complemented());
149 ready.push(at(g.outputs()[ri].lit));
150 valid.push(at(g.outputs()[vi].lit));
151 state = g.latches().iter().map(|l| at(l.next)).collect();
152 }
153 Ok(Alone { aig: m, ready, valid })
154}
155
156fn guesses(a: &Alone, seed: u64) -> Vec<Option<bool>> {
159 let words = 8;
160 let mut state = seed | 1;
161 let mut next = move || {
162 state ^= state << 13;
163 state ^= state >> 7;
164 state ^= state << 17;
165 state
166 };
167 let inputs: Vec<u64> = (0..a.aig.inputs().len() * words).map(|_| next()).collect();
168 let values = a.aig.simulate(words, &inputs, &[]);
169 a.ready
170 .iter()
171 .chain(&a.valid)
172 .map(|&l| {
173 let word = |w: usize| Aig::word(&values, words, l, w);
174 match (0..words).map(word).collect::<Vec<_>>() {
175 v if v.iter().all(|&x| x == 0) => Some(false),
176 v if v.iter().all(|&x| x == !0) => Some(true),
177 _ => None,
178 }
179 })
180 .collect()
181}
182
183fn handshake_pairs(a: &Alone, guessed: &[bool]) -> Vec<(AigLit, AigLit)> {
185 a.ready.iter().chain(&a.valid).zip(guessed).map(|(&l, &b)| (l, if b { AigLit::TRUE } else { AigLit::FALSE })).collect()
186}
187
188fn relevant(env: &Environment, n: usize) -> Vec<usize> {
191 (0..n).filter(|&k| (env.offer)(k)).chain((0..n).filter(|&k| (env.accept)(k)).map(|k| n + k)).collect()
192}
193
194pub fn schedule(side: &StreamSide, env: &Environment, opts: &fraig::Options) -> Result<Schedule, StreamError> {
196 let a = alone(side, env)?;
197 let n = a.ready.len();
198 let name = |i: usize| if i < n { (side.ports.in_ready.clone(), i) } else { (side.ports.out_valid.clone(), i - n) };
199 let all = guesses(&a, opts.seed);
200 let mut guessed = vec![false; 2 * n];
201 let used = relevant(env, n);
202 for &i in &used {
203 guessed[i] = all[i].ok_or_else(|| {
204 let (port, frame) = name(i);
205 StreamError::DataDependent { port, frame }
206 })?;
207 }
208 let pairs = handshake_pairs(&a, &guessed);
209 let asked: Vec<(AigLit, AigLit)> = used.iter().map(|&i| pairs[i]).collect();
210 let (verdicts, proof, _) = fraig::decide(&a.aig, &asked, opts);
211 for (&i, v) in used.iter().zip(&verdicts) {
212 let (port, frame) = name(i);
213 match v {
214 Verdict::Equal => {}
215 Verdict::Differ { .. } => return Err(StreamError::DataDependent { port, frame }),
216 Verdict::Unknown(_) => return Err(StreamError::Undecided { port, frame }),
217 }
218 }
219 let took = (0..n).filter(|&k| (env.offer)(k) && guessed[k]).collect();
220 let gave = (0..n).filter(|&k| (env.accept)(k) && guessed[n + k]).collect();
221 Ok(Schedule { took, gave, proof })
222}
223
224impl Schedule {
225 pub fn check(&self, side: &StreamSide, env: &Environment) -> bool {
228 if !self.took.windows(2).all(|w| w[0] < w[1]) || !self.gave.windows(2).all(|w| w[0] < w[1]) {
229 return false;
230 }
231 let Ok(a) = alone(side, env) else { return false };
232 let n = a.ready.len();
233 let took: HashSet<usize> = self.took.iter().copied().collect();
234 let gave: HashSet<usize> = self.gave.iter().copied().collect();
235 let guessed: Vec<bool> = (0..n).map(|k| (env.offer)(k) && took.contains(&k)).chain((0..n).map(|k| (env.accept)(k) && gave.contains(&k))).collect();
238 if self.took.iter().any(|&k| k >= n || !(env.offer)(k)) || self.gave.iter().any(|&k| k >= n || !(env.accept)(k)) {
239 return false;
240 }
241 let known = relevant(env, n);
242 let pairs = handshake_pairs(&a, &guessed);
243 let claims: Vec<Vec<crate::cdcl::Lit>> =
244 known.iter().filter(|&&i| pairs[i].0 != pairs[i].1).flat_map(|&i| fraig::claim(pairs[i].0, pairs[i].1)).collect();
245 lrat::check_derivation(a.aig.num_nodes(), &fraig::formula(&a.aig), &self.proof, &claims)
246 }
247}
248
249#[derive(Clone, Debug)]
252pub struct Report {
253 pub verdicts: Vec<(String, Verdict)>,
254 pub schedules: [Schedule; 2],
255 pub units: units::Report,
256 pub gave: [usize; 2],
257}
258
259impl Report {
260 pub fn all_equal(&self) -> bool {
261 self.verdicts.iter().all(|(_, v)| *v == Verdict::Equal)
262 }
263}
264
265fn bound<'a>(side: &'a StreamSide<'a>, env: &'a Environment<'a>, took: &'a [usize]) -> impl Fn(&str, usize) -> Binding + 'a {
268 let data: HashSet<String> = side.ports.in_data.iter().cloned().collect();
269 move |name: &str, k: usize| {
270 if name == side.ports.in_valid {
271 Binding::Const((env.offer)(k))
272 } else if side.ports.out_ready.as_deref() == Some(name) {
273 Binding::Const((env.accept)(k))
274 } else if data.contains(name) {
275 match took.iter().position(|&t| t == k) {
276 Some(j) => (side.ports.token)(j, name),
277 None => Binding::Fresh,
278 }
279 } else {
280 (env.other)(name, k)
281 }
282 }
283}
284
285fn claims(side: &StreamSide, gave: &[usize], tokens: usize) -> Vec<Claim> {
287 gave[..tokens]
288 .iter()
289 .enumerate()
290 .flat_map(|(k, &frame)| side.ports.out_data.iter().enumerate().map(move |(i, bit)| Claim { name: format!("out{k}.{i}"), output: bit.clone(), frame }))
291 .collect()
292}
293
294pub fn decide(
297 left: &StreamSide,
298 left_env: &Environment,
299 right: &StreamSide,
300 right_env: &Environment,
301 functions: &[(String, String)],
302 opts: &fraig::Options,
303) -> Result<Report, StreamError> {
304 if left.ports.out_data.len() != right.ports.out_data.len() {
305 return Err(StreamError::Port(format!("{} output bits against {}", left.ports.out_data.len(), right.ports.out_data.len())));
306 }
307 let (ls, rs) = (schedule(left, left_env, opts)?, schedule(right, right_env, opts)?);
308 let tokens = ls.gave.len().min(rs.gave.len());
309 let (lc, rc) = (claims(left, &ls.gave, tokens), claims(right, &rs.gave, tokens));
310 let (lt, rt) = (ls.took.clone(), rs.took.clone());
311 let (lb, rb) = (bound(left, left_env, <), bound(right, right_env, &rt));
312 let l = Unrolled { aig: left.aig, frames: left_env.frames, bind: &lb, claims: &lc, units: left.units };
313 let r = Unrolled { aig: right.aig, frames: right_env.frames, bind: &rb, claims: &rc, units: right.units };
314 let report = units::decide_unrolled(&l, &r, functions, opts).map_err(StreamError::Miter)?;
315 let gave = [ls.gave.len(), rs.gave.len()];
316 Ok(Report { verdicts: report.verdicts.clone(), schedules: [ls, rs], units: report, gave })
317}
318
319impl Report {
320 pub fn check(&self, left: &StreamSide, left_env: &Environment, right: &StreamSide, right_env: &Environment, functions: &[(String, String)]) -> bool {
322 let [ls, rs] = &self.schedules;
323 if !ls.check(left, left_env) || !rs.check(right, right_env) {
324 return false;
325 }
326 let tokens = ls.gave.len().min(rs.gave.len());
327 if ls.gave.len() > left_env.frames || rs.gave.len() > right_env.frames {
328 return false;
329 }
330 let (lc, rc) = (claims(left, &ls.gave, tokens), claims(right, &rs.gave, tokens));
331 let (lb, rb) = (bound(left, left_env, &ls.took), bound(right, right_env, &rs.took));
332 let l = Unrolled { aig: left.aig, frames: left_env.frames, bind: &lb, claims: &lc, units: left.units };
333 let r = Unrolled { aig: right.aig, frames: right_env.frames, bind: &rb, claims: &rc, units: right.units };
334 self.units.check_unrolled(&l, &r, functions)
335 }
336}
337
338#[cfg(test)]
339#[path = "../../logicaffeine_tests/unit/logicaffeine_proof/streams/tests.rs"]
340mod tests;