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.rx_synchronizer import NoRXSynchronizer
|
||||
from artiq.gateware.drtio import *
|
||||
from artiq.gateware.shuttler import Shuttler
|
||||
from artiq.build_soc import *
|
||||
|
||||
|
||||
|
@ -159,6 +160,9 @@ class Satellite(BaseSoC, AMPSoC):
|
|||
self.submodules += 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["RTIO_LOG_CHANNEL"] = len(self.rtio_channels)
|
||||
self.rtio_channels.append(rtio.LogChannel())
|
||||
|
|
Loading…
Reference in New Issue