vj-sim 0.3.0

Netlist, scheduler and process model of the vj SystemVerilog simulator.
Documentation
//! A program with some constraint blocks left out (IEEE 1800-2023 18.9).
//!
//! `constraint_mode(0)` makes a block inactive: nothing it holds applies to the
//! call. That is its boolean constraints, and also every `dist`, `solve ...
//! before` ordering, constraint over `real` variables and `disable soft` it
//! contains, and the `soft` constraints it declares (a `disable soft` of an
//! inactive block no longer discards the softs it named). [`without_blocks`]
//! builds the [`Program`] one call then solves: every table that holds block
//! content has the blocks' entries removed and the hidden variables that name
//! entries by position (the selector of `dist` `j`, the discard flag of soft
//! `k`) are renumbered, so the solver never sees what was switched off, not
//! even as an unused weight to evaluate.

use super::dist::SELECTOR_BASE;
use super::ir::Expr;
use super::priority::SOFT_BASE;
use super::registry::{Outcome, ProgBlock, Program};
use std::ops::Range;

/// What a constraint block holds besides boolean conjuncts: a range of each
/// table of its program (or of the builder's [`ConstraintSet`], empty when the
/// block added nothing to that table).
///
/// [`ConstraintSet`]: super::build::ConstraintSet
#[derive(Clone, Debug, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct BlockItems {
    /// `dist`s ([`Program::dists`]), those of the block's `soft` constraints
    /// included.
    pub dists: Range<usize>,
    /// `soft` constraints ([`Program::softs`]).
    pub softs: Range<usize>,
    /// `solve ... before` edges over integral variables ([`Program::before`]).
    pub before: Range<usize>,
    /// Constraints over real variables (`real.conds` of the program).
    pub real_conds: Range<usize>,
    /// Real `dist`s (`real.dists` of the program).
    pub real_dists: Range<usize>,
    /// `solve ... before` edges over real variables (`real.before` of the
    /// program).
    pub real_before: Range<usize>,
    /// `disable soft`s: indices into the table [`super::priority::Soft::disabled_by`]
    /// names ([`Program::n_disables`] counts it).
    pub disables: Range<usize>,
}

impl BlockItems {
    /// These ranges moved by `by`: the block's items in a table the member's own
    /// table was appended to at `by`.
    pub(super) fn shifted(&self, by: &ItemOffsets) -> BlockItems {
        let mv = |r: &Range<usize>, n: usize| r.start + n..r.end + n;
        BlockItems {
            dists: mv(&self.dists, by.dists),
            softs: mv(&self.softs, by.softs),
            before: mv(&self.before, by.before),
            real_conds: mv(&self.real_conds, 0),
            real_dists: mv(&self.real_dists, 0),
            real_before: mv(&self.real_before, 0),
            disables: mv(&self.disables, by.disables),
        }
    }
}

/// Where a program's tables start inside a merged program's ([`BlockItems::shifted`]).
/// The real tables are never merged (a joint solve refuses real variables).
pub(super) struct ItemOffsets {
    pub dists: usize,
    pub softs: usize,
    pub before: usize,
    pub disables: usize,
}

/// Which entries of a table of `len` entries the `ranges` cover.
fn covered<'a>(
    len: usize,
    ranges: impl Iterator<Item = &'a Range<usize>>,
) -> Result<Vec<bool>, Outcome> {
    let mut gone = vec![false; len];
    for r in ranges {
        gone.get_mut(r.clone()).ok_or(Outcome::Failed)?.fill(true);
    }
    Ok(gone)
}

/// The new position of every entry of a table after the `gone` ones are
/// removed.
fn renumber(gone: &[bool]) -> Vec<Option<u32>> {
    let mut next = 0u32;
    gone.iter()
        .map(|&g| {
            (!g).then(|| {
                next += 1;
                next - 1
            })
        })
        .collect()
}

/// The entries of `items` not marked in `gone`.
fn keep<T: Clone>(items: &[T], gone: &[bool]) -> Vec<T> {
    items
        .iter()
        .zip(gone)
        .filter(|&(_, &g)| !g)
        .map(|(x, _)| x.clone())
        .collect()
}

/// `program` without what the constraint blocks `off` hold (module docs).
/// The result has no blocks: it is for one call. [`Outcome::Failed`] when the
/// program is malformed (a range outside its table, a constraint that names a
/// `dist` or soft of a block that is not in `off`'s company).
pub(super) fn without_blocks(program: &Program, off: &[&ProgBlock]) -> Result<Program, Outcome> {
    let gone_conj = covered(program.conjuncts.len(), off.iter().map(|b| &b.conjuncts))?;
    let gone_dist = covered(program.dists.len(), off.iter().map(|b| &b.items.dists))?;
    let gone_soft = covered(program.softs.len(), off.iter().map(|b| &b.items.softs))?;
    let gone_before = covered(program.before.len(), off.iter().map(|b| &b.items.before))?;
    let gone_rc = covered(
        program.real.conds.len(),
        off.iter().map(|b| &b.items.real_conds),
    )?;
    let gone_rd = covered(
        program.real.dists.len(),
        off.iter().map(|b| &b.items.real_dists),
    )?;
    let gone_rb = covered(
        program.real.before.len(),
        off.iter().map(|b| &b.items.real_before),
    )?;
    let gone_disable = covered(program.n_disables, off.iter().map(|b| &b.items.disables))?;
    let conj_at = renumber(&gone_conj);
    let dist_at = renumber(&gone_dist);
    let soft_at = renumber(&gone_soft);

    // The hidden variables of the surviving conjuncts, renumbered to the
    // surviving tables.
    let mut dangling = false;
    let mut conjuncts: Vec<Expr> = Vec::with_capacity(program.conjuncts.len());
    for (c, _) in program
        .conjuncts
        .iter()
        .zip(&gone_conj)
        .filter(|&(_, &g)| !g)
    {
        conjuncts.push(c.substitute_vars(&mut |v, t| {
            let (base, at) = if v >= SOFT_BASE {
                (SOFT_BASE, &soft_at)
            } else if v >= SELECTOR_BASE {
                (SELECTOR_BASE, &dist_at)
            } else {
                return Expr::Var(v, t);
            };
            match at.get((v - base) as usize).copied().flatten() {
                Some(n) => Expr::Var(base + n, t),
                None => {
                    dangling = true;
                    Expr::Var(v, t)
                }
            }
        }));
    }
    if dangling {
        return Err(Outcome::Failed);
    }

    let mut out = program.clone();
    out.conjuncts = conjuncts;
    for v in &mut out.vars {
        if let Some(d) = v.domain_conjunct {
            // an enum-domain conjunct belongs to no block
            v.domain_conjunct =
                Some(conj_at.get(d).copied().flatten().ok_or(Outcome::Failed)? as usize);
        }
    }
    out.before = keep(&program.before, &gone_before);
    out.dists = keep(&program.dists, &gone_dist);
    out.softs = Vec::with_capacity(program.softs.len());
    for (s, _) in program.softs.iter().zip(&gone_soft).filter(|&(_, &g)| !g) {
        let mut s = s.clone();
        s.dists = s
            .dists
            .iter()
            .map(|&d| dist_at.get(d as usize).copied().flatten())
            .collect::<Option<_>>()
            .ok_or(Outcome::Failed)?;
        // a `disable soft` of an inactive block discards nothing
        s.disabled = s
            .disabled_by
            .iter()
            .any(|&d| gone_disable.get(d as usize) == Some(&false));
        out.softs.push(s);
    }
    out.real.conds = keep(&program.real.conds, &gone_rc);
    out.real.dists = keep(&program.real.dists, &gone_rd);
    out.real.before = keep(&program.real.before, &gone_rb);
    out.blocks = Vec::new();
    Ok(out)
}