Hello,
I am trying to control 2 devices with a script, one of them being the artiq ttl IO hardware, which is why I have the def run function not under a kernel. I find that whenever I try to run a kernel function under a for loop that is not under a kernel, I get underflow errors. Why is this the case and how can I fix this?

This is my code for reference:

    def run(self):
        self.run_kernel()
        for i in range(0, self.numOfSteps):
            for j in range(0, self.numOfSteps):
                PLECount = self.run_kernel2(i)
                currPosX, currPosY, currPosZ = get_curr_positions(amc) # code to control other device
                step_axis(amc, 0, False, self.stepSize_in_um)           # code to control other device
            move_closed_loop_axis(amc, 0, self.initialPos1)          # code to control other device
            step_axis(amc, 1, True, self.stepSize_in_um)                # code to control other device
        print("finished")

    @kernel
    def run_kernel(self):
        self.core.reset()
        self.set_dataset("PLE", np.full(self.n_times, np.nan), broadcast=True)
        delay(1 * ms)

    @kernel
    def run_kernel2(self, i):
        with parallel:
            self.ttl6.pulse(self.pulseDuration)
            pointPLE = self.countRisingEdges()
        delay(10 * ms)
        self.mutate_dataset("PLE", i, pointPLE)
        return pointPLE

Hello, were you able to run the code without the error?

5 days later

You get an underflow only in run_kernel2 right? If so, the reason is that you enter this kernel function after some arbitrary amount of time has passed sending commands to other devices, which ARTIQ cannot control and is most likely much larger than the remaining slack after the last time you exited a kernel function.

To fix this, you need a self.core.break_realtime() at the beginning of run_kernel2, then there should be sufficient slack for the following commands. Mind though, that this means you do not have a fixed amount of time between the previous function (step_axis) and the first TTL pulse (self.ttl6.pulse) anymore. If you do require some deterministic amount of time here, you would need to calculate it by hand and adjust delays appropriately.

I was able to get the code working when I added the line: self.core.reset() under the run_kernel2(self, i) function.

Is there any difference if I use self.core.break_realtime() vsself.core.reset()?

Yes, self.core.reset() is only advised to be used when starting the system or recovering from an error where it is necessary, because it resets many different parameters. self.core.break_realtime() only adds the necessary slack.