Merge branch 'master' into new

pull/1212/head
Sebastien Bourdeauducq 2018-11-16 15:20:32 +08:00
commit 1b841805f6
12 changed files with 276 additions and 69 deletions

View File

@ -26,22 +26,22 @@ _AD9910_REG_CFR1 = 0x00
_AD9910_REG_CFR2 = 0x01
_AD9910_REG_CFR3 = 0x02
_AD9910_REG_AUX_DAC = 0x03
_AD9910_REG_IO_UPD = 0x04
_AD9910_REG_IO_UPDATE = 0x04
_AD9910_REG_FTW = 0x07
_AD9910_REG_POW = 0x08
_AD9910_REG_ASF = 0x09
_AD9910_REG_MSYNC = 0x0A
_AD9910_REG_DRAMPL = 0x0B
_AD9910_REG_DRAMPS = 0x0C
_AD9910_REG_DRAMPR = 0x0D
_AD9910_REG_PR0 = 0x0E
_AD9910_REG_PR1 = 0x0F
_AD9910_REG_PR2 = 0x10
_AD9910_REG_PR3 = 0x11
_AD9910_REG_PR4 = 0x12
_AD9910_REG_PR5 = 0x13
_AD9910_REG_PR6 = 0x14
_AD9910_REG_PR7 = 0x15
_AD9910_REG_SYNC = 0x0a
_AD9910_REG_RAMP_LIMIT = 0x0b
_AD9910_REG_RAMP_STEP = 0x0c
_AD9910_REG_RAMP_RATE = 0x0d
_AD9910_REG_PROFILE0 = 0x0e
_AD9910_REG_PROFILE1 = 0x0f
_AD9910_REG_PROFILE2 = 0x10
_AD9910_REG_PROFILE3 = 0x11
_AD9910_REG_PROFILE4 = 0x12
_AD9910_REG_PROFILE5 = 0x13
_AD9910_REG_PROFILE6 = 0x14
_AD9910_REG_PROFILE7 = 0x15
_AD9910_REG_RAM = 0x16
@ -256,7 +256,7 @@ class AD9910:
@kernel
def set_mu(self, ftw, pow=0, asf=0x3fff, phase_mode=_PHASE_MODE_DEFAULT,
ref_time=-1):
ref_time=-1, profile=0):
"""Set profile 0 data in machine units.
This uses machine units (FTW, POW, ASF). The frequency tuning word
@ -276,6 +276,7 @@ class AD9910:
by :meth:`set_phase_mode` for this call.
:param ref_time: Fiducial time used to compute absolute or tracking
phase updates. In machine units as obtained by `now_mu()`.
:param profile: Profile number to set (0-7, default: 0).
:return: Resulting phase offset word after application of phase
tracking offset. When using :const:`PHASE_MODE_CONTINUOUS` in
subsequent calls, use this value as the "current" phase.
@ -297,7 +298,7 @@ class AD9910:
# is equivalent to an output pipeline latency.
dt = int32(now_mu()) - int32(ref_time)
pow += dt*ftw*self.sysclk_per_mu >> 16
self.write64(_AD9910_REG_PR0, (asf << 16) | pow, ftw)
self.write64(_AD9910_REG_PROFILE0 + profile, (asf << 16) | pow, ftw)
delay_mu(int64(self.io_update_delay))
self.cpld.io_update.pulse_mu(8) # assumes 8 mu > t_SYSCLK
at_mu(now_mu() & ~0xf)
@ -332,7 +333,7 @@ class AD9910:
@kernel
def set(self, frequency, phase=0.0, amplitude=1.0,
phase_mode=_PHASE_MODE_DEFAULT, ref_time=-1):
phase_mode=_PHASE_MODE_DEFAULT, ref_time=-1, profile=0):
"""Set profile 0 data in SI units.
.. seealso:: :meth:`set_mu`
@ -342,11 +343,12 @@ class AD9910:
:param asf: Amplitude in units of full scale
:param phase_mode: Phase mode constant
:param ref_time: Fiducial time stamp in machine units
:param profile: Profile to affect
:return: Resulting phase offset in turns
"""
return self.pow_to_turns(self.set_mu(
self.frequency_to_ftw(frequency), self.turns_to_pow(phase),
self.amplitude_to_asf(amplitude), phase_mode, ref_time))
self.amplitude_to_asf(amplitude), phase_mode, ref_time, profile))
@kernel
def set_att_mu(self, att):
@ -389,7 +391,7 @@ class AD9910:
:param window: Symmetric SYNC_IN validation window (0-15) in
steps of ~75ps for both hold and setup margin.
"""
self.write32(_AD9910_REG_MSYNC,
self.write32(_AD9910_REG_SYNC,
(window << 28) | # SYNC S/H validation delay
(1 << 27) | # SYNC receiver enable
(0 << 26) | # SYNC generator disable
@ -466,17 +468,18 @@ class AD9910:
raise ValueError("no valid window/delay")
@kernel
def measure_io_update_alignment(self, io_up_delay):
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 RTIO time stamp.
(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`.
After scanning the alignment, an IO_UPDATE delay midway between two
edges should be chosen.
:return: odd/even SYNC_CLK cycle indicator
: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
# DRG ACC autoclear and LRR on io update
@ -484,19 +487,21 @@ class AD9910:
# DRG -> FTW, DRG enable
self.write32(_AD9910_REG_CFR2, 0x01090000)
# no limits
self.write64(_AD9910_REG_DRAMPL, -1, 0)
self.write64(_AD9910_REG_RAMP_LIMIT, -1, 0)
# DRCTL=0, dt=1 t_SYNC_CLK
self.write32(_AD9910_REG_DRAMPR, 0x00010000)
self.write32(_AD9910_REG_RAMP_RATE, 0x00010000)
# dFTW = 1, (work around negative slope)
self.write64(_AD9910_REG_DRAMPS, -1, 0)
at_mu(now_mu() + 0x10 & ~0xf) # align to RTIO/2
self.cpld.io_update.pulse_mu(8)
self.write64(_AD9910_REG_RAMP_STEP, -1, 0)
# delay io_update after RTIO/2 edge
t = now_mu() + 0x10 & ~0xf
at_mu(t + delay_start)
self.cpld.io_update.pulse_mu(32 - delay_start) # realign
# disable DRG autoclear and LRR on io_update
self.write32(_AD9910_REG_CFR1, 0x00000002)
# stop DRG
self.write64(_AD9910_REG_DRAMPS, 0, 0)
at_mu((now_mu() + 0x10 & ~0xf) + io_up_delay) # delay
self.cpld.io_update.pulse_mu(32 - io_up_delay) # realign
self.write64(_AD9910_REG_RAMP_STEP, 0, 0)
at_mu(t + 0x1000 + delay_stop)
self.cpld.io_update.pulse_mu(32 - delay_stop) # realign
ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW
delay(100*us) # slack
# disable DRG
@ -510,22 +515,35 @@ class AD9910:
Scan through increasing IO_UPDATE delays until a delay is found that
lets IO_UPDATE be registered in the next SYNC_CLK cycle. Return a
IO_UPDATE delay that is midway between two such SYNC_CLK transitions.
IO_UPDATE delay that is as far away from that SYNC_CLK edge
as possible.
This method assumes that the IO_UPDATE TTLOut device has one machine
unit resolution (SERDES) and that the ratio between fine RTIO frequency
(RTIO time machine units) and SYNC_CLK is 4.
unit resolution (SERDES).
:return: Stable IO_UPDATE delay to be passed to the constructor
:class:`AD9910` via the device database.
"""
period = 4 # f_RTIO/f_SYNC = 4
max_delay = 8 # mu, 1 ns
d0 = self.io_update_delay
t0 = int32(self.measure_io_update_alignment(d0))
for i in range(max_delay - 1):
t = self.measure_io_update_alignment(
(d0 + i + 1) & (max_delay - 1))
if t != t0:
return (d0 + i + period//2) & (period - 1)
period = self.sysclk_per_mu * 4 # SYNC_CLK period
repeat = 100
for i in range(period):
t = 0
# check whether the sync edge is strictly between i, i+2
for j in range(repeat):
t += self.measure_io_update_alignment(i, i + 2)
if t != 0: # no certain edge
continue
# check left/right half: i,i+1 and i+1,i+2
t1 = [0, 0]
for j in range(repeat):
t1[0] += self.measure_io_update_alignment(i, i + 1)
t1[1] += self.measure_io_update_alignment(i + 1, i + 2)
if ((t1[0] == 0 and t1[1] == 0) or
(t1[0] == repeat and t1[1] == repeat)):
# edge is not close to i + 1, can't interpret result
raise ValueError(
"no clear IO_UPDATE-SYNC_CLK alignment edge found")
else:
# the good delay is period//2 after the edge
return (i + 1 + period//2) & (period - 1)
raise ValueError("no IO_UPDATE-SYNC_CLK alignment edge found")

View File

@ -14,7 +14,8 @@ SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END |
# SPI clock write and read dividers
SPIT_CFG_WR = 2
SPIT_CFG_RD = 16
SPIT_ATT_WR = 2
# 30 MHz fmax, 20 ns setup, 40 ns shift to latch (limiting)
SPIT_ATT_WR = 6
SPIT_ATT_RD = 16
SPIT_DDS_WR = 2
SPIT_DDS_RD = 16
@ -174,7 +175,7 @@ class CPLD:
self.cfg_reg = urukul_cfg(rf_sw=rf_sw, led=0, profile=0,
io_update=0, mask_nu=0, clk_sel=clk_sel,
sync_sel=sync_sel, rst=0, io_rst=0)
self.att_reg = att
self.att_reg = int32(att)
self.sync_div = sync_div
@kernel
@ -299,7 +300,8 @@ class CPLD:
:param channel: Attenuator channel (0-3).
:param att: Attenuation setting in dB. Higher value is more
attenuation.
attenuation. Minimum attenuation is 0*dB, maximum attenuation is
31.5*dB.
"""
self.set_att_mu(channel, 255 - int32(round(att*8)))
@ -333,3 +335,15 @@ class CPLD:
ftw = ftw_max//div
assert ftw*div == ftw_max
self.sync.set_mu(ftw)
@kernel
def set_profile(self, profile):
"""Set the PROFILE pins.
The PROFILE pins are common to all four DDS channels.
:param profile: PROFILE pins in numeric representation (0-7).
"""
cfg = self.cfg_reg & ~(7 << CFG_PROFILE)
cfg |= (profile & 7) << CFG_PROFILE
self.cfg_write(cfg)

View File

@ -242,6 +242,12 @@ mod ddr {
ddrphy::dly_sel_write(1 << (DQS_SIGNAL_COUNT - n - 1));
ddrphy::rdly_dq_rst_write(1);
#[cfg(soc_platform = "kasli")]
{
for _ in 0..3 {
ddrphy::rdly_dq_bitslip_write(1);
}
}
for _ in 0..DDRPHY_MAX_DELAY {
let mut working = true;
@ -327,6 +333,12 @@ mod ddr {
let mut max_seen_valid = 0;
ddrphy::rdly_dq_rst_write(1);
#[cfg(soc_platform = "kasli")]
{
for _ in 0..3 {
ddrphy::rdly_dq_bitslip_write(1);
}
}
for delay in 0..DDRPHY_MAX_DELAY {
let mut valid = true;
@ -384,6 +396,12 @@ mod ddr {
// Set delay to the middle
ddrphy::rdly_dq_rst_write(1);
#[cfg(soc_platform = "kasli")]
{
for _ in 0..3 {
ddrphy::rdly_dq_bitslip_write(1);
}
}
for _ in 0..mean_delay {
ddrphy::rdly_dq_inc_write(1);
}

View File

@ -541,7 +541,7 @@ class SUServo(_EEM):
target.submodules += phy
target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4))
pads = target.platform.request("{}_dds_reset".format(eem_urukul1))
pads = target.platform.request("{}_dds_reset_sync_in".format(eem_urukul1))
target.specials += DifferentialOutput(0, pads.p, pads.n)
for i, signal in enumerate("sw0 sw1 sw2 sw3".split()):

View File

@ -275,6 +275,78 @@ class _RangeScan(LayoutWidget):
randomize.setChecked(state["randomize"])
class _CenterScan(LayoutWidget):
def __init__(self, procdesc, state):
LayoutWidget.__init__(self)
scale = procdesc["scale"]
def apply_properties(widget):
widget.setDecimals(procdesc["ndecimals"])
if procdesc["global_min"] is not None:
widget.setMinimum(procdesc["global_min"]/scale)
else:
widget.setMinimum(float("-inf"))
if procdesc["global_max"] is not None:
widget.setMaximum(procdesc["global_max"]/scale)
else:
widget.setMaximum(float("inf"))
if procdesc["global_step"] is not None:
widget.setSingleStep(procdesc["global_step"]/scale)
if procdesc["unit"]:
widget.setSuffix(" " + procdesc["unit"])
center = ScientificSpinBox()
disable_scroll_wheel(center)
apply_properties(center)
center.setPrecision()
center.setRelativeStep()
center.setValue(state["center"])
self.addWidget(center, 0, 1)
self.addWidget(QtWidgets.QLabel("Center:"), 0, 0)
span = ScientificSpinBox()
disable_scroll_wheel(span)
apply_properties(span)
span.setPrecision()
span.setRelativeStep()
span.setMinimum(0)
span.setValue(state["span"])
self.addWidget(span, 1, 1)
self.addWidget(QtWidgets.QLabel("Span:"), 1, 0)
step = ScientificSpinBox()
disable_scroll_wheel(step)
apply_properties(step)
step.setPrecision()
step.setRelativeStep()
step.setMinimum(0)
step.setValue(state["step"])
self.addWidget(step, 2, 1)
self.addWidget(QtWidgets.QLabel("Step:"), 2, 0)
randomize = QtWidgets.QCheckBox("Randomize")
self.addWidget(randomize, 3, 1)
randomize.setChecked(state["randomize"])
def update_center(value):
state["center"] = value*scale
def update_span(value):
state["span"] = value*scale
def update_step(value):
state["step"] = value*scale
def update_randomize(value):
state["randomize"] = value
center.valueChanged.connect(update_center)
span.valueChanged.connect(update_span)
step.valueChanged.connect(update_step)
randomize.stateChanged.connect(update_randomize)
class _ExplicitScan(LayoutWidget):
def __init__(self, state):
LayoutWidget.__init__(self)
@ -307,6 +379,7 @@ class ScanEntry(LayoutWidget):
self.widgets = OrderedDict()
self.widgets["NoScan"] = _NoScan(procdesc, state["NoScan"])
self.widgets["RangeScan"] = _RangeScan(procdesc, state["RangeScan"])
self.widgets["CenterScan"] = _CenterScan(procdesc, state["CenterScan"])
self.widgets["ExplicitScan"] = _ExplicitScan(state["ExplicitScan"])
for widget in self.widgets.values():
self.stack.addWidget(widget)
@ -314,6 +387,7 @@ class ScanEntry(LayoutWidget):
self.radiobuttons = OrderedDict()
self.radiobuttons["NoScan"] = QtWidgets.QRadioButton("No scan")
self.radiobuttons["RangeScan"] = QtWidgets.QRadioButton("Range")
self.radiobuttons["CenterScan"] = QtWidgets.QRadioButton("Center")
self.radiobuttons["ExplicitScan"] = QtWidgets.QRadioButton("Explicit")
scan_type = QtWidgets.QButtonGroup()
for n, b in enumerate(self.radiobuttons.values()):
@ -343,6 +417,8 @@ class ScanEntry(LayoutWidget):
"NoScan": {"value": 0.0, "repetitions": 1},
"RangeScan": {"start": 0.0, "stop": 100.0*scale, "npoints": 10,
"randomize": False},
"CenterScan": {"center": 0.*scale, "span": 100.*scale,
"step": 10.*scale, "randomize": False},
"ExplicitScan": {"sequence": []}
}
if "default" in procdesc:
@ -361,6 +437,9 @@ class ScanEntry(LayoutWidget):
state[ty]["npoints"] = default["npoints"]
state[ty]["randomize"] = default["randomize"]
state[ty]["seed"] = default["seed"]
elif ty == "CenterScan":
for key in "center span step randomize seed".split():
state[ty][key] = default[key]
elif ty == "ExplicitScan":
state[ty]["sequence"] = default["sequence"]
else:

View File

@ -27,7 +27,7 @@ from artiq.language import units
__all__ = ["ScanObject",
"NoScan", "RangeScan", "ExplicitScan",
"NoScan", "RangeScan", "CenterScan", "ExplicitScan",
"Scannable", "MultiScanManager"]
@ -93,6 +93,43 @@ class RangeScan(ScanObject):
"seed": self.seed}
class CenterScan(ScanObject):
"""A scan object that yields evenly spaced values within a span around a
center. If ``step`` is finite, then ``center`` is always included.
Values outside ``span`` around center are never included.
If ``randomize`` is True the points are randomly ordered."""
def __init__(self, center, span, step, randomize=False, seed=None):
self.center = center
self.span = span
self.step = step
self.randomize = randomize
self.seed = seed
if step == 0.:
self.sequence = []
else:
n = 1 + int(span/(2.*step))
self.sequence = [center + sign*i*step
for i in range(n) for sign in [-1, 1]][1:]
if randomize:
rng = random.Random(seed)
random.shuffle(self.sequence, rng.random)
def __iter__(self):
return iter(self.sequence)
def __len__(self):
return len(self.sequence)
def describe(self):
return {"ty": "CenterScan",
"center": self.center, "step": self.step,
"span": self.span,
"randomize": self.randomize,
"seed": self.seed}
class ExplicitScan(ScanObject):
"""A scan object that yields values from an explicitly defined sequence."""
def __init__(self, sequence):
@ -111,6 +148,7 @@ class ExplicitScan(ScanObject):
_ty_to_scan = {
"NoScan": NoScan,
"RangeScan": RangeScan,
"CenterScan": CenterScan,
"ExplicitScan": ExplicitScan
}

View File

@ -104,18 +104,21 @@ class AD9910Exp(EnvExperiment):
self.core.break_realtime()
self.dev.cpld.init()
self.dev.init()
bins = [0]*8
self.scan_io_delay(bins)
self.set_dataset("bins", bins)
self.set_dataset("dly", self.dev.io_update_delay)
bins1 = [0]*4
bins2 = [0]*4
self.scan_io_delay(bins1, bins2)
self.set_dataset("bins1", bins1)
self.set_dataset("bins2", bins2)
self.set_dataset("dly", self.dev.tune_io_update_delay())
@kernel
def scan_io_delay(self, bins):
def scan_io_delay(self, bins1, bins2):
delay(100*us)
n = 100
for i in range(n):
for phase in range(len(bins)):
bins[phase] += self.dev.measure_io_update_alignment(phase)
for j in range(len(bins1)):
bins1[j] += self.dev.measure_io_update_alignment(j, j + 1)
bins2[j] += self.dev.measure_io_update_alignment(j, j + 2)
delay(10*ms)
@kernel
@ -131,6 +134,24 @@ class AD9910Exp(EnvExperiment):
sw_off = (self.dev.cpld.sta_read() >> (self.dev.chip_select - 4)) & 1
self.set_dataset("sw", (sw_on, sw_off))
@kernel
def profile_readback(self):
self.core.break_realtime()
self.dev.cpld.init()
self.dev.init()
for i in range(8):
self.dev.set_mu(ftw=i, profile=i)
ftw = [0] * 8
for i in range(8):
self.dev.cpld.set_profile(i)
# If PROFILE is not alligned to SYNC_CLK a multi-bit change
# doesn't transfer cleanly. Use IO_UPDATE to load the profile
# again.
self.dev.cpld.io_update.pulse_mu(8)
ftw[i] = self.dev.read32(_AD9910_REG_FTW)
delay(100*us)
self.set_dataset("ftw", ftw)
class AD9910Test(ExperimentCase):
def test_instantiate(self):
@ -174,13 +195,19 @@ class AD9910Test(ExperimentCase):
def test_io_update_delay(self):
self.execute(AD9910Exp, "io_update_delay")
dly = self.dataset_mgr.get("dly")
bins = self.dataset_mgr.get("bins")
print(dly, bins)
n = max(bins)
# test for 4-periodicity (SYNC_CLK) and maximal contrast
for i in range(len(bins)):
self.assertEqual(abs(bins[i] - bins[(i + 4) % 8]), n)
bins1 = self.dataset_mgr.get("bins1")
bins2 = self.dataset_mgr.get("bins2")
print(dly, bins1, bins2)
n = max(bins2)
# no edge at optimal delay
self.assertEqual(bins2[(dly + 1) & 3], 0)
# edge at expected position
self.assertEqual(bins2[(dly + 3) & 3], n)
def test_sw_readback(self):
self.execute(AD9910Exp, "sw_readback")
self.assertEqual(self.dataset_mgr.get("sw"), (1, 0))
def test_profile_readback(self):
self.execute(AD9910Exp, "profile_readback")
self.assertEqual(self.dataset_mgr.get("ftw"), list(range(8)))

View File

@ -98,6 +98,13 @@ class UrukulExp(EnvExperiment):
self.dev.init()
self.dev.set_sync_div(2)
@kernel
def profile(self):
self.core.break_realtime()
self.dev.init()
self.dev.set_profile(7)
self.dev.set_profile(0)
class UrukulTest(ExperimentCase):
def test_instantiate(self):
@ -147,3 +154,6 @@ class UrukulTest(ExperimentCase):
def test_sync(self):
self.execute(UrukulExp, "sync")
def test_profile(self):
self.execute(UrukulExp, "profile")

View File

@ -15,7 +15,7 @@ requirements:
- python >=3.5.3,<3.6
- setuptools 33.1.1
- migen 0.8 py35_0+git2d62c0c
- misoc 0.11 py35_31+git5ce139dd
- misoc 0.11 py35_33+git128750aa
- jesd204b 0.10
- microscope
- binutils-or1k-linux >=2.27

View File

@ -46,4 +46,6 @@ Default network ports
+---------------------------------+--------------+
| TOPTICA Laser SDK (out-of-tree) | 3272 |
+---------------------------------+--------------+
| HighFinesse (out-of-tree) | 3273 |
+---------------------------------+--------------+

View File

@ -131,7 +131,8 @@ On Windows, a third-party tool, `Zadig <http://zadig.akeo.ie/>`_, is necessary.
1. Make sure the FPGA board's JTAG USB port is connected to your computer.
2. Activate Options → List All Devices.
3. Select the "Digilent Adept USB Device (Interface 0)" device from the drop-down list.
3. Select the "Digilent Adept USB Device (Interface 0)" or "FTDI Quad-RS232 HS" (or similar)
device from the drop-down list.
4. Select WinUSB from the spinner list.
5. Click "Install Driver" or "Replace Driver".

View File

@ -1,14 +1,14 @@
{ stdenv, fetchgit, llvm-or1k, makeWrapper, python3, ncurses, zlib, python3Packages }:
{ stdenv, fetchFromGitHub, llvm-or1k, makeWrapper, python3, ncurses, zlib, python3Packages }:
let
version = "0f4ebae";
in
stdenv.mkDerivation rec {
name = "llvmlite-${version}";
src = fetchgit {
url = "https://github.com/m-labs/llvmlite";
src = fetchFromGitHub {
rev = "401dfb713166bdd2bc0d3ab2b7ebf12e7a434130";
sha256 = "1ci1pnpspv1pqz712yix1nmplq7568vpsr6gzzl3a33w9s0sw2nq";
leaveDotGit = true;
owner = "m-labs";
repo = "llvmlite";
sha256 = "1hqahd87ihwgjsaxv0y2iywldi1zgyjxjfy3sy3rr1gnwvxb47xw";
};
buildInputs = [ makeWrapper python3 ncurses zlib llvm-or1k python3Packages.setuptools ];