|
6 | 6 |
|
7 | 7 | # REQuest β ACKnowledge |
8 | 8 |
|
9 | | -JavaScript Tool set to construct, transform and analyze digital circuits based on elastic transactional [protocol](./docs/protocol.md) and Request-Acknowledge handshake. |
| 9 | +JavaScript tool set to construct, transform, and analyze digital circuits based on |
| 10 | +an elastic (latency-insensitive) transactional [protocol](./docs/protocol.md) and a |
| 11 | +Request-Acknowledge handshake. |
10 | 12 |
|
11 | | -User describes circuit JavaScript API, add standard components from the library, or create new componets. |
| 13 | +You describe a circuit through a small JavaScript API, wire together standard components |
| 14 | +from the library, or define your own. The resulting graph is then rendered to synthesizable |
| 15 | +Verilog RTL and to diagrams (SVG / Graphviz dot). |
12 | 16 |
|
13 | | -Several standard [controllers](./docs/controller.md) provided. |
| 17 | +- **Elastic by construction** β every channel carries `req`/`ack`/`dat`. Backpressure and |
| 18 | + flow control are generated for you. |
| 19 | +- **Correct-by-construction transformations** β change a channel's buffer capacity, retime, |
| 20 | + or insert bubbles without altering functional behavior. |
| 21 | +- **Pluggable components** β standard [controllers](./docs/controller.md) (fork, join, MIMO) |
| 22 | + plus custom node macros. |
14 | 23 |
|
15 | | -User can transform constructed circuit by changing buffer capacity or performing other correct by construction transformations. |
16 | | - |
17 | | -## Usage |
18 | | - |
19 | | -The package can be installed from `npm`: |
| 24 | +## Install |
20 | 25 |
|
21 | 26 | ```sh |
22 | 27 | npm i reqack |
23 | 28 | ``` |
24 | 29 |
|
25 | | -and imported into your JavaScript code: |
26 | | - |
27 | 30 | ```js |
28 | 31 | const reqack = require('reqack'); |
29 | 32 | ``` |
30 | 33 |
|
31 | | -A **circuit** can be constructed this way: |
| 34 | +## Concepts |
| 35 | + |
| 36 | +A circuit is a directed hypergraph: |
| 37 | + |
| 38 | +- **Circuit** β the top-level graph. Becomes a Verilog module. |
| 39 | +- **Node** β an operation, a custom component, or an I/O port. A node with no incoming |
| 40 | + edges is a **target** (module input); a node with no outgoing edges is an **initiator** |
| 41 | + (module output). |
| 42 | +- **Edge (channel)** β a link between nodes carrying `{width, capacity}`. The `capacity` |
| 43 | + selects the elastic buffer inserted on that channel. |
| 44 | + |
| 45 | +## Constructing a circuit |
| 46 | + |
| 47 | +The core API in [lib/fhyper.js](lib/fhyper.js) is intentionally compact: circuits, nodes, |
| 48 | +and edges are built by calling the value returned from the previous step. |
| 49 | + |
| 50 | +A **circuit** is created by calling `reqack.circuit`. The optional label becomes the |
| 51 | +Verilog module name. |
32 | 52 |
|
33 | 53 | ```js |
34 | 54 | const g = reqack.circuit('circuit_name'); |
35 | 55 | ``` |
36 | 56 |
|
37 | | -A **node** can be constructed by calling the circuit function. Optional `node_label` string will be used as standard or custom operation or as a root of a signal name. |
| 57 | +A **node** is created by calling the circuit. The optional label is used as a standard |
| 58 | +operator (see [Operators](#operators)), a custom macro name, or the root of a signal name. |
38 | 59 |
|
39 | 60 | ```js |
40 | 61 | const node1 = g('node_label'); |
41 | 62 | ``` |
42 | 63 |
|
43 | | -A **edge** can be constructed by calling the node function. Optional argument is an `Object` with two major properties (width, capacity). |
| 64 | +An **edge** is created by calling a node. The optional argument is an object with two main |
| 65 | +properties, `width` and `capacity`. |
44 | 66 |
|
45 | 67 | ```js |
46 | 68 | const edge1 = node1({width: 32, capacity: 1}); |
47 | 69 | ``` |
48 | 70 |
|
49 | | -One node can be **connected** to another node by calling edge with a destination node. |
| 71 | +An edge is **connected** to a destination node by calling it with that node. The call |
| 72 | +returns the same edge, so connections chain (this is a fork β one source, many targets). |
| 73 | + |
| 74 | +```js |
| 75 | +edge1(node2)(node3); // fan edge1 out to node2 and node3 |
| 76 | +``` |
| 77 | + |
| 78 | +Nodes also accept upstream edges as extra arguments (a join β many sources, one node), |
| 79 | +including arrays of edges: |
| 80 | + |
| 81 | +```js |
| 82 | +const add = g('+', g()(), g()()); // two inputs feeding an adder |
| 83 | +const sum = g('+', [a, b, c].map(x => x())); // n inputs from an array |
| 84 | +``` |
| 85 | + |
| 86 | +### Edge capacity |
| 87 | + |
| 88 | +`capacity` selects the channel's elastic buffer: |
| 89 | + |
| 90 | +| capacity | buffer | latency | notes | |
| 91 | +| ------------- | ----------- | ------- | --------------------------------------- | |
| 92 | +| `0` / omitted | wire | 0 | asynchronous, combinational backpressure | |
| 93 | +| `1` | EB1 | 1 | async backpressure | |
| 94 | +| `1.5` | EB1.5 | 1 | synchronous backpressure, stall cap 2 | |
| 95 | +| `1.7` | EB1.7 | 1 | synchronous backpressure | |
| 96 | +| integer `>= 2`| FIFO | 1 | N-entry queue | |
| 97 | + |
| 98 | +See [docs/controller.md](docs/controller.md) for the controller logic behind each. |
| 99 | + |
| 100 | +## Operators |
| 101 | + |
| 102 | +A node whose label is a string is treated as a datapath operator. Multi-operator labels |
| 103 | +are evaluated in **RPN** (space-separated), consuming the node's inputs in order β see |
| 104 | +[lib/rpn.js](lib/rpn.js). |
| 105 | + |
| 106 | +```js |
| 107 | +g('+'); // add |
| 108 | +g('-'); // subtract |
| 109 | +g('*'); // signed multiply |
| 110 | +g('~ & |'); // (in RPN) invert, AND, OR |
| 111 | +``` |
| 112 | + |
| 113 | +Supported binary operators: `+ - * / % & | ^ << >> > >= < <= == != && ||`. |
| 114 | +Unary: `! ~`. Numeric literals and `swap` are also recognized. |
| 115 | + |
| 116 | +## Custom components (macros) |
| 117 | + |
| 118 | +Pass a `macros` object to `reqack.verilog` to define nodes that emit their own datapath |
| 119 | +and/or control logic. A macro may provide `data`, `ctrl`, `ctrl2data`, or `parameters`. |
| 120 | + |
| 121 | +```js |
| 122 | +const macros = { |
| 123 | + custom: { |
| 124 | + // p.t = target (input) sockets, p.i = initiator (output) sockets |
| 125 | + data: p => p.i.map(lhs => |
| 126 | + `assign ${lhs.wire} = ${p.t.map(e => e.wire).join(' ^ ')};`) |
| 127 | + } |
| 128 | +}; |
| 129 | + |
| 130 | +const g = reqack.circuit(); |
| 131 | +const fn = g('custom'); |
| 132 | +g()()(fn); |
| 133 | +g()()(fn); |
| 134 | +fn({capacity: 1})(); |
| 135 | + |
| 136 | +const rtl = reqack.verilog(g, macros); |
| 137 | +``` |
| 138 | + |
| 139 | +The built-in `reqack.macros` (currently `deconcat`) and `reqack.ctrl.fork` provide ready |
| 140 | +components. See [test/basic.js](test/basic.js) for many worked examples. |
| 141 | + |
| 142 | +## Generating output |
50 | 143 |
|
51 | 144 | ```js |
52 | | -edge1(node2); // -> edge1 |
| 145 | +const rtl = reqack.verilog(g, macros); // synthesizable Verilog (datapath + _ctrl module) |
| 146 | +const svg = reqack.svg(g); // SVG diagram (dagre layout) |
| 147 | +const dot = reqack.dot(g); // Graphviz dot source |
| 148 | +const manifest = reqack.manifest(g, name); // project manifest (module.exports = {...}) |
53 | 149 | ``` |
54 | 150 |
|
55 | | -Resulted Verilog RTL can be produced by calling |
| 151 | +`reqack.verilog` emits two modules: the datapath `<name>` and its handshake controller |
| 152 | +`<name>_ctrl`. |
| 153 | + |
| 154 | +## Worked example |
| 155 | + |
| 156 | +A single 4-bit channel with a 1-entry elastic buffer: |
56 | 157 |
|
57 | 158 | ```js |
58 | | -const verilogString = reqack.verilog(g, {}); |
| 159 | +const g = reqack.circuit('eb1'); |
| 160 | +g()({width: 4, capacity: 1})(); // target -> EB1 edge -> initiator |
| 161 | +const rtl = reqack.verilog(g, {}); |
59 | 162 | ``` |
60 | 163 |
|
61 | | -SVG image can be rendered by calling |
| 164 | +produces the datapath module and its handshake controller: |
| 165 | + |
| 166 | +```verilog |
| 167 | +module eb1 ( |
| 168 | + // per node (target / initiator) |
| 169 | + input clk, |
| 170 | + input reset_n, |
| 171 | + input [3:0] t_0_dat, |
| 172 | + input t_0_req, |
| 173 | + output t_0_ack, |
| 174 | + output [3:0] i_1_dat, |
| 175 | + output i_1_req, |
| 176 | + input i_1_ack |
| 177 | +); |
| 178 | +wire [3:0] dat0, dat0_nxt; |
| 179 | +// per node |
| 180 | +assign dat0_nxt = t_0_dat; // node:0 is target port |
| 181 | +assign i_1_dat = dat0; // node:1 is initiator port |
| 182 | +// per edge |
| 183 | +wire en0; // edge:0 EB1 |
| 184 | +reg [3:0] dat0_r; |
| 185 | +always @(posedge clk) if (en0) dat0_r <= dat0_nxt; |
| 186 | +assign dat0 = dat0_r; |
| 187 | +
|
| 188 | +eb1_ctrl uctrl ( |
| 189 | + .clk(clk), |
| 190 | + .reset_n(reset_n), |
| 191 | + .t_0_req(t_0_req), |
| 192 | + .t_0_ack(t_0_ack), |
| 193 | + .i_1_req(i_1_req), |
| 194 | + .i_1_ack(i_1_ack), |
| 195 | + .en0(en0) |
| 196 | +); |
| 197 | +endmodule // eb1 |
| 198 | +
|
| 199 | +module eb1_ctrl ( |
| 200 | + // per node (target / initiator) |
| 201 | + input clk, |
| 202 | + input reset_n, |
| 203 | + input t_0_req, |
| 204 | + output t_0_ack, |
| 205 | + output i_1_req, |
| 206 | + input i_1_ack, |
| 207 | + output en0 |
| 208 | +); |
| 209 | +wire req0, ack0, ack0_0, req0_0; |
| 210 | +// node:t_0 target |
| 211 | +assign req0 = t_0_req; |
| 212 | +assign t_0_ack = ack0; |
| 213 | +// edge:0 EB1 |
| 214 | +wire ack0m; |
| 215 | +reg req0m; |
| 216 | +assign en0 = req0 & ack0; |
| 217 | +assign ack0 = ~req0m | ack0m; |
| 218 | +always @(posedge clk or negedge reset_n) if (~reset_n) req0m <= 1'b0; else req0m <= ~ack0 | req0; |
| 219 | +
|
| 220 | +// edge:0 fork |
| 221 | +assign req0_0 = req0m; |
| 222 | +assign ack0m = ack0_0; |
| 223 | +// node:1 initiator |
| 224 | +assign i_1_req = req0_0; |
| 225 | +assign ack0_0 = i_1_ack; |
| 226 | +endmodule // eb1_ctrl |
| 227 | +``` |
| 228 | + |
| 229 | +The data path (`eb1`) holds registers and multiplexing; the controller (`eb1_ctrl`) |
| 230 | +holds the `req`/`ack` handshake FSM and drives each register's enable (`en0`). |
| 231 | + |
| 232 | +## High-level synthesis (HLS) |
| 233 | + |
| 234 | +`reqack.hls` turns a JavaScript arithmetic expression into a circuit, mapping operators to |
| 235 | +elastic nodes β see [lib/hls.js](lib/hls.js). |
62 | 236 |
|
63 | 237 | ```js |
64 | | -const svg = reqack.svg(g); |
| 238 | +const hls = reqack.hls; |
| 239 | +const resultEdge = hls((a, b, c) => (a + b) - c)('a', 'b', 'c'); |
65 | 240 | ``` |
66 | 241 |
|
| 242 | +## Also exported |
| 243 | + |
| 244 | +- `reqack.dagre` β the bundled [dagre](https://github.com/dagrejs/dagre) layout engine. |
| 245 | +- `reqack.firrtl` β FIRRTL backend (placeholder, not yet implemented). |
| 246 | + |
| 247 | +## Documentation |
| 248 | + |
| 249 | +- [Link protocol](docs/protocol.md) β the `req`/`ack`/`dat` signaling rules. |
| 250 | +- [Controllers](docs/controller.md) β fork, join, MIMO, and edge buffers (EB0/1/1.5/β¦). |
| 251 | +- [Transformations](docs/transformations.md) β retiming, bubble insertion, memory bypass. |
| 252 | +- [Labeling](docs/labeling.md) β how graph/node/edge labels are interpreted. |
| 253 | +- [References](docs/references.md) β background reading. |
| 254 | +- [LLM-friendly API](docs/llm-api.md) β **roadmap / proposal**. Describes a future |
| 255 | + named-method API (`addNode`, `addEdge`, `connectTo`, validation, types). Not yet |
| 256 | + implemented β the current API is the one documented above. |
| 257 | + |
67 | 258 | ## Testing |
68 | 259 |
|
69 | 260 | ```sh |
|
0 commit comments