forked from M-Labs/artiq
shuttler: add pdq-based waveform generator
This commit is contained in:
parent
1f58cd505c
commit
df99450faa
|
@ -0,0 +1,91 @@
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
from artiq.language.core import *
|
||||||
|
from artiq.language.types import *
|
||||||
|
from artiq.coredevice.rtio import rtio_output
|
||||||
|
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
kernel_invariants = {"core", "channel", "target_o"}
|
||||||
|
|
||||||
|
def __init__(self, dmgr, channel, core_device="core"):
|
||||||
|
self.core = dmgr.get(core_device)
|
||||||
|
self.channel = channel
|
||||||
|
self.target_o = channel << 8
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_config(self, config):
|
||||||
|
rtio_output(self.target_o, config)
|
||||||
|
|
||||||
|
|
||||||
|
class Volt:
|
||||||
|
kernel_invariants = {"core", "channel", "target_o"}
|
||||||
|
|
||||||
|
def __init__(self, dmgr, channel, core_device="core"):
|
||||||
|
self.core = dmgr.get(core_device)
|
||||||
|
self.channel = channel
|
||||||
|
self.target_o = channel << 8
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_waveform(self, a0: TInt32, a1: TInt32, a2: TInt64, a3: TInt64):
|
||||||
|
pdq_words = [
|
||||||
|
a0,
|
||||||
|
a1,
|
||||||
|
a1 >> 16,
|
||||||
|
a2 & 0xFFFF,
|
||||||
|
(a2 >> 16) & 0xFFFF,
|
||||||
|
(a2 >> 32) & 0xFFFF,
|
||||||
|
a3 & 0xFFFF,
|
||||||
|
(a3 >> 16) & 0xFFFF,
|
||||||
|
(a3 >> 32) & 0xFFFF,
|
||||||
|
]
|
||||||
|
|
||||||
|
for i in range(len(pdq_words)):
|
||||||
|
rtio_output(self.target_o | i, pdq_words[i])
|
||||||
|
delay_mu(int64(self.core.ref_multiplier))
|
||||||
|
|
||||||
|
|
||||||
|
class Dds:
|
||||||
|
kernel_invariants = {"core", "channel", "target_o"}
|
||||||
|
|
||||||
|
def __init__(self, dmgr, channel, core_device="core"):
|
||||||
|
self.core = dmgr.get(core_device)
|
||||||
|
self.channel = channel
|
||||||
|
self.target_o = channel << 8
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_waveform(self, b0: TInt32, b1: TInt32, b2: TInt64, b3: TInt64,
|
||||||
|
c0: TInt32, c1: TInt32, c2: TInt32):
|
||||||
|
pdq_words = [
|
||||||
|
b0,
|
||||||
|
b1,
|
||||||
|
b1 >> 16,
|
||||||
|
b2 & 0xFFFF,
|
||||||
|
(b2 >> 16) & 0xFFFF,
|
||||||
|
(b2 >> 32) & 0xFFFF,
|
||||||
|
b3 & 0xFFFF,
|
||||||
|
(b3 >> 16) & 0xFFFF,
|
||||||
|
(b3 >> 32) & 0xFFFF,
|
||||||
|
c0,
|
||||||
|
c1,
|
||||||
|
c1 >> 16,
|
||||||
|
c2,
|
||||||
|
c2 >> 16,
|
||||||
|
]
|
||||||
|
|
||||||
|
for i in range(len(pdq_words)):
|
||||||
|
rtio_output(self.target_o | i, pdq_words[i])
|
||||||
|
delay_mu(int64(self.core.ref_multiplier))
|
||||||
|
|
||||||
|
|
||||||
|
class Trigger:
|
||||||
|
kernel_invariants = {"core", "channel", "target_o"}
|
||||||
|
|
||||||
|
def __init__(self, dmgr, channel, core_device="core"):
|
||||||
|
self.core = dmgr.get(core_device)
|
||||||
|
self.channel = channel
|
||||||
|
self.target_o = channel << 8
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def trigger(self, trig_out):
|
||||||
|
rtio_output(self.target_o, trig_out)
|
|
@ -0,0 +1,224 @@
|
||||||
|
# Copyright 2013-2017 Robert Jordens <jordens@gmail.com>
|
||||||
|
#
|
||||||
|
# pdq is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pdq is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with pdq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
from operator import add
|
||||||
|
|
||||||
|
from migen import *
|
||||||
|
from misoc.interconnect.stream import Endpoint
|
||||||
|
from misoc.cores.cordic import Cordic
|
||||||
|
from artiq.gateware.rtio import rtlink
|
||||||
|
|
||||||
|
|
||||||
|
class Dac(Module):
|
||||||
|
"""Output module.
|
||||||
|
|
||||||
|
Holds the two output line executors.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
data (Signal[16]): Output value to be send to the DAC.
|
||||||
|
clear (Signal): Clear accumulated phase offset when loading a new
|
||||||
|
waveform. Input.
|
||||||
|
i (Endpoint[]): Coefficients of the output lines.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.clear = Signal()
|
||||||
|
self.data = Signal(16)
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
subs = [
|
||||||
|
Volt(),
|
||||||
|
Dds(self.clear),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.sync.rio += [
|
||||||
|
self.data.eq(reduce(add, [sub.data for sub in subs])),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.i = [ sub.i for sub in subs ]
|
||||||
|
self.submodules += subs
|
||||||
|
|
||||||
|
|
||||||
|
class Volt(Module):
|
||||||
|
"""DC bias spline interpolator.
|
||||||
|
|
||||||
|
The line data is interpreted as a concatenation of:
|
||||||
|
|
||||||
|
* 16 bit amplitude offset
|
||||||
|
* 32 bit amplitude first order derivative
|
||||||
|
* 48 bit amplitude second order derivative
|
||||||
|
* 48 bit amplitude third order derivative
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
data (Signal[16]): Output data from this spline.
|
||||||
|
i (Endpoint): Coefficients of the DC bias spline, along with its
|
||||||
|
latency compensation.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.data = Signal(16)
|
||||||
|
self.i = Endpoint([("data", 144)])
|
||||||
|
self.i.latency = 17
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
v = [Signal(48) for i in range(4)] # amp, damp, ddamp, dddamp
|
||||||
|
|
||||||
|
# Increase latency of stb by 17 cycles to compensate CORDIC latency
|
||||||
|
stb_r = [ Signal() for _ in range(17) ]
|
||||||
|
self.sync.rio += [
|
||||||
|
stb_r[0].eq(self.i.stb),
|
||||||
|
]
|
||||||
|
for idx in range(16):
|
||||||
|
self.sync.rio += stb_r[idx+1].eq(stb_r[idx])
|
||||||
|
|
||||||
|
self.sync.rio += [
|
||||||
|
v[0].eq(v[0] + v[1]),
|
||||||
|
v[1].eq(v[1] + v[2]),
|
||||||
|
v[2].eq(v[2] + v[3]),
|
||||||
|
If(stb_r[16],
|
||||||
|
v[0].eq(0),
|
||||||
|
v[1].eq(0),
|
||||||
|
Cat(v[0][32:], v[1][16:], v[2], v[3]).eq(self.i.payload.raw_bits()),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
self.comb += self.data.eq(v[0][32:])
|
||||||
|
|
||||||
|
|
||||||
|
class Dds(Module):
|
||||||
|
"""DDS spline interpolator.
|
||||||
|
|
||||||
|
The line data is interpreted as:
|
||||||
|
|
||||||
|
* 16 bit amplitude offset
|
||||||
|
* 32 bit amplitude first order derivative
|
||||||
|
* 48 bit amplitude second order derivative
|
||||||
|
* 48 bit amplitude third order derivative
|
||||||
|
* 16 bit phase offset
|
||||||
|
* 32 bit frequency word
|
||||||
|
* 32 bit chirp
|
||||||
|
|
||||||
|
Args:
|
||||||
|
line (Record[line_layout]): Next line to be executed. Input.
|
||||||
|
clear (Signal): Clear accumulated phase offset when loading a new
|
||||||
|
waveform. Input.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
data (Signal[16]): Output data from this spline.
|
||||||
|
i (Endpoint): Coefficients of the DDS spline, along with its latency
|
||||||
|
compensation.
|
||||||
|
"""
|
||||||
|
def __init__(self, clear):
|
||||||
|
self.data = Signal(16)
|
||||||
|
self.i = Endpoint([("data", 224)])
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
self.submodules.cordic = Cordic(width=16, eval_mode="pipelined",
|
||||||
|
guard=None)
|
||||||
|
|
||||||
|
za = Signal(32)
|
||||||
|
z = [Signal(32) for i in range(3)] # phase, dphase, ddphase
|
||||||
|
x = [Signal(48) for i in range(4)] # amp, damp, ddamp, dddamp
|
||||||
|
self.comb += [
|
||||||
|
self.cordic.xi.eq(x[0][32:]),
|
||||||
|
self.cordic.yi.eq(0),
|
||||||
|
self.cordic.zi.eq(za[16:] + z[0][16:]),
|
||||||
|
self.data.eq(self.cordic.xo),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.sync.rio += [
|
||||||
|
za.eq(za + z[1]),
|
||||||
|
x[0].eq(x[0] + x[1]),
|
||||||
|
x[1].eq(x[1] + x[2]),
|
||||||
|
x[2].eq(x[2] + x[3]),
|
||||||
|
z[1].eq(z[1] + z[2]),
|
||||||
|
If(self.i.stb,
|
||||||
|
x[0].eq(0),
|
||||||
|
x[1].eq(0),
|
||||||
|
Cat(x[0][32:], x[1][16:], x[2], x[3], z[0][16:], z[1], z[2]
|
||||||
|
).eq(self.i.payload.raw_bits()),
|
||||||
|
If(clear,
|
||||||
|
za.eq(0),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Config(Module):
|
||||||
|
def __init__(self):
|
||||||
|
self.clr = Signal(16, reset=0xFFFF)
|
||||||
|
self.i = Endpoint([("data", 16)])
|
||||||
|
|
||||||
|
# This introduces 1 extra latency to everything in config
|
||||||
|
# See the latency/delay attributes in Volt & DDS Endpoints/rtlinks
|
||||||
|
self.sync.rio += If(self.i.stb, self.clr.eq(self.i.data))
|
||||||
|
|
||||||
|
|
||||||
|
Phy = namedtuple("Phy", "rtlink probes overrides")
|
||||||
|
|
||||||
|
|
||||||
|
class Shuttler(Module):
|
||||||
|
"""Shuttler module.
|
||||||
|
|
||||||
|
Used both in functional simulation and final gateware.
|
||||||
|
|
||||||
|
Holds the DACs and the configuration register. The DAC and Config are
|
||||||
|
collected and adapted into RTIO interface.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
phys (list): List of Endpoints.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
NUM_OF_DACS = 16
|
||||||
|
|
||||||
|
self.phys = []
|
||||||
|
|
||||||
|
self.submodules.cfg = Config()
|
||||||
|
cfg_rtl_iface = rtlink.Interface(rtlink.OInterface(
|
||||||
|
data_width=len(self.cfg.i.data),
|
||||||
|
enable_replace=False))
|
||||||
|
|
||||||
|
self.comb += [
|
||||||
|
self.cfg.i.stb.eq(cfg_rtl_iface.o.stb),
|
||||||
|
self.cfg.i.data.eq(cfg_rtl_iface.o.data),
|
||||||
|
]
|
||||||
|
self.phys.append(Phy(cfg_rtl_iface, [], []))
|
||||||
|
|
||||||
|
trigger_iface = rtlink.Interface(rtlink.OInterface(
|
||||||
|
data_width=NUM_OF_DACS,
|
||||||
|
enable_replace=False))
|
||||||
|
self.phys.append(Phy(trigger_iface, [], []))
|
||||||
|
|
||||||
|
for idx in range(NUM_OF_DACS):
|
||||||
|
dac = Dac()
|
||||||
|
self.comb += dac.clear.eq(self.cfg.clr[idx]),
|
||||||
|
|
||||||
|
for i in dac.i:
|
||||||
|
delay = getattr(i, "latency", 0)
|
||||||
|
rtl_iface = rtlink.Interface(rtlink.OInterface(
|
||||||
|
data_width=16, address_width=4, delay=delay))
|
||||||
|
array = Array(i.data[wi: wi+16] for wi in range(0, len(i.data), 16))
|
||||||
|
|
||||||
|
self.sync.rio += [
|
||||||
|
i.stb.eq(trigger_iface.o.data[idx] & trigger_iface.o.stb),
|
||||||
|
If(rtl_iface.o.stb,
|
||||||
|
array[rtl_iface.o.address].eq(rtl_iface.o.data),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.phys.append(Phy(rtl_iface, [], []))
|
||||||
|
|
||||||
|
self.submodules += dac
|
|
@ -16,6 +16,7 @@ from artiq.gateware.rtio.phy import ttl_simple
|
||||||
from artiq.gateware.drtio.transceiver import eem_serdes
|
from artiq.gateware.drtio.transceiver import eem_serdes
|
||||||
from artiq.gateware.drtio.rx_synchronizer import NoRXSynchronizer
|
from artiq.gateware.drtio.rx_synchronizer import NoRXSynchronizer
|
||||||
from artiq.gateware.drtio import *
|
from artiq.gateware.drtio import *
|
||||||
|
from artiq.gateware.shuttler import Shuttler
|
||||||
from artiq.build_soc import *
|
from artiq.build_soc import *
|
||||||
|
|
||||||
|
|
||||||
|
@ -159,6 +160,9 @@ class Satellite(BaseSoC, AMPSoC):
|
||||||
self.submodules += phy
|
self.submodules += phy
|
||||||
self.rtio_channels.append(rtio.Channel.from_phy(phy))
|
self.rtio_channels.append(rtio.Channel.from_phy(phy))
|
||||||
|
|
||||||
|
self.submodules.shuttler = Shuttler()
|
||||||
|
self.rtio_channels.extend(rtio.Channel.from_phy(phy) for phy in self.shuttler.phys)
|
||||||
|
|
||||||
self.config["HAS_RTIO_LOG"] = None
|
self.config["HAS_RTIO_LOG"] = None
|
||||||
self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels)
|
self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels)
|
||||||
self.rtio_channels.append(rtio.LogChannel())
|
self.rtio_channels.append(rtio.LogChannel())
|
||||||
|
|
Loading…
Reference in New Issue