Modules

In Rust, the FnOnce trait is automatically implemented for types that can be called once. For example, the primitive function fn(i: Ingress) -> Egress and closures that might consume captured variables (|i: Ingress| -> Egress { .. }) implements FnOnce(Ingress) -> Egress trait.

And in HazardFlow, we treat a function m that implements the FnOnce(Ingress) -> Egress trait where Ingress and Egress implement the Interface trait as a module with Ingress as its ingress interface and Egress as its egress interface.

The common way to construct a module is chaining interface combinators. Please refer to the Interface Combinators for more information.

Example: FIR Filter

In the tutorial, the FIR filter module was implemented as follows:

#![allow(unused)]
fn main() {
fn fir_filter(input: Valid<u32>) -> Valid<u32> {
    let weight = Array::<u32, 3>::from([4, 2, 3]);

    input
        .window::<3>()
        .map(|ip| ip.zip(weight).map(|(e, wt)| e * wt))
        .sum()
}
}

The fir_filter function implements FnOnce(Valid<u32>) -> Valid<u32>, so we can treat it as a module with Valid<u32> as both its ingress and egress interface.

Module Combinators

We provide some convenient module combinators that take a module, modify it, and return a new module.

seq

Generates a 1D systolic array from an array of modules.

#![allow(unused)]
fn main() {
fn seq<I: Interface, O: Interface, J: Interface, const N: usize>(
    ms: [fn(I, J) -> (O, J); N],
) -> impl FnOnce([I; N], J) -> ([O; N], J)
}

You can construct an array of modules explicitly from elements, or if all the modules have the same behavior, you can use the from_fn API.

flip

Flips a module's input and output.

#![allow(unused)]
fn main() {
fn flip<I1: Interface, I2: Interface, O1: Interface, O2: Interface, T>(
    f: T
) -> impl FnOnce(I2, I1) -> (O2, O1)
where
    T: FnOnce(I1, I2) -> (O1, O2),
}