from migen import *
from migen.genlib.cdc import MultiReg, PulseSynchronizer
from migen.genlib.fsm import FSM

from artiq.gateware.rtio import rtlink
from artiq.gateware.grabber import deserializer_7series
from artiq.gateware.grabber.core import *


__all__ = ["Grabber"]


class Synchronizer(Module):
    def __init__(self, roi_engines):
        counts_in = [roi_engine.out.count for roi_engine in roi_engines]

        # This assumes all ROI engines update at the same time.
        self.update = Signal()
        # stays valid until the next frame after self.update is pulsed.
        self.counts = [Signal.like(count) for count in counts_in]

        # # #

        self.comb += [o.eq(i) for i, o in zip(counts_in, self.counts)]

        ps = PulseSynchronizer("cl", "sys")
        self.submodules += ps
        self.comb += ps.i.eq(roi_engines[0].out.update)
        self.sync += self.update.eq(ps.o)


class Serializer(Module):
    def __init__(self, update, counts, rtlink_i):
        self.gate = Signal(len(counts))

        # # #

        gate = Signal(len(counts))
        sentinel = 2**(len(rtlink_i.data) - 1)

        fsm = ClockDomainsRenamer("rio")(FSM())
        self.submodules += fsm

        fsm.act("INIT",
            rtlink_i.data.eq(sentinel),
            If(update & (self.gate != 0),
                NextValue(gate, self.gate),
                rtlink_i.stb.eq(1),
                NextState(0)
            )
        )
        for n, count in enumerate(counts):
            last = n == len(counts)-1
            fsm.act(n,
                rtlink_i.data.eq(count),
                rtlink_i.stb.eq(gate[n]),
                NextState("INIT" if last else n+1)
            )


class Grabber(Module):
    def __init__(self, pins, roi_engine_count=16, res_width=12, count_shift=0):
        self.config = rtlink.Interface(
            rtlink.OInterface(res_width,
                              bits_for(4*roi_engine_count-1)))
        self.gate_data = rtlink.Interface(
            rtlink.OInterface(roi_engine_count),
            rtlink.IInterface(1+ROI.count_len(res_width, count_shift),
                              timestamped=False))

        self.submodules.deserializer = deserializer_7series.Deserializer(pins)
        self.submodules.frequency_counter = FrequencyCounter()
        self.submodules.parser = Parser(res_width)
        self.comb += self.parser.cl.eq(self.deserializer.q)
        self.roi_engines = [ROI(self.parser.pix, count_shift) for _ in range(roi_engine_count)]
        self.submodules += self.roi_engines
        self.submodules.synchronizer = Synchronizer(self.roi_engines)
        self.submodules.serializer = Serializer(self.synchronizer.update, self.synchronizer.counts,
                                                self.gate_data.i)

        for n, roi_engine in enumerate(self.roi_engines):
            for offset, target in enumerate([roi_engine.cfg.x0, roi_engine.cfg.y0,
                                             roi_engine.cfg.x1, roi_engine.cfg.y1]):
                roi_boundary = Signal.like(target)
                roi_boundary.attr.add("no_retiming")
                self.sync += If(self.config.o.stb & (self.config.o.address == 4*n+offset),
                    roi_boundary.eq(self.config.o.data))
                self.specials += MultiReg(roi_boundary, target, "cl")

        self.sync.rio += If(self.gate_data.o.stb,
            self.serializer.gate.eq(self.gate_data.o.data))

    def get_csrs(self):
        return (
            self.deserializer.get_csrs() +
            self.frequency_counter.get_csrs() +
            self.parser.get_csrs())
