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
|
#!/usr/bin/env python3
#
# Copyright 2016 Ettus Research
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import math
import rfnocsim
class UsrpX310(rfnocsim.SimComp):
# Hardware specific constants
RADIO_LATENCY = 1e-6
IO_LATENCY = 1e-6
MAX_SAMP_RATE = 300e6 # Limited by 10GbE
BPI = 4 # Bytes per sample (item)
"""
Simulation model for the USRP X310
- Has two producers and consumers of FFT data
- Computes bandwidth and latency using FFT size and overlap
"""
def __init__(self, sim_core, index, app_settings):
rfnocsim.SimComp.__init__(self, sim_core, name='USRP_%03d' % (index), ctype=rfnocsim.comptype.hardware)
# USRP i carries data for radio 2i and 2i+1 interleaved into one stream
self.index = index
items = [rfnocsim.DataStream.submatrix_gen('rx', [2*index]),
rfnocsim.DataStream.submatrix_gen('rx', [2*index+1])]
# Samples are 4 bytes I and Q
latency = (self.RADIO_LATENCY + self.IO_LATENCY/2) * self.get_tick_rate()
if app_settings['domain'] == 'frequency':
# Max latency per direction depends on the FFT size and sample rate
latency += self.__get_fft_latency(
app_settings['fft_size'], app_settings['samp_rate'], self.get_tick_rate())
# An X310 Radio has two producers (RX data) and consumers (TX data) (i.e. two ethernet ports)
# Both ports can carry data from both radio frontends
self.sources = ([
rfnocsim.Producer(sim_core, self.name + '/TX0', self.BPI, items, self.MAX_SAMP_RATE, latency),
rfnocsim.Producer(sim_core, self.name + '/TX1', self.BPI, items, self.MAX_SAMP_RATE, latency)])
self.sinks = ([
rfnocsim.Consumer(sim_core, self.name + '/RX0', self.BPI * self.MAX_SAMP_RATE, latency),
rfnocsim.Consumer(sim_core, self.name + '/RX1', self.BPI * self.MAX_SAMP_RATE, latency)])
# The actual sample rate depends over the wire depends on the radio sample rate,
# the FFT size and FFT overlap
for src in self.sources:
if app_settings['domain'] == 'frequency':
src.set_rate(app_settings['samp_rate'] *
(1.0 + (float(app_settings['fft_overlap'])/app_settings['fft_size'])))
else:
src.set_rate(app_settings['samp_rate'])
def inputs(self, i, bind=False):
return self.sinks[i].inputs(0, bind)
def connect(self, i, dest):
self.sources[i].connect(0, dest)
def get_utilization(self, what):
return 0.0
def get_util_attrs(self):
return []
def validate(self, chan):
recvd = self.sinks[chan].get_items()
idxs = []
for i in recvd:
(str_id, idx) = rfnocsim.DataStream.submatrix_parse(i)
if str_id != 'tx':
raise RuntimeError(self.name + ' received incorrect TX data on channel ' + str(chan))
idxs.append(idx[0][0])
if sorted(idxs) != [self.index*2, self.index*2 + 1]:
raise RuntimeError(self.name + ' received incorrect TX data. Got: ' + str(sorted(idxs)))
def __get_fft_latency(self, fft_size, samp_rate, tick_rate):
FFT_CLK_RATE = 200e6
fft_cycles = {128:349, 256:611, 512:1133, 1024:2163, 2048:4221, 4096:8323}
latency = max(
fft_cycles[fft_size] / FFT_CLK_RATE, #Min time to leave FFT
fft_size / samp_rate) #Min time to enter FFT
return latency * tick_rate
class Bee7Fpga(rfnocsim.SimComp):
"""
Simulation model for a single Beecube BEE7 FPGA
- Type = hardware
- Contains 80 IO lanes per FPGA: 16 each to neighboring
FPGAs and 32 lanes going outside
"""
# IO lanes (How the various IO lanes in an FPGA are allocated)
EW_IO_LANES = list(range(0,16))
NS_IO_LANES = list(range(16,32))
XX_IO_LANES = list(range(32,48))
EXT_IO_LANES = list(range(48,80))
# External IO lane connections
FP_BASE = 0 # Front panel FMC
FP_LANES = 16
BP_BASE = 16 # Backplane RTM
BP_LANES = 16
# Hardware specific constants
IO_LN_LATENCY = 1.5e-6
IO_LN_BW = 10e9/8
ELASTIC_BUFF_FULLNESS = 0.5
BRAM_BYTES = 18e3/8
def __init__(self, sim_core, name):
self.sim_core = sim_core
rfnocsim.SimComp.__init__(self, sim_core, name, rfnocsim.comptype.hardware)
# Max resources from Virtex7 datasheet
self.max_resources = rfnocsim.HwRsrcs()
self.max_resources.add('DSP', 3600)
self.max_resources.add('BRAM_18kb', 2940)
self.resources = rfnocsim.HwRsrcs()
# Each FPGA has 80 SERDES lanes
self.max_io = 80
self.serdes_i = dict()
self.serdes_o = dict()
# Each lane can carry at most 10GB/s
# Each SERDES needs to have some buffering. We assume elastic buffering (50% full on avg).
io_buff_size = (self.IO_LN_BW * self.IO_LN_LATENCY) / self.ELASTIC_BUFF_FULLNESS
# Worst case lane latency
lane_latency = self.IO_LN_LATENCY * self.get_tick_rate()
for i in range(self.max_io):
self.serdes_i[i] = rfnocsim.Channel(sim_core, self.__ioln_name(i)+'/I', self.IO_LN_BW, lane_latency / 2)
self.serdes_o[i] = rfnocsim.Channel(sim_core, self.__ioln_name(i)+'/O', self.IO_LN_BW, lane_latency / 2)
self.resources.add('BRAM_18kb', 1 + math.ceil(io_buff_size / self.BRAM_BYTES)) #input buffering per lane
self.resources.add('BRAM_18kb', 1) #output buffering per lane
# Other resources
self.resources.add('BRAM_18kb', 72) # BPS infrastructure + microblaze
self.resources.add('BRAM_18kb', 128) # 2 MIGs
self.functions = dict()
def inputs(self, i, bind=False):
return self.serdes_i[i].inputs(0, bind)
def connect(self, i, dest):
self.serdes_o[i].connect(0, dest)
def get_utilization(self, what):
if self.max_resources.get(what) != 0:
return self.resources.get(what) / self.max_resources.get(what)
else:
return 0.0
def get_util_attrs(self):
return ['DSP', 'BRAM_18kb']
def rename(self, name):
self.name = name
def add_function(self, func):
if func.name not in self.functions:
self.functions[func.name] = func
else:
raise RuntimeError('Function ' + self.name + ' already defined in ' + self.name)
self.resources.merge(func.get_rsrcs())
def __ioln_name(self, i):
if i in self.EW_IO_LANES:
return '%s/SER_EW_%02d'%(self.name,i-self.EW_IO_LANES[0])
elif i in self.NS_IO_LANES:
return '%s/SER_NS_%02d'%(self.name,i-self.NS_IO_LANES[0])
elif i in self.XX_IO_LANES:
return '%s/SER_XX_%02d'%(self.name,i-self.XX_IO_LANES[0])
else:
return '%s/SER_EXT_%02d'%(self.name,i-self.EXT_IO_LANES[0])
class Bee7Blade(rfnocsim.SimComp):
"""
Simulation model for a single Beecube BEE7
- Contains 4 FPGAs (fully connected with 16 lanes)
"""
NUM_FPGAS = 4
# FPGA positions in the blade
NW_FPGA = 0
NE_FPGA = 1
SW_FPGA = 2
SE_FPGA = 3
def __init__(self, sim_core, index):
self.sim_core = sim_core
self.name = name='BEE7_%03d' % (index)
# Add FPGAs
names = ['FPGA_NW', 'FPGA_NE', 'FPGA_SW', 'FPGA_SE']
self.fpgas = []
for i in range(self.NUM_FPGAS):
self.fpgas.append(Bee7Fpga(sim_core, name + '/' + names[i]))
# Build a fully connected network of FPGA
# 4 FPGAs x 3 Links x 2 directions = 12 connections
self.sim_core.connect_multi_bidir(
self.fpgas[self.NW_FPGA], Bee7Fpga.EW_IO_LANES, self.fpgas[self.NE_FPGA], Bee7Fpga.EW_IO_LANES)
self.sim_core.connect_multi_bidir(
self.fpgas[self.NW_FPGA], Bee7Fpga.NS_IO_LANES, self.fpgas[self.SW_FPGA], Bee7Fpga.NS_IO_LANES)
self.sim_core.connect_multi_bidir(
self.fpgas[self.NW_FPGA], Bee7Fpga.XX_IO_LANES, self.fpgas[self.SE_FPGA], Bee7Fpga.XX_IO_LANES)
self.sim_core.connect_multi_bidir(
self.fpgas[self.NE_FPGA], Bee7Fpga.XX_IO_LANES, self.fpgas[self.SW_FPGA], Bee7Fpga.XX_IO_LANES)
self.sim_core.connect_multi_bidir(
self.fpgas[self.NE_FPGA], Bee7Fpga.NS_IO_LANES, self.fpgas[self.SE_FPGA], Bee7Fpga.NS_IO_LANES)
self.sim_core.connect_multi_bidir(
self.fpgas[self.SW_FPGA], Bee7Fpga.EW_IO_LANES, self.fpgas[self.SE_FPGA], Bee7Fpga.EW_IO_LANES)
def inputs(self, i, bind=False):
IO_PER_FPGA = len(Bee7Fpga.EXT_IO_LANES)
return self.fpgas[int(i/IO_PER_FPGA)].inputs(Bee7Fpga.EXT_IO_LANES[i%IO_PER_FPGA], bind)
def connect(self, i, dest):
IO_PER_FPGA = len(Bee7Fpga.EXT_IO_LANES)
self.fpgas[int(i/IO_PER_FPGA)].connect(Bee7Fpga.EXT_IO_LANES[i%IO_PER_FPGA], dest)
@staticmethod
def io_lane(fpga, fpga_lane):
IO_PER_FPGA = len(Bee7Fpga.EXT_IO_LANES)
return (fpga_lane - Bee7Fpga.EXT_IO_LANES[0]) + (fpga * IO_PER_FPGA)
class ManagementHostandSwitch(rfnocsim.SimComp):
"""
Simulation model for a management host computer
- Sources channel coefficients
- Configures radio
"""
def __init__(self, sim_core, index, num_coeffs, switch_ports, app_settings):
rfnocsim.SimComp.__init__(self, sim_core, name='MGMT_HOST_%03d'%(index), ctype=rfnocsim.comptype.other)
if app_settings['domain'] == 'frequency':
k = app_settings['fft_size']
else:
k = app_settings['fir_taps']
self.sources = dict()
self.sinks = dict()
for l in range(switch_ports):
self.sources[l] = rfnocsim.Producer(
sim_core, '%s/COEFF_%d'%(self.name,l), 4, ['coeff_%03d[%d]'%(index,l)], (10e9/8)/switch_ports, 0)
self.sinks[l] = rfnocsim.Consumer(sim_core, self.name + '%s/ACK%d'%(self.name,l))
self.sources[l].set_rate(k*num_coeffs*app_settings['coherence_rate'])
def inputs(self, i, bind=False):
return self.sinks[i].inputs(0, bind)
def connect(self, i, dest):
self.sources[i].connect(0, dest)
def get_utilization(self, what):
return 0.0
def get_util_attrs(self):
return []
|