mirror of
https://github.com/m-labs/artiq.git
synced 2024-12-25 03:08:27 +08:00
coredevice.dds: reimplement fully in ARTIQ Python.
This commit also drops AD9858 support from software.
This commit is contained in:
parent
55ea68da7f
commit
f4b7666768
@ -182,10 +182,7 @@ class DDSHandler:
|
||||
self.vcd_manager.get_channel("dds/" + name + "/frequency", 64)
|
||||
dds_channel["vcd_phase"] = \
|
||||
self.vcd_manager.get_channel("dds/" + name + "/phase", 64)
|
||||
if self.dds_type == "AD9858":
|
||||
dds_channel["ftw"] = [None, None, None, None]
|
||||
dds_channel["pow"] = [None, None]
|
||||
elif self.dds_type == "AD9914":
|
||||
if self.dds_type == "DDSChannelAD9914":
|
||||
dds_channel["ftw"] = [None, None]
|
||||
dds_channel["pow"] = None
|
||||
self.dds_channels[dds_channel_nr] = dds_channel
|
||||
@ -205,26 +202,6 @@ class DDSHandler:
|
||||
else:
|
||||
return {gpio}
|
||||
|
||||
def _decode_ad9858_write(self, message):
|
||||
if message.address == 0x41:
|
||||
self.selected_dds_channels = self._gpio_to_channels(message.data)
|
||||
for dds_channel_nr in self.selected_dds_channels:
|
||||
dds_channel = self.dds_channels[dds_channel_nr]
|
||||
if message.address in range(0x0a, 0x0e):
|
||||
dds_channel["ftw"][message.address - 0x0a] = message.data
|
||||
elif message.address in range(0x0e, 0x10):
|
||||
dds_channel["pow"][message.address - 0x0e] = message.data
|
||||
elif message.address == 0x40: # FUD
|
||||
if None not in dds_channel["ftw"]:
|
||||
ftw = sum(x << i*8
|
||||
for i, x in enumerate(dds_channel["ftw"]))
|
||||
frequency = ftw*self.sysclk/2**32
|
||||
dds_channel["vcd_frequency"].set_value_double(frequency)
|
||||
if None not in dds_channel["pow"]:
|
||||
pow = dds_channel["pow"][0] | (dds_channel["pow"][1] & 0x3f) << 8
|
||||
phase = pow/2**14
|
||||
dds_channel["vcd_phase"].set_value_double(phase)
|
||||
|
||||
def _decode_ad9914_write(self, message):
|
||||
if message.address == 0x81:
|
||||
self.selected_dds_channels = self._gpio_to_channels(message.data)
|
||||
@ -251,9 +228,7 @@ class DDSHandler:
|
||||
logger.debug("DDS write @%d 0x%04x to 0x%02x, selected channels: %s",
|
||||
message.timestamp, message.data, message.address,
|
||||
self.selected_dds_channels)
|
||||
if self.dds_type == "AD9858":
|
||||
self._decode_ad9858_write(message)
|
||||
elif self.dds_type == "AD9914":
|
||||
if self.dds_type == "DDSChannelAD9914":
|
||||
self._decode_ad9914_write(message)
|
||||
|
||||
|
||||
@ -312,7 +287,7 @@ def get_single_device_argument(devices, module, cls, argument):
|
||||
for desc in devices.values():
|
||||
if isinstance(desc, dict) and desc["type"] == "local":
|
||||
if (desc["module"] == module
|
||||
and desc["class"] == cls):
|
||||
and desc["class"] in cls):
|
||||
if ref_period is None:
|
||||
ref_period = desc["arguments"][argument]
|
||||
else:
|
||||
@ -322,12 +297,12 @@ def get_single_device_argument(devices, module, cls, argument):
|
||||
|
||||
def get_ref_period(devices):
|
||||
return get_single_device_argument(devices, "artiq.coredevice.core",
|
||||
"Core", "ref_period")
|
||||
("Core",), "ref_period")
|
||||
|
||||
|
||||
def get_dds_sysclk(devices):
|
||||
return get_single_device_argument(devices, "artiq.coredevice.dds",
|
||||
"CoreDDS", "sysclk")
|
||||
("DDSGroupAD9914",), "sysclk")
|
||||
|
||||
|
||||
def create_channel_handlers(vcd_manager, devices, ref_period,
|
||||
@ -344,7 +319,7 @@ def create_channel_handlers(vcd_manager, devices, ref_period,
|
||||
channel = desc["arguments"]["channel"]
|
||||
channel_handlers[channel] = TTLClockGenHandler(vcd_manager, name, ref_period)
|
||||
if (desc["module"] == "artiq.coredevice.dds"
|
||||
and desc["class"] in {"AD9858", "AD9914"}):
|
||||
and desc["class"] in {"DDSChannelAD9914"}):
|
||||
dds_bus_channel = desc["arguments"]["bus_channel"]
|
||||
dds_channel = desc["arguments"]["channel"]
|
||||
if dds_bus_channel in channel_handlers:
|
||||
|
@ -1,56 +1,84 @@
|
||||
from artiq.language.core import *
|
||||
from artiq.language.types import *
|
||||
from artiq.language.units import *
|
||||
from numpy import int64
|
||||
from artiq.coredevice.rtio import rtio_output
|
||||
from artiq.coredevice.exceptions import DDSError
|
||||
|
||||
from numpy import int32, int64
|
||||
|
||||
|
||||
_PHASE_MODE_DEFAULT = -1
|
||||
# keep in sync with dds.h
|
||||
_PHASE_MODE_DEFAULT = -1
|
||||
PHASE_MODE_CONTINUOUS = 0
|
||||
PHASE_MODE_ABSOLUTE = 1
|
||||
PHASE_MODE_TRACKING = 2
|
||||
PHASE_MODE_ABSOLUTE = 1
|
||||
PHASE_MODE_TRACKING = 2
|
||||
|
||||
|
||||
@syscall(flags={"nowrite"})
|
||||
def dds_init(time_mu: TInt64, bus_channel: TInt32, channel: TInt32) -> TNone:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
@syscall(flags={"nowrite"})
|
||||
def dds_init_sync(time_mu: TInt64, bus_channel: TInt32,
|
||||
channel: TInt32, sync_delay: TInt32) -> TNone:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
@syscall(flags={"nowrite"})
|
||||
def dds_set(time_mu: TInt64, bus_channel: TInt32, channel: TInt32, ftw: TInt32,
|
||||
pow: TInt32, phase_mode: TInt32, amplitude: TInt32) -> TNone:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
@syscall(flags={"nowrite"})
|
||||
def dds_batch_enter(time_mu: TInt64) -> TNone:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
@syscall(flags={"nowrite"})
|
||||
def dds_batch_exit() -> TNone:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
class DDSParams:
|
||||
def __init__(self):
|
||||
self.bus_channel = 0
|
||||
self.channel = 0
|
||||
self.ftw = 0
|
||||
self.pow = 0
|
||||
self.phase_mode = 0
|
||||
self.amplitude = 0
|
||||
|
||||
|
||||
class _BatchContextManager:
|
||||
kernel_invariants = {"core", "core_dds"}
|
||||
class BatchContextManager:
|
||||
kernel_invariants = {"core", "core_dds", "params"}
|
||||
|
||||
def __init__(self, core_dds):
|
||||
self.core_dds = core_dds
|
||||
self.core = self.core_dds.core
|
||||
self.core = self.core_dds.core
|
||||
self.active = False
|
||||
self.params = [DDSParams() for _ in range(16)]
|
||||
self.count = 0
|
||||
self.ref_time = int64(0)
|
||||
|
||||
@kernel
|
||||
def __enter__(self):
|
||||
self.core_dds.dds_batch_enter()
|
||||
"""Starts a DDS command batch. All DDS commands are buffered
|
||||
after this call, until ``batch_exit`` is called.
|
||||
|
||||
The time of execution of the DDS commands is the time cursor position
|
||||
when the batch is entered."""
|
||||
if self.active:
|
||||
raise DDSError("DDS batch entered twice")
|
||||
|
||||
self.active = True
|
||||
self.count = 0
|
||||
self.ref_time = now_mu()
|
||||
|
||||
@kernel
|
||||
def append(self, bus_channel, channel, ftw, pow, phase_mode, amplitude):
|
||||
if self.count == len(self.params):
|
||||
raise DDSError("Too many commands in DDS batch")
|
||||
|
||||
params = self.params[self.count]
|
||||
params.bus_channel = bus_channel
|
||||
params.channel = channel
|
||||
params.ftw = ftw
|
||||
params.pow = pow
|
||||
params.phase_mode = phase_mode
|
||||
params.amplitude = amplitude
|
||||
self.count += 1
|
||||
|
||||
@kernel
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.core_dds.dds_batch_exit()
|
||||
"""Ends a DDS command batch. All buffered DDS commands are issued
|
||||
on the bus."""
|
||||
if not self.active:
|
||||
raise DDSError("DDS batch exited twice")
|
||||
|
||||
self.active = False
|
||||
at_mu(self.ref_time - self.core_dds.batch_duration_mu())
|
||||
for i in range(self.count):
|
||||
param = self.params[i]
|
||||
self.core_dds.program(self.ref_time,
|
||||
param.bus_channel, param.channel, param.ftw,
|
||||
param.pow, param.phase_mode, param.amplitude)
|
||||
|
||||
|
||||
class CoreDDS:
|
||||
class DDSGroup:
|
||||
"""Core device Direct Digital Synthesis (DDS) driver.
|
||||
|
||||
Gives access to the DDS functionality of the core device.
|
||||
@ -62,33 +90,77 @@ class CoreDDS:
|
||||
kernel_invariants = {"core", "sysclk", "batch"}
|
||||
|
||||
def __init__(self, dmgr, sysclk, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
self.core = dmgr.get(core_device)
|
||||
self.sysclk = sysclk
|
||||
self.batch = _BatchContextManager(self)
|
||||
self.batch = BatchContextManager(self)
|
||||
|
||||
@kernel
|
||||
def dds_batch_enter(self):
|
||||
"""Starts a DDS command batch. All DDS commands are buffered
|
||||
after this call, until ``batch_exit`` is called.
|
||||
|
||||
The time of execution of the DDS commands is the time cursor position
|
||||
when the batch is entered."""
|
||||
dds_batch_enter(now_mu())
|
||||
def batch_duration_mu(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@kernel
|
||||
def dds_batch_exit(self):
|
||||
"""Ends a DDS command batch. All buffered DDS commands are issued
|
||||
on the bus."""
|
||||
dds_batch_exit()
|
||||
def init(self, bus_channel, channel):
|
||||
raise NotImplementedError
|
||||
|
||||
@kernel
|
||||
def program(self, ref_time, bus_channel, channel, ftw, pow, phase_mode, amplitude):
|
||||
raise NotImplementedError
|
||||
|
||||
@kernel
|
||||
def set(self, bus_channel, channel, ftw, pow, phase_mode, amplitude):
|
||||
if self.batch.active:
|
||||
self.batch.append(bus_channel, channel, ftw, pow, phase_mode, amplitude)
|
||||
else:
|
||||
ref_time = now_mu()
|
||||
at_mu(ref_time - self.program_duration_mu)
|
||||
self.program(ref_time,
|
||||
bus_channel, channel, ftw, pow, phase_mode, amplitude)
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def frequency_to_ftw(self, frequency):
|
||||
"""Returns the frequency tuning word corresponding to the given
|
||||
frequency.
|
||||
"""
|
||||
return round(float(int64(2)**32*frequency/self.sysclk))
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def ftw_to_frequency(self, ftw):
|
||||
"""Returns the frequency corresponding to the given frequency tuning
|
||||
word.
|
||||
"""
|
||||
return ftw*self.sysclk/int64(2)**32
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def turns_to_pow(self, turns):
|
||||
"""Returns the phase offset word corresponding to the given phase
|
||||
in turns."""
|
||||
return round(float(turns*2**self.pow_width))
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def pow_to_turns(self, pow):
|
||||
"""Returns the phase in turns corresponding to the given phase offset
|
||||
word."""
|
||||
return pow/2**self.pow_width
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def amplitude_to_asf(self, amplitude):
|
||||
"""Returns amplitude scale factor corresponding to given amplitude."""
|
||||
return round(float(amplitude*0x0fff))
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def asf_to_amplitude(self, asf):
|
||||
"""Returns the amplitude corresponding to the given amplitude scale
|
||||
factor."""
|
||||
return asf/0x0fff
|
||||
|
||||
|
||||
class _DDSGeneric:
|
||||
class DDSChannel:
|
||||
"""Core device Direct Digital Synthesis (DDS) channel driver.
|
||||
|
||||
Controls one DDS channel managed directly by the core device's runtime.
|
||||
|
||||
This class should not be used directly, instead, use the chip-specific
|
||||
drivers such as ``AD9858`` and ``AD9914``.
|
||||
drivers such as ``DDSChannelAD9914``.
|
||||
|
||||
The time cursor is not modified by any function in this class.
|
||||
|
||||
@ -97,52 +169,15 @@ class _DDSGeneric:
|
||||
"""
|
||||
|
||||
kernel_invariants = {
|
||||
"core", "core_dds", "bus_channel", "channel", "pow_width"
|
||||
"core", "core_dds", "bus_channel", "channel",
|
||||
}
|
||||
|
||||
def __init__(self, dmgr, bus_channel, channel, core_dds_device="core_dds"):
|
||||
self.core_dds = dmgr.get(core_dds_device)
|
||||
self.core = self.core_dds.core
|
||||
self.core_dds = dmgr.get(core_dds_device)
|
||||
self.core = self.core_dds.core
|
||||
self.bus_channel = bus_channel
|
||||
self.channel = channel
|
||||
self.phase_mode = PHASE_MODE_CONTINUOUS
|
||||
|
||||
@portable(flags=["fast-math"])
|
||||
def frequency_to_ftw(self, frequency):
|
||||
"""Returns the frequency tuning word corresponding to the given
|
||||
frequency.
|
||||
"""
|
||||
return round(float(int64(2)**32*frequency/self.core_dds.sysclk))
|
||||
|
||||
@portable(flags=["fast-math"])
|
||||
def ftw_to_frequency(self, ftw):
|
||||
"""Returns the frequency corresponding to the given frequency tuning
|
||||
word.
|
||||
"""
|
||||
return ftw*self.core_dds.sysclk/int64(2)**32
|
||||
|
||||
@portable(flags=["fast-math"])
|
||||
def turns_to_pow(self, turns):
|
||||
"""Returns the phase offset word corresponding to the given phase
|
||||
in turns."""
|
||||
return round(float(turns*2**self.pow_width))
|
||||
|
||||
@portable(flags=["fast-math"])
|
||||
def pow_to_turns(self, pow):
|
||||
"""Returns the phase in turns corresponding to the given phase offset
|
||||
word."""
|
||||
return pow/2**self.pow_width
|
||||
|
||||
@portable(flags=["fast-math"])
|
||||
def amplitude_to_asf(self, amplitude):
|
||||
"""Returns amplitude scale factor corresponding to given amplitude."""
|
||||
return round(float(amplitude*0x0fff))
|
||||
|
||||
@portable(flags=["fast-math"])
|
||||
def asf_to_amplitude(self, asf):
|
||||
"""Returns the amplitude corresponding to the given amplitude scale
|
||||
factor."""
|
||||
return asf/0x0fff
|
||||
self.channel = channel
|
||||
self.phase_mode = PHASE_MODE_CONTINUOUS
|
||||
|
||||
@kernel
|
||||
def init(self):
|
||||
@ -155,7 +190,7 @@ class _DDSGeneric:
|
||||
initializing multiple DDS channels is to call this function
|
||||
sequentially with a delay between the calls. 2ms provides a good
|
||||
timing margin."""
|
||||
dds_init(now_mu(), self.bus_channel, self.channel)
|
||||
self.core_dds.init(self.bus_channel, self.channel)
|
||||
|
||||
@kernel
|
||||
def set_phase_mode(self, phase_mode):
|
||||
@ -197,29 +232,147 @@ class _DDSGeneric:
|
||||
"""
|
||||
if phase_mode == _PHASE_MODE_DEFAULT:
|
||||
phase_mode = self.phase_mode
|
||||
dds_set(now_mu(), self.bus_channel, self.channel,
|
||||
frequency, phase, phase_mode, amplitude)
|
||||
self.core_dds.set(self.bus_channel, self.channel, frequency, phase, phase_mode, amplitude)
|
||||
|
||||
@kernel
|
||||
def set(self, frequency, phase=0.0, phase_mode=_PHASE_MODE_DEFAULT,
|
||||
amplitude=1.0):
|
||||
"""Like ``set_mu``, but uses Hz and turns."""
|
||||
self.set_mu(self.frequency_to_ftw(frequency),
|
||||
self.turns_to_pow(phase), phase_mode,
|
||||
self.amplitude_to_asf(amplitude))
|
||||
self.set_mu(self.core_dds.frequency_to_ftw(frequency),
|
||||
self.core_dds.turns_to_pow(phase), phase_mode,
|
||||
self.core_dds.amplitude_to_asf(amplitude))
|
||||
|
||||
|
||||
class AD9858(_DDSGeneric):
|
||||
"""Driver for AD9858 DDS chips. See ``_DDSGeneric`` for a description
|
||||
AD9914_REG_CFR1L = 0x01
|
||||
AD9914_REG_CFR1H = 0x03
|
||||
AD9914_REG_CFR2L = 0x05
|
||||
AD9914_REG_CFR2H = 0x07
|
||||
AD9914_REG_CFR3L = 0x09
|
||||
AD9914_REG_CFR3H = 0x0b
|
||||
AD9914_REG_CFR4L = 0x0d
|
||||
AD9914_REG_CFR4H = 0x0f
|
||||
AD9914_REG_FTWL = 0x2d
|
||||
AD9914_REG_FTWH = 0x2f
|
||||
AD9914_REG_POW = 0x31
|
||||
AD9914_REG_ASF = 0x33
|
||||
AD9914_REG_USR0 = 0x6d
|
||||
AD9914_FUD = 0x80
|
||||
AD9914_GPIO = 0x81
|
||||
|
||||
|
||||
class DDSGroupAD9914(DDSGroup):
|
||||
"""Driver for AD9914 DDS chips. See ``DDSGroup`` for a description
|
||||
of the functionality."""
|
||||
pow_width = 14
|
||||
kernel_invariants = DDSGroup.kernel_invariants.union({
|
||||
"pow_width", "rtio_period_mu", "sysclk_per_mu", "write_duration_mu", "dac_cal_duration_mu",
|
||||
"init_duration_mu", "init_sync_duration_mu", "program_duration_mu",
|
||||
"first_dds_bus_channel", "dds_channel_count", "continuous_phase_comp"
|
||||
})
|
||||
|
||||
|
||||
class AD9914(_DDSGeneric):
|
||||
"""Driver for AD9914 DDS chips. See ``_DDSGeneric`` for a description
|
||||
of the functionality."""
|
||||
pow_width = 16
|
||||
|
||||
def __init__(self, *args, first_dds_bus_channel, dds_bus_count, dds_channel_count, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.first_dds_bus_channel = first_dds_bus_channel
|
||||
self.dds_bus_count = dds_bus_count
|
||||
self.dds_channel_count = dds_channel_count
|
||||
|
||||
self.rtio_period_mu = int64(8)
|
||||
self.sysclk_per_mu = int32(self.sysclk * self.core.ref_period)
|
||||
|
||||
self.write_duration_mu = 5 * self.rtio_period_mu
|
||||
self.dac_cal_duration_mu = 147000 * self.rtio_period_mu
|
||||
self.init_duration_mu = 8 * self.write_duration_mu + self.dac_cal_duration_mu
|
||||
self.init_sync_duration_mu = 16 * self.write_duration_mu + 2 * self.dac_cal_duration_mu
|
||||
self.program_duration_mu = 6 * self.write_duration_mu
|
||||
|
||||
self.continuous_phase_comp = [0] * (self.dds_bus_count * self.dds_channel_count)
|
||||
|
||||
@kernel
|
||||
def batch_duration_mu(self):
|
||||
return self.batch.count * (self.program_duration_mu +
|
||||
self.write_duration_mu) # + FUD time
|
||||
|
||||
@kernel
|
||||
def write(self, bus_channel, addr, data):
|
||||
rtio_output(now_mu(), bus_channel, addr, data)
|
||||
delay_mu(self.write_duration_mu)
|
||||
|
||||
@kernel
|
||||
def init(self, bus_channel, channel):
|
||||
delay_mu(-self.init_duration_mu)
|
||||
self.write(bus_channel, AD9914_GPIO, (1 << channel) << 1);
|
||||
|
||||
self.write(bus_channel, AD9914_REG_CFR1H, 0x0000) # Enable cosine output
|
||||
self.write(bus_channel, AD9914_REG_CFR2L, 0x8900) # Enable matched latency
|
||||
self.write(bus_channel, AD9914_REG_CFR2H, 0x0080) # Enable profile mode
|
||||
self.write(bus_channel, AD9914_REG_ASF, 0x0fff) # Set amplitude to maximum
|
||||
self.write(bus_channel, AD9914_REG_CFR4H, 0x0105) # Enable DAC calibration
|
||||
self.write(bus_channel, AD9914_FUD, 0)
|
||||
delay_mu(self.dac_cal_duration_mu)
|
||||
self.write(bus_channel, AD9914_REG_CFR4H, 0x0005) # Disable DAC calibration
|
||||
self.write(bus_channel, AD9914_FUD, 0)
|
||||
|
||||
@kernel
|
||||
def init_sync(self, bus_channel, channel, sync_delay):
|
||||
delay_mu(-self.init_sync_duration_mu)
|
||||
self.write(bus_channel, AD9914_GPIO, (1 << channel) << 1)
|
||||
|
||||
self.write(bus_channel, AD9914_REG_CFR4H, 0x0105) # Enable DAC calibration
|
||||
self.write(bus_channel, AD9914_FUD, 0)
|
||||
delay_mu(self.dac_cal_duration_mu)
|
||||
self.write(bus_channel, AD9914_REG_CFR4H, 0x0005) # Disable DAC calibration
|
||||
self.write(bus_channel, AD9914_FUD, 0)
|
||||
self.write(bus_channel, AD9914_REG_CFR2L, 0x8b00) # Enable matched latency and sync_out
|
||||
self.write(bus_channel, AD9914_FUD, 0)
|
||||
# Set cal with sync and set sync_out and sync_in delay
|
||||
self.write(bus_channel, AD9914_REG_USR0, 0x0840 | (sync_delay & 0x3f))
|
||||
self.write(bus_channel, AD9914_FUD, 0)
|
||||
self.write(bus_channel, AD9914_REG_CFR4H, 0x0105) # Enable DAC calibration
|
||||
self.write(bus_channel, AD9914_FUD, 0)
|
||||
delay_mu(self.dac_cal_duration_mu)
|
||||
self.write(bus_channel, AD9914_REG_CFR4H, 0x0005) # Disable DAC calibration
|
||||
self.write(bus_channel, AD9914_FUD, 0)
|
||||
self.write(bus_channel, AD9914_REG_CFR1H, 0x0000) # Enable cosine output
|
||||
self.write(bus_channel, AD9914_REG_CFR2H, 0x0080) # Enable profile mode
|
||||
self.write(bus_channel, AD9914_REG_ASF, 0x0fff) # Set amplitude to maximum
|
||||
self.write(bus_channel, AD9914_FUD, 0)
|
||||
|
||||
@kernel
|
||||
def program(self, ref_time, bus_channel, channel, ftw, pow, phase_mode, amplitude):
|
||||
self.write(bus_channel, AD9914_GPIO, (1 << channel) << 1)
|
||||
|
||||
self.write(bus_channel, AD9914_REG_FTWL, ftw & 0xffff)
|
||||
self.write(bus_channel, AD9914_REG_FTWH, (ftw >> 16) & 0xffff)
|
||||
|
||||
# We need the RTIO fine timestamp clock to be phase-locked
|
||||
# to DDS SYSCLK, and divided by an integer self.sysclk_per_mu.
|
||||
dds_bus_index = bus_channel - self.first_dds_bus_channel
|
||||
phase_comp_index = dds_bus_index * self.dds_channel_count + channel
|
||||
if phase_mode == PHASE_MODE_CONTINUOUS:
|
||||
# Do not clear phase accumulator on FUD
|
||||
# Disable autoclear phase accumulator and enables OSK.
|
||||
self.write(bus_channel, AD9914_REG_CFR1L, 0x0108)
|
||||
pow += self.continuous_phase_comp[phase_comp_index]
|
||||
else:
|
||||
# Clear phase accumulator on FUD
|
||||
# Enable autoclear phase accumulator and enables OSK.
|
||||
self.write(bus_channel, AD9914_REG_CFR1L, 0x2108)
|
||||
fud_time = now_mu() + 2 * self.write_duration_mu
|
||||
pow -= int32((ref_time - fud_time) * self.sysclk_per_mu * ftw >> (32 - self.pow_width))
|
||||
if phase_mode == PHASE_MODE_TRACKING:
|
||||
pow += int32(ref_time * self.sysclk_per_mu * ftw >> (32 - self.pow_width))
|
||||
self.continuous_phase_comp[phase_comp_index] = pow
|
||||
|
||||
self.write(bus_channel, AD9914_REG_POW, pow)
|
||||
self.write(bus_channel, AD9914_REG_ASF, amplitude)
|
||||
self.write(bus_channel, AD9914_FUD, 0)
|
||||
|
||||
|
||||
class DDSChannelAD9914(DDSChannel):
|
||||
"""Driver for AD9914 DDS chips. See ``DDSChannel`` for a description
|
||||
of the functionality."""
|
||||
@kernel
|
||||
def init_sync(self, sync_delay=0):
|
||||
"""Resets and initializes the DDS channel as well as configures
|
||||
@ -237,4 +390,4 @@ class AD9914(_DDSGeneric):
|
||||
:param sync_delay: integer from 0 to 0x3f that sets the value of
|
||||
SYNC_OUT (bits 3-5) and SYNC_IN (bits 0-2) delay ADJ bits.
|
||||
"""
|
||||
dds_init_sync(now_mu(), self.bus_channel, self.channel, sync_delay)
|
||||
self.core_dds.init_sync(self.bus_channel, self.channel, sync_delay)
|
||||
|
@ -125,7 +125,6 @@ class DDSError(Exception):
|
||||
when too many commands are batched, and when DDS channel settings are
|
||||
incorrect.
|
||||
"""
|
||||
artiq_builtin = True
|
||||
|
||||
class I2CError(Exception):
|
||||
"""Raised with a I2C transaction fails."""
|
||||
|
@ -219,10 +219,10 @@ class _DeviceManager:
|
||||
self.ttl_widgets[k] = widget
|
||||
self.ttl_cb()
|
||||
if (v["module"] == "artiq.coredevice.dds"
|
||||
and v["class"] == "CoreDDS"):
|
||||
and v["class"] == "DDSGroupAD9914"):
|
||||
self.dds_sysclk = v["arguments"]["sysclk"]
|
||||
if (v["module"] == "artiq.coredevice.dds"
|
||||
and v["class"] in {"AD9858", "AD9914"}):
|
||||
and v["class"] in {"DDSChannelAD9914"}):
|
||||
bus_channel = v["arguments"]["bus_channel"]
|
||||
channel = v["arguments"]["channel"]
|
||||
widget = _DDSWidget(
|
||||
|
@ -23,8 +23,13 @@
|
||||
"core_dds": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.dds",
|
||||
"class": "CoreDDS",
|
||||
"arguments": {"sysclk": 3e9}
|
||||
"class": "DDSGroupAD9914",
|
||||
"arguments": {
|
||||
"sysclk": 3e9,
|
||||
"first_dds_bus_channel": 26,
|
||||
"dds_bus_count": 2,
|
||||
"dds_channel_count": 3
|
||||
}
|
||||
},
|
||||
|
||||
"i2c_switch": {
|
||||
@ -136,20 +141,20 @@
|
||||
"dds0": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.dds",
|
||||
"class": "AD9914",
|
||||
"class": "DDSChannelAD9914",
|
||||
"arguments": {"bus_channel": 26, "channel": 0},
|
||||
"comment": "Comments work in DDS panel as well"
|
||||
},
|
||||
"dds1": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.dds",
|
||||
"class": "AD9914",
|
||||
"class": "DDSChannelAD9914",
|
||||
"arguments": {"bus_channel": 26, "channel": 1}
|
||||
},
|
||||
"dds2": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.dds",
|
||||
"class": "AD9914",
|
||||
"class": "DDSChannelAD9914",
|
||||
"arguments": {"bus_channel": 26, "channel": 2}
|
||||
},
|
||||
|
||||
|
@ -15,7 +15,7 @@ class DDSSetter(EnvExperiment):
|
||||
if (isinstance(v, dict)
|
||||
and v["type"] == "local"
|
||||
and v["module"] == "artiq.coredevice.dds"
|
||||
and v["class"] in {"AD9858", "AD9914"}):
|
||||
and v["class"] in {"DDSChannelAD9914"}):
|
||||
self.dds[k] = {
|
||||
"driver": self.get_device(k),
|
||||
"frequency": self.get_argument(
|
||||
|
@ -144,7 +144,6 @@ class _NIST_Ions(MiniSoC, AMPSoC):
|
||||
self.csr_devices.append("rtio_crg")
|
||||
self.submodules.rtio = rtio.RTIO(rtio_channels)
|
||||
self.register_kernel_cpu_csrdevice("rtio")
|
||||
self.config["RTIO_FINE_TS_WIDTH"] = self.rtio.fine_ts_width
|
||||
self.submodules.rtio_moninj = rtio.MonInj(rtio_channels)
|
||||
self.csr_devices.append("rtio_moninj")
|
||||
|
||||
|
@ -225,7 +225,6 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd
|
||||
# RTIO logic
|
||||
self.submodules.rtio = rtio.RTIO(rtio_channels)
|
||||
self.register_kernel_cpu_csrdevice("rtio")
|
||||
self.config["RTIO_FINE_TS_WIDTH"] = self.rtio.fine_ts_width
|
||||
self.config["DDS_RTIO_CLK_RATIO"] = 8 >> self.rtio.fine_ts_width
|
||||
self.submodules.rtio_moninj = rtio.MonInj(rtio_channels)
|
||||
self.csr_devices.append("rtio_moninj")
|
||||
|
@ -4,7 +4,7 @@ include $(MISOC_DIRECTORY)/software/common.mak
|
||||
PYTHON ?= python3.5
|
||||
|
||||
OBJECTS := flash_storage.o main.o
|
||||
OBJECTS_KSUPPORT := ksupport_glue.o artiq_personality.o rtio.o dds.o i2c.o
|
||||
OBJECTS_KSUPPORT := ksupport_glue.o artiq_personality.o rtio.o i2c.o
|
||||
|
||||
RUSTOUT_DIRECTORY := cargo/or1k-unknown-none/debug
|
||||
CORE_IO_COMMIT := d40c593f42fafbac1ff3d827f6df96338b5b7d8b
|
||||
|
@ -1,263 +0,0 @@
|
||||
#include <generated/csr.h>
|
||||
|
||||
#if ((defined CONFIG_RTIO_DDS_COUNT) && (CONFIG_RTIO_DDS_COUNT > 0))
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "artiq_personality.h"
|
||||
#include "rtio.h"
|
||||
#include "dds.h"
|
||||
|
||||
#define DURATION_WRITE (5 << CONFIG_RTIO_FINE_TS_WIDTH)
|
||||
|
||||
#if defined CONFIG_DDS_AD9858
|
||||
/* Assume 8-bit bus */
|
||||
#define DURATION_INIT (7*DURATION_WRITE) /* not counting FUD */
|
||||
#define DURATION_PROGRAM (8*DURATION_WRITE) /* not counting FUD */
|
||||
|
||||
#elif defined CONFIG_DDS_AD9914
|
||||
/* Assume 16-bit bus */
|
||||
/* DAC calibration takes max. 1ms as per datasheet */
|
||||
#define DURATION_DAC_CAL (147000 << CONFIG_RTIO_FINE_TS_WIDTH)
|
||||
/* not counting final FUD */
|
||||
#define DURATION_INIT (8*DURATION_WRITE + DURATION_DAC_CAL)
|
||||
#define DURATION_INIT_SYNC (16*DURATION_WRITE + 2*DURATION_DAC_CAL)
|
||||
#define DURATION_PROGRAM (6*DURATION_WRITE) /* not counting FUD */
|
||||
|
||||
#else
|
||||
#error Unknown DDS configuration
|
||||
#endif
|
||||
|
||||
#define DDS_WRITE(addr, data) do { \
|
||||
rtio_output(now, bus_channel, addr, data); \
|
||||
now += DURATION_WRITE; \
|
||||
} while(0)
|
||||
|
||||
void dds_init(long long int timestamp, int bus_channel, int channel)
|
||||
{
|
||||
long long int now;
|
||||
|
||||
now = timestamp - DURATION_INIT;
|
||||
|
||||
#ifdef CONFIG_DDS_ONEHOT_SEL
|
||||
channel = 1 << channel;
|
||||
#endif
|
||||
channel <<= 1;
|
||||
DDS_WRITE(DDS_GPIO, channel);
|
||||
#ifndef CONFIG_DDS_AD9914
|
||||
/*
|
||||
* Resetting a AD9914 intermittently crashes it. It does not produce any
|
||||
* output until power-cycled.
|
||||
* Increasing the reset pulse length and the delay until the first write
|
||||
* to 300ns do not solve the problem.
|
||||
* The chips seem fine without a reset.
|
||||
*/
|
||||
DDS_WRITE(DDS_GPIO, channel | 1); /* reset */
|
||||
DDS_WRITE(DDS_GPIO, channel);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DDS_AD9858
|
||||
/*
|
||||
* 2GHz divider disable
|
||||
* SYNCLK disable
|
||||
* Mixer power-down
|
||||
* Phase detect power down
|
||||
*/
|
||||
DDS_WRITE(DDS_CFR0, 0x78);
|
||||
DDS_WRITE(DDS_CFR1, 0x00);
|
||||
DDS_WRITE(DDS_CFR2, 0x00);
|
||||
DDS_WRITE(DDS_CFR3, 0x00);
|
||||
DDS_WRITE(DDS_FUD, 0);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DDS_AD9914
|
||||
DDS_WRITE(DDS_CFR1H, 0x0000); /* Enable cosine output */
|
||||
DDS_WRITE(DDS_CFR2L, 0x8900); /* Enable matched latency */
|
||||
DDS_WRITE(DDS_CFR2H, 0x0080); /* Enable profile mode */
|
||||
DDS_WRITE(DDS_ASF, 0x0fff); /* Set amplitude to maximum */
|
||||
DDS_WRITE(DDS_CFR4H, 0x0105); /* Enable DAC calibration */
|
||||
DDS_WRITE(DDS_FUD, 0);
|
||||
now += DURATION_DAC_CAL;
|
||||
DDS_WRITE(DDS_CFR4H, 0x0005); /* Disable DAC calibration */
|
||||
DDS_WRITE(DDS_FUD, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void dds_init_sync(long long int timestamp, int bus_channel, int channel, int sync_delay)
|
||||
{
|
||||
#ifdef CONFIG_DDS_AD9914
|
||||
long long int now;
|
||||
|
||||
now = timestamp - DURATION_INIT_SYNC;
|
||||
|
||||
#ifdef CONFIG_DDS_ONEHOT_SEL
|
||||
channel = 1 << channel;
|
||||
#endif
|
||||
channel <<= 1;
|
||||
DDS_WRITE(DDS_GPIO, channel);
|
||||
|
||||
DDS_WRITE(DDS_CFR4H, 0x0105); /* Enable DAC calibration */
|
||||
DDS_WRITE(DDS_FUD, 0);
|
||||
now += DURATION_DAC_CAL;
|
||||
DDS_WRITE(DDS_CFR4H, 0x0005); /* Disable DAC calibration */
|
||||
DDS_WRITE(DDS_FUD, 0);
|
||||
DDS_WRITE(DDS_CFR2L, 0x8b00); /* Enable matched latency and sync_out*/
|
||||
DDS_WRITE(DDS_FUD, 0);
|
||||
/* Set cal with sync and set sync_out and sync_in delay */
|
||||
DDS_WRITE(DDS_USR0, 0x0840 | (sync_delay & 0x3f));
|
||||
DDS_WRITE(DDS_FUD, 0);
|
||||
DDS_WRITE(DDS_CFR4H, 0x0105); /* Enable DAC calibration */
|
||||
DDS_WRITE(DDS_FUD, 0);
|
||||
now += DURATION_DAC_CAL;
|
||||
DDS_WRITE(DDS_CFR4H, 0x0005); /* Disable DAC calibration */
|
||||
DDS_WRITE(DDS_FUD, 0);
|
||||
DDS_WRITE(DDS_CFR1H, 0x0000); /* Enable cosine output */
|
||||
DDS_WRITE(DDS_CFR2H, 0x0080); /* Enable profile mode */
|
||||
DDS_WRITE(DDS_ASF, 0x0fff); /* Set amplitude to maximum */
|
||||
DDS_WRITE(DDS_FUD, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Compensation to keep phase continuity when switching from absolute or tracking
|
||||
* to continuous phase mode. */
|
||||
static unsigned int continuous_phase_comp[CONFIG_RTIO_DDS_COUNT][CONFIG_DDS_CHANNELS_PER_BUS];
|
||||
|
||||
static void dds_set_one(long long int now, long long int ref_time,
|
||||
int bus_channel, int channel,
|
||||
unsigned int ftw, unsigned int pow, int phase_mode, unsigned int amplitude)
|
||||
{
|
||||
unsigned int channel_enc;
|
||||
|
||||
if((channel < 0) || (channel >= CONFIG_DDS_CHANNELS_PER_BUS))
|
||||
artiq_raise_from_c("DDSError", "Attempted to set invalid DDS channel", 0, 0, 0);
|
||||
if((bus_channel < CONFIG_RTIO_FIRST_DDS_CHANNEL)
|
||||
|| (bus_channel >= (CONFIG_RTIO_FIRST_DDS_CHANNEL+CONFIG_RTIO_DDS_COUNT)))
|
||||
artiq_raise_from_c("DDSError", "Attempted to use invalid DDS bus", 0, 0, 0);
|
||||
|
||||
#ifdef CONFIG_DDS_ONEHOT_SEL
|
||||
channel_enc = 1 << channel;
|
||||
#else
|
||||
channel_enc = channel;
|
||||
#endif
|
||||
DDS_WRITE(DDS_GPIO, channel_enc << 1);
|
||||
|
||||
#ifdef CONFIG_DDS_AD9858
|
||||
DDS_WRITE(DDS_FTW0, ftw & 0xff);
|
||||
DDS_WRITE(DDS_FTW1, (ftw >> 8) & 0xff);
|
||||
DDS_WRITE(DDS_FTW2, (ftw >> 16) & 0xff);
|
||||
DDS_WRITE(DDS_FTW3, (ftw >> 24) & 0xff);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DDS_AD9914
|
||||
DDS_WRITE(DDS_FTWL, ftw & 0xffff);
|
||||
DDS_WRITE(DDS_FTWH, (ftw >> 16) & 0xffff);
|
||||
#endif
|
||||
|
||||
/* We need the RTIO fine timestamp clock to be phase-locked
|
||||
* to DDS SYSCLK, and divided by an integer CONFIG_DDS_RTIO_CLK_RATIO.
|
||||
*/
|
||||
if(phase_mode == PHASE_MODE_CONTINUOUS) {
|
||||
/* Do not clear phase accumulator on FUD */
|
||||
#ifdef CONFIG_DDS_AD9858
|
||||
DDS_WRITE(DDS_CFR2, 0x00);
|
||||
#endif
|
||||
#ifdef CONFIG_DDS_AD9914
|
||||
/* Disable autoclear phase accumulator and enables OSK. */
|
||||
DDS_WRITE(DDS_CFR1L, 0x0108);
|
||||
#endif
|
||||
pow += continuous_phase_comp[bus_channel-CONFIG_RTIO_FIRST_DDS_CHANNEL][channel];
|
||||
} else {
|
||||
long long int fud_time;
|
||||
|
||||
/* Clear phase accumulator on FUD */
|
||||
#ifdef CONFIG_DDS_AD9858
|
||||
DDS_WRITE(DDS_CFR2, 0x40);
|
||||
#endif
|
||||
#ifdef CONFIG_DDS_AD9914
|
||||
/* Enable autoclear phase accumulator and enables OSK. */
|
||||
DDS_WRITE(DDS_CFR1L, 0x2108);
|
||||
#endif
|
||||
fud_time = now + 2*DURATION_WRITE;
|
||||
pow -= (ref_time - fud_time)*CONFIG_DDS_RTIO_CLK_RATIO*ftw >> (32-DDS_POW_WIDTH);
|
||||
if(phase_mode == PHASE_MODE_TRACKING)
|
||||
pow += ref_time*CONFIG_DDS_RTIO_CLK_RATIO*ftw >> (32-DDS_POW_WIDTH);
|
||||
continuous_phase_comp[bus_channel-CONFIG_RTIO_FIRST_DDS_CHANNEL][channel] = pow;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DDS_AD9858
|
||||
DDS_WRITE(DDS_POW0, pow & 0xff);
|
||||
DDS_WRITE(DDS_POW1, (pow >> 8) & 0x3f);
|
||||
#endif
|
||||
#ifdef CONFIG_DDS_AD9914
|
||||
DDS_WRITE(DDS_POW, pow);
|
||||
#endif
|
||||
#ifdef CONFIG_DDS_AD9914
|
||||
DDS_WRITE(DDS_ASF, amplitude);
|
||||
#endif
|
||||
DDS_WRITE(DDS_FUD, 0);
|
||||
}
|
||||
|
||||
struct dds_set_params {
|
||||
int bus_channel;
|
||||
int channel;
|
||||
unsigned int ftw;
|
||||
unsigned int pow;
|
||||
int phase_mode;
|
||||
unsigned int amplitude;
|
||||
};
|
||||
|
||||
static int batch_mode;
|
||||
static int batch_count;
|
||||
static long long int batch_ref_time;
|
||||
static struct dds_set_params batch[DDS_MAX_BATCH];
|
||||
|
||||
void dds_batch_enter(long long int timestamp)
|
||||
{
|
||||
if(batch_mode)
|
||||
artiq_raise_from_c("DDSError", "DDS batch entered twice", 0, 0, 0);
|
||||
batch_mode = 1;
|
||||
batch_count = 0;
|
||||
batch_ref_time = timestamp;
|
||||
}
|
||||
|
||||
void dds_batch_exit(void)
|
||||
{
|
||||
long long int now;
|
||||
int i;
|
||||
|
||||
if(!batch_mode)
|
||||
artiq_raise_from_c("DDSError", "DDS batch exited twice", 0, 0, 0);
|
||||
batch_mode = 0;
|
||||
/* + FUD time */
|
||||
now = batch_ref_time - batch_count*(DURATION_PROGRAM + DURATION_WRITE);
|
||||
for(i=0;i<batch_count;i++) {
|
||||
dds_set_one(now, batch_ref_time,
|
||||
batch[i].bus_channel, batch[i].channel,
|
||||
batch[i].ftw, batch[i].pow, batch[i].phase_mode,
|
||||
batch[i].amplitude);
|
||||
now += DURATION_PROGRAM + DURATION_WRITE;
|
||||
}
|
||||
}
|
||||
|
||||
void dds_set(long long int timestamp, int bus_channel, int channel,
|
||||
unsigned int ftw, unsigned int pow, int phase_mode, unsigned int amplitude)
|
||||
{
|
||||
if(batch_mode) {
|
||||
if(batch_count >= DDS_MAX_BATCH)
|
||||
artiq_raise_from_c("DDSError", "Too many commands in DDS batch", 0, 0, 0);
|
||||
/* timestamp parameter ignored (determined by batch) */
|
||||
batch[batch_count].bus_channel = bus_channel;
|
||||
batch[batch_count].channel = channel;
|
||||
batch[batch_count].ftw = ftw;
|
||||
batch[batch_count].pow = pow;
|
||||
batch[batch_count].phase_mode = phase_mode;
|
||||
batch[batch_count].amplitude = amplitude;
|
||||
batch_count++;
|
||||
} else {
|
||||
dds_set_one(timestamp - DURATION_PROGRAM, timestamp,
|
||||
bus_channel, channel,
|
||||
ftw, pow, phase_mode, amplitude);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CONFIG_RTIO_DDS_COUNT */
|
@ -1,70 +0,0 @@
|
||||
#ifndef __DDS_H
|
||||
#define __DDS_H
|
||||
|
||||
#include <hw/common.h>
|
||||
#include <generated/csr.h>
|
||||
#include <generated/mem.h>
|
||||
|
||||
#if ((defined CONFIG_RTIO_DDS_COUNT) && (CONFIG_RTIO_DDS_COUNT > 0))
|
||||
|
||||
/* Maximum number of commands in a batch */
|
||||
#define DDS_MAX_BATCH 16
|
||||
|
||||
#ifdef CONFIG_DDS_AD9858
|
||||
#define DDS_CFR0 0x00
|
||||
#define DDS_CFR1 0x01
|
||||
#define DDS_CFR2 0x02
|
||||
#define DDS_CFR3 0x03
|
||||
#define DDS_FTW0 0x0a
|
||||
#define DDS_FTW1 0x0b
|
||||
#define DDS_FTW2 0x0c
|
||||
#define DDS_FTW3 0x0d
|
||||
#define DDS_POW0 0x0e
|
||||
#define DDS_POW1 0x0f
|
||||
#define DDS_FUD 0x40
|
||||
#define DDS_GPIO 0x41
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DDS_AD9914
|
||||
#define DDS_CFR1L 0x01
|
||||
#define DDS_CFR1H 0x03
|
||||
#define DDS_CFR2L 0x05
|
||||
#define DDS_CFR2H 0x07
|
||||
#define DDS_CFR3L 0x09
|
||||
#define DDS_CFR3H 0x0b
|
||||
#define DDS_CFR4L 0x0d
|
||||
#define DDS_CFR4H 0x0f
|
||||
#define DDS_FTWL 0x2d
|
||||
#define DDS_FTWH 0x2f
|
||||
#define DDS_POW 0x31
|
||||
#define DDS_ASF 0x33
|
||||
#define DDS_USR0 0x6d
|
||||
#define DDS_FUD 0x80
|
||||
#define DDS_GPIO 0x81
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DDS_AD9858
|
||||
#define DDS_POW_WIDTH 14
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DDS_AD9914
|
||||
#define DDS_POW_WIDTH 16
|
||||
#endif
|
||||
|
||||
enum {
|
||||
PHASE_MODE_CONTINUOUS = 0,
|
||||
PHASE_MODE_ABSOLUTE = 1,
|
||||
PHASE_MODE_TRACKING = 2
|
||||
};
|
||||
|
||||
void dds_init(long long int timestamp, int bus_channel, int channel);
|
||||
void dds_init_sync(long long int timestamp, int bus_channel, int channel,
|
||||
int sync_delay);
|
||||
void dds_batch_enter(long long int timestamp);
|
||||
void dds_batch_exit(void);
|
||||
void dds_set(long long int timestamp, int bus_channel, int channel,
|
||||
unsigned int ftw, unsigned int pow, int phase_mode, unsigned int amplitude);
|
||||
|
||||
#endif /* CONFIG_RTIO_DDS_COUNT */
|
||||
|
||||
#endif /* __DDS_H */
|
Loading…
Reference in New Issue
Block a user