1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
//! Hazard protocol.
pub mod selector;
use core::marker::{ConstParamTy, PhantomData};
pub use mux::*;
pub use selector::*;
use super::interface::*;
use crate::prelude::*;
use crate::std::*;
/// A hazard protocol with given payload, resolver, and ready function.
///
/// A struct represents a hazard protocol when it implements this trait.
pub trait Hazard {
/// Payload type.
type P: Copy;
/// Resolver type.
type R: Copy;
/// Indicates whether the receiver of the payload is ready to receive the payload.
///
/// This ready condition is not automatically enforced by just using a hazard interface. If you want to enforce the
/// condition, you may use `Hazard::ready` directly in the combinational logic. Note that all the `std` combinators
/// already check the condition.
fn ready(p: Self::P, r: Self::R) -> bool;
}
/// Dependency type of a hazard interface.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ConstParamTy)]
pub enum Dep {
/// The payload (`Fwd`) does not depend on the resolver (`Bwd`).
Helpful = 0,
/// The payload (`Fwd`) depends on the resolver (`Bwd`), and they satisfy the condition that if the payload is
/// `Some`, `Hazard::ready(p, r)` is true.
///
/// It is a bug to make the payload depend on the resolver but break the condition.
Demanding = 1,
}
/// Hazard interface.
#[derive(Debug)]
#[must_use]
pub struct I<H: Hazard, const D: Dep> {
_marker: PhantomData<H>,
}
impl<H: Hazard, const D: Dep> Interface for I<H, D> {
/// Resolver.
type Bwd = H::R;
/// Payload.
///
/// `Some(p)` means a valid payload with data `p`, and `None` means an invalid payload.
type Fwd = HOption<H::P>;
}
/// Wrapping resolver type for `AndH`.
#[derive(Debug, Clone, Copy)]
pub struct Ready<R> {
/// Whether the receiver of the payload is ready to accept a new payload.
pub ready: bool,
/// Inner resolver type.
pub inner: R,
}
impl<R: Copy> Ready<R> {
/// Generates a new `Ready` with the given `ready` bit and inner resolver.
pub fn new(ready: bool, inner: R) -> Self {
Self { ready, inner }
}
/// Creates a new invalid signal.
// TODO: We should add `inner` as parameter to set the inner hazard value when creating invalid signal.
// This is needed because the inner hazard value should be allowed as don't-care value only when explicit `unsafe` reasoning by user is given.
#[allow(unreachable_code)]
pub fn invalid() -> Self {
Self { ready: false, inner: unsafe { x::<R>() } }
}
/// Creates a new valid signal.
pub fn valid(inner: R) -> Self {
Ready::new(true, inner)
}
}
/// Transforms `Ready<R>` to `Option<R>`.
///
/// It is mainly used when the structural hazard (ready bit) has higher priority than data/control hazards.
impl<R: Copy> From<Ready<R>> for HOption<R> {
fn from(value: Ready<R>) -> Self {
if value.ready {
// If the ready bit high, pass the inner hazard.
Some(value.inner)
} else {
// Otherwise, block.
None
}
}
}
impl<R1> Ready<R1> {
/// Maps the inner resolver to another inner resolver type.
pub fn map<R2>(self, f: impl Fn(R1) -> R2) -> Ready<R2> {
Ready { ready: self.ready, inner: f(self.inner) }
}
}
/// Hazard for wrapping a hazard with a `ready` bit (to represent a structural hazard).
#[derive(Debug, Clone, Copy)]
pub struct AndH<H: Hazard> {
_marker: PhantomData<H>,
}
impl<H: Hazard> Hazard for AndH<H> {
type P = H::P;
type R = Ready<H::R>;
fn ready(p: H::P, r: Ready<H::R>) -> bool {
if r.ready {
H::ready(p, r.inner)
} else {
false
}
}
}
impl<H: Hazard, const D: Dep> I<H, D> {
/// A generic FSM combinator for a hazard interface.
///
/// For more information, you can check the documentation for [`Interface::fsm`].
///
/// # Safety
///
/// To enforce the invariant of the hazard protocol, you have to consider the following depending on the dependency
/// type of the ingress/egress interface.
///
/// - Ingress interface
/// - [`Dep::Helpful`]: In the ingress interface's `fsm`, its payload does not depend on its resolver. So in
/// this `fsm`, we can use the fact that `ip` does not depend on `ir`. That means we can make `ir` depend on
/// `ip`.
/// - [`Dep::Demanding`]: In the ingress interface's `fsm`, its payload depends on its resolver, and if the
/// payload is `Some`, `Hazard::ready(p, r)` is true. So in this `fsm`, we must consider that `ip` depends
/// on `ir`, but can assume that if `ip` is `Some`, `H::ready(ip, ir)` is true regardless of `ir`.
/// - Egress interface
/// - [`Dep::Helpful`]: In this `fsm`, we ensure that `ep` does not depend on `er`. If the dependency chain goes
/// through the ingress interface, we must consider that as well.
/// - [`Dep::Demanding`]: In this `fsm`, we make `ep` depend on `er`, and guarantee that if `ep` is `Some`,
/// `EH::ready(ep, er)` is true.
///
/// # Type parameters
///
/// - `H`: The ingress interface's hazard type.
/// - `D`: The ingress interface's dependency type.
/// - `S`: The state type.
/// - `ED`: The egress interface's dependency type.
/// - `EH`: The egress interface's hazard type.
///
/// # Parameters
///
/// - `self`: The ingress interface.
/// - `init_state`: The initial state.
/// - `f`: Output calculation and state transition logic. If `let (ep, ir, s_next) = f(ip, er, s)`,
/// - `ip`: The ingress payload.
/// - `er`: The egress resolver.
/// - `s`: The current state.
/// - `ep`: The egress payload.
/// - `ir`: The ingress resolver.
/// - `s_next`: The next state.
///
/// # Note: preventing combinational loops
///
/// Combinational loops are among the most common causes of instability and unreliability in digital designs.
/// Combinational loops generally violate synchronous design principles by establishing a direct feedback loop that
/// contains no registers.
///
/// - To prevent combinational loops, programmers have to make sure that **there is no circular dependency between
/// the payload and resolver of the same interface**.
/// - Dependency types help with this.
pub unsafe fn fsm<S: Copy, const ED: Dep, EH: Hazard>(
self,
init_state: S,
f: impl Fn(HOption<H::P>, EH::R, S) -> (HOption<EH::P>, H::R, S),
) -> I<EH, ED> {
unsafe { <Self as Interface>::fsm::<I<EH, ED>, S>(self, init_state, f) }
}
}