Interface

Interface is a communication protocol between modules. HazardFlow HDL provides a Hazard that abstracts communication protocol with arbitrary transfer conditions (e.g., valid-ready protocol). Like signals, interfaces also support compound types such as tuples and structs.

Hazard

Motivation: Handshake

In hardware semantics, a communication protocol is described as a handshake mechanism.

As an example, the most commonly used valid-ready protocol is described as below:

  • The sender generates a 1-bit valid signal and a payload signal every clock cycle.
  • The receiver generates a 1-bit ready signal each clock cycle.
  • A transfer occurs when both the valid and ready signals are asserted (i.e., both are true).
  • It's important to note that the payload is continuously present on the wire, regardless of the valid or ready signals.

Example waveform:

  • At cycle 1, the sender turns on the valid bit with payload 0x42.
    • Transfer does not happen because the receiver is not ready.
  • At cycle 2, the receiver turns on the ready bit.
    • Transfer happens because both the valid and ready signals are true.

Specification

In HazardFlow HDL, we abstracted any arbitraty communication protocol into Hazard trait. It describes the necessary information for communication: payload, resolver, and ready function.

trait Hazard {
    type P: Copy;
    type R: Copy;

    fn ready(p: Self::P, r: Self::R) -> bool;
}

For any hazard type H, its member type and functions have the following meaning:

  • H::P: Payload signal type.
  • H::R: Resolver signal type.
  • H::ready: Returns if the receiver is ready to receive with the current payload and resolver pair.

Examples

We provide a few handy primitive hazard interfaces for developers.

ValidH

ValidH represents a communication without backpressure (always ready to receive).

It has the following specification:

struct ValidH<P: Copy, R: Copy>;

impl<P: Copy, R: Copy> Hazard for ValidH<P, R> {
    type P = P;
    type R = R;

    fn ready(p: P, r: R) -> bool {
        true
    }
}

For reusability, we added additional resolver signals that simply flow from the receiver to the sender.

AndH

For a given hazard specification H, the conjunctive AndH<H> specification adds to H's resolver signal an availability bit flag. Then the receiver is ready if it is available and ready according to the internal specification H at the same time.

struct AndH<H: Hazard>;

struct Ready<R: Copy> {
    ready: bool,
    inner: R,
}

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 {
        r.ready && H::ready(p, r.inner)
    }
}

The ready field of the Ready struct represents the availability of the receiver.

VrH

We defined the valid-ready hazard VrH<P, R> as AndH<ValidH<P, R>>.

type VrH<P: Copy, R: Copy> = AndH<ValidH<P, R>>;

For reusability, we added additional resolver signals that simply flow from the receiver to the sender.

Interface

An interface is an abstraction that represents the IO of a hardware module.

Typically, a single interface is composed of zero, one, or multiple hazard interfaces.

Specification

trait Interface {
    type Fwd: Copy;
    type Bwd: Copy;

    ...
}

For any interface type I, its member types have the following meaning:

  • I::Fwd: Forward signal type.
  • I::Bwd: Backward signal type.

It contains the other methods related to the module and combinator, please refer to these sections for further reading.

Hazard Interface

For an arbitraty hazard specification H, we define the hazard interface I<H, D>, where D is the dependency type. (For more information of the dependency, please refer to the dependency section)

struct I<H: Hazard, D: Dep>;

impl<H: Hazard, const D: Dep> Interface for I<H, D> {
    type Fwd = HOption<H::P>,
    type Bwd = H::R,
}
  • The interface's forward signal is an HOption type of hazard payload.
  • The backward signal is the hazard's resolver.
  • When the forward signal is Some(p) means the sender is sending a valid payload, else it is sending an invalid payload signal.
  • When we have payload.is_some_and(|p| H::ready(p, r)), the transfer happens.

We define Valid and Vr as the hazard interface types for ValidH and VrH, respectively.

type Valid<P> = I<ValidH<P, ()>, { Dep::Helpful }>;
type Vr<P> = I<VrH<P, ()>, { Dep::Helpful }>;

Compound Interface

Compound types such as tuple, struct, and array also implement the Interface trait.

These interfaces are commonly used for IO of 1-to-N or N-to-1 combinators.

Tuple

Tuple of interfaces (If1, If2) implements Interface trait as follows:

// In practice, it is implemented as a macro.
impl<If1: Interface, If2: Interface> Interface for (If1, If2) {
    type Fwd = (If1::Fwd, If2::Fwd);
    type Bwd = (If1::Bwd, If2::Bwd);
}
  • The forward signal of the array interface is the tuple of the interfaces' forward signals.
  • The backward signal of the array interface is the tuple of the interfaces' backward signals.

Struct

#[derive(Debug, Interface)]
struct<If1: Interface, If2: Interface> StructIf<If1, If2> {
    i1: If1,
    i2: If2,
}

By applying the Interface derive macro to a struct in which all fields are interface type, the struct itself can also become an interface type.

Array

Array of interfaces [If; N] also implements Interface trait as follows:

impl<If: Interface, const N: usize> Interface for [If; N] {
    type Fwd = Array<If::Fwd, N>;
    type Bwd = Array<If::Bwd, N>;
}
  • The forward signal of the array interface is the array of the interfaces' forward signal.
  • The backward signal of the array interface is the array of the interfaces' backward signal.