1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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
|