diff --git a/doc/docs/figure/combinator-branch-waveform.svg b/doc/docs/figure/combinator-branch-waveform.svg
new file mode 100644
index 0000000..26fbc07
--- /dev/null
+++ b/doc/docs/figure/combinator-branch-waveform.svg
@@ -0,0 +1,4857 @@
+
+
diff --git a/doc/docs/figure/combinator-branch.svg b/doc/docs/figure/combinator-branch.svg
new file mode 100644
index 0000000..103de0a
--- /dev/null
+++ b/doc/docs/figure/combinator-branch.svg
@@ -0,0 +1,301 @@
+
+
diff --git a/doc/docs/figure/combinator-filter-map-waveform.svg b/doc/docs/figure/combinator-filter-map-waveform.svg
new file mode 100644
index 0000000..7ea17d4
--- /dev/null
+++ b/doc/docs/figure/combinator-filter-map-waveform.svg
@@ -0,0 +1,4601 @@
+
+
diff --git a/doc/docs/figure/combinator-filter-map.svg b/doc/docs/figure/combinator-filter-map.svg
new file mode 100644
index 0000000..65d1fd1
--- /dev/null
+++ b/doc/docs/figure/combinator-filter-map.svg
@@ -0,0 +1,237 @@
+
+
diff --git a/doc/docs/figure/combinator-fsm.svg b/doc/docs/figure/combinator-fsm.svg
new file mode 100644
index 0000000..796bf1c
--- /dev/null
+++ b/doc/docs/figure/combinator-fsm.svg
@@ -0,0 +1,305 @@
+
+
diff --git a/doc/docs/figure/combinator-join-waveform.svg b/doc/docs/figure/combinator-join-waveform.svg
new file mode 100644
index 0000000..221fd9d
--- /dev/null
+++ b/doc/docs/figure/combinator-join-waveform.svg
@@ -0,0 +1,4892 @@
+
+
diff --git a/doc/docs/figure/combinator-join.svg b/doc/docs/figure/combinator-join.svg
new file mode 100644
index 0000000..c3ee10a
--- /dev/null
+++ b/doc/docs/figure/combinator-join.svg
@@ -0,0 +1,321 @@
+
+
diff --git a/doc/docs/figure/combinator-lfork-waveform.svg b/doc/docs/figure/combinator-lfork-waveform.svg
new file mode 100644
index 0000000..2b65821
--- /dev/null
+++ b/doc/docs/figure/combinator-lfork-waveform.svg
@@ -0,0 +1,4892 @@
+
+
diff --git a/doc/docs/figure/combinator-lfork.svg b/doc/docs/figure/combinator-lfork.svg
new file mode 100644
index 0000000..dc109ab
--- /dev/null
+++ b/doc/docs/figure/combinator-lfork.svg
@@ -0,0 +1,321 @@
+
+
diff --git a/doc/docs/figure/combinator-map-resolver-waveform.svg b/doc/docs/figure/combinator-map-resolver-waveform.svg
new file mode 100644
index 0000000..2a2abd1
--- /dev/null
+++ b/doc/docs/figure/combinator-map-resolver-waveform.svg
@@ -0,0 +1,4808 @@
+
+
diff --git a/doc/docs/figure/combinator-map-resolver.svg b/doc/docs/figure/combinator-map-resolver.svg
new file mode 100644
index 0000000..96edee9
--- /dev/null
+++ b/doc/docs/figure/combinator-map-resolver.svg
@@ -0,0 +1,253 @@
+
+
diff --git a/doc/docs/figure/combinator-merge-waveform.svg b/doc/docs/figure/combinator-merge-waveform.svg
new file mode 100644
index 0000000..7291c90
--- /dev/null
+++ b/doc/docs/figure/combinator-merge-waveform.svg
@@ -0,0 +1,4871 @@
+
+
diff --git a/doc/docs/figure/combinator-merge.svg b/doc/docs/figure/combinator-merge.svg
new file mode 100644
index 0000000..820daf8
--- /dev/null
+++ b/doc/docs/figure/combinator-merge.svg
@@ -0,0 +1,309 @@
+
+
diff --git a/doc/docs/figure/filter-map.svg b/doc/docs/figure/filter-map.svg
deleted file mode 100644
index 36137c5..0000000
--- a/doc/docs/figure/filter-map.svg
+++ /dev/null
@@ -1,291 +0,0 @@
-
-
diff --git a/doc/docs/figure/handshake-waveform.svg b/doc/docs/figure/handshake-waveform.svg
new file mode 100644
index 0000000..8eb9fc6
--- /dev/null
+++ b/doc/docs/figure/handshake-waveform.svg
@@ -0,0 +1,4157 @@
+
+
diff --git a/doc/docs/figure/handshake.drawio.svg b/doc/docs/figure/handshake.drawio.svg
deleted file mode 100644
index ace1260..0000000
--- a/doc/docs/figure/handshake.drawio.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/doc/docs/figure/handshake.svg b/doc/docs/figure/handshake.svg
new file mode 100644
index 0000000..67ed7fe
--- /dev/null
+++ b/doc/docs/figure/handshake.svg
@@ -0,0 +1,276 @@
+
+
diff --git a/doc/docs/figure/hazard-and.svg b/doc/docs/figure/hazard-and.svg
new file mode 100644
index 0000000..13709b2
--- /dev/null
+++ b/doc/docs/figure/hazard-and.svg
@@ -0,0 +1,394 @@
+
+
diff --git a/doc/docs/figure/hazard-valid.svg b/doc/docs/figure/hazard-valid.svg
new file mode 100644
index 0000000..0558f14
--- /dev/null
+++ b/doc/docs/figure/hazard-valid.svg
@@ -0,0 +1,358 @@
+
+
diff --git a/doc/docs/figure/hazard-vr.svg b/doc/docs/figure/hazard-vr.svg
new file mode 100644
index 0000000..8813df2
--- /dev/null
+++ b/doc/docs/figure/hazard-vr.svg
@@ -0,0 +1,363 @@
+
+
diff --git a/doc/docs/figure/wave_form.drawio.svg b/doc/docs/figure/wave_form.drawio.svg
deleted file mode 100644
index bd76411..0000000
--- a/doc/docs/figure/wave_form.drawio.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/doc/docs/lang/combinator.md b/doc/docs/lang/combinator.md
index 0f597ca..b7d628b 100644
--- a/doc/docs/lang/combinator.md
+++ b/doc/docs/lang/combinator.md
@@ -6,17 +6,17 @@ The combinator specifies the combinational logic of calculating the egress inter
Note that in this section, you will see the dependency type `D` in the combinators' signature, for more information about the dependency, please refer to the [dependency section](../advanced/dependency.md).
-
-
-
+## Generic FSM Combinator
-The essence of the combinator is the `fsm` function.
-* The `fsm` function is the idiomatic mechanism to chain two interfaces in HazardFlow HDL.
+The essence of the interface combinator is the generic `fsm` combinator.
+
+
+* We assume the combinational logic of calculating the egress interface payload and ingress interface resolver is calculated every cycle. -->
-## Generic FSM Combinator
+
+
+
We provide the `fsm` combinator that transforms the ingress interface to egress interface with finite state machine.
With this combinator, you can represent an arbitrary FSM.
@@ -40,9 +40,17 @@ pub trait Interface: Sized {
It accepts two arguments which are the FSM's initial state and the combinational logic.
-We represent the combinational logic as a function.
-There are three input signals, which are ingress interface's forward signal, egress interface's backward signal, and current state.
-And there are three output signals, which are egress interface's forward signal, ingress interface's backward signal, and next state.
+We represent the combinational logic as a function (`f`). It takes three input signals:
+
+- Ingress interface's forward signal (`Self::Fwd`)
+- Egress interface's backward signal (`E::Bwd`)
+- Current state (`S`)
+
+and returns three output signals:
+
+- Egress interface's forward signal (`E::Fwd`)
+- Ingress interface's backward signal (`Self::Bwd`)
+- Next state (`S`)
## Standard Combinator Library
@@ -50,12 +58,12 @@ We provide standard combinator library for developers to facilitate their work.
We can roughly categorize the library into the following categories.
| Category | Description |
-|-------------|-----------------------------------------------------------------------------|
+| ----------- | --------------------------------------------------------------------------- |
| Mapping | Maintains the 1-to-1 relation between the ingress/egress interface. |
| 1-to-N | Splits the ingress interface into multiple egress interfaces. |
| N-to-1 | Merges multiple ingress interfaces into one egress interface. |
| Register | Stores the state into registers and could delay for one or multiple cycles. |
-| Source/Sink | Generates or consumes the interface. |
+| Source/sink | Generates or consumes the interface. |
| FSM | Runs a finite state machine with an internal state. |
| Conversion | Converts ingress hazard interface into egress hazard interface. |
@@ -63,18 +71,18 @@ For more details about all combinators, please refer to the [rustdoc](TODO).
### Mapping
-These combinators transform the ingress payload to egress payload or transform the egress resolver to ingress resolver.
-They could also filter out the undesired signals.
-The two most representative mapping combinators are `filter_map` and `map_resolver`.
+These combinators either transform the payload (ingress to egress) or transform the resolver (egress to ingress).
+
+We demonstrate the two most representative combinators: `filter_map` and `map_resolver`.
#### `filter_map`
As the name suggested, `filter_map` combinator has the functionality of `filter` and `map`.
It filters out the payload not satisfying certain conditions and transforms the ingress payload `P` to egress payload `EP`.
-We demonstrate the `filter_map` with the ingress interface of `Vr
`.
+We demonstrate the `filter_map` which takes `Vr
` and returns `Vr`.
-
+
It can be implemented with using `fsm` combinator like this:
@@ -92,26 +100,77 @@ impl Vr
{
It is stateless, and egress transfer happens when (1) ingress transfer happens, and (2) `f` returns `Some` with given ingress payload.
-Let's assume we define a function `f`, which finds out if the input signal is an even number, if yes return `Some(true)` else `Some(false)`, and return `None` if the signal if input is 0.
+For example, let's consider the following `f`:
+
+```rust,noplayground
+fn f(i: u32) -> HOption {
+ if i == 0 {
+ // If `i` is zero, filters out the payload.
+ None
+ } else if i & 1 == 0 {
+ // If `i` is even number, returns `true`.
+ Some(true)
+ } else {
+ // If `i` is odd number, returns `false`.
+ Some(false)
+ }
+}
+```
+
+Then the cycle-level behavior of `filter_map` is as follows:
+
+
+
+
-The cycle level behavior of `filter_map` (abbreviated `true` to `T`, `false` to `F`):
+
+
-- At cycle 0, transfer happens both at ingress and egress side.
-- At cycle 2, transfer happens at ingress side, but it is dropped by the filter condition and transfer does not happen at egress side.
+(`T` and `F` means `true` and `false`, respectively)
-
+- Cycle 0: transfer happens both at ingress and egress side.
+- Cycle 2: transfer happens at ingress side, but it was filtered out by `f`.
+- Cycle 5: transfer does not happens because egress is not ready to receive.
Note that `filter_map` can work with ingress interfaces `I, D>`, `I, D>` and `I` and there are variants of the `filter_map` like `filter_map_drop_with_r`.
@@ -120,6 +179,10 @@ Note that `filter_map` can work with ingress interfaces `I, D>`, `I, D>`. Similar to `filter_map`, `map_resolver` has other variants and can also work with other ingress interfaces.
+
+
+
+
```rust,noplayground
impl I, D> {
fn map_resolver(self, f: impl Fn(Ready) -> R) -> I, D> {
@@ -138,27 +201,79 @@ impl I, D> {
* It leaves the ingress payload untouched.
* This combinator usually being used as a connector for two other combinators whose resolver types are not compatible.
-Let's assume we define a function `f`, which transforms the register's address and register's data into a write-back resolver `struct` and passes it to the earlier stage in the 5-stage pipelined CPU core.
-The ingress payload is a simple `HOption`.
+For example, let's consider the following `f`:
-The cycle level behavior of `map_resolver`:
+```rust,noplayground
+fn f(i: u32) -> bool {
+ // Returns the parity of `i`.
+ i & 1 == 0
+}
+```
+
+
+
+Then the cycle-level behavior of `map_resolver` is as follows:
+
+
+
+
+
+
+
-It transforms the register's address and data into a write-back resolver signal `WbR`.
-Transfer only happens at both ingress and egress side at cycle 0.
+- It does not touch the forward signals and backward ready signal.
+- It transforms the backward inner signal to the parity.
+- Transfer happens at both sides at cycle 1 and 5.
-
+
### 1-to-N
-These combinators can either duplicate a single ingress interface into multiple egress interfaces or select one from the numerous egress interfaces to transfer the payload.
-The most two representative combinators are `lfork` and `branch`.
+These combinators can either duplicate a single ingress interface into multiple egress interfaces or select one from the numerous egress interfaces to transfer the payload.
+
+We demonstrate the two most representative combinators: `lfork` and `branch`.
#### lfork
@@ -166,41 +281,116 @@ This combinator delivers the ingress payload to all the egress interfaces' egres
We demonstrate `lfork` with the ingress interface `Vr
`, whose resolver is `()`.
Note that `lfork` can work with other ingress interfaces such as `I, D>`, `I>, D>`, etc.
+
+
+
+
```rust,noplayground
impl Vr
{
fn lfork(self) -> (Vr
, Vr
) {
- // @zhao: please fill out this.
+ self.fsm::<(Vr
, Vr
), ()>(|ip, (er1, er2), _| {
+ let ep1 = if er2.ready { ip } else { None };
+ let ep2 = if er1.ready { ip } else { None };
+ let ir = Ready::new(er1.ready && er2.ready, ());
+ ((ep1, ep2), ir, ())
+ })
}
}
```
- It is stateless.
-- This combinator splits the ingress interface into two egress interfaces with both types `Vr
`.
+- Ingress, first egress, and second egress transfer happens at same cycle.
+
+- The ingress interface ready signal is `true` when all the egress interfaces' ready signals are `true`. -->
-Let's assume the ingress payload is `HOption`, er1 is `bool` and er2 is `bool`.
-The cycle level behavior of `lfork`:
+The example cycle-level behavior of `lfork` is as follows:
+
+
+
+
+
+
+
-- At cycle 0, transfer happens at ingress side and both egress sides.
+- Cycle 1, 4, 5: transfer happens at ingress, first egress, and second egress sides.
#### branch
This combinator splits a single ingress interface into multiple egress interfaces and only selects one of the egress interfaces to transfer the payload, also combines all the egress interfaces' resolvers into the ingress resolver.
We demonstrate `branch` with the ingress interface `Vr
>`.
+
`BoundedU` can be considered as a bounded unsigned integer, if `N` is 3, the possible unsigned integers are 0, 1, 2.
For more information of the `BoundedU`, please refer to the `doc.rs`.
+
+
+
+
```rust,noplayground
-// @zhao: please fill out this.
-fn branch(self) -> [Vr
; N], ()>(|ip, ers, _| {
+ let Some((ip, sel)) = ip else {
+ // Ingress ready signal is true when valid signal is false.
+ return (None.repeat::(), Ready::new(true, er.map(|r| r.inner)), s);
+ };
+
+ let ep = None.repeat::().set(sel.value(), Some(ip));
+ let ir = Ready::new(ers[sel.value()].ready, ers.map(|r| r.inner));
+
+ (ep, ir, s)
+ })
+ }
+}
+
```
* `self` is the ingress interface `Vr<(P, BoundedU)>`.
@@ -214,35 +404,90 @@ The payload is `u32`, and we split the ingress interface into 2 egress interface
The cycle level behavior of `branch`:
+
+
+
+
+
+
+
-- At cycle 0, transfer happens at ingress side and the first egress side.
-
-
+- Cycle 2: transfer happens at ingress and first egress side.
+- Cycle 5: transfer happens at ingress and second egress side.
### N-to-1
These combinators merge multiple ingress interfaces into a single egress interface.
The egress interface could contain all the ingress interfaces' payload and resolver or select one of the ingress interfaces.
-The most two representative N-to-1 combinators are `join` and `cmerge`.
+
+We demonstrate the two most representative combinators: `join` and `merge`.
#### join
This combinator merges the ingress interfaces' payload and resolver.
-We demonstrate this combinator with the ingress interfaces `[Vr
; N]`.
+We demonstrate this combinator with the ingress interfaces `(Vr, Vr)`.
-```rust,noplayground
-impl JoinVrExt for [Vr
; N] {
- type E = Vr>;
+
+
+
- fn join(self) -> Vr> {
- .. // @zhao: please fill out this.
+```rust,noplayground
+impl JoinExt for (Vr, Vr) {
+ type E = Vr<(P1, P2)>;
+
+ fn join(self) -> Vr<(P1, P2)> {
+ self.fsm::, ()>(|(ip1, ip2), er, _| {
+ let ep = ip1.zip(ip2);
+ let ir1 = if ip2.is_some() { er } else { Ready::invalid(()) };
+ let ir2 = if ip1.is_some() { er } else { Ready::invalid(()) };
+ (ep, (ir1, ir2), ())
+ })
}
}
```
@@ -252,52 +497,161 @@ impl JoinVrExt for [Vr
; N] {
* The egress payload will be valid only when all the ingress payloads are valid.
* The ingress transfer happens only when all the ingress payloads are valid and also egress interface is ready to receive payload.
-Let's assume the 2 ingress interfaces' type is `Vr`.
-The cycle level behavior of `join`:
+The example cycle-level behavior of `join` is as follows:
+
+
+
+
+
+
+
-- At cycle 0, both ingress and egress transfer happen, the egress payload is an array of the ingress payloads.
-- At cycle 2, both the ingress and egress transfer do not happen even though both ingress payloads are valid, since the egress ready signal is `fasle`.
+- Cycle 1, 4, 5: transfer happens.
-#### cmerge
+#### merge
This combinator will select one from the ingress interfaces to deliver the ingress payload to the egress payload and also leave the inner of the egress resolver untouched to the ingress interfaces.
We will demonstrate the `cmerge` combinator with 2 ingress interfaces `[Vr, Vr]`.
Note that in our code base, we implement `cmerge` with ingress interfaces `[I, D>; N]`. We can consider the `H` as `ValidH
` and `N` as 2.
+
+
+
+
```rust,noplayground
-// @zhao: please fill out this.
-fn cmerge(self) -> I, N>, { Dep::Demanding }>
+impl MergeExt for (Vr
, Vr
) {
+ type E = Vr
;
+
+ fn merge(self) -> Vr
{
+ self.fsm::, ()>(|(ip1, ip2), er, _| {
+ let ep = ip1.or(ip2);
+ let ir1 = er;
+ let ir2 = if ip1.is_none() { er } else { Ready::invalid(()) };
+ (ep, (ir1, ir2), ())
+ })
+ }
+}
```
* `SelH` wraps `H` with additional selector bit in payload `P = (H::P, BoundedU)`.
* This combinator will select the first ingress interface, whose ingress payload is valid, from the array of the ingress interfaces, when the egress interface is ready to receive the payload.
-Let's assume the 2 ingress interfaces' type is `Vr`.
-The cycle level behavior of `cmerge`:
+The example cycle-level behavior of `merge` is as follows:
+
+
+
+
-| cycle | ip1 | ip2 | er | ep | ir1 | ir2 |
-|-------|-----------|-----------|-----|-----------------|-----|-----|
-| 0 | `None` | `Some(8)` | `T` | `Some((8, 1))` | `T` | `T` |
-| 1 | `Some(88)`| `Some(9)` | `T` | `Some((88, 0))` | `T` | `F` |
-| 2 | `None` | `None` | `T` | `None` | `F` | `F` |
+
-- At cycle 0, transfer happens at second ingress side and egress side.
-- At cycle 1, transfer happens at first ingress side and egress side.
+
-
+- Cycle 1: first ingress and egress transfer happens.
+- Cycle 3: first ingress and egress transfer happens.
+- Cycle 5: second ingress and egress transfer happens.
### Register slices
These registers can maintain the states in their registers and could delay one or multiple cycles to send out the states.
-We demonstrate the usage of `reg_fwd` with ingress interface `I, D>` and `fifo` with ingress interfaces `I, D>`.
-Similar to other combinators, register style combinators have other variants too, e.g. `naked_reg_bwd`, `shift_reg_fwd`, etc.
+
+We demonstrate the two most representative combinators: `reg_fwd` and `fifo`.
+
+
#### `reg_fwd`
@@ -329,7 +683,7 @@ Let's assume the ingress interface type is `Vr` and we turn on the `pipe`.
The cycle level behavior of `reg_fwd`:
| cycle | ip | er | state (= ep) | ir |
-|-------|------------|-----|--------------|-----|
+| ----- | ---------- | --- | ------------ | --- |
| 0 | `Some(11)` | `T` | `None` | `T` |
| 1 | `Some(12)` | `T` | `Some(11)` | `T` |
| 2 | `Some(13)` | `F` | `Some(12)` | `F` |
@@ -339,52 +693,57 @@ The cycle level behavior of `reg_fwd`:
- At cycle 0, 1, 3, 4, ingress transfer happens.
- At cycle 1, 3, 4, egress transfer happens.
-
-
-#### FIFO
+#### fifo
This combinator is a pipelined FIFO queue, it can accept a new element every cycle if the queue is not full.
Let's assume the ingress interface type is `Vr` and queue size is 3.
The cycle level behavior of `fifo`:
-| cycle | ip | er | state | ep | ir |
-|-------|-----------|-----|-------|--|--|
-| 0 | `Some(0)` | `T` | `init_state (input): empty`|`None`|`T`|
-| 1 | `Some(1)` | `T` | `[Some(0)]`|`Some(0)`|`T`|
-| 2 | `Some(2)` | `F` | `[Some(1)]`|`Some(1)`|`T`|
-| 3 | `Some(3)` | `F` | `[Some(2), Some(1)]`|`Some(1)`|`T`|
-| 4 | `Some(4)` | `F` | `[Some(3), Some(2), Some(1)]`|`Some(1)`|`F`|
-| 5 | `Some(4)` | `T` | `[Some(3), Some(2), Some(1)]`|`Some(1)`|`F`|
-| 6 | `Some(4)` | `T` | `[Some(3), Some(2)]`|`Some(2)`|`T`|
-| 7 | `Some(5)` | `T` | `[Some(4), Some(3)]`|`Some(3)`|`T`|
+| cycle | ip | er | state | ep | ir |
+| ----- | --------- | --- | ----------------------------- | --------- | --- |
+| 0 | `Some(0)` | `T` | `init_state (input): empty` | `None` | `T` |
+| 1 | `Some(1)` | `T` | `[Some(0)]` | `Some(0)` | `T` |
+| 2 | `Some(2)` | `F` | `[Some(1)]` | `Some(1)` | `T` |
+| 3 | `Some(3)` | `F` | `[Some(2), Some(1)]` | `Some(1)` | `T` |
+| 4 | `Some(4)` | `F` | `[Some(3), Some(2), Some(1)]` | `Some(1)` | `F` |
+| 5 | `Some(4)` | `T` | `[Some(3), Some(2), Some(1)]` | `Some(1)` | `F` |
+| 6 | `Some(4)` | `T` | `[Some(3), Some(2)]` | `Some(2)` | `T` |
+| 7 | `Some(5)` | `T` | `[Some(4), Some(3)]` | `Some(3)` | `T` |
- At cycle 0, 1, 2, 3, 6, 7, ingress transfer happens.
- At cycle 1, 5, 6, 7, egress transfer happens.
-* The ingress ready signal is true as long as the queue is not full.
-* At `T5`, even though the egress transfer happens, the ingress transfer does not happen since the queue is still full.
-* The ingress transfer happens again at `T6` since the queue is not full anymore.
-* The state is updated in the next clock cycle.
+- The ingress ready signal is true as long as the queue is not full.
+- At `T5`, even though the egress transfer happens, the ingress transfer does not happen since the queue is still full.
+- The ingress transfer happens again at `T6` since the queue is not full anymore.
+- The state is updated in the next clock cycle.
### Source and sink
These combinators have only either ingress interface or egress interface.
+We demonstrate the two most representative combinators: `source` and `sink`.
+
#### source
This combinator immediately returns the data to the payload when the data is coming from resolver.
The `source` combinator only has the egress interface.
```rust,noplayground
-// @zhao: please fill out this.
-pub fn source() -> I, { Dep::Demanding }>
+impl I, { Dep::Demanding }> {
+ fn source() -> I, { Dep::Demanding }> {
+ ().fsm::, { Dep::Demanding }>, ()>((), |_, er, _| {
+ let ip = if er.ready { Some(er.inner) } else { None };
+ (ip, (), ())
+ })
+ }
+}
```
-* The egress resolver type is the same as the egress payload type `P`.
-* The egress transfer happens as long as the egress resolver ready signal is true.
-* It transfer the resolver to the payload within the same clock cycle with egress transfer happens.
+- The egress resolver type is the same as the egress payload type `P`.
+- The egress transfer happens as long as the egress resolver ready signal is true.
+- It transfer the resolver to the payload within the same clock cycle with egress transfer happens.
#### sink
@@ -392,11 +751,18 @@ This combinator maintains a state and calculates the ingress resolver with `f`,
The `sink` combinator only has the ingress interface.
```rust,noplayground
-pub fn sink(self, init_state: S, f: impl Fn(HOption, S) -> (H::R, S))
+impl Vr
{
+ fn sink(self) {
+ self.fsm::<(), ()>((), |ip, _, _| {
+ let ir = Ready::valid(());
+ ((), ir, ())
+ })
+ }
+}
```
-* The ingress resolver is calculated every clock cycle.
-* The state is updated only when ingress transfer happens.
+- The ingress resolver is calculated every clock cycle.
+- The state is updated only when ingress transfer happens.
### FSM
@@ -417,7 +783,7 @@ After the resulting state is transferred, the FSM is reset and starts accumulati
```rust,noplayground
// @zhao: please fill out this.
-pub fn fsm_ingress(self, init: S, f: impl Fn(P, R, S) -> (S, bool)) -> I, { Dep::Helpful }>
+fn fsm_ingress(self, init: S, f: impl Fn(P, R, S) -> (S, bool)) -> I, { Dep::Helpful }>
```
* `self` is the ingress interface `I, D>`.
@@ -444,7 +810,7 @@ fn sum_3(input: Vr) -> Vr {
The cycle level behavior of `fsm_ingress` in `sum_3`:
| cycle | ip | er | state | ep | ir |
-|-------|-----------|-----|----------------|-----------------|-----|
+| ----- | --------- | --- | -------------- | --------------- | --- |
| 0 | `Some(0)` | `T` | `((0, 0), F)` | `None` | `T` |
| 1 | `Some(1)` | `T` | `((1, 0), F)` | `None` | `T` |
| 2 | `Some(2)` | `T` | `((2, 1), F)` | `None` | `T` |
@@ -471,7 +837,7 @@ Only after the FSM is finished, the combinator can accept a new ingress payload.
```rust,noplayground
// @zhao: please fill out this.
-pub fn fsm_egress(self, init: S, flow: bool, f: impl Fn(P, S) -> (EP, S, bool)) -> I, D>
+fn fsm_egress(self, init: S, flow: bool, f: impl Fn(P, S) -> (EP, S, bool)) -> I, D>
```
* `self` is the ingress interface `I, D>`.
@@ -496,7 +862,7 @@ fn consecutive_3(input: Vr) -> Vr {
The cycle level behavior of `fsm_egress` in `consecutive_3`:
| cycle | ip | er | state | ep | ir |
-|-------|-----------|-----|----------------|-----------|-----|
+| ----- | --------- | --- | -------------- | --------- | --- |
| 0 | `Some(0)` | `T` | `(None, 0)` | `Some(0)` | `T` |
| 1 | `Some(1)` | `T` | `(Some(0), 1)` | `Some(1)` | `F` |
| 2 | `Some(1)` | `T` | `(Some(0), 2)` | `Some(2)` | `T` |
diff --git a/doc/docs/lang/interface.md b/doc/docs/lang/interface.md
index 93d52a8..5503fe0 100644
--- a/doc/docs/lang/interface.md
+++ b/doc/docs/lang/interface.md
@@ -9,40 +9,52 @@ We define a `struct` implementing the `Interface` trait and containing the `Haza
## Hazard
-### Handshake
+### 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 computes `Valid` signal and `Payload` signal each clock cycle.
-* The receiver computes `Ready` signal each clock cycle.
-* The `Valid` and `Ready` signals are shared between the sender and the receiver.
-* When both `Valid` and `Ready` signals are `true`, we define it as **Transfer happens**.
-* Note that the `Payload` is always flowing through the wires.
+- 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.
-Wave form diagram:
+Example waveform:
-
+
-* At cycle 2, the sender turns on the valid bit until cycle 4.
-* The receiver turns on the ready bit at cycle 3 until cycle 4.
-* The transfer happens at cycle 3 since only when both the valid bit and the ready bit are turned on.
+
+
+- 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.
-
-
```rust,noplayground
pub trait Hazard {
type P: Copy;
@@ -54,21 +66,28 @@ pub trait Hazard {
For any hazard type `H`, its member type and functions has the following meaning:
-* `H::P`: Payload signal type.
-* `H::R`: Resolver signal type.
-* `H::ready`: Indicates the receiver is ready to receive with current pair of payload and resolver.
+- `H::P`: Payload signal type.
+- `H::R`: Resolver signal type.
+- `H::ready`: Indicates whether the receiver is ready to receive with current pair of payload and resolver.
+
+
### Examples
We provide a few handy primitive hazard interfaces for developers.
-#### Valid
+#### `ValidH`
+
+
+
+
+
+`ValidH` represents a communication without backpressure.
-Valid hazard represents a communication without backpressure, its ready function always returns `true`.
-Its specification is as follows:
+It always ready to receive the payload, it has the following specification:
```rust,noplayground
-pub struct ValidH;
+struct ValidH;
impl Hazard for ValidH
{
type P = P;
@@ -80,26 +99,28 @@ impl Hazard for ValidH
{
}
```
-* The payload type of the Valid Hazard Interface is `HOption
`.
-* The resolver type of the Valid Hazard Interface is `R`.
-* When the payload is valid, which means it is `Some(P)`, transfer happens since the `ready` function always returns `true`.
-* Specially, when the resolver is `()` and the payload signal does not depend on the resolver signal, we define the Valid Hazard Interface as
- ```rust,noplayground
- pub type Valid
= I, { Dep::Helpful }>;
- ```
+For reusability, we allow `ValidH` to have resolver signals that simply flow from the receiver to the sender.
-#### And
+#### `AndH`
+
+
+
+
+
+For a given hazard specification `H`, the conjunctive `AndH` 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.
-We define an **And** hazard `AndH`, whose resolver type is `Ready`. `Ready` is a `struct` containing both a resolver and a ready signal in HazardFlow HDL. The interface containing the And Hazard is an And Hazard Interface.
+
```rust,noplayground
-pub struct AndH;
+struct AndH;
-pub struct Ready {
- pub ready: bool,
- pub inner: R,
+struct Ready {
+ ready: bool,
+ inner: R,
}
impl Hazard for AndH {
@@ -112,30 +133,36 @@ impl Hazard for AndH {
}
```
-* The payload type of the And Hazard Interface is `HOption
`.
-* The resolver type of the And Hazard Interface is `Ready`.
-* When the payload is valid, which means the payload is `Some(P)`, the ready signal in the resolver is `true`, and the `ready` function returns `true`, then transfer happens.
+The `ready` field of the `Ready` struct represents the availability of the receiver.
-#### Valid-Ready
+#### `VrH`
-When the resolver is `()`, we combine the Valid Hazard and And Hazard and form the **Valid-Ready** hazard `VrH
`. We define the Valid-Ready Hazard as `pub type VrH
= AndH>`. The interface containing the Valid Ready Hazard is a Valid-Ready Interface.
+
+
+
+
+We define the valid-ready `VrH` specification as `AndH>`.
-We can represent the Valid-Ready protocol with using And and Valid protocol as follows:
+For reusability, we allow `VrH` to have resolver signals that simply flow from the receiver to the sender.
```rust,noplayground
-pub struct VrH = AndH>;
+type VrH = AndH>;
```
-* The payload type of the Valid-Ready Interface is `HOption
`.
+
## Interface
-We define the interface as a protocol with forward signal, backward signal, and some other methods.
-The other methods are related to the [combinator](./combinator.md) and [module](./module.md), please refer to the corresponding section.
-Any `struct` implements the interface protocol we can consider it as an 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
```rust,noplayground
pub trait Interface {
@@ -146,12 +173,12 @@ pub trait Interface {
}
```
-* Forward signal
- * This specifies the forward signal type.
-* Backward signal
- * This specifies the backward signal type.
-* Other functions
- * These functions are related to the [combinator](./combinator.md) and [module](./module.md), please refer to these sections for further reading.
+For any interface type `I`, its member type has the following meaning:
+
+- `I::Fwd`: Forward signal type.
+- `I::Bwd`: Backward signal type.
+
+It contains the other functions related to the [combinator](./combinator.md) and [module](./module.md), please refer to these sections for further reading.
### Hazard Interface
@@ -159,7 +186,9 @@ pub trait Interface {
-If a `struct` implements the interface trait and also contains a hazard, we consider it as a **hazard interface**. In the HazardFlow HDL, we define it as `I`, where `H` is the hazard, and `D` is the dependency type of hazard protocol. For more information of the dependency, please refer to the [dependency section](../advanced/dependency.md).
+For an arbitraty hazard specification `H`, we define the hazard interface `I`, where `D` is the dependency type. (For more information of the dependency, please refer to the [dependency section](../advanced/dependency.md))
+
+
```rust,noplayground
pub struct I;
@@ -170,36 +199,61 @@ impl Interface for I {
}
```
-* 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.
+- 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.
+
+```rust,noplayground
+type Valid
= I, { Dep::Helpful }>;
+type Vr
= I, { Dep::Helpful }>;
+```
### Compound Interface
Compound types such as tuple, struct, and array also implement the `Interface` trait.
-These types are useful when we use "1-to-N" or "N-to-1" combinators.
-For example, array of interfaces also implements `Interface` trait as follows:
+These interfaces are commonly used for IO of "1-to-N" or "N-to-1" modules.
+
+#### Tuple
+
+Tuple of interfaces `(If1, If2)` implements `Interface` trait as follows:
```rust,noplayground
-impl Interface for [If; N] {
- type Fwd = Array;
- type Bwd = Array;
+// In practice, it is implemented as a macro.
+impl 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 array of the interface's forward signal.
-* The backward signal of the array interface is the array of the interface's backward signal.
+- The forward signal of the array interface is the tuple of the interface's forward signal.
+- The backward signal of the array interface is the tuple of the interface's backward signal.
-As another example, tuple of interfaces also implements `Interface` trait as follows (in actual implementation, it is implemented as a macro):
+#### Struct
```rust,noplayground
-impl Interface for (If1, If2) {
- type Fwd = (If1::Fwd, If2::Fwd);
- type Bwd = (If1::Bwd, If2::Bwd);
+#[derive(Debug, Interface)]
+struct StructIf {
+ i1: If1,
+ i2: If2,
+}
+```
+
+By applying the `Interface` derive macro to a struct in which all fields are of interface type, the struct itself can also become an interface type.
+
+#### Array
+
+Array of interfaces `[If; N]` also implements `Interface` trait as follows:
+
+```rust,noplayground
+impl Interface for [If; N] {
+ type Fwd = Array;
+ type Bwd = Array;
}
```
-* The forward signal of the array interface is the tuple of the interface's forward signal.
-* The backward signal of the array interface is the tuple of the interface's backward signal.
+- The forward signal of the array interface is the array of the interface's forward signal.
+- The backward signal of the array interface is the array of the interface's backward signal.
diff --git a/doc/docs/lang/module.md b/doc/docs/lang/module.md
index cf37c6b..c5b9ab5 100644
--- a/doc/docs/lang/module.md
+++ b/doc/docs/lang/module.md
@@ -1,36 +1,44 @@
# Modules
-Modules are used to structure the design of complex hardware systems, such as processors, by breaking them down into smaller, manageable, and reusable components.
-In HazardFlow HDL, we define a module as a function takes the ingress interface as the input and returns the egress interface.
+
+
+We consider a module as a function takes the ingress interface and returns the egress interface.
```rust,noplayground
m: impl FnOnce(I) -> E
```
-In HazardFlow HDL, we can construct a module as a class of interface combinators. Please refer to the [Interface Combinators](./combinator.md) for more information.
+
+We can construct a module as a class of interface combinators. Please refer to the [Interface Combinators](./combinator.md) for more information.
## Combine Black Box Module to Interface
-The `comb` function within the interface trait is used to combine the black box module to the given interface and return the egress interface.
+The `comb` method within the interface trait is used to combine the black box module to the given interface and return the egress interface.
```rust,noplayground
fn comb(self, m: impl FnOnce(Self) -> E) -> E {
m(self)
}
```
-* Applying the given interface to the module is essentially applying the module function `m` to the ingress interface.
-* It is useful when we want to combine multiple modules together.
+
+- Applying the given interface to the module is essentially applying the module function `m` to the ingress interface.
+- It is useful when we want to combine multiple modules together.
In the CPU core, we can combine the multiple stage modules by using `comb`.
+
```rust,noplayground
pub fn core(
- imem: impl FnOnce(Vr) -> Vr,
+ imem: impl FnOnce(Vr) -> Vr,
dmem: impl FnOnce(Vr) -> Vr,
) {
- fetch::(imem).comb(decode).comb(exe).comb(move |ingress| mem(ingress, dmem)).comb(wb)
+ fetch::(imem)
+ .comb(decode)
+ .comb(exe)
+ .comb(move |ingress| mem(ingress, dmem))
+ .comb(wb)
}
```
-* `imem` is the instruction memory module and `dmem` is the data memory module.
-* We chain the 5 sub-modules `fetch`, `decode`, `exe`, `mem`, and `wb` modules by using the `comb` function.
+- `imem` and `dmem` are modules for instruction memory and data memory, respectively.
+- We chain the 5 sub-modules `fetch`, `decode`, `exe`, `mem`, and `wb` by using the `comb` method.
TODO: more module combinators @minseong