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 config_bitstream(cls, bee7fpga, app_settings, in_chans, out_chans, total_num_chans, is_radio_node): if len(in_chans) != 64: raise bee7fpga.SimCompError('in_chans must be 64 channels wide. Got ' + str(len(in_chans))) if len(out_chans) != 16: raise bee7fpga.SimCompError('out_chans must be 16 channels wide. Got ' + str(len(out_chans))) GRP_LEN = 16 / 2 # 2 radio channesl per USRP # Broadcast raw data streams to all internal and external FPGAs for i in range(GRP_LEN): in_ln = bee7fpga.EXT_IO_LANES[bee7fpga.BP_BASE+i] bee7fpga.sim_core.connect(bee7fpga.serdes_i[in_ln], 0, bee7fpga.serdes_o[bee7fpga.EW_IO_LANES[i]], 0) bee7fpga.sim_core.connect(bee7fpga.serdes_i[in_ln], 0, bee7fpga.serdes_o[bee7fpga.NS_IO_LANES[i]], 0) bee7fpga.sim_core.connect(bee7fpga.serdes_i[in_ln], 0, bee7fpga.serdes_o[bee7fpga.XX_IO_LANES[i]], 0) bee7fpga.sim_core.connect(bee7fpga.serdes_i[in_ln], 0, bee7fpga.serdes_o[bee7fpga.EXT_IO_LANES[bee7fpga.BP_BASE+8+i]], 0) # Create an internal bus to hold the generated partial products bee7fpga.pp_bus = dict() for i in range(GRP_LEN): bee7fpga.pp_bus[i] = rfnocsim.Channel(bee7fpga.sim_core, '%s/_INTERNAL_PP_%02d' % (bee7fpga.name,i)) # We need to compute partial products of the data that is broadcast to us # pp_input_lanes represents the IO lanes that hold this data pp_input_lanes = bee7fpga.EXT_IO_LANES[bee7fpga.BP_BASE:bee7fpga.BP_BASE+GRP_LEN] + \ bee7fpga.EW_IO_LANES[0:GRP_LEN] + bee7fpga.NS_IO_LANES[0:GRP_LEN] + bee7fpga.XX_IO_LANES[0:GRP_LEN] # The function that computes the partial products func = PartialContribComputer( sim_core=bee7fpga.sim_core, name=bee7fpga.name + '/pp_computer/', size=len(pp_input_lanes), dst_chans=out_chans, items_per_stream=2, app_settings=app_settings) for i in range(len(pp_input_lanes)): bee7fpga.sim_core.connect(bee7fpga.serdes_i[pp_input_lanes[i]], 0, func, i) for i in range(GRP_LEN): #Outputs of function bee7fpga.sim_core.connect(func, i, bee7fpga.pp_bus[i], 0) bee7fpga.add_function(func) # Add a function combine all partial products (one per IO lane) for i in range(GRP_LEN): func = PartialContribCombiner( sim_core=bee7fpga.sim_core, name=bee7fpga.name + '/pp_combiner_%d/' % (i), radix=2, app_settings=app_settings, reducer_filter=(list(range(total_num_chans)), 'tx')) # Partial products generated internally have to be added to a partial # sum coming from outside bee7fpga.sim_core.connect(bee7fpga.serdes_i[bee7fpga.EXT_IO_LANES[bee7fpga.FP_BASE+i]], 0, func, 0) bee7fpga.sim_core.connect(bee7fpga.pp_bus[i], 0, func, 1) # If this FPGA is hooked up to the radio then send partial products # back to when samples came from. Otherwise send it out to the PP output bus if is_radio_node: bee7fpga.sim_core.connect(func, 0, bee7fpga.serdes_o[bee7fpga.EXT_IO_LANES[bee7fpga.BP_BASE+i]], 0) else: bee7fpga.sim_core.connect(func, 0, bee7fpga.serdes_o[bee7fpga.EXT_IO_LANES[bee7fpga.FP_BASE+8+i]], 0) bee7fpga.add_function(func)
def config_bitstream(cls, bee7fpga, app_settings, fpga_addr): """ Defines the FPGA behavior for the current FPGA. This function will make create the necessary simulation functions, connect them to IO lanes and define the various utilization metrics for the image. config_bitstream(bee7fpga, app_settings, fpga_addr): - bee7fpga: The FPGA simulation object being configured - fpga_addr: Address of the FPGA in 3-D space - app_settings: Application information """ if len(fpga_addr) != 3: raise bee7fpga.SimCompError('fpga_addr must be 3-dimensional. Got ' + str(len(fpga_addr))) # Map that stores lane indices for all neighbors of this node (router_map, terminal_map) = cls.get_portmap(fpga_addr) # USRPs are connected in the X dimension (RTM) because it has SFP+ ports base_usrp_lane = terminal_map['X'] DIM_WIDTH = 4 # Dimension size for the 3-D network MAX_USRPS = 4 # Max USRPs that can possibly be connected to each FPGA NUM_USRPS = 2 # Number of USRPs actually connected to each FPGA CHANS_PER_USRP = 2 # How many radio channels does each USRP have ALL_CHANS = list(range(pow(DIM_WIDTH, 3) * NUM_USRPS * CHANS_PER_USRP)) # Each FPGA will forward the sample stream from each USRP to all of its # X-axis neighbors for ri in router_map['X']: for li in range(MAX_USRPS): # li = GT Lane index bee7fpga.sim_core.connect(bee7fpga.serdes_i[base_usrp_lane + li], 0, bee7fpga.serdes_o[router_map['X'][ri] + li], 0) # Consequently, this FPGA will receive the USRP sample streams from each of # its X-axis neighbors. Define an internal bus to aggregate all the neighbor # streams with the native ones. Order the streams such that each FPGA sees the # same data streams. bee7fpga.int_samp_bus = dict() for i in range(DIM_WIDTH): for li in range(MAX_USRPS): # li = GT Lane index bee7fpga.int_samp_bus[(MAX_USRPS*i) + li] = rfnocsim.Channel( bee7fpga.sim_core, '%s/_INT_SAMP_%02d' % (bee7fpga.name,(MAX_USRPS*i) + li)) ln_base = base_usrp_lane if i == fpga_addr['X'] else router_map['X'][i] bee7fpga.sim_core.connect(bee7fpga.serdes_i[ln_base + li], 0, bee7fpga.int_samp_bus[(MAX_USRPS*i) + li], 0) # Forward the X-axis aggregated sample streams to all Y-axis neighbors for ri in router_map['Y']: for li in range(DIM_WIDTH*DIM_WIDTH): # li = GT Lane index bee7fpga.sim_core.connect(bee7fpga.int_samp_bus[li], 0, bee7fpga.serdes_o[router_map['Y'][ri] + li], 0) # What partial products will this FPGA compute? # Generate channel list to compute partial products pp_chans = list() for cg in range(DIM_WIDTH): # cg = Channel group for r in range(NUM_USRPS): radio_num = cls.get_radio_num({'X':fpga_addr['X'], 'Y':fpga_addr['Y'], 'Z':cg}, r, NUM_USRPS) for ch in range(CHANS_PER_USRP): pp_chans.append(radio_num*CHANS_PER_USRP + ch) # Instantiate partial product computer bee7fpga.func_pp_comp = PartialContribComputer( sim_core=bee7fpga.sim_core, name=bee7fpga.name+'/pp_computer/', size=DIM_WIDTH*DIM_WIDTH*NUM_USRPS, dst_chans=pp_chans, items_per_stream=CHANS_PER_USRP, app_settings=app_settings) bee7fpga.add_function(bee7fpga.func_pp_comp) # Partial product computer takes inputs from all Y-axis links for sg in range(DIM_WIDTH): # sg = Group of sexdectects for qi in range(DIM_WIDTH): # qi = GT Quad index for li in range(NUM_USRPS): func_inln = (sg * DIM_WIDTH * NUM_USRPS) + (qi * NUM_USRPS) + li if sg == fpga_addr['Y']: bee7fpga.sim_core.connect(bee7fpga.int_samp_bus[(qi * DIM_WIDTH) + li], 0, bee7fpga.func_pp_comp, func_inln) else: bee7fpga.sim_core.connect(bee7fpga.serdes_i[router_map['Y'][sg] + (qi * DIM_WIDTH) + li], 0, bee7fpga.func_pp_comp, func_inln) # Internal bus to hold aggregated partial products bee7fpga.pp_bus = dict() for i in range(DIM_WIDTH*NUM_USRPS): bee7fpga.pp_bus[i] = rfnocsim.Channel(bee7fpga.sim_core, '%s/_INT_PP_%02d' % (bee7fpga.name,i)) bee7fpga.sim_core.connect(bee7fpga.func_pp_comp, i, bee7fpga.pp_bus[i], 0) # Forward partial products to Z-axis neighbors for ri in router_map['Z']: for li in range(NUM_USRPS): # li = GT Lane index bee7fpga.sim_core.connect(bee7fpga.pp_bus[ri*NUM_USRPS + li], 0, bee7fpga.serdes_o[router_map['Z'][ri] + li], 0) # Instantiate partial product adder bee7fpga.func_pp_comb = dict() for i in range(NUM_USRPS): bee7fpga.func_pp_comb[i] = PartialContribCombiner( sim_core=bee7fpga.sim_core, name=bee7fpga.name + '/pp_combiner_%d/'%(i), radix=DIM_WIDTH, app_settings=app_settings, reducer_filter=(ALL_CHANS, 'tx'), items_per_stream=CHANS_PER_USRP) bee7fpga.add_function(bee7fpga.func_pp_comb[i]) # Aggregate partial products from Z-axis neighbors for u in range(NUM_USRPS): for ri in range(DIM_WIDTH): if ri in router_map['Z']: bee7fpga.sim_core.connect(bee7fpga.serdes_i[router_map['Z'][ri] + u], 0, bee7fpga.func_pp_comb[u], ri) else: bee7fpga.sim_core.connect(bee7fpga.pp_bus[ri*NUM_USRPS + u], 0, bee7fpga.func_pp_comb[u], ri) # Instantiate partial product adder for u in range(NUM_USRPS): bee7fpga.sim_core.connect(bee7fpga.func_pp_comb[u], 0, bee7fpga.serdes_o[base_usrp_lane + u], 0) # Coefficient consumer bee7fpga.coeff_sink = rfnocsim.Consumer(bee7fpga.sim_core, bee7fpga.name + '/coeff_sink', 10e9/8, 0.0) bee7fpga.sim_core.connect(bee7fpga.serdes_i[terminal_map['X'] + NUM_USRPS], 0, bee7fpga.coeff_sink, 0)