summaryrefslogtreecommitdiff
path: root/src/router.sv
diff options
context:
space:
mode:
authorKimplul <kimi.h.kuparinen@gmail.com>2026-09-27 22:30:41 +0300
committerKimplul <kimi.h.kuparinen@gmail.com>2026-09-27 22:30:41 +0300
commitf4afcfa58eb90d14abd55d79640ecda13b23dc21 (patch)
treec0f63b35502d74cbdf3d2bc881675859a235596d /src/router.sv
parentbc5941a1fcfd0a607a6aef51f7659c3f233b65dc (diff)
downloadttarv32-f4afcfa58eb90d14abd55d79640ecda13b23dc21.tar.gz
ttarv32-f4afcfa58eb90d14abd55d79640ecda13b23dc21.zip
implement circular routerHEADmaster
+ 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/router.sv')
-rw-r--r--src/router.sv160
1 files changed, 160 insertions, 0 deletions
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