from migen import *
from migen.genlib.cdc import MultiReg
from migen.genlib.io import DifferentialOutput, DifferentialInput, DDROutput
from misoc.cores.liteeth_mini.mac.crc import LiteEthMACCRCEngine

from artiq.gateware.rtio import rtlink
from .fastlink import SerDes, SerInterface


class Fastino(Module):
    def __init__(self, pins, pins_n, log2_width=0):
        width = 1 << log2_width
        self.rtlink = rtlink.Interface(
            rtlink.OInterface(data_width=max(16*width, 32),
                address_width=8,
                enable_replace=False),
            rtlink.IInterface(data_width=14))

        self.submodules.serializer = SerDes(
            n_data=8, t_clk=7, d_clk=0b1100011,
            n_frame=14, n_crc=12, poly=0x80f)
        self.submodules.intf = SerInterface(pins, pins_n)
        self.comb += [
            Cat(self.intf.data[:-1]).eq(Cat(self.serializer.data[:-1])),
            self.serializer.data[-1].eq(self.intf.data[-1]),
        ]

        # dac data words
        dacs = [Signal(16) for i in range(32)]

        header = Record([
            ("cfg", 4),
            ("leds", 8),
            ("typ", 1),
            ("reserved", 7),
            ("addr", 4),
            ("enable", len(dacs)),
        ])
        assert len(Cat(header.raw_bits(), dacs)) == len(self.serializer.payload)

        # # #

        # Support staging DAC data (in `dacs`) by writing to the
        # DAC RTIO addresses, if a channel is not "held" by its
        # bit in `hold` the next frame will contain the update.
        # For the DACs held, the update is triggered by setting the
        # corresponding bit in `update`. Update is self-clearing.
        # This enables atomic DAC updates synchronized to a frame edge.
        #
        # The `log2_width=0` RTIO layout uses one DAC channel per RTIO address
        # and a dense RTIO address space. The RTIO words are narrow.
        # (32 bit compared to 512) and few-channel updates are efficient.
        # There is the least amount of DAC state tracking in kernels,
        # at the cost of more DMA and RTIO data ((n*(32+32+64) vs
        # 32+32*16+64))
        #
        # Other `log2_width` (up to `log2_width=5) settings pack multiple
        # (in powers of two) DAC channels into one group and
        # into one RTIO write.
        # The RTIO data width increases accordingly. The `log2_width`
        # LSBs of the RTIO address for a DAC channel write must be zero and the
        # address space is sparse.

        hold = Signal.like(header.enable)
        continuous = Signal.like(header.enable)
        cic_config = Signal(16)

        read_regs = Array([Signal.like(self.serializer.readback)
            for _ in range(1 << len(header.addr))])

        cases = {
            # update
            0x20: [
                header.enable.eq(self.rtlink.o.data),
                header.typ.eq(0),
            ],
            # hold
            0x21: hold.eq(self.rtlink.o.data),
            # cfg
            0x22: header.cfg.eq(self.rtlink.o.data),
            # leds
            0x23: header.leds.eq(self.rtlink.o.data),
            # reserved bits
            0x24: header.reserved.eq(self.rtlink.o.data),
            # force continuous DAC updates
            0x25: continuous.eq(self.rtlink.o.data),
            # interpolator configuration stage
            0x26: cic_config.eq(self.rtlink.o.data),
            # interpolator update flags
            0x27: [
                header.enable.eq(self.rtlink.o.data),
                header.typ.eq(1),
            ],
        }
        for i in range(0, len(dacs), width):
            cases[i] = [
                Cat(dacs[i:i + width]).eq(self.rtlink.o.data),
                [If(~hold[i + j] & (header.typ == 0),
                    header.enable[i + j].eq(1),
                ) for j in range(width)]
            ]

        self.comb += [
            If(header.typ == 0,
                self.serializer.payload.eq(Cat(header.raw_bits(), dacs)),
            ).Else(
                self.serializer.payload.eq(Cat(header.raw_bits(), Replicate(cic_config, len(dacs)))),
            ),
        ]

        self.sync.rio_phy += [
            If(self.serializer.stb,
                header.typ.eq(0),
                header.enable.eq(continuous),
                read_regs[header.addr].eq(self.serializer.readback),
                header.addr.eq(header.addr + 1),
            ),
            If(self.rtlink.o.stb,
                Case(self.rtlink.o.address, cases),
            ),
        ]

        self.sync += [
            self.rtlink.i.stb.eq(self.rtlink.o.stb &
                self.rtlink.o.address[-1]),
            self.rtlink.i.data.eq(
                read_regs[self.rtlink.o.address[:-1]]),
        ]
