#dag #hdl #lookup-tables #rtl

星光

实验性的查找表DAG的HDL和优化器

6个版本 (破坏性更新)

0.4.0 2024年2月22日
0.3.0 2024年1月23日
0.2.0 2023年12月12日
0.1.1 2023年12月5日
0.0.0 2022年7月7日

#177 in 算法

MIT/Apache

575KB
12K SLoC

星光

这提供了一个HDL(硬件设计语言)、组合和时序逻辑模拟器和优化器,以及通用路由器,用于FPGA等。其特别之处在于,它是用普通的Rust代码编写的,具有Rust提供的所有功能。

此crate的大部分MVP功能已就绪,但路由器(Router)仍处于WIP状态,并且有很多todo!()

请参阅awint/awint_dag的文档,它是此的后端。 awint是基础库,操作据此建模。 awint_dag允许记录任意位宽整数操作的DAG。 starlight将高级操作降低到简单的查找表DAG,并添加了如Loop之类的时序结构。它可以优化、评估和反推DAG中的值,以满足各种目的。

此crate有多个功能可启用awint功能。 u32_ptrs功能显著减少了算法的内存消耗,但限制了可能的内部引用数约为40亿,这可能导致最大的电路无法适应。

use std::num::NonZeroUsize;
use starlight::{awi, dag, Epoch, EvalAwi, LazyAwi};

// in the scope where this is glob imported, all arbitrary width types, some primitives, and
// the mechanisms in the macros will use mimicking types and be lazily evaluated in general.
use dag::*;

// This is just some arbitrary example I coded up, note that you can use
// almost all of Rust's features that you can use on the normal types
struct StateMachine {
    data: inlawi_ty!(16),
    counter: Awi,
}

impl StateMachine {
    pub fn new(w: NonZeroUsize) -> Self {
        Self {
            data: inlawi!(0u16),
            counter: Awi::zero(w),
        }
    }

    pub fn update(&mut self, input: &Bits) -> Option<()> {
        self.counter.inc_(true);

        let mut s0 = inlawi!(0u4);
        let mut s1 = inlawi!(0u4);
        let mut s2 = inlawi!(0u4);
        let mut s3 = inlawi!(0u4);
        cc!(self.data; s3, s2, s1, s0)?;
        s2.xor_(&s0)?;
        s3.xor_(&s1)?;
        s1.xor_(&s2)?;
        s0.xor_(&s3)?;
        s3.rotl_(1)?;
        s2.mux_(&input, input.get(0)?)?;
        cc!(s3, s2, s1, s0; self.data)?;
        Some(())
    }
}

// First, create an epoch, this will live until this struct is dropped. The
// epoch needs to live until all mimicking operations are done and states are
// lowered. Manually drop it with the `drop` function to avoid mistakes.
let epoch = Epoch::new();

let mut m = StateMachine::new(bw(4));

// this is initially an opaque value that cannot be eagerly evaluated
let input = LazyAwi::opaque(bw(4));

// if we later retroactively assign this to an unequal value, the
// `assert_assertions_strict` call will error and show the location of the
// assertion that errored
mimick::assert_eq!(Awi::from(&input), awi!(0101));

// step the state machine forward
m.update(&input).unwrap();
m.update(&awi!(0110)).unwrap();
m.update(&awi!(0110)).unwrap();

// use `EvalAwi`s to evaluate the resulting values
let output_counter = EvalAwi::from(m.counter);
let output_data = EvalAwi::from(m.data);

{
    // switch back to normal structs
    use awi::*;

    // discard all unused mimicking states so the render is cleaner
    epoch.prune_unused_states().unwrap();

    // See the mimicking state DAG before it is lowered
    epoch
        .render_to_svgs_in_dir(std::path::PathBuf::from("./".to_owned()))
        .unwrap();

    // lower into purely static bit movements and lookup tables and optimize
    epoch.optimize().unwrap();

    // Now the combinational logic is described in a DAG of lookup tables that we
    // could use for various purposes
    epoch.ensemble(|ensemble| {
        for state in ensemble.stator.states.vals() {
            assert!(state.lowered_to_lnodes);
        }
    });

    // "retroactively" assign the input with a non-opaque value
    input.retro_(&awi!(0101)).unwrap();
    // check assertions (all `dag::assert*` functions and dynamic `unwrap`s done
    // during the current `Epoch`)
    epoch.assert_assertions(true).unwrap();
    // evaluate the outputs
    assert_eq!(output_counter.eval().unwrap(), awi!(0011));
    assert_eq!(output_data.eval().unwrap(), awi!(0xa505_u16));

    // reassign and reevaluate
    input.retro_(&awi!(1011)).unwrap();
    assert!(epoch.assert_assertions(true).is_err());
    assert_eq!(output_data.eval().unwrap(), awi!(0x7b0b_u16));
}
drop(epoch);
use starlight::{dag, awi, Epoch, EvalAwi};

use dag::*;

let epoch = Epoch::new();

let mut lhs = inlawi!(zero: ..8);
let rhs = inlawi!(umax: ..8);
let x = inlawi!(10101010);
let y = InlAwi::from_u64(4);

let mut output = inlawi!(0xffu8);

// error: expected `bool`, found struct `bool`
//if lhs.ult(&rhs).unwrap() {
//    output.xor_(&x).unwrap();
//} else {
//    output.lshr_(y.to_usize()).unwrap();
//};

// A little more cumbersome, but we get to use all the features of
// normal Rust in metaprogramming and don't have to support an entire DSL.
// In the future we will have more macros to help with this.

let lt = lhs.ult(&rhs).unwrap();

let mut tmp0 = output;
tmp0.xor_(&x).unwrap();
output.mux_(&tmp0, lt).unwrap();

let mut tmp1 = output;
tmp1.lshr_(y.to_usize()).unwrap();
output.mux_(&tmp1, !lt).unwrap();

let output_eval = EvalAwi::from(&output);

{
    use awi::*;
    assert_eq!(output_eval.eval().unwrap(), awi!(01010101));
}
drop(epoch);

依赖关系

~2.5–3.5MB
~65K SLoC