def as_bytes(self): """Get a byte array representing this entry. :return: ndarray(dtype='u1') of bytes for this entry. :rtype: np.ndarray """ data = np.zeros(8, dtype='u1') data[0:3] = littleEndian(self.from_addr, 3) data[3:6] = littleEndian(self.to_addr, 3) data[6:8] = self.operation.as_bytes() return data
def regRun(cls, mode, info, reps, startDelay=0): """ Returns a numpy array of register bytes to run the board Register Write: These registers control the AD functions. This card is always set in slave mode for daisychain initiation of AD functions (see GHzDAC board). l(0) length[15..8] set to 0 ; ( length[15..0] = 59 ) l(1) length[7..0] set to 59 d(0) start[7..0] Output & command function 0 = off 1 = register readback 2 = average mode, auto start (use n=1) 3 = average mode, daisychain start 4 = demodulator mode, auto start (use n=1) 5 = demodulator mode, daisychain start 6 = set PLL of 1GHz clock with ser[23..0], no readback 7 = recalibrate AD converters, no readback d(1) startdelay[7..0] Start delay after daisychain signal, compensates for dchain delays d(2) startdelay[15..8] Note longer delays than for GHzDAC d(3) ser1[7..0] 8 lowest PLL bits of serial interface d(4) ser2[7..0] 8 middle PLL bits d(5) ser3[7..0] 8 highest PLL bits d(6) spare d(7) n[7..0] n = Number of averages in average mode d(8) n[15..8] n = Number of total events in demodulator mode d(9) bitflip[7..0] XOR mask for bit readout d(10) mon0[7..0] SMA mon0 and mon1 programming, like DAC board d(11) mon1[7..0] ... d(58) spare """ regs = np.zeros(cls.REG_PACKET_LEN, dtype='<u1') regs[0] = mode regs[1:3] = littleEndian(startDelay, 2) #Daisychain delay regs[7:9] = littleEndian(reps, 2) #Number of repetitions regs[9] = littleEndian(0, 1)[0] #XOR bit flip mask mon0 = info.get('mon0', 'start') mon1 = info.get('mon1', 'don') if isinstance(mon0, str): mon0 = mondict.MONDICT[mon0] if isinstance(mon1, str): mon1 = mondict.MONDICT[mon1] regs[10] = mon0 regs[11] = mon1 return regs
def regRun(cls, mode, info, reps, startDelay=0): """ Returns a numpy array of register bytes to run the board Register Write: These registers control the AD functions. This card is always set in slave mode for daisychain initiation of AD functions (see GHzDAC board). l(0) length[15..8] set to 0 ; ( length[15..0] = 59 ) l(1) length[7..0] set to 59 d(0) start[7..0] Output & command function 0 = off 1 = register readback 2 = average mode, auto start (use n=1) 3 = average mode, daisychain start 4 = demodulator mode, auto start (use n=1) 5 = demodulator mode, daisychain start 6 = set PLL of 1GHz clock with ser[23..0], no readback 7 = recalibrate AD converters, no readback d(1) startdelay[7..0] Start delay after daisychain signal, compensates for dchain delays d(2) startdelay[15..8] Note longer delays than for GHzDAC d(3) ser1[7..0] 8 lowest PLL bits of serial interface d(4) ser2[7..0] 8 middle PLL bits d(5) ser3[7..0] 8 highest PLL bits d(6) spare d(7) n[7..0] n = Number of averages in average mode d(8) n[15..8] n = Number of total events in demodulator mode d(9) bitflip[7..0] XOR mask for bit readout d(10) mon0[7..0] SMA mon0 and mon1 programming, like DAC board d(11) mon1[7..0] ... d(58) spare """ regs = np.zeros(cls.REG_PACKET_LEN, dtype='<u1') regs[0] = mode regs[1:3] = littleEndian(startDelay, 2) #Daisychain delay regs[7:9] = littleEndian(reps, 2) #Number of repetitions regs[9] = littleEndian(0, 1)[0] #XOR bit flip mask mon0 = info.get('mon0', 'start') mon1 = info.get('mon1', 'don') if isinstance(mon0,str): mon0 = mondict.MONDICT[mon0] if isinstance(mon1,str): mon1 = mondict.MONDICT[mon1] regs[10] = mon0 regs[11] = mon1 return regs
def toString(self): """Serialize jump table to a byte string for the FPGA""" data = np.zeros(self.PACKET_LEN, dtype='<u1') # Set counter values. Each one is 4 bytes for i, c in enumerate(self.counters): data[i * 4:(i + 1) * 4] = littleEndian(c, 4) # Set start address data[16:19] = littleEndian(self.start_addr, 3) data[19:22] = littleEndian(self.start_addr, 3) # Start op code data[22] = 5 data[23] = 0 for i, jump in enumerate(self.jumps): ofs = 24 + i * 8 data[ofs:ofs + 8] = jump.as_bytes() return data.tostring()
def as_bytes(self): """Get bytes for an END. The op code is xxxxxxxx xxxxx111 """ return littleEndian(7, 2)
def makeMixerTable(cls, demods, p): """ Page 1-12 of SRAM has the mixer tables for demodulators 0-11 respectively. (2) For the multiplier lookup tables, adrstart is taken from the following table adrstart[20..8]= channel n + 1. This offsets by 1 from the above trigger page. The Ethernet packet for channel n is: l(1) length[15..8] set to 4; ( length[15..0]= 1024+2=1026 ) l(2) length[7..0] set to 2 d(1) adrstart[15..8] SRAM page for start address: middle bits = n+1 d(2) adrstart[23..16] upper bits; size of SRAM implies bits [23..19] = 0 d(3) sram(+0)[7..0] multsin(0) Multiplier time 0 d(4) sram(+1)[7..0] multcos(0) d(5) sram(+2)[7..0] multsin(1) Multiplier time 1 (1/2 clock, 2ns) d(6) sram(+3)[7..0] multcos(1) ... d(1025)sram(+1022)[7..0] multsin(511) d(1026)sram(+1023)[7..0] multcos(511) """ for idx, demod in enumerate(demods): data = np.zeros(cls.SRAM_MIXER_PKT_LEN, dtype='<i1') # retrigger table is page 0, mixer tables are pages 1-13, factor of 4 from stripping least significant bits data[0:2] = littleEndian((idx + 1), 2) # SRAM page address mixerTable = demod['mixerTable'] for tidx, row in enumerate(mixerTable): I, Q = row data[(tidx * 2) + 2] = I data[(tidx * 2 + 1) + 2] = Q p.write(data.tostring())
def toString(self): """Serialize jump table to a byte string for the FPGA""" data = np.zeros(self.PACKET_LEN, dtype='<u1') # Set counter values. Each one is 4 bytes for i, c in enumerate(self.counters): data[i * 4:(i + 1) * 4] = littleEndian(c, 4) # Set start address data[16:19] = littleEndian(self.start_addr, 3) data[19:22] = littleEndian(self.start_addr, 3) # Start op code data[22] = 5 data[23] = 0 for i, jump in enumerate(self.jumps): ofs = 24 + i * 8 data[ofs:ofs+8] = jump.as_bytes() return data.tostring()
def regRun(cls, mode, reps, filterFunc, filterStretchAt, filterStretchLen, demods, startDelay=0): """Returns a numpy array of register bytes to run the board""" regs = np.zeros(cls.REG_PACKET_LEN, dtype='<u1') regs[0] = mode regs[1:3] = littleEndian(startDelay, 2) #Daisychain delay regs[7:9] = littleEndian(reps, 2) #Number of repetitions if len(filterFunc)<=1: raise Exception('Filter function must be at least 2') #Filter function end address. -1 comes from 0 indexing. regs[9:11] = littleEndian(len(filterFunc)-1, 2) #Stretch address for filter regs[11:13] = littleEndian(filterStretchAt, 2) #Filter function stretch length regs[13:15] = littleEndian(filterStretchLen, 2) for i in range(cls.DEMOD_CHANNELS): if i not in demods: continue addr = 15 + 4*i #Lookup table step per sample regs[addr:addr+2] = littleEndian(demods[i]['dPhi'], 2) #Lookup table start address regs[addr+2:addr+4] = littleEndian(demods[i]['phi0'], 2) return regs
def regRun(cls, mode, reps, filterFunc, filterStretchAt, filterStretchLen, demods, startDelay=0): """Returns a numpy array of register bytes to run the board""" regs = np.zeros(cls.REG_PACKET_LEN, dtype='<u1') regs[0] = mode regs[1:3] = littleEndian(startDelay, 2) #Daisychain delay regs[7:9] = littleEndian(reps, 2) #Number of repetitions if len(filterFunc) <= 1: raise Exception('Filter function must be at least 2') #Filter function end address. -1 comes from 0 indexing. regs[9:11] = littleEndian(len(filterFunc) - 1, 2) #Stretch address for filter regs[11:13] = littleEndian(filterStretchAt, 2) #Filter function stretch length regs[13:15] = littleEndian(filterStretchLen, 2) for i in range(cls.DEMOD_CHANNELS): if i not in demods: continue addr = 15 + 4 * i #Lookup table step per sample regs[addr:addr + 2] = littleEndian(demods[i]['dPhi'], 2) #Lookup table start address regs[addr + 2:addr + 4] = littleEndian(demods[i]['phi0'], 2) return regs
def as_bytes(self): """Get bytes for a JUMP The op code is xxjjjjjj xxxx1101 where jjjjjj is the jump index to set after the jump. """ # binary 1101 = decimal 13 val = (self.jump_index << 8) + 13 return littleEndian(val, 2)
def as_bytes(self): """Get bytes for an IDLE. The op code is dddddddd ddddddd0 where d[14..0] is the number of FPGA cycles to idle. """ if not (IDLE_MIN_CYCLES <= self.cycles <= IDLE_MAX_CYCLES): raise ValueError( "IDLE num cycles must fit in {} bits".format(IDLE_NUM_BITS)) return littleEndian(self.cycles << 1, 2)
def as_bytes(self): """Get bytes for a CYCLE. The op code is xxjjjjjj xxccx011 where jjjjjj is the jump index to set if the cycle is not done. cc is which counter to increment. """ jump_index = self.jump_index << 8 counter = self.counter << 4 op = 3 return littleEndian(jump_index + counter + op, 2)
def as_bytes(self): """Get bytes for an IDLE. The op code is dddddddd ddddddd0 where d[14..0] is the number of FPGA cycles to idle. """ if not (IDLE_MIN_CYCLES <= self.cycles <= IDLE_MAX_CYCLES): raise ValueError( "IDLE num cycles must fit in {} bits".format( IDLE_NUM_BITS ) ) return littleEndian(self.cycles << 1, 2)
def as_bytes(self): """Get bytes for a CHECK The op code is xxjjjjjj iiiin001 where jjjjjj jump index to set when check is True iiii specifies which daisychain bit to check n selects whether we check for bit ON or OFF """ jump_idx = self.jump_index << 8 which_daisy_bit = self.which_daisy_bit << 4 bit_state = int(self.bit_state) << 3 op = 1 val = jump_idx + which_daisy_bit + bit_state + op return littleEndian(val, 2)
def pktWriteSram(cls, derp, data): """ Get a numpy array of bytes to write one derp of SRAM data - ndarray: numeric data to be written. This must be formatted such that .tostring will yield the proper byte string for the direct ethernet packet. """ assert 0 <= derp < cls.SRAM_WRITE_DERPS, \ 'SRAM derp out of range: %d' % derp # Ensure data is a numpy array. # This should not be needed, as it should have happened already data = np.asarray(data) pkt = np.zeros(cls.SRAM_WRITE_PKT_LEN + 2, dtype='<u1') pkt[0:2] = littleEndian(derp, 2) pkt[2:2 + len(data)] = data return pkt
def pktWriteSram(cls, derp, data): """ Get a numpy array of bytes to write one derp of SRAM data - ndarray: numeric data to be written. This must be formatted such that .tostring will yield the proper byte string for the direct ethernet packet. """ assert 0 <= derp < cls.SRAM_WRITE_DERPS, \ 'SRAM derp out of range: %d' % derp # Ensure data is a numpy array. # This should not be needed, as it should have happened already data = np.asarray(data) pkt = np.zeros(cls.SRAM_WRITE_PKT_LEN + 2, dtype='<u1') pkt[0:2] = littleEndian(derp, 2) pkt[2:2+len(data)] = data return pkt
def makeMixerTable(cls, demods, p): """ Page 1-12 of SRAM has the mixer tables for demodulators 0-11 respectively. (2) For the multiplier lookup tables, adrstart is taken from the following table adrstart[20..8]= channel n + 1. This offsets by 1 from the above trigger page. The Ethernet packet for channel n is: l(1) length[15..8] set to 4; ( length[15..0]= 1024+2=1026 ) l(2) length[7..0] set to 2 d(1) adrstart[15..8] SRAM page for start address: middle bits = n+1 d(2) adrstart[23..16] upper bits; size of SRAM implies bits [23..19] = 0 d(3) sram(+0)[7..0] multsin(0) Multiplier time 0 d(4) sram(+1)[7..0] multcos(0) d(5) sram(+2)[7..0] multsin(1) Multiplier time 1 (1/2 clock, 2ns) d(6) sram(+3)[7..0] multcos(1) ... d(1025)sram(+1022)[7..0] multsin(511) d(1026)sram(+1023)[7..0] multcos(511) """ for idx,demod in enumerate(demods): data = np.zeros(cls.SRAM_MIXER_PKT_LEN, dtype='<i1') # retrigger table is page 0, mixer tables are pages 1-13, factor of 4 from stripping least significant bits data[0:2] = littleEndian((idx+1),2) # SRAM page address mixerTable = demod['mixerTable'] for tidx, row in enumerate(mixerTable): I, Q = row data[(tidx*2)+2] = I data[(tidx*2+1)+2] = Q p.write(data.tostring())
def as_bytes(self): """Get bytes for a NOP. The op code is xxxxxxxx xxxx0101 """ return littleEndian(5, 2)
def makeTriggerTable(cls, triggerTable, p): """ Page 0 of SRAM has a table defining ADC trigger repetition counts and delays SRAM Write: The controller must write to SRAM in the AD board to define two tables, a retrigger table to define multiple AD triggers for an experiment start, and a multiplier table for demodulation of the incoming signal for each demodulation channel. (1) The retrigger table defines multiple AD triggers per master start. The table starts at address 0, then increments up (to a maximum 127) after performing the functions of each table entry, until it reaches a table entry with rdelay[15..8]=0, at which time the retriggering stops. Note that an empty table with rdelay[15..9]=0 at the address 0 will not ever trigger the AD. In the table entry, rdelay[15..0]+ 3 is the number of 4 ns cycles between the AD start (or last end of ADon) and the AD trigger. After this delay, the AD is turned on for rlength[7..0]+1 cycles, during which ADon demultiplexer (don) is high. The value rcount[15..0]+1 is the number of AD triggers per table entry. An AD done signal pulses 3 cycles after the last ADon goes low. Note that there is approximately 7 clock cycle delay that needs to be measured and taken into account to get demodulation time correct. Channels 0 to rchan[3..0]-1 is read out; maximum rchan[3..0] is 11 for 12 channels. If rchan[3..0]=0, then save data in bit readout mode - see below. Note that you have multiple triggers, as for the DAC board. But for each trigger, you can have multiple retriggering to account for multiple AD demodulations during each sequence. (1) The retrigger table is stored in SRAM memory with adrstart[20..8] = 0. The Ethernet packet is given by l(0) length[15..8] set to 4; ( length[15..0]= 1024+2=1026 ) l(1) length[7..0] set to 2 d(0) adrstart[15..8] SRAM page for start address: middle bits = 0, d(1) adrstart[23..16] upper bits; size of SRAM implies bits [23..19] = 0 d(2) sram(+0)[7..0] rcount[7..0](0) +1 = number AD cycles d(3) sram(+1)[7..0] rcount[15..8](0) d(4) sram(+2)[7..0] rdelay[7..0](0) +3= clock delay before Multiplier on d(5) sram(+3)[7..0] rdelay[15..8](0) (units of 4 ns) d(6) sram(+4)[7..0] rlength(7..0](0) +1 = clock length of Multiplier on d(7) sram(+5)[7..0] rchan[3..0](0) Number channels saved, 0=bit mode d(8) sram(+6)[7..0] spare d(9) sram(+7)[7..0] spare d(10) sram(+8)[7..0] rcount[7..0](1) ... d(1022)sram(+1022)[7..0] rcount[15..8](127) ... d(1025)sram(+1022)[7..0] spare """ # takes trigger table and turns it into a packet for ADC if len(triggerTable) > 128: # no memory for more than 128 entries raise Exception("Trigger table max len = 128") if triggerTable[0][1] < 1: raise Exception("Trigger table row0 rdelay is 0") rlens = [row[1] < 50 for row in triggerTable] # if there is a spacing of <50 clock cycles between demods then the FIFO gets backed up if any(rlens): count0 = triggerTable[0][0] rlen0 = triggerTable[0][1] if rlen0 < 50 and count0 == 1 and not any( rlens[1:]): # if its only the start delay, no exception pass else: raise Exception( "rlen < 50 clock cycles (200 ns) can cause FIFO backup for 12 chans" ) data = np.zeros(cls.SRAM_RETRIGGER_PKT_LEN, dtype='<u1') data[0] = 0 # SRAM page for start address: middle bits = 0, data[1] = 0 # upper bits; size of SRAM implies bits [23..19] = 0 for idx, entry in enumerate(triggerTable): currCount, currDelay, currLength, currChans = entry # WARNING: currChans defined in a funny way # if you want a trigger to measure a subset of channels, # those channels must be the lowest channels. # i.e. you can only read out: # (0, 0-1, 0-2, ... 0-11) # you cannot cherry pick which channels to read out # as far as JK and TW understand it. # For now, we will not make use of currChans(rchan), # rather default it all channels always read out. # See documentation above currCount -= 1 # compensate for FPGA offsets currDelay -= 4 # compensate for FPGA offsets currLength -= 1 # compensate for FPGA offsets midx = idx * 8 + 2 data[midx:midx + 2] = littleEndian(currCount, 2) data[midx + 2:midx + 4] = littleEndian(currDelay, 2) data[midx + 4] = currLength data[midx + 5] = currChans data[midx + 6:midx + 8] = littleEndian(0, 2) # spare p.write(data.tostring())
def regSerial(cls, bits): """Returns a numpy array of register bytes to write to PLL""" regs = np.zeros(cls.REG_PACKET_LEN, dtype='<u1') regs[0] = 6 regs[3:6] = littleEndian(bits, 3) return regs
def makeTriggerTable(cls, triggerTable, p): """ Page 0 of SRAM has a table defining ADC trigger repetition counts and delays SRAM Write: The controller must write to SRAM in the AD board to define two tables, a retrigger table to define multiple AD triggers for an experiment start, and a multiplier table for demodulation of the incoming signal for each demodulation channel. (1) The retrigger table defines multiple AD triggers per master start. The table starts at address 0, then increments up (to a maximum 127) after performing the functions of each table entry, until it reaches a table entry with rdelay[15..8]=0, at which time the retriggering stops. Note that an empty table with rdelay[15..9]=0 at the address 0 will not ever trigger the AD. In the table entry, rdelay[15..0]+ 3 is the number of 4 ns cycles between the AD start (or last end of ADon) and the AD trigger. After this delay, the AD is turned on for rlength[7..0]+1 cycles, during which ADon demultiplexer (don) is high. The value rcount[15..0]+1 is the number of AD triggers per table entry. An AD done signal pulses 3 cycles after the last ADon goes low. Note that there is approximately 7 clock cycle delay that needs to be measured and taken into account to get demodulation time correct. Channels 0 to rchan[3..0]-1 is read out; maximum rchan[3..0] is 11 for 12 channels. If rchan[3..0]=0, then save data in bit readout mode - see below. Note that you have multiple triggers, as for the DAC board. But for each trigger, you can have multiple retriggering to account for multiple AD demodulations during each sequence. (1) The retrigger table is stored in SRAM memory with adrstart[20..8] = 0. The Ethernet packet is given by l(0) length[15..8] set to 4; ( length[15..0]= 1024+2=1026 ) l(1) length[7..0] set to 2 d(0) adrstart[15..8] SRAM page for start address: middle bits = 0, d(1) adrstart[23..16] upper bits; size of SRAM implies bits [23..19] = 0 d(2) sram(+0)[7..0] rcount[7..0](0) +1 = number AD cycles d(3) sram(+1)[7..0] rcount[15..8](0) d(4) sram(+2)[7..0] rdelay[7..0](0) +3= clock delay before Multiplier on d(5) sram(+3)[7..0] rdelay[15..8](0) (units of 4 ns) d(6) sram(+4)[7..0] rlength(7..0](0) +1 = clock length of Multiplier on d(7) sram(+5)[7..0] rchan[3..0](0) Number channels saved, 0=bit mode d(8) sram(+6)[7..0] spare d(9) sram(+7)[7..0] spare d(10) sram(+8)[7..0] rcount[7..0](1) ... d(1022)sram(+1022)[7..0] rcount[15..8](127) ... d(1025)sram(+1022)[7..0] spare """ # takes trigger table and turns it into a packet for ADC if len(triggerTable) > 128: # no memory for more than 128 entries raise Exception("Trigger table max len = 128") if triggerTable[0][1] < 1: raise Exception("Trigger table row0 rdelay is 0") rlens = [row[1]<50 for row in triggerTable] # if there is a spacing of <50 clock cycles between demods then the FIFO gets backed up if any(rlens): count0 = triggerTable[0][0] rlen0 = triggerTable[0][1] if rlen0<50 and count0==1 and not any(rlens[1:]): # if its only the start delay, no exception pass else: raise Exception("rlen < 50 clock cycles (200 ns) can cause FIFO backup for 12 chans") data = np.zeros(cls.SRAM_RETRIGGER_PKT_LEN, dtype='<u1') data[0] = 0 # SRAM page for start address: middle bits = 0, data[1] = 0 # upper bits; size of SRAM implies bits [23..19] = 0 for idx,entry in enumerate(triggerTable): currCount, currDelay, currLength, currChans = entry # WARNING: currChans defined in a funny way # if you want a trigger to measure a subset of channels, # those channels must be the lowest channels. # i.e. you can only read out: # (0, 0-1, 0-2, ... 0-11) # you cannot cherry pick which channels to read out # as far as JK and TW understand it. # For now, we will not make use of currChans(rchan), # rather default it all channels always read out. # See documentation above currCount -= 1 # compensate for FPGA offsets currDelay -= 4 # compensate for FPGA offsets currLength -=1 # compensate for FPGA offsets midx = idx * 8 + 2 data[midx:midx+2] = littleEndian(currCount, 2) data[midx+2:midx+4] = littleEndian(currDelay, 2) data[midx+4] = currLength data[midx+5] = currChans data[midx+6:midx+8] = littleEndian(0, 2) # spare p.write(data.tostring())