Implementing Your Own Combinators
The HazardFlow HDL standard library provides some primitive combinators, however we need to implement our custom combinators sometimes for specific logic. In fact, the HazardFlow HDL standard library is also implemented in the same way as what we will introduce in this section.
We introduced the masked_merge
combinator specification in the Tutorial section,
but we did not give out the concrete implementation.
The masked_merge
combinator is a custom combinator which is not provided by the HazardFlow HDL standard library,
thus we need to implement its logic by ourselves.
Implementation
To define a custom combinator for interface type [Vr<P>; N]
, we need to define a custom trait first.
/// Masked merge trait
trait MaskedMergeExt<P: Copy + Default, const N: usize>: Interface
where [(); clog2(N)]:
{
/// Hazard type
type EH: Hazard;
/// Fair Mux
fn masked_merge(self) -> I<Self::EH, { Dep::Demanding }>;
}
- The custom trait's name is
MaskedMergeExt
.- It specifies the general payload type
P
need to be constrained by theCopy
andDefault
traits. const N: usize
specifies the number of ingress interfaces.- The
MaskedMergeExt
need to extend theInterface trait
, since we need toimpl
this trait later for Implementing our custom combinator. where [(); clog2(N)]
is telling the HazardFlow HDL compiler thatclog2(N)
is a constant.
- It specifies the general payload type
- We define the egress hazard as
Hazard
type, which is a hazard protocol with a given payload, resolver, and theready
function. fn masked_merge(self) -> I<Self::EH, { Dep::Demanding }>
defines the combinator's namemasked_merge
and specifies the egress hazard isEH
.
We can define the combinational logic now.
impl<P: Copy + Default, const N: usize> MaskedMergeExt<P, N> for [Vr<P>; N]
where [(); clog2(N)]:
{
type EH = VrH<(P, U<{ clog2(N) }>), Array<bool, N>>;
fn masked_merge(self) -> I<Self::EH, { Dep::Demanding }> {
unsafe {
self.fsm::<I<VrH<(P, U<{ clog2(N) }>), Array<bool, N>>, { Dep::Demanding }>, ()>((), |ip, er, s| {
if !er.ready {
let ir = Ready::new(false, ()).repeat();
let ep = None;
return (ep, ir, s);
}
let ep_idx = ip.zip(er.inner).find_idx(|(p, selected)| p.is_some() && !selected);
let ep = if let Some(idx) = ep_idx { Some((ip[idx].unwrap(), idx)) } else { None };
let ir = Ready::invalid().repeat::<N>().set_cond(ep.is_some(), ep_idx.unwrap(), Ready::valid(()));
(ep, ir, s)
})
}
}
}
- We
impl
the custom traitMaskedMergeExt
for the compound interface type[Vr<P>; N]
. - We define the egress hazard as
VrH<(P, U<{ clog2(N) }>), Array<bool, N>>
- The egress interface is a valid-ready interface with valid-ready hazard.
U<{ clog2(N) }>
is the bit representation of the index of the ingress interfaces.- The inner resolver is
Array<bool, N>
which indicates the index of the ingress interfaces are present in the current queue.
unsafe
code block is necessary for Implementing your own custom combinator, sincefsm
function need to be in theunsafe
code block.- The
fsm
function specifies the egress interface type isI<VrH<(P, U<{ clog2(N) }>), Array<bool, N>
. - The
fsm
function takes two inputs and returns the egress interface.- The initial state of the combinator.
- An anonymous function takes three inputs.
- The ingress payload
ip
. The type isArray<HOption<P>, N>
, which is all the ingress interfaces' payload. - The egress resolver
er
. The type isReady<Array<bool, N>>
, which is a ready signal with an array that indicates the index of the ingress interfaces present in the current queue. - The initial state
s
. The type is simply a()
, since we do not have a state in this combinator.
- The ingress payload
- The anonymous function returns a tuple including:
- The egress payload
ep
. The type isHOption<(P, Array<bool, _>)>
, which is the actual data we want to transfer and also the index of the ingress interface. Note that the bit representation of an unsigned integer is an array ofbool
. - The ingress resolver
ip
. The type isArray<Ready<()>, N>
, which is an array of ready signals with sizeN
. It is a collection of the ready signal for each ingress interface.
- The egress payload
- If the egress resolver ready signal is
false
, which means the egress interface is not ready to transfer the payload, we are not transferring any payload.- We set the ingress resolver's ready signal as
false
- We set the egress payload as
None
.
- We set the ingress resolver's ready signal as
- If the egress resolver ready signal is
true
, which means the egress interface is ready to transfer the payload.- We find the first index of the ingress interfaces whose payload is
Some(P)
and also not in the current queue.zip
function zips two arrays and returns a new array whose element is a tuple of both elements from the two arrays.find_idx
finds the index of first element that satisfies given condition.
- We set the egress payload as a tuple containing the selected ingress interface's actual payload and its index.
- We set the selected ingress resolver ready signal as
true
and the rest of the ingress interfaces' ready signal asfalse
.
- We find the first index of the ingress interfaces whose payload is