Hi all, first post here. Running with ARTIQ 7 on Kasli-EEM Controller.

I'm hoping for a little help trying to understand how to read out a TTL channel very quickly as I believe I have some confusion over how things work.

For context, I have a PMT running into a TTL input and it works fine. Generally, I just use something like the following code for my needs:

...
@kernel
def readout(self):
    pmt_active = self.pmt.gate_rising(2 * ms)
    total_counts = self.pmt.count(pmt_active)
    ...

Recently, I'd like to bin the exposure instead; i.e. I would like to break my 2 ms exposure time into many short bins of say 1-20 us and possibly insert some logic between them. I am constantly running into RTIO Underflow errors unless I insert delays of about 5 us between bins. This is far too long, so I am wondering if there are some tricks for speeding things up. One idea I had come across was to split the PMT signal into 2 TTL inputs and read them out alternatingly:

...
@kernel
def fast_readout(self):
    for _ in range(200):
        pmt1_active = self.pmt1.gate_rising(10*us)
        total_counts+=self.pmt1.count(pmt1_active)
        delay(100*ns)
        pmt2_active = self.pmt2.gate_rising(10*us)
        total_counts+=self.pmt2.count(pmt2_active)
        delay(100*ns)
    ...

To my surprise, this code produces RTIO Underflow errors until the delay between the exposure periods is... 5 us, exactly the same as with a single input channel. My understanding was that the gate_rising() call would advance the timeline cursor to the end of the active period at which point the next exposure would be scheduled and so on... but I must be mistaken. I had been basing this understanding off of the RTI/O Docs, of course;
https://m-labs.hk/artiq/manual-release-3/rtio.html#input-channels-and-events

I am wondering if anyone can weigh in as to why my approach doesn't work? Do I even need to bother splitting the PMT into 2 channels in the first place?

It seems like you're using the normal TTL counter - using edge_counter (you'll have to reflash your gateware) would do what you want more happily. With the normal TTL counter, the "count" events block until completion and happen in software (vs gateware for edge counter), making it bothersome/expensive to schedule them.

I will say that, from my experience, 1-20us bins is gonna be kind of hard, depending on what sort of logic you're going to insert.
One option is to first schedule multiple bins to give you enough slack, such that you're always processing an OLDER bin. Of course, at the end, you'll have extra bin events, but you can just consume then throw them away.
As an example:

# schedule some number of count bins in advance
self.pmt1.gate_rising(bin_time_us)   # bin 0
self.pmt1.gate_rising(bin_time_us)   # bin 1

for _ in range(200):
   counts_tmp = self.pmt1.fetch_count()   # get counts from bin 0
   self.pmt1.gate_rising(bin_time_us)   # bin 2
   logic_func()   # do logic