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), }