Skip to content

Commit

Permalink
Update book
Browse files Browse the repository at this point in the history
  • Loading branch information
minseongg committed Oct 2, 2024
1 parent 05a29bb commit fc6533a
Show file tree
Hide file tree
Showing 14 changed files with 498 additions and 97 deletions.
2 changes: 1 addition & 1 deletion doc/docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

- [Tutorial](./tutorial/tutorial.md)
+ [FIR Filter](./tutorial/fir_filter.md)
+ [Masked Merge](./tutorial/masked_merge.md)
+ [FIFO without duplication](./tutorial/custom_fifo.md)
- [Concepts](./lang/concepts.md)
+ [Signals](./lang/signal.md)
+ [Interfaces](./lang/interface.md)
Expand Down
376 changes: 376 additions & 0 deletions doc/docs/figure/custom-fifo-spec.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion doc/docs/lang/combinator.md
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@ These registers can maintain the states in their registers and could delay one o
We demonstrate the two most representative combinators: `reg_fwd` and `fifo`.

<!-- We demonstrate the usage of `reg_fwd` with ingress interface `I<VrH<P, R>, D>` and `fifo` with ingress interfaces `I<VrH<P, R>, D>`.
Similar to other combinators, register style combinators have other variants too, e.g. `naked_reg_bwd`, `shift_reg_fwd`, etc. -->
Similar to other combinators, register style combinators have other variants too, e.g. `transparent_reg_bwd`, `shift_reg_fwd`, etc. -->

#### `reg_fwd`

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,43 @@
# Masked Merge
# FIFO without duplication

In this tutorial, we will use the `masked_merge` combinator to implement a custom arbiter that does not select the recently chosen ingress interface again.
In this tutorial, we will implement a custom FIFO which can be used to provide balanced transfers for multiple input streams, thereby offering better latency.

## Specification

The custom FIFO with and *N* input streams and *M* entries performs the following operation:

<center><img src="../figure/custom-fifo-spec.svg" width="85%"></center>

It has *M* entries and receives payloads from *N* input streams.
The key point is that it does not accept payloads from an input stream that already has an entry in the FIFO.

For example, in the figure above, the FIFO contains P1 and P2, which are payloads received from the `in_1` and `in_N`, respectively.
Therefore, the next payload will be accepted from an input stream other than `in_1` and `in_N`.
`in_1` and `in_N` become selectable again after P1 and P2 are removed from the FIFO via output
(the circles in front of the FIFO indicates whether each input stream can be received; a red circle means cannot, a green circle means can).

## Modular Design

We could represent the custom FIFO in modular way as follows (`U<M>` is a bitvector of width `M`):

<p align="center">
<img src="../figure/masked-merge-use-case.svg" width=95% />
</p>

**`masked_merge` submodule:**

<!--
In this example, a FIFO is placed after `masked_merge` to track which ingress interface the recent transfers occurred on.
This information is used to prevent consecutive selections from the same ingress interface.
-->

<!--
The `masked_merge` combinator performs the following operation (`U<M>` is a bitvector of width `M`):
<p align="center">
<img src="../figure/masked-merge.svg" width=80%/>
</p>
-->

<!-- TODO: to explain masked merge, we don't need to think of FIFO queue. It can be an example of using masked merge, but the current text *assumes* there should be a FIFO queue: "It indicates which ingress interfaces are present in the current queue." -->

Expand All @@ -19,6 +48,7 @@ The `masked_merge` combinator performs the following operation (`U<M>` is a bitv
- It will select from the ingress interface which has valid payload with the mask bit has `false`.
+ For example, if `N` is 4 and the mask bits are `[true, true, false, true]`, then it will try to select from the third ingress interface.
+ If multiple ingress interfaces are selectable, the one with the smallest index is chosen.
+ In this example, the mask bits means the origin of the current payloads in the FIFO.

<!-- * We can think of a valid-ready Interface as a valid interface `Valid<P>` with an extra ready signal (Boolean value) in its resolver. -->
<!-- * The transfer happens only when the payload is `Some(P)`, and the ready signal in the resolver is `true`. -->
Expand All @@ -39,34 +69,23 @@ The Masked Merge combinator egress interface is also a valid-ready hazard interf
* If there are 4 ingress interfaces and the array is `[true, false, false, true]`, it indicates the ingress interface 1's and ingress interface 2's payloads are not currently in the queue.
-->

## Modular Design

It explain the example use-case of the `masked_merge` combinator.

In this example, a FIFO is placed after `masked_merge` to track which ingress interface the recent transfers occurred on.
This information is used to prevent consecutive selections from the same ingress interface.

<p align="center">
<img src="../figure/masked-merge-use-case.svg" width=95% />
</p>

**`masked_merge` combinator:**

<!--
- It selects one of the ingress interfaces to transfer its payload to the next combinator.
- The selection is based on the mask bits (`U<N>`) from the egress resolver.
- If multiple ingress interfaces are selectable, the one with the smallest index is chosen.
-->

**`map_resolver` combinator:**
**`map_resolver` submodule:**

- It transforms the inner resolver signal from the FIFO state (`FifoS`) to the mask bits (`U<N>`).
+ `FifoS` indicates the current state of the FIFO, including elements and head/tail indices.
+ The `i`-th element of mask bits (`U<N>`) indicates that one of the payloads currently in the FIFO has been selected from the `i`-th ingress interface of `M0`.
+ The `i`-th element of mask bits (`U<N>`) indicates that one of the payload currently in the FIFO has been selected from the `i`-th ingress interface of `M0`.
- It does not touch the forward signals and the ready signal.
<!--
- The `U<N>` indicates which ingress interfaces are present in the current FIFO.
-->

**`transparent_fifo` combinator:**
**`transparent_fifo` submodule:**

- It takes a payload from the ingress interface when the FIFO is not full.
- It returns the oldest payload in the FIFO to the egress interface.
Expand All @@ -78,27 +97,16 @@ This information is used to prevent consecutive selections from the same ingress
The FIFO Queue egress interface is a simple valid-ready interface `Vr<P>`. -->

**`map` combinator:**
**`map` submodule:**

- It drops the unnecessary index information in the payload.

## Implementation

- We use `u32` as the actual payload type for demonstrating a more concrete example.
- We also set the number of ingress interfaces as 5, the same as the FIFO size.
- `fifo_s.inner` is the inner elements of the FIFO.
- We `fold` the inner elements of the FIFO in the `map_resolver` combinator:
- The initializer is a Boolean array with all elements as `false` of size 5.
- The index of the initializer array indicates the index of the ingress interfaces.
- We iterate through all the elements within the FIFO and set the accumulator's value at the index in each FIFO element to `true`.
- The final result indicates which ingress interfaces are present in the current FIFO.
- We send back this resolver to the `masked_merge` combinator to make decision for choosing the next ingress interface.
- We filter out the unnecessary index information in the last `map` combinator.
- The implementation of the `masked_merge()` combinator will be explained in the [Implementing Combinators](../advanced/combinator.md) section.
Based on the above submodules, we can implement the custom FIFO in a concise and modular way:

```rust,noplayground
/// Example module using `masked_merge` combinator.
pub fn m(ingress: [Vr<u32>; 5]) -> Vr<u32> {
fn custom_fifo(ingress: [Vr<u32>; 5]) -> Vr<u32> {
ingress
.masked_merge()
.map_resolver::<((), FifoS<(u32, U<{ clog2(5) }>), 5>)>(|er| {
Expand All @@ -110,6 +118,19 @@ pub fn m(ingress: [Vr<u32>; 5]) -> Vr<u32> {
}
```

You can find the full implementation in [masked_merge.rs](https://github.com/kaist-cp/hazardflow/blob/main/hazardflow-designs/src/examples/masked_merge.rs).
- We used `u32` for the payload type, and 5 for the number of ingress interfaces and FIFO size.
- `fifo_s.inner` represents the inner elements of the FIFO.
- We `fold` the inner elements of the FIFO in the `map_resolver` combinator:
- The initializer is a boolean array with all elements as `false` of size 5.
- The index of the initializer array indicates the index of the ingress interfaces.
- We iterate through all the elements within the FIFO and set the accumulator's value at the index in each FIFO element to `true`.
- The final result indicates which ingress interfaces are present in the current FIFO.
- We send back this resolver to the `masked_merge` combinator to make decision for choosing the next ingress interface.
- We filter out the unnecessary index information in the last `map` combinator.
- The implementation of the `masked_merge()` combinator will be explained in the [Implementing Combinators](../advanced/combinator.md) section.

You can find the full implementation in [custom_fifo.rs](https://github.com/kaist-cp/hazardflow/blob/main/hazardflow-designs/src/examples/custom_fifo.rs).

---

Congratulations! You finished the tutorial!
26 changes: 11 additions & 15 deletions doc/docs/tutorial/fir_filter.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ Based on the above submodules, we can implement the FIR filter in a concise and
fn fir_filter(input: Valid<u32>) -> Valid<u32> {
input
.window::<3>()
.weight([4, 2, 3])
.map(|ip| ip.zip(Array::from([4, 2, 3])).map(|(e, wt)| e * wt))
.sum()
}
```

We can describe the FIR filter with `window`, `weight`, and `sum` combinators in the HazardFlow HDL and we assume the input interface `Valid<u32>` is provided.
We can describe the FIR filter with `window`, `map`, and `sum` combinators in the HazardFlow HDL and we assume the input interface `Valid<u32>` is provided.
`Valid<u32>`, which is an alias of [`I<ValidH<u32, ()>>`](../lang/interface.md#validh) is a **valid interface** where its payload is `u32`, the resolver is empty `()`, and its `ready` function always returns `true`.
In other words, as long as the input interface's forward signal is `Some(u32)` at a specific clock cycle, the receiver receives a valid payload.
We can interpret this input interface as a stream of signal values flowing through the wires.
Expand All @@ -76,9 +76,9 @@ The `window` combinator is defined as follows:
```rust,noplayground
impl<P: Copy + Default> Valid<P> {
fn window<const N: usize>(self) -> Valid<Array<P, N>> {
self.fsm_map(P::default().repeat::<N>(), |ip, s| {
let ep = s.append(ip.repeat::<1>()).clip_const::<N>(0);
let s_next = ep;
self.fsm_map(P::default().repeat::<{ N - 1 }>(), |ip, s| {
let ep = ip.repeat::<1>().append(s).resize::<N>();
let s_next = ep.clip_const::<{ N - 1 }>(0);
(ep, s_next)
})
}
Expand Down Expand Up @@ -107,20 +107,16 @@ The anonymous function is where we specify the fsm logic from the `(ingress payl
* The `clip_const::<N>(0)` function clips the array from index 0 of size `N`.
* Note that in HazardFlow HDL, the array index is in descending order from left to right, for more details please refer to the [signal](./signal.md) section. -->

**`weight` combinator:**
**`map` combinator:**

The `weight` combinator is defined as follows:
The `map` combinator is used to represent the `weight` submodule.

```rust,noplayground
impl<const N: usize> Valid<Array<u32, N>> {
fn weight(self, weight: [u32; N]) -> Valid<Array<u32, N>> {
self.map(|ip| ip.zip(weight).map(|(ele, weight)| ele * weight))
}
}
map(|ip| ip.zip(Array::from([4, 2, 3])).map(|(e, wt)| e * wt))
```

It takes an `Valid<Array<u32, N>>` and returns an egress hazard interface `Valid<Array<u32, N>>`.
It transforms the `i`-th element of ingress payload `ip[i]` into `weight[i] * ip[i]`, and leaves the resolver as untouched.
It transforms the `i`-th element of ingress payload `ip[i]` into `ip[i] * weight[i]`, and leaves the resolver as untouched.
The [`map` interface combinator](https://kaist-cp.github.io/hazardflow/docs/hazardflow_designs/std/hazard/struct.I.html#method.map) is provided by the HazardFlow HDL standard library.
We can interpret it as stateless version of `fsm_map`.
In the application-specific logic in `map` interface combinator, we use `zip` and `map` methods for manipulating the ingress payload signal.
Expand All @@ -141,13 +137,13 @@ The `sum` combinator is defined as follows:
```rust,noplayground
impl<const N: usize> Valid<Array<u32, N>> {
fn sum(self) -> Valid<u32> {
self.map(|ip| ip.fold_assoc(|e1, e2| e1 + e2))
self.map(|ip| ip.fold(0, |acc, e| acc + e))
}
}
```

It takes an `Valid<Array<u32, N>>` and returns `Valid<u32>`.
It transforms the ingress payload to sum of them.
In the application-specific logic in `map` interface combinator, we use `fold_assoc` method which aggregates the data within array of signal.
In the application-specific logic in `map` interface combinator, we use `fold` method which aggregates the data within array of signal.

You can find the implementation in [fir_filter.rs](https://github.com/kaist-cp/hazardflow/blob/main/hazardflow-designs/src/examples/fir_filter.rs).
2 changes: 1 addition & 1 deletion doc/docs/tutorial/tutorial.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Tutorial

We will introduce some basic concepts in the HazardFlow HDL programming model and use HazardFlow HDL to describe an [FIR (finite impulse response) filter](https://en.wikipedia.org/wiki/Finite_impulse_response) and a masked merge combinator.
We will introduce some basic concepts in the HazardFlow HDL programming model and use HazardFlow HDL to describe an [FIR (finite impulse response) filter](https://en.wikipedia.org/wiki/Finite_impulse_response) and a custom FIFO which does not allow duplicated entries.

HazardFlow HDL's implementation is based on the concept of [hazard interface](../lang/interface.md).
Here, we will provide a brief overview of the interface and combinator to help you understand how to use them for implementing hardware modules.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ where [(); clog2(N)]:

/// Masked Merge Combinator
#[synthesize]
pub fn m(ingress: [Vr<u32>; 5]) -> Vr<u32> {
pub fn custom_fifo(ingress: [Vr<u32>; 5]) -> Vr<u32> {
ingress
.masked_merge()
.map_resolver::<((), FifoS<(u32, U<{ clog2(5) }>), 5>)>(|er| {
let (_, fifo_s) = er.inner;
fifo_s.inner.fold(Array::from([false; 5]), |acc, (_p, idx)| acc.set(idx, true))
})
.naked_fifo()
.transparent_fifo()
.map(|(ip, _idx)| ip)
}
2 changes: 1 addition & 1 deletion hazardflow-designs/src/examples/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! HazardFlow examples.
pub mod custom_fifo;
pub mod fir_filter;
pub mod masked_merge;
6 changes: 3 additions & 3 deletions hazardflow-designs/src/gemmini/execute/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ where
{
let cmd = cmd
.map_resolver_inner::<((), FifoS<GemminiCmd, EX_QUEUE_LENGTH>)>(|_| ())
.multi_headed_naked_fifo()
.multi_headed_transparent_fifo()
.map(|fifo_s| {
// Transforms `FifoS` into array of commands
range::<EX_QUEUE_LENGTH>().map(|i| {
Expand All @@ -343,7 +343,7 @@ where

(cmd_wrapped, cmd_wrapped.is_some())
})
.naked_fsm_map::<(ExeCmdT<EX_QUEUE_LENGTH>, ConfigS)>(ConfigS::default(), |cmd, s_config| {
.transparent_fsm_map::<(ExeCmdT<EX_QUEUE_LENGTH>, ConfigS)>(ConfigS::default(), |cmd, s_config| {
// Update the configuration state if the command is a `ex_config` command.
let cmd = cmd.unwrap();
let config_updated = update_ex_config(cmd, s_config);
Expand Down Expand Up @@ -393,7 +393,7 @@ where

(pop_count, any_matmul_in_progress, any_pending_robs)
})
.naked_reg_fwd::<ExeH<EX_QUEUE_LENGTH>>(true)
.transparent_reg_fwd::<ExeH<EX_QUEUE_LENGTH>>(true)
.filter_map_drop_with_r::<VrH<((ExeCmdT<EX_QUEUE_LENGTH>, ConfigS), BoundedU<2>), (U<2>, TagsInProgress, bool)>>(
|(cmd_decoded, config), _| match cmd_decoded {
ExeCmdT::Config(_) => Some(((cmd_decoded, config), BoundedU::new(0.into_u()))),
Expand Down
14 changes: 7 additions & 7 deletions hazardflow-designs/src/std/combinators/fifo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl<P: Copy, R: Copy, const D: Dep> I<VrH<P, R>, D> {
[(); clog2(N) + 1]:,
[(); clog2(N + 1) + 1]:,
{
self.map_resolver_inner::<(R, _)>(|er| er.0).naked_fifo()
self.map_resolver_inner::<(R, _)>(|er| er.0).transparent_fifo()
}
}

Expand All @@ -73,12 +73,12 @@ where
// If pipe bit is valid and FIFO is full, ingress payload can come in if egress payload goes out.
// Refer to below link for more details:
// <https://github.com/chipsalliance/chisel/blob/v3.2.1/src/main/scala/chisel3/util/Decoupled.scala#L235-L246>
pub fn naked_fifo(self) -> I<VrH<P, R>, { Dep::Helpful }>
pub fn transparent_fifo(self) -> I<VrH<P, R>, { Dep::Helpful }>
where
[(); clog2(N) + 1]:,
[(); clog2(N + 1) + 1]:,
{
self.multi_headed_naked_fifo().map_resolver_inner(|r| (r, U::from(1))).filter_map(|s| {
self.multi_headed_transparent_fifo().map_resolver_inner(|r| (r, U::from(1))).filter_map(|s| {
if s.len == 0.into_u() {
None
} else {
Expand All @@ -87,19 +87,19 @@ where
})
}

/// A variation of [`I::naked_fifo`] that outputs the FIFO state instead of the front element as the egress payload,
/// A variation of [`I::transparent_fifo`] that outputs the FIFO state instead of the front element as the egress payload,
/// and takes an additional egress resolver signal representing how many elements will be popped.
///
/// - Payload: The same behavior as [`I::naked_fifo`], but the FIFO state `FifoS<P, N>` is outputted instead.
/// - Resolver: The same behavior as [`I::naked_fifo`], but additionally takes a `U<{ clog2(N + 1) }>` that
/// - Payload: The same behavior as [`I::transparent_fifo`], but the FIFO state `FifoS<P, N>` is outputted instead.
/// - Resolver: The same behavior as [`I::transparent_fifo`], but additionally takes a `U<{ clog2(N + 1) }>` that
/// represents how many elements to pop.
///
/// | Interface | Ingress | Egress |
/// | :-------: | ------------------------- | --------------------------------- |
/// | **Fwd** | `HOption<P>` | `HOption<FifoS<P, N>>` |
/// | **Bwd** | `Ready<(R, FifoS<P, N>)>` | `Ready<(R, U<{ clog2(N + 1) }>)>` |
#[allow(clippy::type_complexity)]
pub fn multi_headed_naked_fifo(self) -> I<VrH<FifoS<P, N>, (R, U<{ clog2(N + 1) }>)>, { Dep::Helpful }>
pub fn multi_headed_transparent_fifo(self) -> I<VrH<FifoS<P, N>, (R, U<{ clog2(N + 1) }>)>, { Dep::Helpful }>
where
[(); clog2(N) + 1]:,
[(); clog2(N + 1) + 1]:,
Expand Down
4 changes: 2 additions & 2 deletions hazardflow-designs/src/std/combinators/fsm_egress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ impl<P: Copy, R: Copy, const D: Dep> I<VrH<P, R>, D> {
flow: bool,
f: impl Fn(P, S) -> (EP, S, bool),
) -> I<VrH<EP, R>, D> {
self.map_resolver_inner::<(R, (HOption<P>, S))>(|(r, _)| r).naked_fsm_egress(init, pipe, flow, f)
self.map_resolver_inner::<(R, (HOption<P>, S))>(|(r, _)| r).transparent_fsm_egress(init, pipe, flow, f)
}
}

Expand All @@ -131,7 +131,7 @@ impl<P: Copy, R: Copy, S: Copy, const D: Dep> I<VrH<P, (R, (HOption<P>, S))>, D>
/// | :-------: | ----------------------------- | ------------- |
/// | **Fwd** | `HOption<P>` | `HOption<EP>` |
/// | **Bwd** | `Ready<(R, (HOption<P>, S))>` | `Ready<R>` |
pub fn naked_fsm_egress<EP: Copy>(
pub fn transparent_fsm_egress<EP: Copy>(
self,
init: S,
pipe: bool,
Expand Down
Loading

0 comments on commit fc6533a

Please sign in to comment.