/* 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