From 34aacd3c5f8eb0a2ad7402d803e5306352f438ea Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 8 Jul 2015 17:22:43 +0200 Subject: [PATCH] complete AD9914 support (no programmable modulus, untested) --- artiq/coredevice/dds.py | 27 ++++++-- artiq/gateware/ad9xxx.py | 8 +-- doc/manual/core_drivers_reference.rst | 3 + examples/master/ddb.pyon | 6 +- soc/runtime/bridge.c | 4 +- soc/runtime/dds.c | 93 +++++++++++++++++++++++---- soc/runtime/dds.h | 32 ++++++++- soc/runtime/main.c | 24 +++---- soc/runtime/test_mode.c | 75 +++++++++++++++++---- soc/targets/artiq_pipistrello.py | 1 + 10 files changed, 221 insertions(+), 52 deletions(-) diff --git a/artiq/coredevice/dds.py b/artiq/coredevice/dds.py index 09c4cb78b..323209479 100644 --- a/artiq/coredevice/dds.py +++ b/artiq/coredevice/dds.py @@ -46,11 +46,14 @@ class DDSBus(AutoDB): syscall("dds_batch_exit") -class DDS(AutoDB): +class _DDSGeneric(AutoDB): """Core device Direct Digital Synthesis (DDS) 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``. + :param sysclk: DDS system frequency. :param channel: channel number of the DDS device to control. """ @@ -80,13 +83,13 @@ class DDS(AutoDB): def turns_to_pow(self, turns): """Returns the phase offset word corresponding to the given phase in turns.""" - return round(turns*2**14) + return round(turns*2**self.pow_width) @portable def pow_to_turns(self, pow): """Returns the phase in turns corresponding to the given phase offset word.""" - return pow/2**14 + return pow/2**self.pow_width @kernel def init(self): @@ -119,7 +122,9 @@ class DDS(AutoDB): def set_mu(self, frequency, phase=0, phase_mode=_PHASE_MODE_DEFAULT): """Sets the DDS channel to the specified frequency and phase. - This uses machine units (FTW and POW). + This uses machine units (FTW and POW). The frequency tuning word width + is 32, whereas the phase offset word width depends on the type of DDS + chip and can be retrieved via the ``pow_width`` attribute. :param frequency: frequency to generate. :param phase: adds an offset, in turns, to the phase. @@ -129,10 +134,22 @@ class DDS(AutoDB): if phase_mode == _PHASE_MODE_DEFAULT: phase_mode = self.phase_mode syscall("dds_set", now_mu(), self.channel, - frequency, round(phase*2**14), phase_mode) + frequency, round(phase*2**self.pow_width), phase_mode) @kernel def set(self, frequency, phase=0, phase_mode=_PHASE_MODE_DEFAULT): """Like ``set_mu``, but uses Hz and turns.""" self.set_mu(self.frequency_to_ftw(frequency), self.turns_to_pow(phase), phase_mode) + + +class AD9858(_DDSGeneric): + """Driver for AD9858 DDS chips. See ``_DDSGeneric`` for a description + of the functionality.""" + pow_width = 14 + + +class AD9914(_DDSGeneric): + """Driver for AD9914 DDS chips. See ``_DDSGeneric`` for a description + of the functionality.""" + pow_width = 16 diff --git a/artiq/gateware/ad9xxx.py b/artiq/gateware/ad9xxx.py index f774f63ed..aa087053f 100644 --- a/artiq/gateware/ad9xxx.py +++ b/artiq/gateware/ad9xxx.py @@ -13,7 +13,7 @@ class AD9xxx(Module): Write to address 2**flen(pads.a) to pulse the FUD signal. Address 2**flen(pads.a)+1 is a GPIO register that controls the - sel and reset signals. sel is mapped to the lower bits, followed by reset. + sel and reset signals. rst is mapped to bit 0, followed by sel. Write timing: Address is set one cycle before assertion of we_n. @@ -58,11 +58,11 @@ class AD9xxx(Module): gpio = Signal(flen(pads.sel) + 1) gpio_load = Signal() self.sync += If(gpio_load, gpio.eq(bus.dat_w)) - self.comb += pads.sel.eq(gpio), if hasattr(pads, "rst"): - self.comb += pads.rst.eq(gpio[-1]) + self.comb += pads.rst.eq(gpio[0]) else: - self.comb += pads.rst_n.eq(~gpio[-1]) + self.comb += pads.rst_n.eq(~gpio[0]) + self.comb += pads.sel.eq(gpio[1:]) bus_r_gpio = Signal() self.comb += If(bus_r_gpio, diff --git a/doc/manual/core_drivers_reference.rst b/doc/manual/core_drivers_reference.rst index 0c9c38301..56fca095b 100644 --- a/doc/manual/core_drivers_reference.rst +++ b/doc/manual/core_drivers_reference.rst @@ -12,6 +12,9 @@ These drivers are for peripherals closely integrated into the core device, which :mod:`artiq.coredevice.dds` module ---------------------------------- +.. autoclass:: artiq.coredevice.dds._DDSGeneric + :members: + .. automodule:: artiq.coredevice.dds :members: diff --git a/examples/master/ddb.pyon b/examples/master/ddb.pyon index 81903fca5..ff945141a 100644 --- a/examples/master/ddb.pyon +++ b/examples/master/ddb.pyon @@ -65,19 +65,19 @@ "dds0": { "type": "local", "module": "artiq.coredevice.dds", - "class": "DDS", + "class": "AD9858", "arguments": {"sysclk": 1e9, "channel": 0} }, "dds1": { "type": "local", "module": "artiq.coredevice.dds", - "class": "DDS", + "class": "AD9858", "arguments": {"sysclk": 1e9, "channel": 1} }, "dds2": { "type": "local", "module": "artiq.coredevice.dds", - "class": "DDS", + "class": "AD9858", "arguments": {"sysclk": 1e9, "channel": 2} }, diff --git a/soc/runtime/bridge.c b/soc/runtime/bridge.c index c61875236..eb16587de 100644 --- a/soc/runtime/bridge.c +++ b/soc/runtime/bridge.c @@ -66,7 +66,7 @@ void bridge_main(void) struct msg_brg_dds_sel *msg; msg = (struct msg_brg_dds_sel *)umsg; - dds_write(DDS_GPIO, msg->channel); + dds_write(DDS_GPIO, msg->channel << 1); mailbox_acknowledge(); break; } @@ -74,7 +74,7 @@ void bridge_main(void) unsigned int g; g = dds_read(DDS_GPIO); - dds_write(DDS_GPIO, g | (1 << 7)); + dds_write(DDS_GPIO, g | 1); dds_write(DDS_GPIO, g); mailbox_acknowledge(); diff --git a/soc/runtime/dds.c b/soc/runtime/dds.c index 326c48845..5f9f8650e 100644 --- a/soc/runtime/dds.c +++ b/soc/runtime/dds.c @@ -7,9 +7,24 @@ #include "dds.h" #define DURATION_WRITE (5 << RTIO_FINE_TS_WIDTH) + +#if defined 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 DDS_AD9914 +/* Assume 16-bit bus */ +/* DAC calibration takes max. 135us as per datasheet. Take a good margin. */ +#define DURATION_DAC_CAL (30000 << RTIO_FINE_TS_WIDTH) +/* not counting final FUD */ +#define DURATION_INIT (8*DURATION_WRITE + DURATION_DAC_CAL) +#define DURATION_PROGRAM (5*DURATION_WRITE) /* not counting FUD */ + +#else +#error Unknown DDS configuration +#endif + #define DDS_WRITE(addr, data) do { \ rtio_o_address_write(addr); \ rtio_o_data_write(data); \ @@ -39,16 +54,43 @@ void dds_init(long long int timestamp, int channel) now = timestamp - DURATION_INIT; +#ifdef DDS_ONEHOT_SEL + channel = 1 << channel; +#endif + channel <<= 1; DDS_WRITE(DDS_GPIO, channel); - DDS_WRITE(DDS_GPIO, channel | (1 << 5)); + DDS_WRITE(DDS_GPIO, channel | 1); /* reset */ DDS_WRITE(DDS_GPIO, channel); - DDS_WRITE(0x00, 0x78); - DDS_WRITE(0x01, 0x00); - DDS_WRITE(0x02, 0x00); - DDS_WRITE(0x03, 0x00); - +#ifdef 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 DDS_AD9914 + /* + * Enable cosine output (to match AD9858 behavior) + * Enable DAC calibration + * Leave SYNCLK enabled and PLL/divider disabled + */ + DDS_WRITE(DDS_CFR1L, 0x0008); + DDS_WRITE(DDS_CFR1H, 0x0000); + DDS_WRITE(DDS_CFR4H, 0x0105); + DDS_WRITE(DDS_FUD, 0); + /* Disable DAC calibration */ + now += DURATION_DAC_CAL; + DDS_WRITE(DDS_CFR4H, 0x0005); + DDS_WRITE(DDS_FUD, 0); +#endif } /* Compensation to keep phase continuity when switching from absolute or tracking @@ -58,38 +100,67 @@ static unsigned int continuous_phase_comp[DDS_CHANNEL_COUNT]; static void dds_set_one(long long int now, long long int ref_time, unsigned int channel, unsigned int ftw, unsigned int pow, int phase_mode) { + unsigned int channel_enc; + if(channel >= DDS_CHANNEL_COUNT) { log("Attempted to set invalid DDS channel"); return; } - DDS_WRITE(DDS_GPIO, channel); +#ifdef DDS_ONEHOT_SEL + channel_enc = 1 << channel; +#else + channel_enc = channel; +#endif + DDS_WRITE(DDS_GPIO, channel_enc << 1); +#ifdef 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 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 DDS_RTIO_CLK_RATIO. */ if(phase_mode == PHASE_MODE_CONTINUOUS) { /* Do not clear phase accumulator on FUD */ - DDS_WRITE(0x02, 0x00); +#ifdef DDS_AD9858 + DDS_WRITE(DDS_CFR2, 0x00); +#endif +#ifdef DDS_AD9914 + DDS_WRITE(DDS_CFR1L, 0x0008); +#endif pow += continuous_phase_comp[channel]; } else { long long int fud_time; /* Clear phase accumulator on FUD */ - DDS_WRITE(0x02, 0x40); +#ifdef DDS_AD9858 + DDS_WRITE(DDS_CFR2, 0x40); +#endif +#ifdef DDS_AD9914 + DDS_WRITE(DDS_CFR1L, 0x2008); +#endif fud_time = now + 2*DURATION_WRITE; - pow -= (ref_time - fud_time)*DDS_RTIO_CLK_RATIO*ftw >> 18; + pow -= (ref_time - fud_time)*DDS_RTIO_CLK_RATIO*ftw >> (32-DDS_POW_WIDTH); if(phase_mode == PHASE_MODE_TRACKING) - pow += ref_time*DDS_RTIO_CLK_RATIO*ftw >> 18; + pow += ref_time*DDS_RTIO_CLK_RATIO*ftw >> (32-DDS_POW_WIDTH); continuous_phase_comp[channel] = pow; } +#ifdef DDS_AD9858 DDS_WRITE(DDS_POW0, pow & 0xff); DDS_WRITE(DDS_POW1, (pow >> 8) & 0x3f); +#endif +#ifdef DDS_AD9914 + DDS_WRITE(DDS_POW, pow); +#endif DDS_WRITE(DDS_FUD, 0); } diff --git a/soc/runtime/dds.h b/soc/runtime/dds.h index e145b5b01..6ca9d1c1e 100644 --- a/soc/runtime/dds.h +++ b/soc/runtime/dds.h @@ -2,12 +2,17 @@ #define __DDS_H #include +#include #include /* Maximum number of commands in a batch */ #define DDS_MAX_BATCH 16 -/* DDS core registers */ +#ifdef 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 @@ -16,6 +21,31 @@ #define DDS_POW1 0x0f #define DDS_FUD 0x40 #define DDS_GPIO 0x41 +#endif + +#ifdef 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_FUD 0x80 +#define DDS_GPIO 0x81 +#endif + +#ifdef DDS_AD9858 +#define DDS_POW_WIDTH 14 +#endif + +#ifdef DDS_AD9914 +#define DDS_POW_WIDTH 16 +#endif enum { PHASE_MODE_CONTINUOUS = 0, diff --git a/soc/runtime/main.c b/soc/runtime/main.c index 41433d264..4feb8c9a0 100644 --- a/soc/runtime/main.c +++ b/soc/runtime/main.c @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -32,7 +31,6 @@ static void common_init(void) { - clock_init(); brg_start(); brg_ddsinitall(); kloader_stop(); @@ -211,34 +209,31 @@ static void regular_main(void) static void blink_led(void) { - int i, ev, p; + int i; + long long int t; - p = identifier_frequency_read()/10; - time_init(); for(i=0;i<3;i++) { leds_out_write(1); - while(!elapsed(&ev, p)); + t = clock_get_ms(); + while(clock_get_ms() < t + 250); leds_out_write(0); - while(!elapsed(&ev, p)); + t = clock_get_ms(); + while(clock_get_ms() < t + 250); } } static int check_test_mode(void) { char c; + long long int t; - timer0_en_write(0); - timer0_reload_write(0); - timer0_load_write(identifier_frequency_read() >> 2); - timer0_en_write(1); - timer0_update_value_write(1); - while(timer0_value_read()) { + t = clock_get_ms(); + while(clock_get_ms() < t + 1000) { if(readchar_nonblock()) { c = readchar(); if((c == 't')||(c == 'T')) return 1; } - timer0_update_value_write(1); } return 0; } @@ -251,6 +246,7 @@ int main(void) puts("ARTIQ runtime built "__DATE__" "__TIME__"\n"); + clock_init(); puts("Press 't' to enter test mode..."); blink_led(); diff --git a/soc/runtime/test_mode.c b/soc/runtime/test_mode.c index fbdc0ce52..3c0ce02f0 100644 --- a/soc/runtime/test_mode.c +++ b/soc/runtime/test_mode.c @@ -10,6 +10,7 @@ #include "dds.h" #include "flash_storage.h" #include "bridge_ctl.h" +#include "clock.h" #include "test_mode.h" static void leds(char *value) @@ -114,6 +115,9 @@ static void ddssel(char *n) return; } +#ifdef DDS_ONEHOT_SEL + n2 = 1 << n2; +#endif brg_ddssel(n2); } @@ -157,7 +161,12 @@ static void ddsr(char *addr) return; } +#ifdef DDS_AD9858 printf("0x%02x\n", brg_ddsread(addr2)); +#endif +#ifdef DDS_AD9914 + printf("0x%04x\n", brg_ddsread(addr2)); +#endif } static void ddsfud(void) @@ -186,11 +195,22 @@ static void ddsftw(char *n, char *ftw) return; } +#ifdef DDS_ONEHOT_SEL + n2 = 1 << n2; +#endif brg_ddssel(n2); + +#ifdef DDS_AD9858 brg_ddswrite(DDS_FTW0, ftw2 & 0xff); brg_ddswrite(DDS_FTW1, (ftw2 >> 8) & 0xff); brg_ddswrite(DDS_FTW2, (ftw2 >> 16) & 0xff); brg_ddswrite(DDS_FTW3, (ftw2 >> 24) & 0xff); +#endif +#ifdef DDS_AD9914 + brg_ddswrite(DDS_FTWL, ftw2 & 0xffff); + brg_ddswrite(DDS_FTWH, (ftw2 >> 16) & 0xffff); +#endif + brg_ddsfud(); } @@ -199,15 +219,34 @@ static void ddsreset(void) brg_ddsreset(); } +#ifdef DDS_AD9858 static void ddsinit(void) { brg_ddsreset(); - brg_ddswrite(0x00, 0x78); - brg_ddswrite(0x01, 0x00); - brg_ddswrite(0x02, 0x00); - brg_ddswrite(0x03, 0x00); + brg_ddswrite(DDS_CFR0, 0x78); + brg_ddswrite(DDS_CFR1, 0x00); + brg_ddswrite(DDS_CFR2, 0x00); + brg_ddswrite(DDS_CFR3, 0x00); brg_ddsfud(); } +#endif + +#ifdef DDS_AD9914 +static void ddsinit(void) +{ + long long int t; + + brg_ddsreset(); + brg_ddswrite(DDS_CFR1L, 0x0008); + brg_ddswrite(DDS_CFR1H, 0x0000); + brg_ddswrite(DDS_CFR4H, 0x0105); + brg_ddswrite(DDS_FUD, 0); + t = clock_get_ms(); + while(clock_get_ms() < t + 2); + brg_ddswrite(DDS_CFR4H, 0x0005); + brg_ddsfud(); +} +#endif static void ddstest_one(unsigned int i) { @@ -223,15 +262,27 @@ static void ddstest_one(unsigned int i) for(j=0; j<12; j++) { f = v[j]; - brg_ddswrite(0x0a, f & 0xff); - brg_ddswrite(0x0b, (f >> 8) & 0xff); - brg_ddswrite(0x0c, (f >> 16) & 0xff); - brg_ddswrite(0x0d, (f >> 24) & 0xff); +#ifdef DDS_AD9858 + brg_ddswrite(DDS_FTW0, f & 0xff); + brg_ddswrite(DDS_FTW1, (f >> 8) & 0xff); + brg_ddswrite(DDS_FTW2, (f >> 16) & 0xff); + brg_ddswrite(DDS_FTW3, (f >> 24) & 0xff); +#endif +#ifdef DDS_AD9914 + brg_ddswrite(DDS_FTWL, f & 0xffff); + brg_ddswrite(DDS_FTWH, (f >> 16) & 0xffff); +#endif brg_ddsfud(); - g = brg_ddsread(0x0a); - g |= brg_ddsread(0x0b) << 8; - g |= brg_ddsread(0x0c) << 16; - g |= brg_ddsread(0x0d) << 24; +#ifdef DDS_AD9858 + g = brg_ddsread(DDS_FTW0); + g |= brg_ddsread(DDS_FTW1) << 8; + g |= brg_ddsread(DDS_FTW2) << 16; + g |= brg_ddsread(DDS_FTW3) << 24; +#endif +#ifdef DDS_AD9914 + g = brg_ddsread(DDS_FTWL); + g |= brg_ddsread(DDS_FTWH) << 16; +#endif if(g != f) printf("readback fail on DDS %d, 0x%08x != 0x%08x\n", i, g, f); } diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index 77a9e4657..fd09ed373 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -119,6 +119,7 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd self.add_constant("RTIO_DDS_CHANNEL", len(rtio_channels)) self.add_constant("DDS_CHANNEL_COUNT", 8) + self.add_constant("DDS_AD9858") phy = dds.AD9858(platform.request("dds"), 8) self.submodules += phy rtio_channels.append(rtio.Channel.from_phy(phy,