summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common.svh2
-rw-r--r--src/router.sv160
-rw-r--r--src/skid_buffer.sv53
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