-
Notifications
You must be signed in to change notification settings - Fork 1
/
NetFPGA.cs
287 lines (252 loc) · 9.29 KB
/
NetFPGA.cs
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
/*
Emu framework to run Kiwi-compiled code on the NetFPGA platform.
Copyright (C) 2016 -- Salvator Galea <[email protected]>
Nik Sultana, Cambridge University Computer Lab
This software was developed by the University of Cambridge,
Computer Laboratory under EPSRC NaaS Project EP/K034723/1
Use of this source code is governed by the Apache 2.0 license; see LICENSE file
*/
using System;
using System.Diagnostics;
namespace Emu
{
/// <summary>
/// Data and metadata that is passed to us (the "Output Logic") by the "Input Arbiter",
/// and that we pass on to the "Output Queue". (The quoted expressions are modules within
/// NetFPGA's data plane.
/// </summary>
public class NetFPGA_Data
{
public const byte NET_PORTS = 4; // Number of network ports.
// NOTE the maximum-sized frames we can handle is BUF_SIZE * BUS_SIZE, where BUS_SIZE
// is implicitly 64-bits --- it's the width of the bus we have with the IA, which
// is currently mediated by a 256-to-64-bit converter (this should be removed in the
// future). We can increased BUF_SIZE to handle larger frames.
public const uint BUF_SIZE = 30;
// The actual frame's contents, segmented into BUF_SIZE units.
public ulong[] tdata = new ulong[BUF_SIZE];
// Metadata: how much of the last segment to keep.
public byte[] tkeep = new byte[BUF_SIZE];
// Metadata: whether this is the last segment.
public bool[] tlast = new bool[BUF_SIZE];
// Metadata: tuser specifies the input port, output port, and frame size.
// NOTE we don't use tuser_hi
// FIXME we only seem to use tuser_low[0] in application code.
public ulong[] tuser_hi = new ulong[BUF_SIZE];
// Structure of tuser_low: 2 bytes for frame size, 1 byte for source port,
// and 1 byte for destination port.
public const byte tuser_low_destination_port = 24;
public const byte tuser_low_source_port = 16;
public const byte tuser_low_frame_size = 0;
// The structure of source/destination port (in bits) is as follows:
// DMA NF3 DMA NF2 DMA NF1 DMA NF0
// where "DMA" means that the data is sourced/destined from DMA,
// and "NFx" refers to port "x".
public ulong[] tuser_low = new ulong[BUF_SIZE];
// By default we destine a frame to all output ports (NF0-NF3)
// to broadcast it. This results in the following bit mask.
public const ulong default_oqs = (ulong)0x0000000000550000;
}
public class NetFPGA
{
#if ON_HOST // It's useful to enclose this away at compile-time to avoid confusing Kiwi with unbound arrays or complex functions.
// Extract how many bytes we are keeping from a given (8 byte) segment as
// identified by an offset.
// This involves looking up the tkeep[offset] value and carrying out
// the one-hot decoding.
// NOTE we expect that the bits indicating which bits to keep are
// contiguous. We currently don't check this.
public static int to_keep (NetFPGA_Data src, int offset)
{
byte tkeep = src.tkeep[offset];
int keeping = 0;
const int bits_in_a_byte = 8;
for (int i = 0; i < bits_in_a_byte; i++)
{
tkeep = (byte)(tkeep >> i);
if ((tkeep & 0x1) == 1)
{
keeping++;
} else {
break;
}
}
return keeping;
}
// Carry out the one-hot encoding given a byte ("width") indicating
// how many (out of 8) bytes we want to keep.
public static byte one_hot_encode (byte width)
{
const int bits_in_a_byte = 8; // FIXME DRY principle
Debug.Assert(width <= bits_in_a_byte);
byte tkeep = 0;
for (int i = width; i > 0; i--)
{
tkeep = (byte)(tkeep << 1);
tkeep |= 0x1;
}
return tkeep;
}
// Extract the frame from NetFPGA_Data into a byte array.
public static void Get_Frame (NetFPGA_Data src, ref byte[] dst)
{
// FIXME we don't check the bounds of dst.
// FIXME we don't null unused locations in dst.
int offset = 0;
for (int i = 0; i < src.tdata.Length; i++)
{
byte[] bs = BitConverter.GetBytes(src.tdata[i]);
for (int j = 0; j < bs.Length; j++)
{
if (src.tlast[i] && NetFPGA.to_keep (src, i) > j)
{
break;
}
dst[offset] = bs[j];
offset++;
}
if (src.tlast[i])
{
break;
}
}
}
#endif
// Move the contents of a byte array into the frame field in NetFPGA_Data.
public static void Set_Frame (byte[] src, ref NetFPGA_Data dst)
{
// If not ulong (8-byte) aligned then we indicate this using
// NetFPGA_Data.tkeep. We indicate the end of the frame by setting
// NetFPGA_Data.tlast.
int i = 0;
int max = dst.tdata.Length / sizeof(ulong);
byte[] buf = new byte[sizeof(long)];
for (i = 0; i < max; i++)
{
for (int j = 0; j < sizeof(long); j++)
{
buf[j] = src[i + j];
}
dst.tdata[i * sizeof(long)] = BitConverter.ToUInt32(buf, 0);
dst.tlast[i] = false;
}
// Now we handle the last word.
dst.tlast[i] = true;
byte to_keep = (byte)(dst.tdata.Length % sizeof(ulong));
dst.tkeep[i] = one_hot_encode(to_keep);
// Copy remaining bytes.
int k = 0;
for (k = 0; k < to_keep; k++)
{
buf[k] = src[i + k];
}
// Zero-out the bytes until the word boundary.
for (; k < sizeof(ulong); k++)
{
buf[k] = 0;
}
dst.tdata[i * sizeof(long)] = BitConverter.ToUInt32(buf, 0);
}
/// <summary>
/// Set the input port (i.e., port on which we received the frame).
/// </summary>
public static void Set_Input_Port (ref NetFPGA_Data dataplane, ulong value)
{
// "value" indicates network port index, starting with 0.
Debug.Assert (value < NetFPGA_Data.NET_PORTS);
// Map value into a bit-based encoding as described for source/destination
// ports in NetFPGA_Data.
ulong encoded_in_port = 1;
for (int i = (int)value; i > 0; i--)
{
encoded_in_port = encoded_in_port << 2;
}
// Move into position, in preparation for OR-ing.
encoded_in_port = encoded_in_port << NetFPGA_Data.tuser_low_source_port;
// Erase any existing input port.
dataplane.tuser_low[0] = (ulong)(0xFF00FFFF & dataplane.tuser_low[0]);
// Write the new input port.
dataplane.tuser_low[0] = (ulong)(encoded_in_port | dataplane.tuser_low[0]);
}
/// <summary>
/// Read the input port (i.e., port on which we received the frame).
/// </summary>
public static uint Read_Input_Port (NetFPGA_Data dataplane)
{
uint encoded_in_port = (uint)(dataplane.tuser_low[0] >> NetFPGA_Data.tuser_low_source_port);
encoded_in_port &= 0x00FF; // Zero out the destination port.
uint in_port = 0;
while ((encoded_in_port & 1) == 0) {
in_port++;
encoded_in_port = encoded_in_port >> 2;
}
// "in_port" indicates network port index, starting with 0.
Debug.Assert (in_port < NetFPGA_Data.NET_PORTS);
return in_port;
}
/// <summary>
/// Set the output port to a specific value.
/// (i.e., the port to which we are forwarding the frame.)
/// </summary>
public static void Set_Output_Port (ref NetFPGA_Data dataplane, ulong value)
{
// FIXME we currently don't one-hot encode "value"
value = value << NetFPGA_Data.tuser_low_destination_port;
dataplane.tuser_low[0] = (ulong)(value | dataplane.tuser_low[0]);
return;
}
/// <summary>
/// Read the output ports that are currently set in the metadata.
/// (i.e., the port(s) to which we are forwarding the frame.)
/// </summary>
public static byte Get_Output_Ports (ref NetFPGA_Data dataplane,
ref int[] output_ports, int? max_ports = null)
{
ulong value = dataplane.tuser_low[0];
value = (value >> NetFPGA_Data.tuser_low_destination_port);
bool nf0 = (value & 0x01L) > 0;
bool nf1 = (value & 0x04L) > 0;
bool nf2 = (value & 0x10L) > 0;
bool nf3 = (value & 0x40L) > 0;
// FIXME code in this function is a bit dirty -- can be tidied up?
byte dim = 0;
if (nf0) dim++;
if (nf1 && max_ports.HasValue && max_ports.Value > 1) dim++;
if (nf2 && max_ports.HasValue && max_ports.Value > 2) dim++;
if (nf3 && max_ports.HasValue && max_ports.Value > 3) dim++;
Debug.Assert(dim == output_ports.Length);
int i = 0;
if (nf0)
{
output_ports[i] = 0;
i++;
}
if (nf1)
{
output_ports[i] = 1;
i++;
}
if (nf2)
{
output_ports[i] = 2;
i++;
}
if (nf3)
{
output_ports[i] = 3;
i++;
}
return dim;
}
/// <summary>
/// Update the metadata to broadcast the frame (to all ports except the
/// one that the frame was received on).
/// </summary>
public static void Broadcast (ref NetFPGA_Data dataplane)
{
ulong broadcast_ports = ((dataplane.tuser_low[0] ^ NetFPGA_Data.default_oqs) >> 8) << 16 | dataplane.tuser_low[0];
dataplane.tuser_low[0] = (ulong)(broadcast_ports | dataplane.tuser_low[0]);
return;
}
}
}