diff --git a/.travis.yml b/.travis.yml index 1bee784bc..5e9c750b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ before_install: - pip install coveralls install: - conda build conda/artiq - - conda install $HOME/miniconda/conda-bld/linux-64/artiq-*.tar.bz2 + - conda install artiq --use-local script: - coverage run --source=artiq setup.py test - make -C doc/manual html diff --git a/artiq/__init__.py b/artiq/__init__.py index ebdb62601..7e6404c1e 100644 --- a/artiq/__init__.py +++ b/artiq/__init__.py @@ -1,4 +1,2 @@ -from artiq.language.core import * -from artiq.language.experiment import Experiment -from artiq.language.db import * -from artiq.language.units import * +from artiq import language +from artiq.language import * diff --git a/artiq/frontend/lda_controller.py b/artiq/frontend/lda_controller.py index cc70038e9..8286bc143 100755 --- a/artiq/frontend/lda_controller.py +++ b/artiq/frontend/lda_controller.py @@ -18,9 +18,11 @@ def get_argparser(): help="USB serial number of the device. " "The serial number is written on a sticker under " "the device, you should write for example " - "-d \"SN:03461\". You must prepend enough 0 for it " - "to be 5 digits." - " Omit for simulation mode.") + "-d \"SN:03461\". You must prepend enough 0s for " + "it to be 5 digits. If omitted, the first " + "available device will be used.") + parser.add_argument("--simulation", action="store_true", + help="Put the driver in simulation mode.") verbosity_args(parser) return parser @@ -28,7 +30,7 @@ def get_argparser(): def main(): args = get_argparser().parse_args() init_logger(args) - if args.device is None: + if args.simulation: lda = Ldasim() else: lda = Lda(args.device, args.product) diff --git a/artiq/frontend/novatech409b_controller.py b/artiq/frontend/novatech409b_controller.py index 30dcd75f7..57846ae95 100755 --- a/artiq/frontend/novatech409b_controller.py +++ b/artiq/frontend/novatech409b_controller.py @@ -4,6 +4,7 @@ import argparse import logging +import sys from artiq.devices.novatech409b.driver import Novatech409B from artiq.protocols.pc_rpc import simple_server_loop @@ -19,7 +20,10 @@ def get_argparser(): simple_network_args(parser, 3254) parser.add_argument( "-d", "--device", default=None, - help="serial port. Omit for simulation mode.") + help="serial port.") + parser.add_argument( + "--simulation", action="store_true", + help="Put the driver in simulation mode, even if --device is used.") verbosity_args(parser) return parser @@ -28,7 +32,12 @@ def main(): args = get_argparser().parse_args() init_logger(args) - dev = Novatech409B(args.device) + if not args.simulation and args.device is None: + print("You need to specify either --simulation or -d/--device " + "argument. Use --help for more information.") + sys.exit(1) + + dev = Novatech409B(args.device if not args.simulation else None) try: simple_server_loop( {"novatech409b": dev}, args.bind, args.port) diff --git a/artiq/frontend/pdq2_controller.py b/artiq/frontend/pdq2_controller.py index 6404d7d7f..577cdce2b 100755 --- a/artiq/frontend/pdq2_controller.py +++ b/artiq/frontend/pdq2_controller.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import argparse +import sys from artiq.devices.pdq2.driver import Pdq2 from artiq.protocols.pc_rpc import simple_server_loop @@ -12,7 +13,10 @@ def get_argparser(): simple_network_args(parser, 3252) parser.add_argument( "-d", "--device", default=None, - help="serial port. Omit for simulation mode.") + help="serial port.") + parser.add_argument( + "--simulation", action="store_true", + help="Put the driver in simulation mode, even if --device is used.") parser.add_argument( "--dump", default="pdq2_dump.bin", help="file to dump pdq2 data into, for later simulation") @@ -24,7 +28,13 @@ def main(): args = get_argparser().parse_args() init_logger(args) port = None - if args.device is None: + + if not args.simulation and args.device is None: + print("You need to specify either --simulation or -d/--device " + "argument. Use --help for more information.") + sys.exit(1) + + if args.simulation: port = open(args.dump, "wb") dev = Pdq2(url=args.device, dev=port) try: diff --git a/artiq/frontend/pxi6733_controller.py b/artiq/frontend/pxi6733_controller.py index 33948d02d..9658dc6bb 100755 --- a/artiq/frontend/pxi6733_controller.py +++ b/artiq/frontend/pxi6733_controller.py @@ -2,6 +2,7 @@ # Yann Sionneau , 2015 import argparse +import sys from artiq.protocols.pc_rpc import simple_server_loop from artiq.devices.pxi6733.driver import DAQmx, DAQmxSim @@ -12,10 +13,12 @@ def get_argparser(): parser = argparse.ArgumentParser(description="NI PXI 6733 controller") simple_network_args(parser, 3256) parser.add_argument("-C", "--channels", default=None, - help="List of channels (e.g. Dev1/ao0, Dev1/ao1:3)." - " Omit for simulation mode.") + help="List of channels (e.g. Dev1/ao0, Dev1/ao1:3).") parser.add_argument("-c", "--clock", default="PFI5", help="Input clock pin name (default: PFI5)") + parser.add_argument("--simulation", action='store_true', + help="Put the driver in simulation mode, even if " + "--channels is used.") verbosity_args(parser) return parser @@ -24,7 +27,12 @@ def main(): args = get_argparser().parse_args() init_logger(args) - if args.channels is None: + if not args.simulation and args.channels is None: + print("You need to specify either --simulation or -C/--channels " + "argument. Use --help for more information.") + sys.exit(1) + + if args.simulation: daq = DAQmxSim() else: daq = DAQmx(args.channels, diff --git a/artiq/frontend/thorlabs_tcube_controller.py b/artiq/frontend/thorlabs_tcube_controller.py index d6aea8cbf..85ccc6ebc 100755 --- a/artiq/frontend/thorlabs_tcube_controller.py +++ b/artiq/frontend/thorlabs_tcube_controller.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import argparse +import sys from artiq.devices.thorlabs_tcube.driver import Tdc, Tpz, TdcSim, TpzSim from artiq.protocols.pc_rpc import simple_server_loop @@ -14,8 +15,10 @@ def get_argparser(): choices=["TDC001", "TPZ001"]) parser.add_argument("-d", "--device", default=None, help="serial device. See documentation for how to " - "specify a USB Serial Number. Omit for simulation " - "mode.") + "specify a USB Serial Number.") + parser.add_argument("--simulation", action="store_true", + help="Put the driver in simulation mode, even if " + "--device is used.") simple_network_args(parser, 3255) verbosity_args(parser) return parser @@ -25,7 +28,12 @@ def main(): args = get_argparser().parse_args() init_logger(args) - if args.device is None: + if not args.simulation and args.device is None: + print("You need to specify either --simulation or -d/--device " + "argument. Use --help for more information.") + sys.exit(1) + + if args.simulation: if args.product == "TDC001": dev = TdcSim() elif args.product == "TPZ001": diff --git a/artiq/gateware/nist_qc1.py b/artiq/gateware/nist_qc1.py index a2d68e44f..f75f52371 100644 --- a/artiq/gateware/nist_qc1.py +++ b/artiq/gateware/nist_qc1.py @@ -4,10 +4,22 @@ from mibuild.generic_platform import * papilio_adapter_io = [ ("ext_led", 0, Pins("B:7"), IOStandard("LVTTL")), + # to feed the 125 MHz clock (preferrably from DDS SYNC_CLK) + # to the FPGA, use the xtrig pair. + # + # on papiliopro-adapter, xtrig (C:12) is connected to a GCLK + # + # on pipistrello, C:15 is the only GCLK in proximity, used as a button + # input, BTN2/PMT2 in papiliopro-adapter + # either improve the DDS box to feed 125MHz into the PMT2 pair, or: + # + # * disconnect C:15 from its periphery on the adapter board + # * bridge C:15 to the xtrig output of the transciever + # * optionally, disconnect C:12 from its periphery + ("xtrig", 0, Pins("C:12"), IOStandard("LVTTL")), ("pmt", 0, Pins("C:13"), IOStandard("LVTTL")), ("pmt", 1, Pins("C:14"), IOStandard("LVTTL")), - ("xtrig", 0, Pins("C:12"), IOStandard("LVTTL")), - ("dds_clock", 0, Pins("C:15"), IOStandard("LVTTL")), # PMT2 + ("pmt", 2, Pins("C:15"), IOStandard("LVTTL")), # rarely equipped ("ttl", 0, Pins("C:11"), IOStandard("LVTTL")), ("ttl", 1, Pins("C:10"), IOStandard("LVTTL")), diff --git a/artiq/language/__init__.py b/artiq/language/__init__.py index e69de29bb..ebdb62601 100644 --- a/artiq/language/__init__.py +++ b/artiq/language/__init__.py @@ -0,0 +1,4 @@ +from artiq.language.core import * +from artiq.language.experiment import Experiment +from artiq.language.db import * +from artiq.language.units import * diff --git a/artiq/test/coredevice.py b/artiq/test/coredevice.py new file mode 100644 index 000000000..552fc26e1 --- /dev/null +++ b/artiq/test/coredevice.py @@ -0,0 +1,120 @@ +from math import sqrt + +from artiq.language import * +from artiq.test.hardware_testbench import ExperimentCase +from artiq.coredevice.runtime_exceptions import RTIOUnderflow + + +class RTT(Experiment, AutoDB): + class DBKeys: + core = Device() + ttl_inout = Device() + rtt = Result() + + @kernel + def run(self): + self.ttl_inout.output() + delay(1*us) + with parallel: + self.ttl_inout.gate_rising(2*us) + with sequential: + delay(1*us) + t0 = now() + self.ttl_inout.pulse(1*us) + self.rtt = self.ttl_inout.timestamp() - t0 + + +class Loopback(Experiment, AutoDB): + class DBKeys: + core = Device() + loop_in = Device() + loop_out = Device() + rtt = Result() + + @kernel + def run(self): + with parallel: + self.loop_in.gate_rising(2*us) + with sequential: + delay(1*us) + t0 = now() + self.loop_out.pulse(1*us) + self.rtt = self.loop_in.timestamp() - t0 + + +class PulseRate(Experiment, AutoDB): + class DBKeys: + core = Device() + loop_out = Device() + pulse_rate = Result() + + @kernel + def run(self): + dt = time_to_cycles(1000*ns) + while True: + try: + for i in range(1000): + self.loop_out.pulse(cycles_to_time(dt)) + delay(cycles_to_time(dt)) + except RTIOUnderflow: + dt += 1 + self.core.break_realtime() + else: + self.pulse_rate = cycles_to_time(2*dt) + break + + +class CoredeviceTest(ExperimentCase): + def test_rtt(self): + rtt = self.execute(RTT)["rtt"] + print(rtt) + self.assertGreater(rtt, 0*ns) + self.assertLess(rtt, 100*ns) + + def test_loopback(self): + rtt = self.execute(Loopback)["rtt"] + print(rtt) + self.assertGreater(rtt, 0*ns) + self.assertLess(rtt, 40*ns) + + def test_pulse_rate(self): + rate = self.execute(PulseRate)["pulse_rate"] + print(rate) + self.assertGreater(rate, 100*ns) + self.assertLess(rate, 2500*ns) + + +class RPCTiming(Experiment, AutoDB): + class DBKeys: + core = Device() + repeats = Argument(100) + rpc_time_mean = Result() + rpc_time_stddev = Result() + + def nop(self, x): + pass + + @kernel + def bench(self): + self.ts = [0. for _ in range(self.repeats)] + for i in range(self.repeats): + t1 = self.core.get_rtio_time() + self.nop(1) + t2 = self.core.get_rtio_time() + self.ts[i] = t2 - t1 + + def run(self): + self.bench() + mean = sum(self.ts)/self.repeats + self.rpc_time_stddev = sqrt( + sum([(t - mean)**2 for t in self.ts])/self.repeats)*s + self.rpc_time_mean = mean*s + + +class RPCTest(ExperimentCase): + def test_rpc_timing(self): + res = self.execute(RPCTiming) + print(res) + self.assertGreater(res["rpc_time_mean"], 100*ns) + self.assertLess(res["rpc_time_mean"], 10*ms) + self.assertLess(res["rpc_time_stddev"], 1*ms) diff --git a/artiq/test/hardware_testbench.py b/artiq/test/hardware_testbench.py new file mode 100644 index 000000000..bdb1e2e82 --- /dev/null +++ b/artiq/test/hardware_testbench.py @@ -0,0 +1,43 @@ +import os +import sys +import unittest +import logging + +from artiq.language import * +from artiq.protocols.file_db import FlatFileDB +from artiq.master.worker_db import DBHub, ResultDB +from artiq.frontend.artiq_run import ( + DummyScheduler, DummyWatchdog, SimpleParamLogger) + + +artiq_root = os.getenv("ARTIQ_ROOT") +logger = logging.getLogger(__name__) + + +@unittest.skipUnless(artiq_root, "no ARTIQ_ROOT") +class ExperimentCase(unittest.TestCase): + def setUp(self): + self.ddb = FlatFileDB(os.path.join(artiq_root, "ddb.pyon")) + self.pdb = FlatFileDB(os.path.join(artiq_root, "pdb.pyon")) + self.rdb = ResultDB(lambda description: None, lambda mod: None) + self.dbh = DBHub(self.ddb, self.pdb, self.rdb) + + def execute(self, cls, **kwargs): + expid = { + "file": sys.modules[cls.__module__].__file__, + "experiment": cls.__name__, + "arguments": kwargs + } + sched = DummyScheduler(expid) + try: + try: + exp = cls(self.dbh, scheduler=sched, **kwargs) + except KeyError as e: + # skip if ddb does not match requirements + raise unittest.SkipTest(*e.args) + self.rdb.build() + exp.run() + exp.analyze() + return self.rdb.data.read + finally: + self.dbh.close_devices() diff --git a/benchmarks/all.py b/benchmarks/all.py deleted file mode 100644 index d0a34263b..000000000 --- a/benchmarks/all.py +++ /dev/null @@ -1,18 +0,0 @@ -from artiq import * - -import pulse_rate, rtio_skew, rpc_timing - - -_exps = [pulse_rate.PulseRate, rtio_skew.RTIOSkew, rpc_timing.RPCTiming] - -class AllBenchmarks(Experiment, AutoDB): - """All benchmarks""" - - def build(self): - self.se = [] - for exp in _exps: - self.se.append(exp(self.dbh)) - - def run(self): - for se in self.se: - se.run() diff --git a/benchmarks/ddb.pyon b/benchmarks/ddb.pyon deleted file mode 100644 index 8dd20362c..000000000 --- a/benchmarks/ddb.pyon +++ /dev/null @@ -1,28 +0,0 @@ -{ - "comm": { - "type": "local", - "module": "artiq.coredevice.comm_tcp", - "class": "Comm", - "arguments": {"host": "192.168.0.42"} - }, - "core": { - "type": "local", - "module": "artiq.coredevice.core", - "class": "Core", - "arguments": {} - }, - - "pmt0": { - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLInOut", - "arguments": {"channel": 0} - }, - - "ttl0": { - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": 2} - }, -} diff --git a/benchmarks/pdb.pyon b/benchmarks/pdb.pyon deleted file mode 100644 index 0967ef424..000000000 --- a/benchmarks/pdb.pyon +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/benchmarks/pulse_rate.py b/benchmarks/pulse_rate.py deleted file mode 100644 index 4de78fa06..000000000 --- a/benchmarks/pulse_rate.py +++ /dev/null @@ -1,26 +0,0 @@ -from artiq import * -from artiq.coredevice.runtime_exceptions import RTIOUnderflow - - -class PulseRate(Experiment, AutoDB): - """Sustained pulse rate""" - - class DBKeys: - core = Device() - ttl0 = Device() - pulse_rate = Result() - - @kernel - def run(self): - T = time_to_cycles(100*ns) - while True: - try: - for i in range(1000): - self.ttl0.pulse(cycles_to_time(T)) - delay(cycles_to_time(T)) - except RTIOUnderflow: - T += 1 - self.core.break_realtime() - else: - self.pulse_rate = cycles_to_time(2*T) - break diff --git a/benchmarks/rpc_timing.py b/benchmarks/rpc_timing.py deleted file mode 100644 index 33e8152d9..000000000 --- a/benchmarks/rpc_timing.py +++ /dev/null @@ -1,32 +0,0 @@ -from math import sqrt - -from artiq import * - - -class RPCTiming(Experiment, AutoDB): - """RPC timing""" - - class DBKeys: - core = Device() - repeats = Argument(100) - rpc_time_mean = Result() - rpc_time_stddev = Result() - - def nop(self, x): - pass - - @kernel - def bench(self): - self.ts = [0.0 for _ in range(self.repeats)] - for i in range(self.repeats): - t1 = self.core.get_rtio_time() - self.nop(10) - t2 = self.core.get_rtio_time() - self.ts[i] = t2 - t1 - - def run(self): - self.bench() - mean = sum(self.ts)/self.repeats - self.rpc_time_stddev = sqrt( - sum([(t - mean)**2 for t in self.ts])/self.repeats)*s - self.rpc_time_mean = mean*s diff --git a/benchmarks/rtio_skew.py b/benchmarks/rtio_skew.py deleted file mode 100644 index a5770864b..000000000 --- a/benchmarks/rtio_skew.py +++ /dev/null @@ -1,29 +0,0 @@ -from artiq import * - - -class PulseNotReceived(Exception): - pass - - -class RTIOSkew(Experiment, AutoDB): - """RTIO skew""" - - class DBKeys: - core = Device() - pmt0 = Device() - rtio_skew = Result() - - @kernel - def run(self): - self.pmt0.output() - delay(1*us) - with parallel: - self.pmt0.gate_rising(10*us) - with sequential: - delay(5*us) - out_t = now() - self.pmt0.pulse(5*us) - in_t = self.pmt0.timestamp() - if in_t < 0*s: - raise PulseNotReceived - self.rtio_skew = out_t - in_t diff --git a/doc/manual/developing_a_ndsp.rst b/doc/manual/developing_a_ndsp.rst index 98e24e15e..0bc867892 100644 --- a/doc/manual/developing_a_ndsp.rst +++ b/doc/manual/developing_a_ndsp.rst @@ -177,7 +177,7 @@ General guidelines * Use new-style formatting (``str.format``) except for logging where it is not well supported, and double quotes for strings. * The device identification (e.g. serial number, or entry in ``/dev``) to attach to must be passed as a command-line parameter to the controller. We suggest using ``-d`` and ``--device`` as parameter name. * Controllers must be able to operate in "simulation" mode, where they behave properly even if the associated hardware is not connected. For example, they can print the data to the console instead of sending it to the device, or dump it into a file. -* We suggest that the simulation mode is entered whenever the ``-d/--device`` option is omitted. +* The simulation mode is entered whenever the ``--simulation`` option is specified. * Keep command line parameters consistent across clients/controllers. When adding new command line options, look for a client/controller that does a similar thing and follow its use of ``argparse``. If the original client/controller could use ``argparse`` in a better way, improve it. * Use docstrings for all public methods of the driver (note that those will be retrieved by ``artiq_rpctool``). * Choose a free default TCP port and add it to the default port list in this manual. diff --git a/doc/manual/fpga_board_ports.rst b/doc/manual/fpga_board_ports.rst index 13ba6b54a..442c3e691 100644 --- a/doc/manual/fpga_board_ports.rst +++ b/doc/manual/fpga_board_ports.rst @@ -13,20 +13,24 @@ The low-cost Pipistrello FPGA board can be used as a lower-cost but slower alter When plugged to an adapter, the NIST QC1 hardware can be used. The TTL lines are mapped to RTIO channels as follows: -+--------------+----------+-----------------+ -| RTIO channel | TTL line | Capability | -+==============+==========+=================+ -| 0 | PMT0 | Input only | -+--------------+----------+-----------------+ -| 1 | PMT1 | Input only | -+--------------+----------+-----------------+ -| 2-18 | TTL0-16 | Output only | -+--------------+----------+-----------------+ -| 19-21 | LEDs | Output only | -+--------------+----------+-----------------+ -| 22 | TTL2 | Output only | -+--------------+----------+-----------------+ ++--------------+----------+------------+ +| RTIO channel | TTL line | Capability | ++==============+==========+============+ +| 0 | PMT0 | Input | ++--------------+----------+------------+ +| 1 | PMT1 | Input | ++--------------+----------+------------+ +| 2-17 | TTL0-15 | Output | ++--------------+----------+------------+ +| 18 | EXT_LED | Output | ++--------------+----------+------------+ +| 19 | USER_LED | Output | ++--------------+----------+------------+ +| 20 | DDS | Output | ++--------------+----------+------------+ The input only limitation on channels 0 and 1 comes from the QC-DAQ adapter. When the adapter is not used (and physically unplugged from the Pipistrello board), the corresponding pins on the Pipistrello can be used as outputs. Do not configure these channels as outputs when the adapter is plugged, as this would cause electrical contention. -The board can accept an external RTIO clock connected to PMT2. +The board can accept an external RTIO clock connected to PMT2. If the DDS box +does not drive the PMT2 pair, use XTRIG and patch the XTRIG transciever output +on the adapter board onto C:15 disconnecting PMT2. diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index 6f5e6f632..b3bc058c3 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -24,7 +24,7 @@ class _RTIOCRG(Module, AutoCSR): self.specials += Instance("DCM_CLKGEN", p_CLKFXDV_DIVIDE=2, p_CLKFX_DIVIDE=f.denominator, - p_CLKFX_MD_MAX=1.6, + p_CLKFX_MD_MAX=float(f), p_CLKFX_MULTIPLY=f.numerator, p_CLKIN_PERIOD=1e9/clk_freq, p_SPREAD_SPECTRUM="NONE", @@ -34,7 +34,7 @@ class _RTIOCRG(Module, AutoCSR): i_FREEZEDCM=0, i_RST=ResetSignal()) - rtio_external_clk = platform.request("dds_clock") + rtio_external_clk = platform.request("pmt", 2) platform.add_period_constraint(rtio_external_clk, 8.0) self.specials += Instance("BUFGMUX", i_I0=rtio_internal_clk, @@ -83,6 +83,8 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd self.submodules.leds = gpio.GPIOOut(Cat( platform.request("user_led", 0), platform.request("user_led", 1), + platform.request("user_led", 2), + platform.request("user_led", 3), )) self.comb += [ @@ -95,11 +97,8 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd for i in range(2): phy = ttl_simple.Inout(platform.request("pmt", i)) self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=512)) - - phy = ttl_simple.Inout(platform.request("xtrig", 0)) - self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) + rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=512, + ofifo_depth=4)) for i in range(16): phy = ttl_simple.Output(platform.request("ttl", i)) @@ -110,10 +109,10 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd self.submodules += phy rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=4)) - for i in range(2, 5): - phy = ttl_simple.Output(platform.request("user_led", i)) - self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=4)) + phy = ttl_simple.Output(platform.request("user_led", 4)) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=4)) + self.add_constant("RTIO_TTL_COUNT", len(rtio_channels)) self.add_constant("RTIO_DDS_CHANNEL", len(rtio_channels))