llhd/pass/
deseq.rs

1// Copyright (c) 2017-2021 Fabian Schuiki
2
3//! Desequentialization
4
5use crate::{
6    analysis::{TemporalRegion, TemporalRegionGraph},
7    ir::{prelude::*, InstData},
8    opt::prelude::*,
9    value::IntValue,
10};
11use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
12
13/// Desequentialization
14///
15/// This pass implements detection of state-keeping behaviour in processes and
16/// the extraction of such state into explicit `reg` instructions.
17pub struct Desequentialization;
18
19impl Pass for Desequentialization {
20    fn run_on_cfg(ctx: &PassContext, unit: &mut UnitBuilder) -> bool {
21        if unit.kind() == UnitKind::Process {
22            match deseq_process(ctx, unit) {
23                Some(entity) => {
24                    *unit.data() = entity;
25                    true
26                }
27                _ => false,
28            }
29        } else {
30            false
31        }
32    }
33}
34
35fn deseq_process(ctx: &PassContext, unit: &mut UnitBuilder) -> Option<UnitData> {
36    info!("Deseq [{}]", unit.name());
37    let trg = unit.trg();
38
39    // Identify the relevant temporal regions.
40    if trg.regions().count() != 2 {
41        trace!("Skipping (incorrect number of TRs)");
42        return None;
43    }
44    let (tr0, tr1) = {
45        let mut it = trg.regions();
46        (it.next().unwrap().id, it.next().unwrap().id)
47    };
48    if !trg[tr0].entry {
49        trace!("Skipping (TR0 is not entry)");
50        return None;
51    }
52    if trg[tr1].entry {
53        trace!("Skipping (TR1 is entry)");
54        return None;
55    }
56    trace!("Head region {}, trigger region {}", tr0, tr1);
57
58    // Identify the wait instruction and the signals which may trigger a state
59    // change.
60    let (wait_inst, sensitivity) = {
61        let mut it = trg[tr0].tail_insts();
62        let inst = match it.next() {
63            Some(i) => i,
64            None => {
65                trace!("Skipping ({} has no tail inst)", tr0);
66                return None;
67            }
68        };
69        let data = &unit[inst];
70        let sensitivity: BTreeSet<_> = match data.opcode() {
71            Opcode::Wait => data.args().iter().cloned().collect(),
72            Opcode::WaitTime => data.args().iter().skip(1).cloned().collect(),
73            _ => {
74                trace!("Skipping ({} tail inst is not a wait)", tr0);
75                return None;
76            }
77        };
78        (inst, sensitivity)
79    };
80    trace!("Wait Inst: {}", wait_inst.dump(&unit));
81    trace!("Sensitivity: {:?}", sensitivity);
82
83    // Ensure that there is is only one basic block per temporal region.
84    // Lowering more complicated scenarios is possible, but is left for a future
85    // extension.
86    let tr0_num_bb = trg[tr0].blocks().count();
87    let tr1_num_bb = trg[tr1].blocks().count();
88    if tr0_num_bb != 1 || tr1_num_bb != 1 {
89        trace!(
90            "Skipping ({} TR0 blocks, {} TR1 blocks, instead of 1 each)",
91            tr0_num_bb,
92            tr1_num_bb
93        );
94        return None;
95    }
96
97    // Find the canonicalized drive conditions.
98    let mut all_drives = HashSet::new();
99    let mut conds = vec![];
100    for bb in unit.blocks() {
101        for inst in unit.insts(bb) {
102            let data = &unit[inst];
103            if data.opcode() == Opcode::DrvCond {
104                trace!("Canonicalizing condition of {}", inst.dump(&unit));
105                conds.push((
106                    inst,
107                    bb,
108                    canonicalize(ctx, unit, &trg, data.args()[3], false),
109                ));
110                all_drives.insert(inst);
111            } else if data.opcode() == Opcode::Drv {
112                all_drives.insert(inst);
113            }
114        }
115    }
116
117    // Detect the edges and levels for each drive that trigger a state change.
118    let triggers: Vec<(Inst, Block, Vec<Trigger>)> = conds
119        .iter()
120        .flat_map(|(inst, bb, dnf)| {
121            detect_triggers(ctx, unit, tr0, tr1, dnf).map(|trig| (*inst, *bb, trig))
122        })
123        .collect();
124
125    // Create a replacement entity.
126    let mut entity = UnitData::new(UnitKind::Entity, unit.name().clone(), unit.sig().clone());
127    let mut builder = UnitBuilder::new_anonymous(&mut entity);
128    for arg in unit.unit().sig().args() {
129        if let Some(name) = unit.get_name(unit.arg_value(arg)) {
130            let v = builder.arg_value(arg);
131            builder.set_name(v, name.to_string());
132        }
133    }
134    let mut mig = Migrator::new(unit, &mut builder, &trg, tr0, tr1);
135
136    // For each drive where we successfully and exhaustively identified the
137    // triggers, migrate the computation of each next state into a separate
138    // entity.
139    let mut migrated = true;
140    for (inst, bb, trigs) in triggers {
141        migrated &= mig.migrate_drive(inst, bb, &trigs);
142    }
143    // crate::pass::ConstFolding::run_on_entity(ctx, &mut builder);
144    // crate::pass::DeadCodeElim::run_on_entity(ctx, &mut builder);
145
146    // Check if all drives were migrated.
147    // This will currently fail for any unconditional drives, since we don't yet
148    // handle them properly.
149    all_drives
150        .difference(&mig.migrated_drives)
151        .for_each(|inst| {
152            migrated = false;
153            trace!("Skipping ({} not migrated)", inst.dump(&unit));
154        });
155
156    if migrated {
157        Some(entity)
158    } else {
159        trace!("Process {} not migrated", unit.name());
160        None
161    }
162}
163
164/// Canonicalize the conditions of a drive.
165///
166/// This function attempts to bring the drive condition into disjunctive normal
167/// form (DNF), and establish equality/inequality relationships with input
168/// signals where possible.
169fn canonicalize(
170    ctx: &PassContext,
171    unit: &UnitBuilder,
172    trg: &TemporalRegionGraph,
173    cond: Value,
174    inv: bool,
175) -> Dnf {
176    let dnf = canonicalize_inner(ctx, unit, trg, cond, inv);
177    let desc = if let Some(inst) = unit.get_value_inst(cond) {
178        inst.dump(&unit).to_string()
179    } else {
180        cond.dump(&unit).to_string()
181    };
182    trace!(
183        "  {} {{ {} }} => {}",
184        if inv { "neg" } else { "pos" },
185        desc,
186        dnf.dump(&unit),
187    );
188    dnf
189}
190
191fn canonicalize_inner(
192    ctx: &PassContext,
193    unit: &UnitBuilder,
194    trg: &TemporalRegionGraph,
195    cond: Value,
196    inv: bool,
197) -> Dnf {
198    let dfg = unit;
199
200    // Don't bother with values of the wrong type.
201    let ty = unit.value_type(cond);
202    if ty != crate::ty::int_ty(1) {
203        return Dnf::single(Term::Invalid(cond), inv);
204    }
205
206    // Canonicalize instructions.
207    if let Some(inst) = unit.get_value_inst(cond) {
208        let data = &dfg[inst];
209        match data.opcode() {
210            Opcode::ConstInt => {
211                return Dnf::single(Term::Zero, data.get_const_int().unwrap().is_one() ^ inv);
212            }
213            Opcode::Not => return canonicalize(ctx, unit, trg, data.args()[0], !inv),
214            Opcode::And | Opcode::Or => {
215                let lhs = canonicalize(ctx, unit, trg, data.args()[0], inv);
216                let rhs = canonicalize(ctx, unit, trg, data.args()[1], inv);
217                let out = match (data.opcode(), inv) {
218                    (Opcode::And, false) | (Opcode::Or, true) => Dnf::and(&lhs, &rhs),
219                    (Opcode::And, true) | (Opcode::Or, false) => Dnf::or(&lhs, &rhs),
220                    _ => unreachable!(),
221                };
222                return out;
223            }
224            Opcode::Xor | Opcode::Eq | Opcode::Neq => {
225                let lhs_pos = canonicalize(ctx, unit, trg, data.args()[0], false);
226                let rhs_pos = canonicalize(ctx, unit, trg, data.args()[1], false);
227                let lhs_neg = canonicalize(ctx, unit, trg, data.args()[0], true);
228                let rhs_neg = canonicalize(ctx, unit, trg, data.args()[1], true);
229                let polarity = match data.opcode() {
230                    Opcode::Eq => !inv,
231                    _ => inv,
232                };
233                let out = if polarity {
234                    Dnf::or(&Dnf::and(&lhs_pos, &rhs_pos), &Dnf::and(&lhs_neg, &rhs_neg))
235                } else {
236                    Dnf::or(&Dnf::and(&lhs_pos, &rhs_neg), &Dnf::and(&lhs_neg, &rhs_pos))
237                };
238                return out;
239            }
240            Opcode::Prb => {
241                let bb = unit.inst_block(inst).unwrap();
242                return Dnf::single(Term::Signal(data.args()[0], trg[bb]), inv);
243            }
244            _ => (),
245        }
246    }
247    Dnf::single(Term::Invalid(cond), inv)
248}
249
250/// An expression in disjunctive normal form.
251///
252/// A constant `0` is represented as `{}`. A constant `1` is represented as
253/// `{{}}`.
254struct Dnf(BTreeSet<BTreeMap<Term, bool>>);
255
256impl Dnf {
257    /// Create the zero expression `0`.
258    pub fn zero() -> Dnf {
259        Dnf(BTreeSet::new())
260    }
261
262    /// Create the identity expression `1`.
263    pub fn one() -> Dnf {
264        let mut set = BTreeSet::new();
265        set.insert(Default::default());
266        Dnf(set)
267    }
268
269    /// Create a single-term expression.
270    pub fn single(term: Term, inv: bool) -> Dnf {
271        match term {
272            Term::Zero if inv => Self::one(),
273            Term::Zero => Self::zero(),
274            _ => {
275                let mut set = BTreeSet::new();
276                set.insert(Some((term, inv)).into_iter().collect());
277                Dnf(set)
278            }
279        }
280    }
281
282    pub fn dump<'a>(&'a self, unit: &Unit<'a>) -> DnfDumper<'a> {
283        DnfDumper(self, *unit)
284    }
285
286    pub fn dump_term<'a>(term: &'a BTreeMap<Term, bool>, unit: &Unit<'a>) -> DnfTermDumper<'a> {
287        DnfTermDumper(term, *unit)
288    }
289
290    /// Compute the boolean OR of two DNF expressions.
291    pub fn or(lhs: &Dnf, rhs: &Dnf) -> Dnf {
292        let lhs = lhs.0.iter().cloned();
293        let rhs = rhs.0.iter().cloned();
294        Dnf(lhs.chain(rhs).collect())
295    }
296
297    /// Compute the boolean AND of two DNF expressions.
298    pub fn and(lhs: &Dnf, rhs: &Dnf) -> Dnf {
299        let mut set = BTreeSet::new();
300        for lhs_term in &lhs.0 {
301            for rhs_term in &rhs.0 {
302                if let Some(term) = Self::and_term(lhs_term, rhs_term) {
303                    set.insert(term);
304                }
305            }
306        }
307        Dnf(set)
308    }
309
310    /// Compute the boolean AND between two inner terms.
311    fn and_term(
312        lhs: &BTreeMap<Term, bool>,
313        rhs: &BTreeMap<Term, bool>,
314    ) -> Option<BTreeMap<Term, bool>> {
315        let mut out = BTreeMap::new();
316        for (term, &inv) in lhs.iter().chain(rhs.iter()) {
317            // If we insert a term whose complement is already in the AND
318            // expression, the resulting expression is always false.
319            if out.insert(term.clone(), inv) == Some(!inv) {
320                return None;
321            }
322        }
323        Some(out)
324    }
325}
326
327struct DnfDumper<'a>(&'a Dnf, Unit<'a>);
328
329impl std::fmt::Display for DnfDumper<'_> {
330    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
331        use std::iter::{once, repeat};
332        if (self.0).0.is_empty() {
333            return write!(f, "0");
334        }
335        if (self.0).0.len() == 1 {
336            if (self.0).0.iter().next().unwrap().is_empty() {
337                return write!(f, "1");
338            }
339        }
340        for (vs, sep) in (self.0).0.iter().zip(once("").chain(repeat(" | "))) {
341            write!(f, "{}({})", sep, Dnf::dump_term(vs, &self.1))?;
342        }
343        Ok(())
344    }
345}
346
347struct DnfTermDumper<'a>(&'a BTreeMap<Term, bool>, Unit<'a>);
348
349impl std::fmt::Display for DnfTermDumper<'_> {
350    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
351        use std::iter::{once, repeat};
352        if (self.0).is_empty() {
353            return write!(f, "1");
354        }
355        for ((term, inv), sep) in self.0.iter().zip(once("").chain(repeat(" & "))) {
356            write!(f, "{}", sep)?;
357            if *inv {
358                write!(f, "!")?;
359            }
360            match term {
361                Term::Zero => write!(f, "0")?,
362                Term::Signal(sig, tr) => write!(f, "{}@{}", sig.dump(&self.1), tr)?,
363                Term::Invalid(v) => write!(f, "{}?", v.dump(&self.1))?,
364            }
365        }
366        Ok(())
367    }
368}
369
370#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
371enum Term {
372    Zero,
373    Signal(Value, TemporalRegion),
374    Invalid(Value),
375}
376
377/// Detect the edge and level triggers described by a DNF.
378fn detect_triggers(
379    ctx: &PassContext,
380    unit: &UnitBuilder,
381    tr0: TemporalRegion,
382    tr1: TemporalRegion,
383    dnf: &Dnf,
384) -> Option<Vec<Trigger>> {
385    trace!("Detecting triggers in {}", dnf.dump(&unit));
386    let mut trigs = vec![];
387    for conds in &dnf.0 {
388        let trig = match detect_term_triggers(ctx, unit, tr0, tr1, conds) {
389            Some(trig) => trig,
390            None => return None,
391        };
392        trigs.push(trig);
393    }
394    Some(trigs)
395}
396
397fn detect_term_triggers(
398    _ctx: &PassContext,
399    unit: &UnitBuilder,
400    tr0: TemporalRegion,
401    tr1: TemporalRegion,
402    conds: &BTreeMap<Term, bool>,
403) -> Option<Trigger> {
404    trace!("  Analyzing {}", Dnf::dump_term(conds, &unit));
405
406    // Sort the level and edge sensitive terms.
407    let mut edges = BTreeMap::new();
408    let mut levels = BTreeMap::new();
409    for (term, &inv) in conds {
410        match *term {
411            // Signals sampled before the change must be accompanied by the
412            // same signal sampled after the change, but inverted.
413            Term::Signal(sig, tr) if tr == tr0 => {
414                if conds.get(&Term::Signal(sig, tr1)).cloned() == Some(!inv) {
415                    trace!(
416                        "    {} {}",
417                        if inv { "rising" } else { "falling" },
418                        sig.dump(&unit)
419                    );
420                    edges.insert(
421                        sig,
422                        match inv {
423                            true => TriggerEdge::Rise,
424                            false => TriggerEdge::Fall,
425                        },
426                    );
427                } else {
428                    trace!(
429                        "    Skipping ({}@{} without corresponding {}@{})",
430                        sig.dump(&unit),
431                        tr0,
432                        sig.dump(&unit),
433                        tr1
434                    );
435                    return None;
436                }
437            }
438
439            // Signals sampled after the change without accompanying
440            // sampling before the change contribute a level sensitivity.
441            Term::Signal(sig, tr) if tr == tr1 => {
442                if conds.get(&Term::Signal(sig, tr0)).cloned() != Some(!inv) {
443                    trace!(
444                        "    {} {}",
445                        if inv { "low" } else { "high" },
446                        sig.dump(&unit)
447                    );
448                    levels.insert(
449                        sig,
450                        match inv {
451                            false => TriggerLevel::High,
452                            true => TriggerLevel::Low,
453                        },
454                    );
455                }
456            }
457
458            _ => {
459                trace!("    Skipping (invalid term)");
460                return None;
461            }
462        }
463    }
464
465    // Discard multi-edge triggers.
466    if edges.len() > 1 {
467        trace!("    Skipping (multiple edge triggers)");
468        return None;
469    }
470
471    // Either formulate an edge trigger with the levels as conditions, or a
472    // purely level-sensitive trigger.
473    if let Some((sig, edge)) = edges.into_iter().next() {
474        Some(Trigger::Edge(sig, edge, levels))
475    } else {
476        Some(Trigger::Level(levels))
477    }
478}
479
480#[derive(Debug)]
481enum Trigger {
482    Edge(Value, TriggerEdge, BTreeMap<Value, TriggerLevel>),
483    Level(BTreeMap<Value, TriggerLevel>),
484}
485
486#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
487enum TriggerEdge {
488    Rise,
489    Fall,
490}
491
492#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
493enum TriggerLevel {
494    Low,
495    High,
496}
497
498/// A helper struct to migrate data flow into an entity.
499struct Migrator<'a, 'b> {
500    src: &'a UnitBuilder<'b>,
501    dst: &'a mut UnitBuilder<'b>,
502    trg: &'a TemporalRegionGraph,
503    tr0: TemporalRegion,
504    tr1: TemporalRegion,
505    /// Cache of already-migrated instructions.
506    cache: HashMap<InstData, Value>,
507    /// Set of migrated drives in `src`.
508    migrated_drives: HashSet<Inst>,
509}
510
511impl<'a, 'b> Migrator<'a, 'b> {
512    pub fn new(
513        src: &'a UnitBuilder<'b>,
514        dst: &'a mut UnitBuilder<'b>,
515        trg: &'a TemporalRegionGraph,
516        tr0: TemporalRegion,
517        tr1: TemporalRegion,
518    ) -> Self {
519        Self {
520            src,
521            dst,
522            trg,
523            tr0,
524            tr1,
525            cache: Default::default(),
526            migrated_drives: Default::default(),
527        }
528    }
529
530    pub fn migrate_drive(&mut self, drive: Inst, _bb: Block, trigs: &Vec<Trigger>) -> bool {
531        trace!("Migrating {}", drive.dump(&self.src));
532        let drive_target = self.src[drive].args()[0];
533        let drive_value = self.src[drive].args()[1];
534
535        let mig_target = match self.migrate_value(drive_target, &Default::default()) {
536            Some(v) => v,
537            None => return false,
538        };
539
540        let mut reg_triggers = vec![];
541        for trig in trigs {
542            trace!("  Migrating {:?}", trig);
543            match trig {
544                Trigger::Edge(sig, edge, conds) => {
545                    // Setup a map of known signal values, per temporal region.
546                    // Start out with just the trigger.
547                    let mut ties = BTreeMap::new();
548                    let (l0, l1) = match edge {
549                        TriggerEdge::Rise => (TriggerLevel::Low, TriggerLevel::High),
550                        TriggerEdge::Fall => (TriggerLevel::High, TriggerLevel::Low),
551                    };
552                    ties.insert((*sig, self.tr0), l0);
553                    ties.insert((*sig, self.tr1), l1);
554
555                    // Migrate the conditions.
556                    let mut gate = None;
557                    for (&sig, &level) in conds {
558                        ties.insert((sig, self.tr1), level);
559                        let value = self.dst.ins().prb(sig);
560                        let value = match level {
561                            TriggerLevel::Low => self.dst.ins().not(value),
562                            TriggerLevel::High => value,
563                        };
564                        gate = Some(match gate {
565                            Some(gate) => self.dst.ins().and(gate, value),
566                            None => value,
567                        });
568                    }
569
570                    // Migrate the value computation.
571                    let data = match self.migrate_value(drive_value, &ties) {
572                        Some(v) => v,
573                        None => return false,
574                    };
575
576                    // Keep track of this trigger.
577                    let trigger = self.dst.ins().prb(*sig);
578                    let mode = match edge {
579                        TriggerEdge::Rise => RegMode::Rise,
580                        TriggerEdge::Fall => RegMode::Fall,
581                    };
582                    reg_triggers.push(RegTrigger {
583                        data,
584                        mode,
585                        trigger,
586                        gate,
587                    });
588                }
589                Trigger::Level(conds) => {
590                    // Migrate the conditions.
591                    let mut ties = BTreeMap::new();
592                    let mut trigger = None;
593                    for (&sig, &level) in conds {
594                        ties.insert((sig, self.tr1), level);
595                        let value = self.dst.ins().prb(sig);
596                        let value = match level {
597                            TriggerLevel::Low => self.dst.ins().not(value),
598                            TriggerLevel::High => value,
599                        };
600                        trigger = Some(match trigger {
601                            Some(trigger) => self.dst.ins().and(trigger, value),
602                            None => value,
603                        });
604                    }
605                    let trigger = match trigger {
606                        Some(c) => c,
607                        None => {
608                            trace!(
609                                "    Skipping {} (level-sensitive with no trigger)",
610                                drive.dump(&self.src)
611                            );
612                            return false;
613                        }
614                    };
615
616                    // Migrate the value computation.
617                    let data = match self.migrate_value(drive_value, &ties) {
618                        Some(v) => v,
619                        None => return false,
620                    };
621
622                    // Keep track of this trigger.
623                    reg_triggers.push(RegTrigger {
624                        data,
625                        mode: RegMode::High,
626                        trigger,
627                        gate: None,
628                    });
629                }
630            }
631        }
632
633        // Create the register instruction.
634        self.dst.ins().reg(mig_target, reg_triggers);
635
636        // Drive the register value onto the output.
637        self.migrated_drives.insert(drive);
638        true
639    }
640
641    pub fn migrate_value(
642        &mut self,
643        value: Value,
644        ties: &BTreeMap<(Value, TemporalRegion), TriggerLevel>,
645    ) -> Option<Value> {
646        // Migrate arguments.
647        if let Some(arg) = self.src.get_value_arg(value) {
648            return Some(self.dst.arg_value(arg));
649        }
650
651        // Migrate instructions.
652        if let Some(inst) = self.src.get_value_inst(value) {
653            let bb = self.src.inst_block(inst)?;
654            let tr = self.trg[bb];
655
656            // Handle signal probes.
657            if self.src[inst].opcode() == Opcode::Prb {
658                // See if this signal is tied to a fixed value.
659                let sig = self.src[inst].args()[0];
660                if let Some(&level) = ties.get(&(sig, tr)) {
661                    let data = InstData::ConstInt {
662                        opcode: Opcode::ConstInt,
663                        imm: IntValue::from_usize(
664                            1,
665                            match level {
666                                TriggerLevel::High => 1,
667                                TriggerLevel::Low => 0,
668                            },
669                        ),
670                    };
671                    return Some(self.migrate_inst_data(data, value));
672                }
673
674                // Otherwise ensure that the probe occurs *after* the trigger.
675                // This is a requirement for modeling the behaviour with `reg`.
676                if tr != self.tr1 {
677                    trace!("    Skipping {} (probe in wrong TR)", inst.dump(&self.src));
678                    return None;
679                }
680            }
681
682            // Handle regular signals.
683            let mut data = self.src[inst].clone();
684            #[allow(deprecated)]
685            for arg in data.args_mut() {
686                *arg = self.migrate_value(*arg, ties)?;
687            }
688            return Some(self.migrate_inst_data(data, value));
689        }
690
691        // Otherwise just refuse to migrate.
692        trace!(
693            "    Skipping {} (cannot be migrated)",
694            value.dump(&self.src)
695        );
696        None
697    }
698
699    fn migrate_inst_data(&mut self, data: InstData, src_value: Value) -> Value {
700        if let Some(&v) = self.cache.get(&data) {
701            v
702        } else {
703            trace!("    Migrated {}", src_value.dump(&self.src));
704            let ty = self.src.value_type(src_value);
705            let inst = self.dst.ins().build(data.clone(), ty);
706            let value = self.dst.inst_result(inst);
707            self.cache.insert(data, value);
708            if let Some(name) = self.src.get_name(src_value) {
709                self.dst.set_name(value, name.to_string());
710            }
711            value
712        }
713    }
714}