def init(self): """Initialize the servo, Sampler and both Urukuls. Leaves the servo disabled (see :meth:`set_config`), resets and configures all DDS. Urukul initialization is performed blindly as there is no readback from the DDS or the CPLDs. This method does not alter the profile configuration memory or the channel controls. """ self.set_config(enable=0) delay(3*us) # pipeline flush self.pgia.set_config_mu( sampler.SPI_CONFIG | spi.SPI_END, 16, 4, sampler.SPI_CS_PGIA) self.cpld0.init(blind=True) cfg0 = self.cpld0.cfg_reg self.cpld0.cfg_write(cfg0 | (0xf << urukul.CFG_MASK_NU)) self.dds0.init(blind=True) self.cpld0.cfg_write(cfg0) self.cpld1.init(blind=True) cfg1 = self.cpld1.cfg_reg self.cpld1.cfg_write(cfg1 | (0xf << urukul.CFG_MASK_NU)) self.dds1.init(blind=True) self.cpld1.cfg_write(cfg1)
def init(self): """Initialize and configure the DDS. Sets up SPI mode, confirms chip presence, powers down unused blocks, configures the PLL, waits for PLL lock. Uses the IO_UPDATE signal multiple times. """ # Set SPI mode self.write32(_AD9910_REG_CFR1, 0x00000002) self.cpld.io_update.pulse(2*us) # Use the AUX DAC setting to identify and confirm presence aux_dac = self.read32(_AD9910_REG_AUX_DAC) if aux_dac & 0xff != 0x7f: raise ValueError("Urukul AD9910 AUX_DAC mismatch") delay(50*us) # slack # Configure PLL settings and bring up PLL self.write32(_AD9910_REG_CFR2, 0x01400020) self.cpld.io_update.pulse(2*us) cfr3 = (0x0807c100 | (self.pll_vco << 24) | (self.pll_cp << 19) | (self.pll_n << 1)) self.write32(_AD9910_REG_CFR3, cfr3 | 0x400) # PFD reset self.cpld.io_update.pulse(100*us) self.write32(_AD9910_REG_CFR3, cfr3) self.cpld.io_update.pulse(100*us) # Wait for PLL lock, up to 100 ms for i in range(100): sta = self.cpld.sta_read() lock = urukul_sta_pll_lock(sta) delay(1*ms) if lock & (1 << self.chip_select - 4): return raise ValueError("PLL lock timeout")
def burst_mu(self, data, dt_mu, ctrl=0): """Acquire a burst of samples. If the burst is too long and the sample rate too high, there will be RTIO input overflows. High sample rates lead to gain errors since the impedance between the instrumentation amplifier and the ADC is high. :param data: List of data values to write result packets into. In machine units. :param dt: Sample interval in machine units. :param ctrl: ADC control word to write during each result packet transfer. """ self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, 24, self.div, SPI_CS_ADC) for i in range(len(data)): t0 = now_mu() self.cnv.pulse(40*ns) # t_CNVH delay(560*ns) # t_CONV max self.bus.write(ctrl << 24) at_mu(t0 + dt_mu) for i in range(len(data)): data[i] = self.bus.read()
def init(self, blind=False): """Configures the SPI bus, drives LDAC and CLR high, programmes the offset DACs, and enables overtemperature shutdown. This method must be called before any other method at start-up or if the SPI bus has been accessed by another device. :param blind: If ``True``, do not attempt to read back control register or check for overtemperature. """ self.ldac.on() self.clr.on() self.bus.set_config_mu(SPI_AD53XX_CONFIG, 24, self.div_write, self.chip_select) self.write_offset_dacs_mu(self.offset_dacs) if not blind: ctrl = self.read_reg(channel=0, op=AD53XX_READ_CONTROL) if ctrl & 0b10000: raise ValueError("DAC over temperature") delay(25*us) self.bus.write( # enable power and overtemperature shutdown (AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_CONTROL | 0b0010) << 8) if not blind: ctrl = self.read_reg(channel=0, op=AD53XX_READ_CONTROL) if (ctrl & 0b10111) != 0b00010: raise ValueError("DAC CONTROL readback mismatch") delay(15*us)
def init(self): """Initialize and detect Urukul. Resets the DDS and verifies correct CPLD gateware version. """ cfg = self.cfg_reg self.cfg_reg = cfg | (1 << CFG_RST) | (1 << CFG_IO_RST) proto_rev = urukul_sta_proto_rev(self.sta_read()) if proto_rev != STA_PROTO_REV_MATCH: raise ValueError("Urukul proto_rev mismatch") delay(20*us) # slack, reset self.cfg_write(cfg) delay(1*ms) # DDS wake up
def get_att_mu(self): """Return the digital step attenuator settings in machine units. :return: 32 bit attenuator settings """ self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT, 32, SPIT_ATT_RD, CS_ATT) self.bus.write(0) # shift in zeros, shift out current value self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 32, SPIT_ATT_WR, CS_ATT) delay(10*us) self.att_reg = self.bus.read() self.bus.write(self.att_reg) # shift in current value again and latch return self.att_reg
def sample_mu(self, next_ctrl=0): """Acquire a sample: Perform a conversion and transfer the sample. :param next_ctrl: ADC control word for the next sample :return: The ADC result packet (machine units) """ self.cnv.pulse(40*ns) # t_CNVH delay(560*ns) # t_CONV max self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, 24, self.div, SPI_CS_ADC) self.bus.write(next_ctrl << 24) return self.bus.read()
def set(self, data): """Sets the values of the latch outputs. This does not advance the timeline and the waveform is generated before `now`.""" delay(-2*(self.n + 1)*self.dt) for i in range(self.n): if (data >> (self.n-i-1)) & 1 == 0: self.ser.off() else: self.ser.on() self.clk.off() delay(self.dt) self.clk.on() delay(self.dt) self.clk.off() self.latch.on() delay(self.dt) self.latch.off() delay(self.dt)
def get_profile_mu(self, profile, data): """Retrieve profile data. Profile data is returned in the ``data`` argument in machine units packed as: ``[ftw >> 16, b1, pow, adc | (delay << 8), offset, a1, ftw & 0xffff, b0]``. .. seealso:: The individual fields are described in :meth:`set_iir_mu` and :meth:`set_dds_mu`. This method advances the timeline by 32 µs and consumes all slack. :param profile: Profile number (0-31) :param data: List of 8 integers to write the profile data into """ base = (self.servo_channel << 8) | (profile << 3) for i in range(len(data)): data[i] = self.servo.read(base + i) delay(4*us)
def read_reg(self, channel=0, op=AD53XX_READ_X1A): """Read a DAC register. This method advances the timeline by the duration of two SPI transfers plus two RTIO coarse cycles plus 270 ns and consumes all slack. :param channel: Channel number to read from (default: 0) :param op: Operation to perform, one of :const:`AD53XX_READ_X1A`, :const:`AD53XX_READ_X1B`, :const:`AD53XX_READ_OFFSET`, :const:`AD53XX_READ_GAIN` etc. (default: :const:`AD53XX_READ_X1A`). :return: The 16 bit register value """ self.bus.write(ad53xx_cmd_read_ch(channel, op) << 8) self.bus.set_config_mu(SPI_AD53XX_CONFIG | spi.SPI_INPUT, 24, self.div_read, self.chip_select) delay(270*ns) # t_21 min sync high in readback self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_NOP) << 8) self.bus.set_config_mu(SPI_AD53XX_CONFIG, 24, self.div_write, self.chip_select) # FIXME: the int32 should not be needed to resolve unification return self.bus.read() & int32(0xffff)
def measure_io_update_alignment(self, delay_start, delay_stop): """Use the digital ramp generator to locate the alignment between IO_UPDATE and SYNC_CLK. The ramp generator is set up to a linear frequency ramp (dFTW/t_SYNC_CLK=1) and started at a coarse RTIO time stamp plus `delay_start` and stopped at a coarse RTIO time stamp plus `delay_stop`. :param delay_start: Start IO_UPDATE delay in machine units. :param delay_stop: Stop IO_UPDATE delay in machine units. :return: Odd/even SYNC_CLK cycle indicator. """ # set up DRG self.set_cfr1(drg_load_lrr=1, drg_autoclear=1) # DRG -> FTW, DRG enable self.write32(_AD9910_REG_CFR2, 0x01090000) # no limits self.write64(_AD9910_REG_RAMP_LIMIT, -1, 0) # DRCTL=0, dt=1 t_SYNC_CLK self.write32(_AD9910_REG_RAMP_RATE, 0x00010000) # dFTW = 1, (work around negative slope) self.write64(_AD9910_REG_RAMP_STEP, -1, 0) # delay io_update after RTIO edge t = now_mu() + 8 & ~7 at_mu(t + delay_start) # assumes a maximum t_SYNC_CLK period self.cpld.io_update.pulse_mu(16 - delay_start) # realign # disable DRG autoclear and LRR on io_update self.set_cfr1() # stop DRG self.write64(_AD9910_REG_RAMP_STEP, 0, 0) at_mu(t + 0x1000 + delay_stop) self.cpld.io_update.pulse_mu(16 - delay_stop) # realign ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW delay(100*us) # slack # disable DRG self.write32(_AD9910_REG_CFR2, 0x01010000) self.cpld.io_update.pulse_mu(8) return ftw & 1
def init(self): """Initialize and configure the DDS. Sets up SPI mode, confirms chip presence, powers down unused blocks, and configures the PLL. Does not wait for PLL lock. Uses the IO_UPDATE signal multiple times. """ # SPI mode self.write(AD9912_SER_CONF, 0x99, length=1) self.cpld.io_update.pulse(2*us) # Verify chip ID and presence prodid = self.read(AD9912_PRODIDH, length=2) if (prodid != 0x1982) and (prodid != 0x1902): raise ValueError("Urukul AD9912 product id mismatch") delay(50*us) # HSTL power down, CMOS power down self.write(AD9912_PWRCNTRL1, 0x80, length=1) self.cpld.io_update.pulse(2*us) self.write(AD9912_N_DIV, self.pll_n//2 - 2, length=1) self.cpld.io_update.pulse(2*us) # I_cp = 375 µA, VCO high range self.write(AD9912_PLLCFG, 0b00000101, length=1) self.cpld.io_update.pulse(2*us)
def sample_mu(self, data): """Acquire a set of samples. Perform a conversion and transfer the samples. This assumes that the input FIFO of the ADC SPI RTIO channel is deep enough to buffer the samples (half the length of `data` deep). If it is not, there will be RTIO input overflows. :param data: List of data samples to fill. Must have even length. Samples are always read from the last channel (channel 7) down. The `data` list will always be filled with the last item holding to the sample from channel 7. """ self.cnv.pulse(30*ns) # t_CNVH delay(450*ns) # t_CONV mask = 1 << 15 for i in range(len(data)//2): self.bus_adc.write(0) for i in range(len(data) - 1, -1, -2): val = self.bus_adc.read() data[i] = val >> 16 val &= 0xffff data[i - 1] = -(val & mask) + (val & ~mask)
def init(self, blind=False): """Initialize and detect Urukul. Resets the DDS I/O interface and verifies correct CPLD gateware version. Does not pulse the DDS MASTER_RESET as that confuses the AD9910. :param blind: Do not attempt to verify presence and compatibility. """ cfg = self.cfg_reg # Don't pulse MASTER_RESET (m-labs/artiq#940) self.cfg_reg = cfg | (0 << CFG_RST) | (1 << CFG_IO_RST) if blind: self.cfg_write(self.cfg_reg) else: proto_rev = urukul_sta_proto_rev(self.sta_read()) if proto_rev != STA_PROTO_REV_MATCH: raise ValueError("Urukul proto_rev mismatch") delay(100*us) # reset, slack self.cfg_write(cfg) if self.sync_div: at_mu(now_mu() & ~0xf) # align to RTIO/2 self.set_sync_div(self.sync_div) # 125 MHz/2 = 1 GHz/16 delay(1*ms) # DDS wake up
def tune_sync_delay(self, search_seed=15): """Find a stable SYNC_IN delay. This method first locates a valid SYNC_IN delay at zero validation window size (setup/hold margin) by scanning around `search_seed`. It then looks for similar valid delays at successively larger validation window sizes until none can be found. It then decreases the validation window a bit to provide some slack and stability and returns the optimal values. This method and :meth:`tune_io_update_delay` can be run in any order. :param search_seed: Start value for valid SYNC_IN delay search. Defaults to 15 (half range). :return: Tuple of optimal delay and window size. """ if not self.cpld.sync_div: raise ValueError("parent cpld does not drive SYNC") search_span = 31 # FIXME https://github.com/sinara-hw/Urukul/issues/16 # should both be 2-4 once kasli sync_in jitter is identified min_window = 0 margin = 1 # 1*75ps setup and hold for window in range(16): next_seed = -1 for in_delay in range(search_span - 2*window): # alternate search direction around search_seed if in_delay & 1: in_delay = -in_delay in_delay = search_seed + (in_delay >> 1) if in_delay < 0 or in_delay > 31: continue self.set_sync(in_delay, window) self.clear_smp_err() # integrate SMP_ERR statistics for a few hundred cycles delay(100*us) err = urukul_sta_smp_err(self.cpld.sta_read()) delay(100*us) # slack if not (err >> (self.chip_select - 4)) & 1: next_seed = in_delay break if next_seed >= 0: # valid delay found, scan next window search_seed = next_seed continue elif window > min_window: # no valid delay found here, roll back and add margin window = max(min_window, window - 1 - margin) self.set_sync(search_seed, window) self.clear_smp_err() delay(100*us) # slack return search_seed, window else: break raise ValueError("no valid window/delay")
def smooth(self, start: TFloat, stop: TFloat, duration: TFloat, order: TInt32): """Initiate an interpolated value change. For zeroth order (step) interpolation, the step is at ``start + duration/2``. First order interpolation corresponds to a linear value ramp from ``start`` to ``stop`` over ``duration``. The third order interpolation is constrained to have zero first order derivative at both `start` and `stop`. For first order and third order interpolation (linear and cubic) the interpolator needs to be stopped explicitly at the stop time (e.g. by setting spline coefficient data or starting a new :meth:`smooth` interpolation). This method advances the timeline by ``duration``. :param start: Initial value of the change. In physical units. :param stop: Final value of the change. In physical units. :param duration: Duration of the interpolation. In physical units. :param order: Order of the interpolation. Only 0, 1, and 3 are valid: step, linear, cubic. """ if order == 0: delay(duration/2.) self.set_coeff([stop]) delay(duration/2.) elif order == 1: self.set_coeff([start, (stop - start)/duration]) delay(duration) elif order == 3: v2 = 6.*(stop - start)/(duration*duration) self.set_coeff([start, 0., v2, -2.*v2/duration]) delay(duration) else: raise ValueError("Invalid interpolation order. " "Supported orders are: 0, 1, 3.")
def run(self): self.core.reset() delay(1 * ms) freq = 1 * MHz cs = 1 # run a couple of clock cycles with miso high to wake up the card self.spi_mmc.set_config(_SDCARD_SPI_CONFIG, 32, freq, 0) for i in range(10): self.spi_mmc.write(0xffffffff) self.spi_mmc.set_config(_SDCARD_SPI_CONFIG | spi.SPI_END, 32, freq, 0) self.spi_mmc.write(0xffffffff) delay(200 * us) self.spi_mmc.set_config(_SDCARD_SPI_CONFIG, 8, freq, cs) self.spi_mmc.write(0x40 << 24) # CMD self.spi_mmc.set_config(_SDCARD_SPI_CONFIG, 32, freq, cs) self.spi_mmc.write(0x00000000) # ARG self.spi_mmc.set_config(_SDCARD_SPI_CONFIG, 8, freq, cs) self.spi_mmc.write(0x95 << 24) # CRC self.spi_mmc.set_config(_SDCARD_SPI_CONFIG | spi.SPI_INPUT, 8, freq, cs) idle = False response = 0 for i in range(8): self.spi_mmc.write(0xff << 24) # NCR response = self.spi_mmc.read() delay(100 * us) if response == 0x01: idle = True break self.spi_mmc.set_config(_SDCARD_SPI_CONFIG | spi.SPI_END, 8, freq, cs) self.spi_mmc.write(0xff << 24) if not idle: print(response) raise ValueError("SD Card did not reply with IDLE")
def dac_iotest(self, pattern) -> TInt32: """Performs a DAC IO test according to the datasheet. :param pattern: List of four int32 containing the pattern :return: Bit error mask (16 bits) """ if len(pattern) != 4: raise ValueError("pattern length out of bounds") for addr in range(len(pattern)): self.dac_write(0x25 + addr, pattern[addr]) # repeat the pattern twice self.dac_write(0x29 + addr, pattern[addr]) delay(.1 * ms) for ch in range(2): channel = self.channel[ch] channel.set_duc_cfg(select=1) # test # dac test data is i msb, q lsb data = pattern[2 * ch] | (pattern[2 * ch + 1] << 16) channel.set_dac_test(data) if channel.get_dac_data() != data: raise ValueError("DAC test data readback failed") delay(.1 * ms) cfg = self.dac_read(0x01) delay(.1 * ms) self.dac_write(0x01, cfg | 0x8000) # iotest_ena self.dac_write(0x04, 0x0000) # clear iotest_result delay(.2 * ms) # let it rip # no need to go through the alarm register, # just read the error mask # self.clear_dac_alarms() alarms = self.get_dac_alarms() delay(.1 * ms) # slack if alarms & 0x0080: # alarm_from_iotest errors = self.dac_read(0x04) delay(.1 * ms) # slack else: errors = 0 self.dac_write(0x01, cfg) # clear config self.dac_write(0x04, 0x0000) # clear iotest_result return errors
def init(self, debug=False): """Initialize the board. Verifies board and chip presence, resets components, performs communication and configuration tests and establishes initial conditions. """ board_id = self.read8(PHASER_ADDR_BOARD_ID) if board_id != PHASER_BOARD_ID: raise ValueError("invalid board id") delay(.1 * ms) # slack hw_rev = self.read8(PHASER_ADDR_HW_REV) delay(.1 * ms) # slack is_baseband = hw_rev & PHASER_HW_REV_VARIANT gw_rev = self.read8(PHASER_ADDR_GW_REV) if debug: print("gw_rev:", gw_rev) self.core.break_realtime() delay(.1 * ms) # slack # allow a few errors during startup and alignment since boot if self.get_crc_err() > 20: raise ValueError("large number of frame CRC errors") delay(.1 * ms) # slack # determine the origin for frame-aligned timestamps self.measure_frame_timestamp() if self.frame_tstamp < 0: raise ValueError("frame timestamp measurement timed out") # reset self.set_cfg(dac_resetb=0, dac_sleep=1, dac_txena=0, trf0_ps=1, trf1_ps=1, att0_rstn=0, att1_rstn=0) self.set_leds(0x00) self.set_fan_mu(0) # bring dac out of reset, keep tx off self.set_cfg(clk_sel=self.clk_sel, dac_txena=0, trf0_ps=1, trf1_ps=1, att0_rstn=0, att1_rstn=0) delay(.1 * ms) # slack # crossing dac_clk (reference) edges with sync_dly # changes the optimal fifo_offset by 4 self.set_sync_dly(self.sync_dly) # 4 wire SPI, sif4_enable self.dac_write(0x02, 0x0080) if self.dac_read(0x7f) != 0x5409: raise ValueError("DAC version readback invalid") delay(.1 * ms) if self.dac_read(0x00) != 0x049c: raise ValueError("DAC config0 reset readback invalid") delay(.1 * ms) t = self.get_dac_temperature() delay(.1 * ms) if t < 10 or t > 90: raise ValueError("DAC temperature out of bounds") for data in self.dac_mmap: self.dac_write(data >> 16, data) delay(40 * us) self.dac_sync() delay(40 * us) # pll_ndivsync_ena disable config18 = self.dac_read(0x18) delay(.1 * ms) self.dac_write(0x18, config18 & ~0x0800) patterns = [ [0xf05a, 0x05af, 0x5af0, 0xaf05], # test channel/iq/byte/nibble [0x7a7a, 0xb6b6, 0xeaea, 0x4545], # datasheet pattern a [0x1a1a, 0x1616, 0xaaaa, 0xc6c6], # datasheet pattern b ] # A data delay of 2*50 ps heuristically and reproducibly matches # FPGA+board+DAC skews. There is plenty of margin (>= 250 ps # either side) and no need to tune at runtime. # Parity provides another level of safety. for i in range(len(patterns)): delay(.5 * ms) errors = self.dac_iotest(patterns[i]) if errors: raise ValueError("DAC iotest failure") delay(2 * ms) # let it settle lvolt = self.dac_read(0x18) & 7 delay(.1 * ms) if lvolt < 2 or lvolt > 5: raise ValueError("DAC PLL lock failed, check clocking") if self.tune_fifo_offset: fifo_offset = self.dac_tune_fifo_offset() if debug: print("fifo_offset:", fifo_offset) self.core.break_realtime() # self.dac_write(0x20, 0x0000) # stop fifo sync # alarm = self.get_sta() & 1 # delay(.1*ms) self.clear_dac_alarms() delay(2 * ms) # let it run a bit alarms = self.get_dac_alarms() delay(.1 * ms) # slack if alarms & ~0x0040: # ignore PLL alarms (see DS) if debug: print("alarms:", alarms) self.core.break_realtime() # ignore alarms else: raise ValueError("DAC alarm") # avoid malformed output for: mixer_ena=1, nco_ena=0 after power up self.dac_write(self.dac_mmap[2] >> 16, self.dac_mmap[2] | (1 << 4)) delay(40 * us) self.dac_sync() delay(100 * us) self.dac_write(self.dac_mmap[2] >> 16, self.dac_mmap[2]) delay(40 * us) self.dac_sync() delay(100 * us) # power up trfs, release att reset self.set_cfg(clk_sel=self.clk_sel, dac_txena=0) for ch in range(2): channel = self.channel[ch] # test attenuator write and readback channel.set_att_mu(0x5a) if channel.get_att_mu() != 0x5a: raise ValueError("attenuator test failed") delay(.1 * ms) channel.set_att_mu(0x00) # minimum attenuation # test oscillators and DUC for i in range(len(channel.oscillator)): oscillator = channel.oscillator[i] asf = 0 if i == 0: asf = 0x7fff # 6pi/4 phase oscillator.set_amplitude_phase_mu(asf=asf, pow=0xc000, clr=1) delay(1 * us) # 3pi/4 channel.set_duc_phase_mu(0x6000) channel.set_duc_cfg(select=0, clr=1) self.duc_stb() delay(.1 * ms) # settle link, pipeline and impulse response data = channel.get_dac_data() delay(1 * us) channel.oscillator[0].set_amplitude_phase_mu(asf=0, pow=0xc000, clr=1) delay(.1 * ms) sqrt2 = 0x5a81 # 0x7fff/sqrt(2) data_i = data & 0xffff data_q = (data >> 16) & 0xffff # allow ripple if (data_i < sqrt2 - 30 or data_i > sqrt2 or abs(data_i - data_q) > 2): raise ValueError("DUC+oscillator phase/amplitude test failed") if is_baseband: continue if channel.trf_read(0) & 0x7f != 0x68: raise ValueError("TRF identification failed") delay(.1 * ms) delay(.2 * ms) for data in channel.trf_mmap: channel.trf_write(data) channel.cal_trf_vco() delay(2 * ms) # lock if not (self.get_sta() & (PHASER_STA_TRF0_LD << ch)): raise ValueError("TRF lock failure") delay(.1 * ms) if channel.trf_read(0) & 0x1000: raise ValueError("TRF R_SAT_ERR") delay(.1 * ms) channel.en_trf_out() # enable dac tx self.set_cfg(clk_sel=self.clk_sel)
def run(self): self.core.reset() self.core.break_realtime() response = 0xff self.spi_mmc.set_config(_SDCARD_SPI_CONFIG, 500 * kHz, 500 * kHz) self.spi_mmc.set_xfer(0, 8, 0) for i in range(10): self.spi_mmc.write(0xffffffff) delay(-5 * us) delay(100 * us) self.spi_mmc.set_xfer(1, 8, 0) self.spi_mmc.write(0x40000000) delay(-5 * us) self.spi_mmc.write(0x00000000) delay(-5 * us) self.spi_mmc.write(0x00000000) delay(-5 * us) self.spi_mmc.write(0x00000000) delay(-5 * us) self.spi_mmc.write(0x00000000) delay(-5 * us) self.spi_mmc.write(0x95000000) delay(-5 * us) self.spi_mmc.set_xfer(1, 0, 24) self.spi_mmc.write(0xffffffff) response = self.spi_mmc.read_sync() sd_response = False for i in range(3): if ((response >> 8 * i) & 0x0000ff) == 0x01: sd_response = True break self.set_dataset("sd_response", sd_response)
def io_rst(self): delay(1 * us) self.cfg_write(self.cfg_reg | (1 << CFG_IO_RST)) delay(1 * us) self.cfg_write(self.cfg_reg & ~(1 << CFG_IO_RST)) delay(1 * us)
def init(self, blind=False): """Initialize and configure the DDS. Sets up SPI mode, confirms chip presence, powers down unused blocks, configures the PLL, waits for PLL lock. Uses the IO_UPDATE signal multiple times. :param blind: Do not read back DDS identity and do not wait for lock. """ # Set SPI mode self.set_cfr1() self.cpld.io_update.pulse(1*us) delay(1*ms) if not blind: # Use the AUX DAC setting to identify and confirm presence aux_dac = self.read32(_AD9910_REG_AUX_DAC) if aux_dac & 0xff != 0x7f: raise ValueError("Urukul AD9910 AUX_DAC mismatch") delay(50*us) # slack # Configure PLL settings and bring up PLL # enable amplitude scale from profiles # read effective FTW # sync timing validation disable (enabled later) self.write32(_AD9910_REG_CFR2, 0x01010020) self.cpld.io_update.pulse(1*us) cfr3 = (0x0807c000 | (self.pll_vco << 24) | (self.pll_cp << 19) | (self.pll_en << 8) | (self.pll_n << 1)) self.write32(_AD9910_REG_CFR3, cfr3 | 0x400) # PFD reset self.cpld.io_update.pulse(1*us) if self.pll_en: self.write32(_AD9910_REG_CFR3, cfr3) self.cpld.io_update.pulse(1*us) if blind: delay(100*ms) else: # Wait for PLL lock, up to 100 ms for i in range(100): sta = self.cpld.sta_read() lock = urukul_sta_pll_lock(sta) delay(1*ms) if lock & (1 << self.chip_select - 4): break if i >= 100 - 1: raise ValueError("PLL lock timeout") delay(10*us) # slack if self.sync_delay_seed >= 0: self.tune_sync_delay(self.sync_delay_seed) delay(1*ms)
def pulse(self, frequency, duration): time.manager.event(("pulse", self.name, frequency, duration)) delay(duration)
def gate_both(self, duration): time.manager.event(("gate_both", self.name, duration)) delay(duration)
def gate_falling(self, duration): time.manager.event(("gate_falling", self.name, duration)) delay(duration)
def _update_register(self, ch): self.mirny_cpld.write_ext( ALMAZNY_REG_BASE + ch, 8, self._flip_mu_bits(self.att_mu[ch]) | (self.channel_sw[ch] << 6), ALMAZNY_SPIT_WR) delay(100 * us)
def count_gate(self, duration): result = self.prng.randrange(0, 100) time.manager.event(("count_gate", self.name, duration, result)) delay(duration) return result
def wait_edge(self): duration = self.prng.randrange(0, 20) * units.ms time.manager.event(("wait_edge", self.name, duration)) delay(duration)
def wait_edge(self): duration = self.prng.randrange(0, 20)*units.ms time.manager.event(("wait_edge", self.name, duration)) delay(duration)
def pulse(self, t): cfg = self.cpld.cfg_reg self.cpld.cfg_write(cfg | (1 << CFG_IO_UPDATE)) delay(t) self.cpld.cfg_write(cfg)
def init(self, blind: TBool = False): """Initialize and configure the DDS. Sets up SPI mode, confirms chip presence, powers down unused blocks, configures the PLL, waits for PLL lock. Uses the IO_UPDATE signal multiple times. :param blind: Do not read back DDS identity and do not wait for lock. """ self.sync_data.init() if self.sync_data.sync_delay_seed >= 0 and not self.cpld.sync_div: raise ValueError("parent cpld does not drive SYNC") if self.sync_data.sync_delay_seed >= 0: if self.sysclk_per_mu != self.sysclk * self.core.ref_period: raise ValueError("incorrect clock ratio for synchronization") delay(50 * ms) # slack # Set SPI mode self.set_cfr1() self.cpld.io_update.pulse(1 * us) delay(1 * ms) if not blind: # Use the AUX DAC setting to identify and confirm presence aux_dac = self.read32(_AD9910_REG_AUX_DAC) if aux_dac & 0xff != 0x7f: raise ValueError("Urukul AD9910 AUX_DAC mismatch") delay(50 * us) # slack # Configure PLL settings and bring up PLL # enable amplitude scale from profiles # read effective FTW # sync timing validation disable (enabled later) self.set_cfr2(sync_validation_disable=1) self.cpld.io_update.pulse(1 * us) cfr3 = (0x0807c000 | (self.pll_vco << 24) | (self.pll_cp << 19) | (self.pll_en << 8) | (self.pll_n << 1)) self.write32(_AD9910_REG_CFR3, cfr3 | 0x400) # PFD reset self.cpld.io_update.pulse(1 * us) if self.pll_en: self.write32(_AD9910_REG_CFR3, cfr3) self.cpld.io_update.pulse(1 * us) if blind: delay(100 * ms) else: # Wait for PLL lock, up to 100 ms for i in range(100): sta = self.cpld.sta_read() lock = urukul_sta_pll_lock(sta) delay(1 * ms) if lock & (1 << self.chip_select - 4): break if i >= 100 - 1: raise ValueError("PLL lock timeout") delay(10 * us) # slack if self.sync_data.sync_delay_seed >= 0 and not blind: self.tune_sync_delay(self.sync_data.sync_delay_seed) delay(1 * ms)
def run(self): self.core.reset() self.core.break_realtime() response = 0xff self.spi_mmc.set_config(_SDCARD_SPI_CONFIG, 500*kHz, 500*kHz) self.spi_mmc.set_xfer(0, 8, 0) for i in range(10): self.spi_mmc.write(0xffffffff) delay(-5*us) delay(100*us) self.spi_mmc.set_xfer(1, 8, 0) self.spi_mmc.write(0x40000000) delay(-5*us) self.spi_mmc.write(0x00000000) delay(-5*us) self.spi_mmc.write(0x00000000) delay(-5*us) self.spi_mmc.write(0x00000000) delay(-5*us) self.spi_mmc.write(0x00000000) delay(-5*us) self.spi_mmc.write(0x95000000) delay(-5*us) self.spi_mmc.set_xfer(1, 0, 24) self.spi_mmc.write(0xffffffff) response = self.spi_mmc.read_sync() sd_response = False for i in range(3): if ((response >> 8*i) & 0x0000ff) == 0x01: sd_response = True break self.set_dataset("sd_response", sd_response)