aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/tools/utils/rfnoc-system-sim/ni_hw_models.py
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/tools/utils/rfnoc-system-sim/ni_hw_models.py')
-rwxr-xr-xfpga/usrp3/tools/utils/rfnoc-system-sim/ni_hw_models.py261
1 files changed, 261 insertions, 0 deletions
diff --git a/fpga/usrp3/tools/utils/rfnoc-system-sim/ni_hw_models.py b/fpga/usrp3/tools/utils/rfnoc-system-sim/ni_hw_models.py
new file mode 100755
index 000000000..815003c5f
--- /dev/null
+++ b/fpga/usrp3/tools/utils/rfnoc-system-sim/ni_hw_models.py
@@ -0,0 +1,261 @@
+#!/usr/bin/env python
+#
+# 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 rfnocsim
+import math
+
+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 []