diff options
| author | Kimplul <kimi.h.kuparinen@gmail.com> | 2026-09-27 22:30:41 +0300 |
|---|---|---|
| committer | Kimplul <kimi.h.kuparinen@gmail.com> | 2026-09-27 22:30:41 +0300 |
| commit | f4afcfa58eb90d14abd55d79640ecda13b23dc21 (patch) | |
| tree | c0f63b35502d74cbdf3d2bc881675859a235596d /src | |
| parent | bc5941a1fcfd0a607a6aef51f7659c3f233b65dc (diff) | |
| download | ttarv32-master.tar.gz ttarv32-master.zip | |
+ Not great latency, but has a fairly high fmax and low resource usage
on my FPGA, so go with this for now. Likely better suited for data
path, unsure what I'll do for the control path.
Diffstat (limited to 'src')
| -rw-r--r-- | src/common.svh | 2 | ||||
| -rw-r--r-- | src/router.sv | 160 | ||||
| -rw-r--r-- | src/skid_buffer.sv | 53 |
3 files changed, 215 insertions, 0 deletions
diff --git a/src/common.svh b/src/common.svh index 67a87d2..131a2ca 100644 --- a/src/common.svh +++ b/src/common.svh @@ -116,4 +116,6 @@ typedef enum op_t { `define rn(r) (REG + port_t'(r)) `define alu(fu) (ALU + port_t'(fu)) +`define COUNTER(x) ((x) > 1 ? $clog2(x) : 1) + `endif /* __COMMON_SVH__ */ diff --git a/src/router.sv b/src/router.sv new file mode 100644 index 0000000..b496ab9 --- /dev/null +++ b/src/router.sv @@ -0,0 +1,160 @@ +/* Idea: circular router, where packets are added to a ring and popped off one + * when they encounter a node which they match. Relatively high minimum latency, + * but simple implementation and *should* be fairly cheap in hardware, + * let's see I guess. + * + * Skid buffers on inputs. + * + * Should fairly easily allow a form of broadcasting as well by just checking + * that visited is all ones, but that's not currently implemented. + * + * Should presumably also add masking so that users can check when + * a device matches, for now it's just a fixed integer sequence. + */ + +module router #( + /* number of ports */ + parameter unsigned N = 4, + + /* index of this router */ + parameter unsigned I = 0, + + /* arbitrary packet payload type */ + parameter type data_t = logic, + + /* structure with to/from address info, should maybe add some macro for + * generating such a type? */ + parameter type addr_t = logic +)( + input clk_i, + input rstx_i, + + /* TODO SV interfaces? */ + + /* first two are up/down wrt. I, in that order, then come + * devices, referred to by index + 2 */ + input logic [N+2-1:0] valid_i, + output logic [N+2-1:0] ready_o, + input addr_t[N+2-1:0] addr_i, + input data_t[N+2-1:0] data_i, + + output logic [N+2-1:0] valid_o, + input logic [N+2-1:0] ready_i, + output addr_t[N+2-1:0] addr_o, + output data_t[N+2-1:0] data_o +); + +localparam NW = $bits(addr_i[0].dst.node); +localparam DW = $bits(addr_i[0].dst.dev); + +typedef struct packed { + logic alive; + logic[N+2-1:0] visited; + addr_t addr; + data_t data; +} meta_t; + +meta_t[N+2-1:0] ring_d, ring_q; + +logic [N+2-1:0] send, sent, recv; +meta_t[N+2-1:0] handle, insert; + +typedef struct packed { + addr_t addr; + data_t data; +} skid_t; + +logic [N+2-1:0] skid_valid_i, skid_ready_o; +addr_t[N+2-1:0] skid_addr_i; +data_t[N+2-1:0] skid_data_i; + +/* skid buffers kind of make this file more difficult to read, should I start + * doing something like *_skid where I wrap a central block in skip buffers, + * presumably always on the input side or something along those lines? */ +for (genvar i = 0; i < N + 2; ++i) begin :skidbuffers + skid_t in, out; + assign in = '{addr: addr_i[i], data: data_i[i]}; + assign skid_addr_i[i] = out.addr; + assign skid_data_i[i] = out.data; + + skid_buffer #( + .W($bits(skid_t)) + ) skid_i ( + .clk_i, + .rstx_i, + + .valid_i(valid_i[i]), + .ready_o(ready_o[i]), + .data_i (in ), + + .valid_o(skid_valid_i[i]), + .ready_i(skid_ready_o[i]), + .data_o (out ) + ); +end + +for (genvar i = 0; i < N + 2; ++i) +always_comb begin :gen_ring_knot + /* default values */ + handle[i] = ring_d[i]; + send[i] = 1'b0; + + /* first, try to send out an incoming packet */ + if (ring_d[i].alive) begin + /* packet wasn't accepted by anyone, send it back */ + if (&handle[i].visited) + handle[i].addr.dst = handle[i].addr.src; + + /* up */ + if (i == 0) + send[i] = handle[i].addr.dst.node > I[0+:NW]; + /* down */ + else if (i == 1) + /* slightly silly Verilator warning silencing */ + send[i] = I == 0 ? 0 : handle[i].addr.dst.node < I[0+:NW]; + /* dev */ + else + send[i] = handle[i].addr.dst.node == I[0+:NW] + & handle[i].addr.dst.dev == (i - 2); + end + + valid_o[i] = send[i]; + addr_o[i] = handle[i].addr; + data_o[i] = handle[i].data; + sent[i] = valid_o[i] && ready_i[i]; + + /* mark this packet having visited us. If we did try to send it, but + * the receiver wasn't ready, don't set the visited bit so the packet can + * come around a second time in the future to try again. */ + if (send[i]) + handle[i].visited[i] |= sent[i]; + else + handle[i].visited[i] |= 1'b1; + + if (sent[i]) + handle[i].alive = 1'b0; + + /* next, try to insert a new packet into the ring */ + skid_ready_o[i] = !handle[i].alive; + recv[i] = skid_valid_i[i] && skid_ready_o[i]; + + insert[i] = '{ + alive: recv[i], + /* sending to oneself is nonsensical, so we automatically visit + * the starting node */ + visited: 1 << i, + addr: skid_addr_i[i], + data: skid_data_i[i] + }; + + /* send either the new packet or the existing one to the next node in + * the ring */ + ring_q[(i + 1) % (N + 2)] = recv[i] ? insert[i] : handle[i]; +end + +for (genvar i = 0; i < N + 2; ++i) +always_ff @(posedge clk_i or negedge rstx_i) +if (!rstx_i) ring_d[i] <= '0; +else ring_d[i] <= ring_q[i]; + +endmodule // router diff --git a/src/skid_buffer.sv b/src/skid_buffer.sv new file mode 100644 index 0000000..3ea6d0d --- /dev/null +++ b/src/skid_buffer.sv @@ -0,0 +1,53 @@ +/* adjusted from pulp common_cells/spill_register_flushable.sv */ + +module skid_buffer #( + parameter unsigned W = 2 +)( + input clk_i, + input rstx_i, + + input valid_i, + output ready_o, + input [W-1:0] data_i, + + output valid_o, + input ready_i, + output [W-1:0] data_o +); + +logic a_fill, a_full, a_drain, b_fill, b_full, b_drain; +logic [W-1:0] a, b; + +/* reg a */ +always_ff @(posedge clk_i or negedge rstx_i) +if (!rstx_i) + a <= '0; +else if (a_fill) + a <= data_i; + +always_ff @(posedge clk_i or negedge rstx_i) +if (!rstx_i) + a_full <= '0; +else if (a_fill || a_drain) + a_full <= a_fill; + +always_ff @(posedge clk_i or negedge rstx_i) +if (!rstx_i) + b <= '0; +else if (b_fill) + b <= a; + +/* fill A first, if upstream can't read A in time, place A into B and skid the + * input into A until upstream can clean out B */ +assign a_fill = valid_i && ready_o; +assign a_drain = a_full && !b_full; + +assign b_fill = a_drain && !ready_i; +assign b_drain = b_full && ready_i; + +assign ready_o = !a_full || !b_full; +assign valid_o = a_full || b_full; + +assign data_o = b_full ? b : a; + +endmodule // skid_buffer |
