architeuthis
Meaning what exactly? You can only have one synchronous RPC at once, if that's what you mean; but why would you want to have multiple requests for the same arguments out at the same time? A synchronous RPC is not really 'background' in a meaningful sense; the kernel waits for it to return.
I would like to find a "coding style" for writing maintainable, e.g., readable, modular, and semantically logic code for ARTIQ that goes beyond the usual lining up of instructions and delays for our research group.
One idea I have is inspired by coroutines, which I envision to read like
from artiq.experiment import EnvExperiment, NumberValue, BooleanValue
from artiq.experiment import subkernel, kernel, rpc
class CoroutineExperiment(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("ttl0")
self.setattr_device("urukul0_cpld")
self.setattr_device("urukul0_ch0")
self.setattr_argument("logic_level", BooleanValue(False))
self.setattr_argument("frequency", NumberValue(10e6, unit="MHz", precision=0))
def run(self):
self.init()
# start background routines
self.poll_logic_level()
self.poll_frequency()
while True:
# constantly allow the user to update the frequency
self.user_input()
def user_input(self):
with self.interactive(title="Arguments") as interactive:
interactive.setattr_argument(
"frequency",
NumberValue(self.frequency, unit="MHz", precision=0),
)
self.frequency = interactive.frequency
@kernel
def init(self):
self.core.reset()
self.core.break_realtime()
self.urukul0_cpld.init()
self.urukul0_ch0.init()
self.urukul0_ch0.cpld.init()
self.urukul0_ch0.sw.on()
@rpc
def get_frequency(self) -> float:
return self.frequency
@rpc
def get_logic_level(self) -> bool:
return self.logic_level
@subkernel(destination=1)
def poll_frequency(self):
# store the initial frequency setting
current_frequency = self.frequency
# break out of realtime and set the frequency
self.core.break_realtime()
self.urukul0_ch0.set(current_frequency)
while True:
# get the latest frequency from the host
target_frequency = self.get_frequency()
# update the frequency if it has changed
if target_frequency != current_frequency:
self.core.break_realtime()
self.urukul0_ch0.set(target_frequency)
current_frequency = target_frequency
@subkernel(destination=2)
def poll_logic_level(self):
# store the initial logic level setting
current_logic_level = self.logic_level
# break out of realtime and set the logic level
self.core.break_realtime()
self.ttl0.output(current_logic_level)
while True:
# get the latest logic level from the host
target_logic_level = self.get_logic_level()
# update the logic level if it has changed
if target_logic_level != current_logic_level:
self.core.break_realtime()
self.ttl0.output(target_logic_level)
current_logic_level = target_logic_level
The advantage of such an approach (if it scales) would be that it allows fast parameter changes, runs functions on the core device closest to the respective EEM module (by using the @subkernel
decorator), and allows code to be organized independently from another.
Of course, if you have other suggestions on organizing a large ARTIQ codebase, I am happy to hear them. (So far I am only aware of the DAX ARTIQ codebase from Duke university).
No. The purpose of MonInj is to 'override' the values of channels without having to run any sort of experiment, or even during an experiment. You can use it at any time.
Well, for the Urukul it works that way, see set frequency in moninj.py ?
For the TTL devices, it appears to me from comm_moninj.py that we are directly sending DRTIO packages to the core device?