From f4afcfa58eb90d14abd55d79640ecda13b23dc21 Mon Sep 17 00:00:00 2001 From: Kimplul Date: Sun, 27 Sep 2026 22:30:41 +0300 Subject: implement circular router + 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. --- src/router.sv | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 src/router.sv (limited to 'src/router.sv') 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 -- cgit v1.3