suservo: lots of gateware/ runtime changes

tested/validated:

* servo enable/disable
* dds interface, timing, io_update, mask_nu
* channel control (en_out, en_iir, profile)
* profile configuration (coefficients, delays, offsets, channel)
* adc timings and waveforms measured
* asf state readback
* adc readback

individual changes below:

suservo: correct rtio readback

suservo: example, device_db [wip]

suservo: change rtio channel layout

suservo: mem ports in rio domain

suservo: sck clocked from rio_phy

suservo: cleanup, straighten out timing

suservo: dds cs polarity

suservo: simplify pipeline

suservo: drop unused eem names

suservo: decouple adc SR from IIR

suservo: expand coredevice layer

suservo: start the correct stage

suservo: actually load ctrl

suservo: refactor/tweak adc timing

suservo: implement cpld and dds init
This commit is contained in:
Robert Jördens 2018-04-25 21:26:07 +00:00 committed by Robert Jordens
parent 01f762a8f5
commit 307cd07b9d
13 changed files with 288 additions and 145 deletions

132
artiq/coredevice/suservo.py Normal file
View File

@ -0,0 +1,132 @@
from artiq.language.core import kernel, delay, portable, now_mu
from artiq.language.units import us, ms
from artiq.coredevice.rtio import rtio_output, rtio_input_data
from numpy import int32, int64
from artiq.coredevice import spi2 as spi
from artiq.coredevice import urukul, sampler
COEFF_WIDTH = 18
COEFF_DEPTH = 10 + 1
WE = 1 << COEFF_DEPTH + 1
STATE_SEL = 1 << COEFF_DEPTH
CONFIG_SEL = 1 << COEFF_DEPTH - 1
CONFIG_ADDR = CONFIG_SEL | STATE_SEL
class SUServo:
kernel_invariants = {"channel", "core", "pgia", "cpld0", "cpld1",
"dds0", "dds1", "ref_period_mu"}
def __init__(self, dmgr, channel, pgia_device,
cpld0_device, cpld1_device,
dds0_device, dds1_device,
core_device="core"):
self.core = dmgr.get(core_device)
self.pgia = dmgr.get(pgia_device)
self.dds0 = dmgr.get(dds0_device)
self.dds1 = dmgr.get(dds1_device)
self.cpld0 = dmgr.get(cpld0_device)
self.cpld1 = dmgr.get(cpld1_device)
self.channel = channel
self.gains = 0x0000
self.ref_period_mu = self.core.seconds_to_mu(
self.core.coarse_ref_period)
assert self.ref_period_mu == self.core.ref_multiplier
@kernel
def init(self):
self.set_config(0)
delay(2*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)
@kernel
def write(self, addr, value):
rtio_output(now_mu(), self.channel, addr | WE, value)
delay_mu(self.ref_period_mu)
@kernel
def read(self, addr):
rtio_output(now_mu(), self.channel, addr, 0)
return rtio_input_data(self.channel)
@kernel
def set_config(self, start):
self.write(CONFIG_ADDR, start)
@kernel
def get_status(self):
return self.read(CONFIG_ADDR)
@kernel
def get_adc_mu(self, adc):
return self.read(STATE_SEL | (adc << 1) | (1 << 8))
@kernel
def set_gain_mu(self, channel, gain):
"""Set instrumentation amplifier gain of a channel.
The four gain settings (0, 1, 2, 3) corresponds to gains of
(1, 10, 100, 1000) respectively.
:param channel: Channel index
:param gain: Gain setting
"""
gains = self.gains
gains &= ~(0b11 << (channel*2))
gains |= gain << (channel*2)
self.pgia.write(gains << 16)
self.gains = gains
class Channel:
kernel_invariants = {"channel", "core", "servo", "servo_channel"}
def __init__(self, dmgr, channel, servo_device,
core_device="core"):
self.core = dmgr.get(core_device)
self.servo = dmgr.get(servo_device)
self.channel = channel
self.servo_channel = self.channel + 8 - self.servo.channel # FIXME
@kernel
def set(self, en_out, en_iir=0, profile=0):
rtio_output(now_mu(), self.channel, 0,
en_out | (en_iir << 1) | (profile << 2))
@kernel
def set_profile_mu(self, profile, ftw, adc, offset,
a1, b0, b1, delay, pow=0):
base = (self.servo_channel << 8) | (profile << 3)
data = [ftw >> 16, b1, pow, adc | (delay << 8), offset, a1, ftw, b0]
for i in range(8):
self.servo.write(base + i, data[i])
@kernel
def get_profile_mu(self, profile, data):
base = (self.servo_channel << 8) | (profile << 3)
for i in range(8):
data[i] = self.servo.read(base + i)
delay(2*us)
@kernel
def get_asf_mu(self, profile):
return self.servo.read(STATE_SEL | (self.servo_channel << 5) | profile)

View File

@ -1,4 +1,4 @@
core_addr = "lauda.ber.quartiq.de"
core_addr = "10.0.16.119"
device_db = {
"core": {
@ -141,9 +141,11 @@ device_db = {
"class": "SUServo",
"arguments": {
"channel": 24,
"sampler_pgia_device": "spi_sampler0_pgia",
"urukul0_device": "urukul0_cpld",
"urukul1_device": "urukul1_cpld"
"pgia_device": "spi_sampler0_pgia",
"cpld0_device": "urukul0_cpld",
"cpld1_device": "urukul1_cpld",
"dds0_device": "urukul0_dds",
"dds1_device": "urukul1_dds"
}
},
@ -215,10 +217,20 @@ device_db = {
"class": "CPLD",
"arguments": {
"spi_device": "spi_urukul0",
"refclk": 125e6,
"refclk": 100e6,
"clk_sel": 0
}
},
"urukul0_dds": {
"type": "local",
"module": "artiq.coredevice.ad9910",
"class": "AD9910",
"arguments": {
"pll_n": 40,
"chip_select": 3,
"cpld_device": "urukul0_cpld",
}
},
"spi_urukul1": {
"type": "local",
@ -232,10 +244,20 @@ device_db = {
"class": "CPLD",
"arguments": {
"spi_device": "spi_urukul1",
"refclk": 125e6,
"refclk": 100e6,
"clk_sel": 0
}
},
"urukul1_dds": {
"type": "local",
"module": "artiq.coredevice.ad9910",
"class": "AD9910",
"arguments": {
"pll_n": 40,
"chip_select": 3,
"cpld_device": "urukul1_cpld",
}
},
"led0": {
"type": "local",

View File

@ -1,35 +0,0 @@
from artiq.experiment import *
class Sampler(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("sampler0")
def run(self):
self.data = []
self.sample()
for d in self.data:
print(d)
@kernel
def sample(self):
self.core.break_realtime()
self.sampler0.init()
for g in range(4):
for ch in range(8):
self.sampler0.set_gain_mu(ch, g)
self.ret([self.sampler0.get_gains_mu()])
delay(10*ms)
raw = [0] * 8
self.sampler0.sample_mu(raw)
self.ret(raw)
delay(10*ms)
data = [0.] * 8
self.sampler0.sample(data)
self.ret(data)
delay(10*ms)
@rpc(flags={"async"})
def ret(self, data):
self.data.append(data)

View File

@ -0,0 +1,33 @@
from artiq.experiment import *
class SUServo(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("led0")
self.setattr_device("suservo0")
self.setattr_device("suservo0_ch0")
def run(self):
# self.led()
self.init()
@kernel
def init(self):
self.core.break_realtime()
self.core.reset()
self.suservo0.init()
self.suservo0.set_config(1)
print(self.suservo0.get_status())
delay(3*ms)
self.suservo0.set_config(0)
delay(3*ms)
print(self.suservo0.get_status())
@kernel
def led(self):
self.core.break_realtime()
for i in range(10):
self.led0.pulse(.1*s)
delay(.1*s)

View File

@ -7,18 +7,19 @@ class RTServoCtrl(Module):
"""Per channel RTIO control interface"""
def __init__(self, ctrl):
self.rtlink = rtlink.Interface(
rtlink.OInterface(len(ctrl)))
rtlink.OInterface(len(ctrl.profile) + 2))
# # #
self.sync.rio += [
If(self.rtlink.o.stb,
Cat(ctrl.profile, ctrl.en_out, ctrl.en_iir).eq(
self.rtlink.o.data),
)
]
self.comb += [
ctrl.stb.eq(self.rtlink.o.stb)
ctrl.stb.eq(self.rtlink.o.stb),
self.rtlink.o.busy.eq(0)
]
self.sync.rio_phy += [
If(self.rtlink.o.stb,
Cat(ctrl.en_out, ctrl.en_iir, ctrl.profile).eq(
self.rtlink.o.data)
)
]
@ -26,10 +27,11 @@ class RTServoMem(Module):
"""All-channel all-profile coefficient and state RTIO control
interface."""
def __init__(self, w, servo):
m_coeff = servo.m_coeff.get_port(write_capable=True,
we_granularity=w.coeff)
m_coeff = servo.iir.m_coeff.get_port(write_capable=True,
we_granularity=w.coeff, clock_domain="rio")
assert len(m_coeff.we) == 2
m_state = servo.m_state.get_port(write_capable=True)
m_state = servo.iir.m_state.get_port(write_capable=True,
clock_domain="rio")
self.specials += m_state, m_coeff
# just expose the w.coeff (18) MSBs of state
@ -52,8 +54,8 @@ class RTServoMem(Module):
# # #
config = Signal(1, reset=0)
status = Signal(2)
config = Signal(w.coeff, reset=0)
status = Signal(w.coeff)
self.comb += [
Cat(servo.start).eq(config),
status.eq(Cat(servo.start, servo.done))
@ -87,16 +89,18 @@ class RTServoMem(Module):
m_state.we.eq(self.rtlink.o.stb & we & state_sel & ~config_sel),
]
read = Signal()
read_sel = Signal()
read_state = Signal()
read_high = Signal()
read_config = Signal()
self.sync.rio += [
If(read,
read.eq(0)
),
If(self.rtlink.o.stb,
read.eq(~we),
read_sel.eq(state_sel),
read_state.eq(state_sel),
read_high.eq(high_coeff),
read_config.eq(config_sel),
)
]
self.sync.rio_phy += [
@ -107,8 +111,8 @@ class RTServoMem(Module):
self.comb += [
self.rtlink.i.stb.eq(read),
self.rtlink.i.data.eq(
Mux(state_sel,
Mux(config_sel,
Mux(read_state,
Mux(read_config,
status,
m_state.dat_r[w.state - w.coeff:]),
Mux(read_high,

View File

@ -55,13 +55,13 @@ class ADC(Module):
assert p.lanes*t_read == p.width*p.channels
assert all(_ > 0 for _ in (p.t_cnvh, p.t_conv, p.t_rtt))
assert p.t_conv > 1
count = Signal(max=max(p.t_cnvh, p.t_conv - 1, t_read, p.t_rtt + 1) - 1,
count = Signal(max=max(p.t_cnvh, p.t_conv, t_read, p.t_rtt),
reset_less=True)
count_load = Signal.like(count)
count_done = Signal()
self.comb += [
count_done.eq(count == 0),
]
update = Signal()
self.comb += count_done.eq(count == 0)
self.sync += [
count.eq(count - 1),
If(count_done,
@ -78,7 +78,7 @@ class ADC(Module):
)
)
fsm.act("CNVH",
count_load.eq(p.t_conv - 2), # account for sck ODDR delay
count_load.eq(p.t_conv - 1),
pads.cnv.eq(1),
If(count_done,
NextState("CONV")
@ -92,7 +92,7 @@ class ADC(Module):
)
fsm.act("READ",
self.reading.eq(1),
count_load.eq(p.t_rtt), # again account for sck ODDR delay
count_load.eq(p.t_rtt - 1),
pads.sck_en.eq(1),
If(count_done,
NextState("RTT")
@ -101,6 +101,7 @@ class ADC(Module):
fsm.act("RTT", # account for sck->clkout round trip time
self.reading.eq(1),
If(count_done,
update.eq(1),
NextState("IDLE")
)
)
@ -111,10 +112,7 @@ class ADC(Module):
sck_en_ret = 1
self.clock_domains.cd_ret = ClockDomain("ret", reset_less=True)
self.comb += [
# falling clkout makes two bits available
self.cd_ret.clk.eq(pads.clkout)
]
self.comb += self.cd_ret.clk.eq(pads.clkout)
k = p.channels//p.lanes
assert t_read == k*p.width
@ -126,7 +124,9 @@ class ADC(Module):
sdo_sr[0].eq(sdo),
)
]
self.comb += [
Cat(reversed([self.data[i*k + j] for j in range(k)])
).eq(sdo_sr)
self.sync += [
If(update,
Cat(reversed([self.data[i*k + j] for j in range(k)])
).eq(sdo_sr)
)
]

View File

@ -36,13 +36,8 @@ class DDS(spi.SPISimple):
)
]
io_update = pads.io_update
# this assumes that the cycle time (1/125 MHz = 8 ns) is >1 SYNC_CLK
# cycle (1/250 MHz = 4ns)
done = Signal()
self.sync += [
done.eq(self.done)
]
self.comb += [
io_update.eq(self.done & ~done)
]
done_old = Signal()
self.sync += done_old.eq(self.done)
self.comb += pads.io_update.eq(self.done & ~done_old)

View File

@ -3,17 +3,17 @@ from migen.genlib.io import DifferentialOutput, DifferentialInput, DDROutput
class SamplerPads(Module):
def __init__(self, platform, eem0, eem1):
def __init__(self, platform, eem):
self.sck_en = Signal()
self.cnv = Signal()
self.clkout = Signal()
spip = platform.request("{}_adc_spi_p".format(eem0))
spin = platform.request("{}_adc_spi_n".format(eem0))
cnv = platform.request("{}_cnv".format(eem0))
sdr = platform.request("{}_sdr".format(eem0))
dp = platform.request("{}_adc_data_p".format(eem0))
dn = platform.request("{}_adc_data_n".format(eem0))
spip = platform.request("{}_adc_spi_p".format(eem))
spin = platform.request("{}_adc_spi_n".format(eem))
cnv = platform.request("{}_cnv".format(eem))
sdr = platform.request("{}_sdr".format(eem))
dp = platform.request("{}_adc_data_p".format(eem))
dn = platform.request("{}_adc_data_n".format(eem))
clkout_se = Signal()
sck = Signal()
@ -21,7 +21,7 @@ class SamplerPads(Module):
self.specials += [
DifferentialOutput(self.cnv, cnv.p, cnv.n),
DifferentialOutput(1, sdr.p, sdr.n),
DDROutput(self.sck_en, 0, sck),
DDROutput(0, self.sck_en, sck, ClockSignal("rio_phy")),
DifferentialOutput(sck, spip.clk, spin.clk),
DifferentialInput(dp.clkout, dn.clkout, clkout_se),
Instance("BUFR", i_I=clkout_se, o_O=self.clkout)
@ -52,17 +52,17 @@ class SamplerPads(Module):
class UrukulPads(Module):
def __init__(self, platform, eem00, eem01, eem10, eem11):
def __init__(self, platform, eem0, eem1):
spip, spin = [[
platform.request("{}_qspi_{}".format(eem, pol), 0)
for eem in (eem00, eem10)] for pol in "pn"]
for eem in (eem0, eem1)] for pol in "pn"]
ioup = [platform.request("{}_io_update".format(eem), 0)
for eem in (eem00, eem10)]
for eem in (eem0, eem1)]
self.cs_n = Signal()
self.clk = Signal()
self.io_update = Signal()
self.specials += [(
DifferentialOutput(self.cs_n, spip[i].cs_n, spin[i].cs_n),
DifferentialOutput(~self.cs_n, spip[i].cs, spin[i].cs),
DifferentialOutput(self.clk, spip[i].clk, spin[i].clk),
DifferentialOutput(self.io_update, ioup[i].p, ioup[i].n))
for i in range(2)]

View File

@ -24,38 +24,37 @@ class Servo(Module):
assert t_iir + (2 << iir_p.channel) < t_cycle, "need shifting time"
self.start = Signal()
t_restart = t_cycle - t_adc
t_restart = t_cycle - t_adc + 1
assert t_restart > 0
cnt = Signal(max=t_restart + 1)
cnt = Signal(max=t_restart)
cnt_done = Signal()
token = Signal(2)
active = Signal(3)
self.done = Signal()
iir_done = Signal()
self.comb += [
cnt_done.eq(cnt == 0),
iir_done.eq(self.iir.shifting | self.iir.done),
self.adc.start.eq(self.start & cnt_done),
self.iir.start.eq(token[0] & self.adc.done),
self.dds.start.eq(token[1] & iir_done),
self.done.eq(self.dds.done),
]
self.sync += [
If(self.adc.done & ~cnt_done,
cnt.eq(cnt - 1),
If(self.dds.done,
active[2].eq(0)
),
If(self.adc.start,
cnt.eq(t_restart),
If(self.dds.start & self.dds.done,
active[2].eq(1),
active[1].eq(0)
),
If(self.adc.done,
token[0].eq(0)
If(self.iir.start & self.iir.done,
active[1].eq(1),
active[0].eq(0)
),
If(self.adc.start,
token[0].eq(1)
If(~cnt_done & self.adc.done,
cnt.eq(cnt - 1)
),
If(iir_done,
token[1].eq(0)
),
If(self.iir.start,
token[1].eq(1)
If(self.adc.start & self.adc.done,
active[0].eq(1),
cnt.eq(t_restart - 1)
)
]
self.comb += [
cnt_done.eq(cnt == 0),
self.adc.start.eq(self.start & cnt_done),
self.iir.start.eq(active[0] & self.adc.done),
self.dds.start.eq(active[1] &
(self.iir.shifting | self.iir.done)),
self.done.eq(self.dds.done),
]

View File

@ -37,8 +37,6 @@ class SPISimple(Module):
assert p.clk >= 1
cs_n = pads.cs_n
clk = pads.clk
cnt = Signal(max=max(2, p.clk), reset_less=True)
cnt_done = Signal()
cnt_next = Signal()
@ -54,21 +52,17 @@ class SPISimple(Module):
]
for i, d in enumerate(self.data):
self.comb += [
getattr(pads, "mosi{}".format(i)).eq(d[-1])
]
self.comb += getattr(pads, "mosi{}".format(i)).eq(d[-1])
bits = Signal(max=p.width + 1, reset_less=True)
self.submodules.fsm = fsm = CEInserter()(FSM("IDLE"))
self.comb += [
fsm.ce.eq(cnt_done)
]
self.comb += fsm.ce.eq(cnt_done)
fsm.act("IDLE",
self.done.eq(1),
cs_n.eq(1),
pads.cs_n.eq(1),
If(self.start,
cnt_next.eq(1),
NextState("SETUP")
@ -84,7 +78,7 @@ class SPISimple(Module):
)
fsm.act("HOLD",
cnt_next.eq(1),
clk.eq(1),
pads.clk.eq(1),
NextState("SETUP")
)

View File

@ -224,7 +224,7 @@ def _sampler(eem, eem_aux=None):
Subsignal("sdod", Pins(_eem_pin(eem_aux, 4, "n"))),
IOStandard("LVDS_25"),
),
]
]
return ios
@ -362,7 +362,7 @@ def _urukul_qspi(eem0, eem1):
))
ios += [
("{}_qspi_p".format(eem0), 0,
Subsignal("cs_n", Pins(_eem_pin(eem0, 5, "p"))),
Subsignal("cs", Pins(_eem_pin(eem0, 5, "p"))),
Subsignal("clk", Pins(_eem_pin(eem0, 2, "p"))),
Subsignal("mosi0", Pins(_eem_pin(eem1, 0, "p"))),
Subsignal("mosi1", Pins(_eem_pin(eem1, 1, "p"))),
@ -371,7 +371,7 @@ def _urukul_qspi(eem0, eem1):
IOStandard("LVDS_25"),
),
("{}_qspi_n".format(eem0), 0,
Subsignal("cs_n", Pins(_eem_pin(eem0, 5, "n"))),
Subsignal("cs", Pins(_eem_pin(eem0, 5, "n"))),
Subsignal("clk", Pins(_eem_pin(eem0, 2, "n"))),
Subsignal("mosi0", Pins(_eem_pin(eem1, 0, "n"))),
Subsignal("mosi1", Pins(_eem_pin(eem1, 1, "n"))),
@ -525,25 +525,25 @@ class SUServo(_StandaloneBase):
rtio_channels.append(rtio.Channel.from_phy(phy))
# EEM3, EEM2: Sampler
sampler_pads = servo_pads.SamplerPads(self.platform, "eem3", "eem2")
sampler_pads = servo_pads.SamplerPads(self.platform, "eem3")
# EEM5, EEM4 and EEM7, EEM6: Urukul
urukul_pads = servo_pads.UrukulPads(self.platform,
"eem5", "eem4", "eem7", "eem6")
adc_p = servo.ADCParams(width=16, channels=8, lanes=4,
t_cnvh=4, t_conv=57, t_rtt=4)
iir_p = servo.IIRWidths(state=25, coeff=18, adc=16,
asf=14, word=16, accu=48, shift=11,
channel=3, profile=5)
"eem5", "eem7")
adc_p = servo.ADCParams(width=16, channels=8, lanes=4, t_cnvh=4,
# account for SCK pipeline latency
t_conv=57 - 4, t_rtt=4 + 4)
iir_p = servo.IIRWidths(state=25, coeff=18, adc=16, asf=14, word=16,
accu=48, shift=11, channel=3, profile=5)
dds_p = servo.DDSParams(width=8 + 32 + 16 + 16,
channels=adc_p.channels, clk=1)
su = servo.Servo(sampler_pads, urukul_pads, adc_p, iir_p, dds_p)
su = ClockDomainsRenamer({"sys": "rio_phy"})(su)
su = ClockDomainsRenamer("rio_phy")(su)
self.submodules += sampler_pads, urukul_pads, su
ctrls = [rtservo.RTServoCtrl(ctrl) for ctrl in su.iir.ctrl]
self.submodules += ctrls
rtio_channels.extend(rtio.Channel.from_phy(ctrl) for ctrl in ctrls)
mem = rtservo.RTServoMem(iir_p, su.iir)
mem = rtservo.RTServoMem(iir_p, su)
self.submodules += mem
rtio_channels.append(rtio.Channel.from_phy(mem, ififo_depth=4))

View File

@ -37,7 +37,7 @@ class TB(Module):
sr = Signal(p.width*p.channels//p.lanes, reset_less=True)
srs.append(sr)
self.sync.adc += [
sdo.eq(self._dly(sr[-1], -1)),
sdo.eq(self._dly(sr[-1], 0)),
If(adc_sck_en,
sr[1:].eq(sr)
)
@ -52,10 +52,10 @@ class TB(Module):
adc_clk_rec = Signal()
self.comb += [
adc_sck_en.eq(self._dly(self.sck_en, 1)),
adc_sck_en.eq(self._dly(self.sck_en, 0)),
self.sck_en_ret.eq(self._dly(adc_sck_en)),
adc_clk_rec.eq(self._dly(self.sck, 1)),
adc_clk_rec.eq(self._dly(self.sck, 0)),
self.clkout.eq(self._dly(adc_clk_rec)),
]

View File

@ -11,10 +11,9 @@ from artiq.gateware.suservo import servo
class ServoSim(servo.Servo):
def __init__(self):
adc_p = servo.ADCParams(width=16, channels=8, lanes=4,
t_cnvh=4, t_conv=57, t_rtt=4)
iir_p = servo.IIRWidths(state=25, coeff=18, adc=16,
asf=14, word=16, accu=48, shift=11,
channel=3, profile=5)
t_cnvh=4, t_conv=57 - 4, t_rtt=4 + 4)
iir_p = servo.IIRWidths(state=25, coeff=18, adc=16, asf=14, word=16,
accu=48, shift=11, channel=3, profile=5)
dds_p = servo.DDSParams(width=8 + 32 + 16 + 16,
channels=adc_p.channels, clk=1)