Beispiel #1
0
    def __init__(self, clkspertick, clkfreq, bits=64):
        self.clkspertick = int(clkfreq / clkspertick)

        self.intro = ModuleDoc("""TickTimer: A practical systick timer.

        TIMER0 in the system gives a high-resolution, sysclk-speed timer which overflows
        very quickly and requires OS overhead to convert it into a practically usable time source
        which counts off in systicks, instead of sysclks.

        The hardware parameter to the block is the divisor of sysclk, and sysclk. So if
        the divisor is 1000, then the increment for a tick is 1ms. If the divisor is 2000,
        the increment for a tick is 0.5ms. 
        """)

        self.note = ModuleDoc(
            title="Configuration",
            body=
            "This timer was configured with {} bits, which rolls over in {:.2f} years, with each bit giving {}ms resolution"
            .format(bits, (2**bits / (60 * 60 * 24 * 365)) *
                    (self.clkspertick / clkfreq),
                    1000 * (self.clkspertick / clkfreq)))

        prescaler = Signal(max=self.clkspertick, reset=self.clkspertick)
        timer = Signal(bits)  # offer up to 48 bits of system time

        self.control = CSRStorage(
            2,
            fields=[
                CSRField("reset",
                         description=
                         "Write a `1` to this bit to reset the count to 0",
                         pulse=True),
                CSRField(
                    "pause",
                    description=
                    "Write a `1` to this field to pause counting, 0 for free-run"
                )
            ])
        self.time = CSRStatus(bits,
                              name="time",
                              description="""Elapsed time in systicks""")

        self.sync += [
            If(
                self.control.fields.reset,
                timer.eq(0),
                prescaler.eq(self.clkspertick),
            ).Else(
                If(prescaler == 0, prescaler.eq(self.clkspertick),
                   If(
                       self.control.fields.pause == 0,
                       timer.eq(timer + 1),
                   )).Else(prescaler.eq(prescaler - 1), ))
        ]

        self.comb += self.time.status.eq(timer)
Beispiel #2
0
    def __init__(self, parent, offsets=None):

        table = ""
        if offsets is not None:
            arr = [["Image", "Offset"]]
            for i, offset in enumerate(offsets):
                arr.append([str(i), str(offset)])
            table = "\nYou can use this block to reboot into one of these four addresses:\n\n" \
                  + lxsocdoc.rst.make_table(arr)
        self.intro = ModuleDoc("""FPGA Reboot Interface

            This module provides the ability to reboot the FPGA.  It is based on the
            ``SB_WARMBOOT`` primitive built in to the FPGA.

            When power is applied to the FPGA, it reads configuration data from the
            onboard flash chip.  This contains reboot offsets for four images.  It then
            booted from the first image, but kept note of the other addresses.
            {}""".format(table))
        self.ctrl = CSRStorage(fields=[
            CSRField("image",
                     size=2,
                     description="""
                        Which image to reboot to.  ``SB_WARMBOOT`` supports four images that
                        are configured at FPGA startup.  The bootloader is image 0, so set
                        these bits to 0 to reboot back into the bootloader.
                        """),
            CSRField("key",
                     size=6,
                     description="""
                        A reboot key used to prevent accidental reboots when writing to random
                        areas of memory.  To initiate a reboot, set this to ``0b101011``."""
                     )
        ],
                               description="""
                Provides support for rebooting the FPGA.  You can select which of the four images
                to reboot to, just be sure to OR the image number with ``0xac``.  For example,
                to reboot to the bootloader (image 0), write ``0xac``` to this register."""
                               )
        self.addr = CSRStorage(size=32,
                               description="""
                This sets the reset vector for the VexRiscv.  This address will be used whenever
                the CPU is reset, for example through a debug bridge.  You should update this
                address whenever you load a new program, to enable the debugger to run ``mon reset``
                """)
        do_reset = Signal()
        self.comb += [
            # "Reset Key" is 0xac (0b101011xx)
            do_reset.eq(self.ctrl.storage[2] & self.ctrl.storage[3]
                        & ~self.ctrl.storage[4]
                        & self.ctrl.storage[5] & ~self.ctrl.storage[6]
                        & self.ctrl.storage[7])
        ]
        self.specials += Instance(
            "SB_WARMBOOT",
            i_S0=self.ctrl.storage[0],
            i_S1=self.ctrl.storage[1],
            i_BOOT=do_reset,
        )
        parent.config["BITSTREAM_SYNC_HEADER1"] = 0x7e99aa7e
        parent.config["BITSTREAM_SYNC_HEADER2"] = 0x7eaa997e
Beispiel #3
0
    def __init__(self, platform):
        self.intro = ModuleDoc("""Test for bitstream insertion of BRAM initialization contents""")
        platform.toolchain.attr_translate["KEEP"] = ("KEEP", "TRUE")
        platform.toolchain.attr_translate["DONT_TOUCH"] = ("DONT_TOUCH", "TRUE")

        import binascii
        self.address = CSRStorage(8, name="address", description="address for ROM")
        self.data = CSRStatus(32, name="data", description="data from ROM")

        rng = SystemRandom()
        with open("rom.db", "w") as f:
            for bit in range(0,32):
                lutsel = Signal(4)
                for lut in range(4):
                    if lut == 0:
                        lutname = 'A'
                    elif lut == 1:
                        lutname = 'B'
                    elif lut == 2:
                        lutname = 'C'
                    else:
                        lutname = 'D'
                    romval = rng.getrandbits(64)
                    # print("rom bit ", str(bit), lutname, ": ", binascii.hexlify(romval.to_bytes(8, byteorder='big')))
                    rom_name = "KEYROM" + str(bit) + lutname
                    # X36Y99 and counting down
                    if bit % 2 == 0:
                        platform.toolchain.attr_translate[rom_name] = ("LOC", "SLICE_X36Y" + str(50 + bit // 2))
                    else:
                        platform.toolchain.attr_translate[rom_name] = ("LOC", "SLICE_X37Y" + str(50 + bit // 2))
                    platform.toolchain.attr_translate[rom_name + 'BEL'] = ("BEL", lutname + '6LUT')
                    platform.toolchain.attr_translate[rom_name + 'LOCK'] = ( "LOCK_PINS", "I5:A6, I4:A5, I3:A4, I2:A3, I1:A2, I0:A1" )
                    self.specials += [
                        Instance( "LUT6",
                                  name=rom_name,
                                  # p_INIT=0x0000000000000000000000000000000000000000000000000000000000000000,
                                  p_INIT=romval,
                                  i_I0= self.address.storage[0],
                                  i_I1= self.address.storage[1],
                                  i_I2= self.address.storage[2],
                                  i_I3= self.address.storage[3],
                                  i_I4= self.address.storage[4],
                                  i_I5= self.address.storage[5],
                                  o_O= lutsel[lut],
                                  attr=("KEEP", "DONT_TOUCH", rom_name, rom_name + 'BEL', rom_name + 'LOCK')
                                  )
                    ]
                    # record the ROM LUT locations in a DB and annotate the initial random value given
                    f.write("KEYROM " + str(bit) + ' ' + lutname + ' ' + platform.toolchain.attr_translate[rom_name][1] +
                            ' ' + str(binascii.hexlify(romval.to_bytes(8, byteorder='big'))) + '\n')
                self.comb += [
                    If( self.address.storage[6:] == 0,
                        self.data.status[bit].eq(lutsel[2]))
                    .Elif(self.address.storage[6:] == 1,
                          self.data.status[bit].eq(lutsel[3]))
                    .Elif(self.address.storage[6:] == 2,
                          self.data.status[bit].eq(lutsel[0]))
                    .Else(self.data.status[bit].eq(lutsel[1]))
                ]
Beispiel #4
0
 def __init__(self, pin):
     self.ctrl = CSRStorage(
         8, description="Write ``0xac`` here to reboot the device")
     self.intro = ModuleDoc(title="Reboot Control",
                            body="""
     Access reboot control for the badge.  Write the key to ``reboot`` in
     order to initiate a reboot.""")
     self.comb += If(self.ctrl.storage != 0xac, pin.eq(1))
    def __init__(self, btn_pin):
        # Documentation
        self.intro = ModuleDoc("""iCEBreaker Button control.
        The three momentary switches on the breakaway PMOD.
        """)

        # HDL Implementation
        self._in = CSRStatus(len(btn_pin))
        self.specials += MultiReg(btn_pin, self._in.status)
Beispiel #6
0
    def __init__(self):
        self.submodules.fifo = f = fifo.SyncFIFOBuffered(width=8, depth=64)
        in_reg = CSRStorage(8,
                            name="in",
                            description="""
                    Write half of the FIFO to send data out the Messible.
                    Writing to this register advances the write pointer automatically."""
                            )
        out_reg = CSRStatus(8,
                            name="out",
                            description="""
                    Read half of the FIFO to receive data on the Messible.
                    Reading from this register advances the read pointer automatically."""
                            )

        self.__setattr__("in", in_reg)
        self.__setattr__("out", out_reg)
        self.status = status = CSRStatus(fields=[
            CSRField(
                "full",
                description="``0`` if more data can fit into the IN FIFO."),
            CSRField(
                "have",
                description="``1`` if data can be read from the OUT FIFO."),
        ])

        self.intro = ModuleDoc("""
                Messible: An Ansible for Messages

                An Ansible is a system for instant communication across vast distances, from
                a small portable device to a huge terminal far away.  A Messible is a message-
                passing system from embedded devices to a host system.  You can use it to get
                very simple printf()-style support over a debug channel.

                The Messible assumes the host has a way to peek into the device's memory space.
                This is the case with the Wishbone bridge, which allows both the device and
                the host to access the same memory.

                At its core, a Messible is a FIFO.  As long as the ``STATUS.FULL`` bit is ``0``, the
                device can write data into the Messible by writing into the ``IN``.  However, if this
                value is ``1``, you need to decide if you want to wait for it to empty (if the other
                side is just slow), or if you want to drop the message.

                From the host side, you need to read ``STATUS.HAVE`` to see if there is data
                in the FIFO.  If there is, read ``OUT`` to get the most recent byte, which automatically
                advances the ``READ`` pointer.
                """)

        self.comb += [
            f.din.eq(in_reg.storage),
            f.we.eq(in_reg.re),
            out_reg.status.eq(f.dout),
            f.re.eq(out_reg.we),
            status.fields.full.eq(~f.writable),
            status.fields.have.eq(f.readable),
        ]
Beispiel #7
0
    def __init__(self, reproduceable=False):
        self.intro = ModuleDoc("""Place and route seed. Set to a fixed number for reproduceable builds.
        Use a random number or your own number if you are paranoid about hardware implants that target
        fixed locations within the FPGA.""")

        if reproduceable:
          seed_reset = "4" # chosen by fair dice roll. guaranteed to be random.
        else:
          rng        = SystemRandom()
          seed_reset = rng.getrandbits(64)
        self.seed = CSRStatus(64, name="seed", description="Seed used for the build", reset=seed_reset)
    def __init__(self, led_pin, led_polarity=0x00, led_name=[]):
        # Documentation
        self.intro = ModuleDoc("""iCEBreaker LED control.
        The LEDs are inverted as these are negative logic LED. This means that if you set the
        corresponding LED bit to 1 the LED will be off and if you set it to 0 the LED will be on.
        """)

        # HDL Implementation
        self._out = CSRStorage(
            len(led_pin),
            fields=[CSRField(fld[0], description=fld[1]) for fld in led_name])
        self.comb += led_pin.eq(self._out.storage ^ led_polarity)
Beispiel #9
0
    def __init__(self, dram_port, pattern_mem, *, rowbits, row_shift):
        super().__init__(pattern_mem)

        self.doc = ModuleDoc("""
DMA DRAM writer.

Allows to fill DRAM with a predefined pattern using DMA.

Pattern
-------

{common}
        """.format(common=BISTModule.__doc__))

        dma = LiteDRAMDMAWriter(dram_port, fifo_depth=4)
        self.submodules += dma

        cmd_counter = Signal(32)

        self.comb += [
            self.done.eq(cmd_counter),
            # pattern
            self.data_port.adr.eq(cmd_counter & self.data_mask),
            self.addr_port.adr.eq(cmd_counter & self.data_mask),
            # DMA
            dma.sink.address.eq(self.addr_port.dat_r +
                                (cmd_counter & self.mem_mask)),
        ]

        # DMA data may be inverted using AddressSelector
        self.submodules.inverter = RowDataInverter(
            addr=dma.sink.address,
            data_in=self.data_port.dat_r,
            data_out=dma.sink.data,
            rowbits=rowbits,
            row_shift=row_shift,
        )

        self.submodules.fsm = fsm = FSM()
        fsm.act("READY", self.ready.eq(1),
                If(
                    self.start,
                    NextValue(cmd_counter, 0),
                    NextState("WAIT"),
                ))
        fsm.act(
            "WAIT",  # TODO: we could pipeline the access
            If(cmd_counter >= self.count,
               NextState("READY")).Else(NextState("RUN")))
        fsm.act(
            "RUN", dma.sink.valid.eq(1),
            If(dma.sink.ready, NextValue(cmd_counter, cmd_counter + 1),
               NextState("WAIT")))
Beispiel #10
0
    def __init__(self, spi_pads):
        # Documentation
        self.intro = ModuleDoc("SPI slave driver")

        self.wishbone = bus = wishbone.Interface()
        self.submodules.spi = spi = HardSPISlave(spi_pads)

        spi_counter = Signal(4)
        spi_dword_mosi = Signal(32)
        spi_dword_miso = Signal(32)
        self.sync += \
            If(spi.start,
                spi_counter.eq(0),
            ).Elif(spi.byte,
                spi_counter.eq(spi_counter + 1),
                spi_dword_mosi.eq(Cat(spi_dword_mosi[8:], spi.mosi)),
                spi_dword_miso.eq(Cat(spi_dword_miso[8:], Signal(8))),
            )

        address = Signal(16)
        address_hi = Signal(16)

        command = Signal(8)

        self.comb += spi.miso.eq(spi_dword_miso[:8])

        self.submodules.fsm = fsm = FSM(reset_state="IDLE")
        fsm.act("IDLE", If(spi.start, NextState("COMMAND")))
        fsm.act(
            "COMMAND",
            If(spi.done, NextState("IDLE")).Else(
                If(
                    spi_counter == 1,
                    NextValue(command, spi_dword_mosi[-8:]),
                ),
                If(spi_counter == 3, NextValue(address, spi_dword_mosi[-16:]),
                   If(command == 0x03, NextState("READ"))),
                If(
                    (spi_counter == 7) and command == 0x02,
                    #NextValue(data, spi_dword_mosi),
                    NextState("WRITE"),
                )),
        )
        fsm.act(
            "READ", bus.cyc.eq(1), bus.stb.eq(1), bus.we.eq(0),
            bus.adr.eq(Cat(address, address_hi)),
            bus.sel.eq(2**len(bus.sel) - 1),
            If(bus.ack, NextValue(spi_dword_miso, bus.dat_r),
               NextState("IDLE")))
        fsm.act("WRITE", bus.cyc.eq(1), bus.stb.eq(1), bus.we.eq(1),
                bus.adr.eq(Cat(address, address_hi)),
                bus.dat_w.eq(spi_dword_mosi), bus.sel.eq(2**len(bus.sel) - 1),
                If(bus.ack, NextState("IDLE")))
    def __init__(self, segment_pin, segment_name=[]):
        # Documentation
        self.intro = ModuleDoc("""iCEBreaker seven_segment_PMOD1A
        control.
        """)

        # HDL Implementation
        self._out = CSRStorage(len(segment_pin),
                               fields=[
                                   CSRField(fld[0], description=fld[1])
                                   for fld in segment_name
                               ])
        self.comb += segment_pin.eq(self._out.storage)
Beispiel #12
0
    def __init__(self, cntr_size, cntr_out):
        # Documentation
        self.intro = ModuleDoc("""Counter module.

        The CSR value determines the speed of the counter.
        """)

        # HDL Implementation
        self._out = CSRStorage(cntr_size)

        self.counter = Signal(30)

        self.sync.vga += self.counter.eq(self.counter + self._out.storage)
        self.comb += cntr_out.eq(self.counter[len(self.counter) - len(cntr_out):])
Beispiel #13
0
    def __init__(self, dram_port, pattern_mem):
        super().__init__(pattern_mem)

        self.doc = ModuleDoc("""
DMA DRAM writer.

Allows to fill DRAM with a predefined pattern using DMA.

Pattern
-------

{common}
        """.format(common=BISTModule.__doc__))

        # FIXME: Increase fifo depth
        dma = LiteDRAMDMAWriter(dram_port, fifo_depth=1)
        self.submodules += dma

        cmd_counter = Signal(32)

        self.comb += [
            self.done.eq(cmd_counter),
            # pattern
            self.data_port.adr.eq(cmd_counter & self.data_mask),
            self.addr_port.adr.eq(cmd_counter & self.data_mask),
            # DMA
            dma.sink.address.eq(self.addr_port.dat_r +
                                (cmd_counter & self.mem_mask)),
            dma.sink.data.eq(self.data_port.dat_r),
        ]

        self.submodules.fsm = fsm = FSM()
        fsm.act("READY", self.ready.eq(1),
                If(
                    self.start,
                    NextValue(cmd_counter, 0),
                    NextState("WAIT"),
                ))
        fsm.act(
            "WAIT",  # TODO: we could pipeline the access
            If(cmd_counter >= self.count,
               NextState("READY")).Else(NextState("RUN")))
        fsm.act(
            "RUN", dma.sink.valid.eq(1),
            If(dma.sink.ready, NextValue(cmd_counter, cmd_counter + 1),
               NextState("WAIT")))
Beispiel #14
0
    def __init__(self, clkspertick):
        self.clkspertick = int(clkspertick)

        self.intro = ModuleDoc("""TickTimer: A practical systick timer.

        TIMER0 in the system gives a high-resolution, sysclk-speed timer which overflows
        very quickly and requires OS overhead to convert it into a practically usable time source
        which counts off in systicks, instead of sysclks.

        The hardware parameter to the block is the number of sysclocks per tick.
        """)

        prescaler = Signal(max=self.clkspertick, reset=self.clkspertick)
        timer = Signal(48)  # offer up to 48 bits of system time

        self.control = CSRStorage(
            2,
            fields=[
                CSRField("reset",
                         description=
                         "Write a `1` to this bit to reset the count to 0",
                         pulse=True),
                CSRField(
                    "pause",
                    description=
                    "Write a `1` to this field to pause counting, 0 for free-run"
                )
            ])
        self.time = CSRStatus(48,
                              name="time",
                              description="""Elapsed time in systicks""")

        self.sync += [
            If(
                self.control.fields.reset,
                timer.eq(0),
                prescaler.eq(self.clkspertick),
            ).Else(
                If(prescaler == 0, prescaler.eq(self.clkspertick),
                   If(
                       self.control.fields.pause == 0,
                       timer.eq(timer + 1),
                   )).Else(prescaler.eq(prescaler - 1), ))
        ]

        self.comb += self.time.status.eq(timer)
Beispiel #15
0
    def __init__(self, pads):
        self.intro = ModuleDoc("""BtGpio - GPIO interface for betrusted""")

        gpio_in  = Signal(pads.nbits)
        gpio_out = Signal(pads.nbits)
        gpio_oe  = Signal(pads.nbits)

        for g in range(0, pads.nbits):
            gpio_ts = TSTriple(1)
            self.specials += gpio_ts.get_tristate(pads[g])
            self.comb += [
                gpio_ts.oe.eq(gpio_oe[g]),
                gpio_ts.o.eq(gpio_out[g]),
                gpio_in[g].eq(gpio_ts.i),
            ]

        self.output = CSRStorage(pads.nbits, name="output", description="Values to appear on GPIO when respective `drive` bit is asserted")
        self.input  = CSRStatus(pads.nbits,  name="input",  description="Value measured on the respective GPIO pin")
        self.drive  = CSRStorage(pads.nbits, name="drive",  description="When a bit is set to `1`, the respective pad drives its value out")
        self.intena = CSRStatus(pads.nbits,  name="intena", description="Enable interrupts when a respective bit is set")
        self.intpol = CSRStatus(pads.nbits,  name="intpol", description="When a bit is `1`, falling-edges cause interrupts. Otherwise, rising edges cause interrupts.")

        self.specials += MultiReg(gpio_in, self.input.status)
        self.comb += [
            gpio_out.eq(self.output.storage),
            gpio_oe.eq(self.drive.storage),
        ]

        self.submodules.ev = EventManager()

        for i in range(0, pads.nbits):
            setattr(self.ev, "gpioint" + str(i), EventSourcePulse() ) # pulse => rising edge

        self.ev.finalize()

        for i in range(0, pads.nbits):
            # pull from input.status because it's after the MultiReg synchronizer
            self.comb += getattr(self.ev, "gpioint" + str(i)).trigger.eq(self.input.status[i] ^ self.intpol.status[i])
Beispiel #16
0
    def __init__(self, pads):
        self.intro = ModuleDoc("""BtPower - power control pins
        """)

        self.power = CSRStorage(8, fields=[
            CSRField("audio",     size=1, description="Write `1` to power on the audio subsystem"),
            CSRField("self",      size=1, description="Writing `1` forces self power-on (overrides the EC's ability to power me down)", reset=1),
            CSRField("ec_snoop",  size=1, description="Writing `1` allows the insecure EC to snoop a couple keyboard pads for wakeup key sequence recognition"),
            CSRField("state",     size=2, description="Current SoC power state. 0x=off or not ready, 10=on and safe to shutdown, 11=on and not safe to shut down, resets to 01 to allow extSRAM access immediately during init", reset=1),
            CSRField("noisebias", size=1, description="Writing `1` enables the primary bias supply for the noise generator"),
            CSRField("noise",     size=2, description="Controls which of two noise channels are active; all combos valid. noisebias must be on first.")
        ])

        self.comb += [
            pads.audio_on.eq(self.power.fields.audio),
            pads.fpga_sys_on.eq(self.power.fields.self),
            # This signal automatically enables snoop when SoC is powered down
            pads.allow_up5k_n.eq(~self.power.fields.ec_snoop),
            # Ensure SRAM isolation during reset (CE & ZZ = 1 by pull-ups)
            pads.pwr_s0.eq(self.power.fields.state[0] & ~ResetSignal()),
            # pads.pwr_s1.eq(self.power.fields.state[1]),         # PATCH
            # pads.noisebias_on.eq(self.power.fields.noisebias),  # PATCH
            pads.noise_on.eq(self.power.fields.noise),
        ]
Beispiel #17
0
    def __init__(self, pads):
        self.intro = ModuleDoc("""Fomu Touchpads

        Fomu has four single-ended exposed pads on its side.  These pads are designed
        to be connected to some captouch block, or driven in a resistive touch mode
        in order to get simple touchpad support.

        This block simply provides CPU-controlled GPIO support for this block.  It has
        three registers which control the In, Out, and Output Enable functionality of
        each of these pins.
        """)
        touch1 = TSTriple()
        touch2 = TSTriple()
        touch3 = TSTriple()
        touch4 = TSTriple()
        self.specials += touch1.get_tristate(pads.t1)
        self.specials += touch2.get_tristate(pads.t2)
        self.specials += touch3.get_tristate(pads.t3)
        self.specials += touch4.get_tristate(pads.t4)

        self.o = CSRStorage(4, description="Output values for pads 1-4")
        self.oe = CSRStorage(4,
                             description="Output enable control for pads 1-4")
        self.i = CSRStatus(4, description="Input value for pads 1-4")

        self.comb += [
            touch1.o.eq(self.o.storage[0]),
            touch2.o.eq(self.o.storage[1]),
            touch3.o.eq(self.o.storage[2]),
            touch4.o.eq(self.o.storage[3]),
            touch1.oe.eq(self.oe.storage[0]),
            touch2.oe.eq(self.oe.storage[1]),
            touch3.oe.eq(self.oe.storage[2]),
            touch4.oe.eq(self.oe.storage[3]),
            self.i.status.eq(Cat(touch1.i, touch2.i, touch3.i, touch4.i))
        ]
Beispiel #18
0
    def __init__(self, pads):
        self.intro = ModuleDoc("""Simple soft SPI Peripheral module optimized for Betrusted-EC (UP5K arch) use

        Assumes a free-running sclk and csn performs the function of framing bits
        Thus csn must go high between each frame, you cannot hold csn low for burst transfers
        """)

        self.cipo = pads.cipo
        self.copi = pads.copi
        self.sclk = pads.sclk
        self.csn  = pads.csn

        ### FIXME: stand-in for SPI clock input
        self.clock_domains.cd_spi_peripheral = ClockDomain()
        self.comb += self.cd_spi_peripheral.clk.eq(self.sclk)

        self.tx = CSRStorage(16, name="tx", description="""Tx data, to CIPO""")
        self.rx = CSRStatus(16,  name="rx", description="""Rx data, from COPI""")
        self.control = CSRStorage(fields=[
            CSRField("intena", description="Enable interrupt on transaction finished"),
            CSRField("clrerr", description="Clear Rx overrun error", pulse=True),
        ])
        self.status = CSRStatus(fields=[
            CSRField("tip",    description="Set when transaction is in progress"),
            CSRField("rxfull", description="Set when Rx register has new, valid contents to read"),
            CSRField("rxover", description="Set if Rx register was not read before another transaction was started")
        ])

        self.submodules.ev = EventManager()
        self.ev.spi_int    = EventSourceProcess()  # Falling edge triggered
        self.ev.finalize()
        self.comb += self.ev.spi_int.trigger.eq(self.control.fields.intena & self.status.fields.tip)

        # Replicate CSR into "spi" clock domain
        self.txrx     = Signal(16)
        self.tip_r    = Signal()
        self.rxfull_r = Signal()
        self.rxover_r = Signal()
        self.csn_r    = Signal()

        self.specials += MultiReg(self.tip_r, self.status.fields.tip)
        self.comb += self.tip_r.eq(~self.csn)
        tip_d     = Signal()
        donepulse = Signal()
        self.sync += tip_d.eq(self.tip_r)
        self.comb += donepulse.eq(~self.tip_r & tip_d)  # Done pulse goes high when tip drops

        self.comb += self.status.fields.rxfull.eq(self.rxfull_r)
        self.comb += self.status.fields.rxover.eq(self.rxover_r)

        self.sync += [
            If(self.rx.we,
                self.rxfull_r.eq(0),
            ).Else(
                If(donepulse,
                    self.rxfull_r.eq(1)
                ).Else(
                    self.rxfull_r.eq(self.rxfull_r),
                ),

                If(self.tip_r & self.rxfull_r,
                    self.rxover_r.eq(1)
                ).Elif(self.control.fields.clrerr,
                    self.rxover_r.eq(0)
                ).Else(
                    self.rxover_r.eq(self.rxover_r)
                )
            )
        ]

        self.comb += self.cipo.eq(self.txrx[15])
        csn_d = Signal()
        self.sync.spi_peripheral += [
            csn_d.eq(self.csn),
            # "Sloppy" clock boundary crossing allowed because "rxfull" is synchronized and CPU should grab data based on that
            If(self.csn == 0,
                self.txrx.eq(Cat(self.copi, self.txrx[0:15])),
                self.rx.status.eq(self.rx.status),
            ).Else(
                If(self.csn & ~csn_d,
                    self.rx.status.eq(self.txrx),
                ).Else(
                    self.rx.status.eq(self.rx.status)
                ),
                self.txrx.eq(self.tx.storage)
            )
        ]
Beispiel #19
0
    def __init__(self, pads, pipeline_cipo=False):
        self.intro = ModuleDoc("""Simple soft SPI Controller module optimized for Betrusted applications

        Requires a clock domain 'spi', which runs at the speed of the SPI bus.

        Simulation benchmarks 16.5us to transfer 16x16 bit words including setup overhead (sysclk=100MHz, spiclk=25MHz)
        which is about 15Mbps system-level performance, assuming the receiver can keep up.

        A receiver running at 18MHz, with a spiclk of 20MHz, shows about 55us for 16x16bit words, or about 4.5Mbps performance.

        The `pipeline_cipo` argument, when set, introduces an extra pipeline stage on the return path from the peripheral.
        This can help improve the timing closure when talking to slow devices, such as a UP5K. However, it makes the
        bus not standards-compliant. Thus, by default this is set to False.

        For this build, `pipeline_cipo` has been set to {}
        """.format(pipeline_cipo))

        self.cipo = pads.cipo
        self.copi = pads.copi
        self.sclk = pads.sclk
        self.csn  = pads.csn
        self.hold = pads.hold
        hold = Signal()
        self.specials += MultiReg(self.hold, hold)

        self.tx = CSRStorage(16, name="tx", description="""Tx data, for COPI. Note: 32-bit CSRs are required for this block to work!""")
        self.rx = CSRStatus(16, name="rx", description="""Rx data, from CIPO""")
        self.control = CSRStorage(fields=[
            CSRField("intena", description="Enable interrupt on transaction finished"),
            CSRField("autohold", description="Disallow transmission start if hold if asserted"),
        ])
        self.status = CSRStatus(fields=[
            CSRField("tip", description="Set when transaction is in progress"),
            CSRField("hold", description="Set when peripheral asserts hold"),
        ])
        self.comb += self.status.fields.hold.eq(hold)

        self.submodules.ev = EventManager()
        self.ev.spi_int    = EventSourceProcess()  # Falling edge triggered
        self.ev.spi_hold   = EventSourceProcess()  # triggers when hold drops
        self.ev.finalize()
        self.comb += self.ev.spi_int.trigger.eq(self.control.fields.intena & self.status.fields.tip)
        self.comb += self.ev.spi_hold.trigger.eq(hold)

        # Replicate CSR into "spi" clock domain
        self.tx_r       = Signal(16)
        self.rx_r       = Signal(16)
        self.go_r       = Signal()
        self.tx_written = Signal()

        setdone = Signal()
        done_r      = Signal()
        self.specials += MultiReg(setdone, done_r)
        self.sync += [
            If(self.tx.re,
                self.status.fields.tip.eq(1)
            ).Elif(done_r,
                self.status.fields.tip.eq(0)
            ).Else(
                self.status.fields.tip.eq(self.status.fields.tip)
            )
        ]

        self.submodules.txwrite = PulseStretch() # Stretch the go signal to ensure it's picked up in the SPI domain
        self.comb += self.txwrite.i.eq(self.tx.re)
        self.comb += self.tx_written.eq(self.txwrite.o)
        tx_written_d = Signal()
        tx_go        = Signal()
        self.sync.spi += tx_written_d.eq(self.tx_written)
        self.comb += tx_go.eq(~self.tx_written & tx_written_d) # Falling edge of tx_written pulse, guarantees tx.storage is stable

        self.csn_r = Signal(reset=1)
        self.comb += self.csn.eq(self.csn_r)
        fsm = FSM(reset_state="IDLE")
        fsm = ClockDomainsRenamer("spi")(fsm)
        self.submodules += fsm
        spicount = Signal(4)
        # I/ODDR signals
        clk_run = Signal()
        cipo_sampled = Signal()
        fsm.act("IDLE",
            NextValue(clk_run, 0),
            If(tx_go & ~(self.control.fields.autohold & hold),
                NextState("PRE_ASSERT"),
                NextValue(self.tx_r, self.tx.storage),
                # Stability guaranteed so no synchronizer necessary
                NextValue(spicount, 15),
                NextValue(self.csn_r, 0),
            ).Else(
                NextValue(self.csn_r, 1),
            )
        )
        fsm.act("PRE_ASSERT", # assert CS_N for a cycle before sending a clock, so that the receiver can bring its counter out of async clear
                NextState("RUN"),
                NextValue(clk_run, 1),
        )
        if pipeline_cipo:
            turnaround_delay = 2
        else:
            turnaround_delay = 3
        fsm.act("RUN",
            NextValue(self.rx_r, Cat(cipo_sampled, self.rx_r[:15])),
            NextValue(self.tx_r, Cat(0, self.tx_r[:15])),
            If(spicount > 0,
                NextValue(clk_run, 1),
                NextValue(spicount, spicount - 1),
            ).Else(
                NextValue(clk_run, 0),
                NextValue(spicount, turnaround_delay),
                NextState("POST_ASSERT"),
            ),
        )
        if pipeline_cipo:
            fsm.act("POST_ASSERT",
                # one cycle extra at the end, to grab the falling-edge asserted receive data
                NextValue(self.rx_r, Cat(cipo_sampled, self.rx_r[:15])),
                NextValue(self.csn_r, 1),
                NextState("SAMPLE"),
            )
            fsm.act("SAMPLE",
                # another extra cycle to grab pipelined data
                NextValue(self.rx_r, Cat(cipo_sampled, self.rx_r[:15])),
                NextState("WAIT"),
            )
        else:
            fsm.act("POST_ASSERT",
                # one cycle extra at the end, to grab the falling-edge asserted receive data
                NextValue(self.rx_r, Cat(cipo_sampled, self.rx_r[:15])),
                NextValue(self.csn_r, 1),
                NextState("WAIT"),
            )
        fsm.act("WAIT",  # guarantee a minimum CS_N high time after the transaction so Peripheral can capture. Has to perculate through multiregs, 2 cycles/ea + sync FIFO latch.
            NextValue(self.rx.status, self.rx_r),
            NextValue(spicount, spicount - 1),
            If(spicount == 0,
                setdone.eq(1),
                NextState("IDLE"),
            )
        )

        # generate a clock, this is Artix-specific
        # mirror the clock with zero delay
        self.specials += Instance("ODDR",
            p_DDR_CLK_EDGE = "SAME_EDGE",
            p_INIT          = 0,
            p_SRTYPE        = "SYNC",
            o_Q  = self.sclk,
            i_C = ClockSignal("spi"),
            i_CE = 1,
            i_D1 = clk_run,
            i_D2 = 0,
            i_R  = ResetSignal("spi"),
            i_S  = 0,
        )
        self.specials += Instance("ODDR",
            p_DDR_CLK_EDGE = "SAME_EDGE",
            o_Q  = self.copi,
            i_C = ClockSignal("spi"),
            i_CE = 1,
            i_D1 = self.tx_r[15],
            i_D2 = self.tx_r[15],
        )
        self.specials += Instance("IDDR",
            p_DDR_CLK_EDGE = "OPPOSITE_EDGE",
            p_SRTYPE        = "SYNC",
            i_C = ClockSignal("spi"),
            i_CE = 1,
            i_D = self.cipo,
            o_Q1 = cipo_sampled,
            i_R  = ResetSignal("spi"),
            i_S  = 0,
        )
Beispiel #20
0
    def __init__(self, pads, pipeline_cipo=False):
        self.intro = ModuleDoc(
            """SPI peripheral module optimized for Betrusted-EC (UP5K arch) use

The `pipeline_cipo` argument configures the interface to put a pipeline register
on the output of CIPO. Setting this to `False` gives you a standards-compliant SPI Mode 1
interface, but it should run at around 12-15MHz. Also I'm unaware of a method to convince
nextpnr to fix asynchronous delays from fabric to output, and therefore the total delay
from the fabric registers to the output seems to vary quite widely.

Setting `pipeline_cipo` to `True` shifts the CIPO output late by a half clock cycle by
resampling the CIPO line using an SB_IO register, but the clock-to-q timing is better 
(about 14ns versus 21ns). Also, because the origin of the data is from an SB_IO register,
the timing should be very consistent regardless of nextpnr's internal machinations. 
The downside is that the controller needs to sample an extra bit after the SCLK stops.
This isn't a problem for an FPGA design (and the matching spi_7series.SPIController IP
block has a `pipeline_cipo` argument to match this block), but it won't work with most
off-the-shelf microcontrollers. However, by using `pipeline_cipo`, you can increase
the clock rate of the SPI bus to 20MHz with margin to spare.

This module was built with `pipeline_cipo` set to {} 
        """.format(pipeline_cipo))

        self.protocol = ModuleDoc("""Enhanced Protocol for COM
The large performance differential between the SoC and the EC in Precursor/Betrusted means that
it's very easy for the EC to be overwhelmed with requests from the SoC. 

We extend the SPI protocol with a "hold" signal to inform the SoC that read data is not yet ready.

This, in essence, is a matter of tying the Tx FIFO's "read empty" signal to the hold pin. 
The EC signals readiness to accept new commands by writing a single word to the Tx FIFO.
Upon receipt of the word, the FIFO will empty, raising the "read empty" signal and producing a
"hold" condition until the next data is made available for reading.
        """)

        self.copi = pads.copi
        self.csn = pads.csn
        self.hold = Signal()
        self.oe = Signal(
        )  # used to disable driving signals to the target device when it is powered down
        self.hold_ts = TSTriple(1)
        self.specials += [self.hold_ts.get_tristate(pads.hold)]
        self.comb += [
            self.hold_ts.oe.eq(self.oe),
            self.hold_ts.o.eq(self.hold),
        ]

        ### clock is not wired up in this module, it's moved up to CRG for implementation-dependent buffering

        self.control = CSRStorage(fields=[
            CSRField(
                "clrerr", description="Clear FIFO error flags", pulse=True),
            CSRField("host_int",
                     description="0->1 raises an interrupt to the COM host"
                     ),  # rising edge triggered on other side
            CSRField("reset", description="Reset the fifos", pulse=True),
        ])
        self.comb += pads.irq.eq(self.control.fields.host_int)

        self.status = CSRStatus(fields=[
            CSRField("tip", description="Set when transaction is in progress"),
            CSRField(
                "rx_avail",
                description="Set when Rx FIFO has new, valid contents to read"
            ),
            CSRField("rx_over", description="Set if Rx FIFO has overflowed"),
            CSRField("rx_under", description="Set if Rx FIFO underflows"),
            CSRField("rx_level", size=12, description="Level of Rx FIFO"),
            CSRField("tx_avail",
                     description="Set when Tx FIFO has space for new content"),
            CSRField("tx_empty", description="Set when Tx FIFO is empty"),
            CSRField("tx_level", size=12, description="Level of Tx FIFO"),
            CSRField("tx_over", description="Set when Tx FIFO overflows"),
            CSRField("tx_under", description="Set when Tx FIFO underflows"),
        ])
        self.bus = bus = wishbone.Interface()
        rd_ack = Signal()
        wr_ack = Signal()
        self.comb += [
            If(
                bus.we,
                bus.ack.eq(wr_ack),
            ).Else(bus.ack.eq(rd_ack), )
        ]

        # read/rx subsystem
        self.submodules.rx_fifo = rx_fifo = ResetInserter(["sys"])(
            SyncFIFOBuffered(16, 1280)
        )  # should infer SB_RAM256x16's. 2560 depth > 2312 bytes = wifi MTU
        self.comb += self.rx_fifo.reset_sys.eq(self.control.fields.reset
                                               | ResetSignal())
        self.submodules.rx_under = StickyBit()
        self.comb += [
            self.status.fields.rx_level.eq(rx_fifo.level),
            self.status.fields.rx_avail.eq(rx_fifo.readable),
            self.rx_under.clear.eq(self.control.fields.clrerr),
            self.status.fields.rx_under.eq(self.rx_under.bit),
        ]

        bus_read = Signal()
        bus_read_d = Signal()
        rd_ack_pipe = Signal()
        self.comb += bus_read.eq(bus.cyc & bus.stb & ~bus.we & (bus.cti == 0))
        self.sync += [  # This is the bus responder -- only works for uncached memory regions
            bus_read_d.eq(bus_read),
            If(
                bus_read & ~bus_read_d,  # One response, one cycle
                rd_ack_pipe.eq(1),
                If(
                    rx_fifo.readable,
                    bus.dat_r.eq(rx_fifo.dout),
                    rx_fifo.re.eq(1),
                    self.rx_under.flag.eq(0),
                ).Else(
                    # Don't stall the bus indefinitely if we try to read from an empty fifo...just
                    # return garbage
                    bus.dat_r.eq(0xdeadbeef),
                    rx_fifo.re.eq(0),
                    self.rx_under.flag.eq(1),
                )).Else(
                    rx_fifo.re.eq(0),
                    rd_ack_pipe.eq(0),
                    self.rx_under.flag.eq(0),
                ),
            rd_ack.eq(rd_ack_pipe),
        ]

        # tx/write spiperipheral
        self.submodules.tx_fifo = tx_fifo = ResetInserter(["sys"])(
            SyncFIFOBuffered(16, 1280))
        self.comb += self.tx_fifo.reset_sys.eq(self.control.fields.reset
                                               | ResetSignal())
        self.submodules.tx_over = StickyBit()
        self.comb += [
            self.tx_over.clear.eq(self.control.fields.clrerr),
            self.status.fields.tx_over.eq(self.tx_over.bit),
            self.status.fields.tx_empty.eq(~tx_fifo.readable),
            self.status.fields.tx_avail.eq(tx_fifo.writable),
            self.status.fields.tx_level.eq(tx_fifo.level),
        ]

        write_gate = Signal()
        self.sync += [
            If(
                bus.cyc & bus.stb & bus.we & ~bus.ack,
                If(
                    tx_fifo.writable,
                    tx_fifo.din.eq(bus.dat_w),
                    tx_fifo.we.eq(
                        ~write_gate),  # ensure write is just single cycle
                    wr_ack.eq(1),
                    write_gate.eq(1),
                ).Else(
                    self.tx_over.flag.eq(1),
                    tx_fifo.we.eq(0),
                    wr_ack.eq(0),
                    write_gate.eq(0),
                )).Else(
                    write_gate.eq(0),
                    tx_fifo.we.eq(0),
                    wr_ack.eq(0),
                )
        ]

        # Replica CSR into "spi" clock domain
        self.tx = Signal(16, reset_less=True)
        self.tip_r = Signal()
        self.rxfull_r = Signal()
        self.rxover_r = Signal()
        self.csn_r = Signal()

        self.specials += MultiReg(~self.csn, self.tip_r)
        self.comb += self.status.fields.tip.eq(self.tip_r)
        tip_d = Signal()
        donepulse = Signal()
        self.sync += tip_d.eq(self.tip_r)
        self.comb += donepulse.eq(
            ~self.tip_r & tip_d)  # done pulse goes high when tip drops

        self.submodules.rx_over = StickyBit()
        self.comb += [
            self.status.fields.rx_over.eq(self.rx_over.bit),
            self.rx_over.clear.eq(self.control.fields.clrerr),
            self.rx_over.flag.eq(~self.rx_fifo.writable & donepulse),
        ]
        self.submodules.tx_under = StickyBit()
        self.comb += [
            self.status.fields.tx_under.eq(self.tx_under.bit),
            self.tx_under.clear.eq(self.control.fields.clrerr),
            self.tx_under.flag.eq(~self.tx_fifo.readable & donepulse),
        ]

        rx = Signal(16, reset_less=True)
        self.comb += [
            self.rx_fifo.din.eq(
                rx
            ),  # assume CS is high for quite a while before donepulse triggers the write, this stabilizes the rx din
            self.rx_fifo.we.eq(donepulse),
            self.tx_fifo.re.eq(donepulse),
        ]

        # form the SPI-clock domain shift registers.
        self.comb += [
            self.hold.eq(~self.tx_fifo.readable),
        ]
        # input register on copi. Falling edge sampling.
        self.specials += Instance(
            "SB_IO",
            p_IO_STANDARD="SB_LVCMOS",
            p_PIN_TYPE=0b0000_00,
            p_NEG_TRIGGER=1,
            io_PACKAGE_PIN=self.copi,
            i_CLOCK_ENABLE=1,
            i_INPUT_CLK=ClockSignal("sclk"),
            i_OUTPUT_ENABLE=0,
            o_D_IN_0=rx[0],  # D_IN_0 is falling edge when NEG_TRIGGER is 1
        )
        for bit in range(15):
            self.specials += Instance(
                "SB_DFFN",
                i_C=ClockSignal("sclk"),
                i_D=rx[bit],
                o_Q=rx[bit + 1],
            )

        if pipeline_cipo:
            # output register on cipo. produces new result on falling-edge
            # this improves Tc-q timing, and more importantly, keeps it consistent between builds
            self.specials += Instance(
                "SB_IO",
                p_IO_STANDARD="SB_LVCMOS",
                p_PIN_TYPE=0b1001_00,
                p_NEG_TRIGGER=
                1,  # this causes the output to update on the falling edge
                io_PACKAGE_PIN=pads.cipo,
                i_CLOCK_ENABLE=1,
                i_OUTPUT_CLK=ClockSignal("sclk"),
                i_OUTPUT_ENABLE=self.oe,
                i_D_OUT_0=self.tx[15],
            )
            # tx is updated on the rising edge, but the SB_IO primitive pushes the new data out on the falling edge
            # so the total time we have to move the data from this shift register to the output register is a half
            # clock cycle.
            spi_load = Signal()
            self.sync.sclk += [
                If(
                    spi_load,
                    If(
                        self.tx_fifo.readable,
                        self.tx.eq(self.tx_fifo.dout),
                    ).Else(
                        self.tx.eq(
                            0xDDDD),  # in case of underflow send error code
                    ),
                ).Else(
                    self.tx.eq(Cat(1, self.tx[0:15])
                               )  # if we overshift, we eventually get all 1's
                )
            ]
            self.specials += Instance(
                "SB_DFFS",
                i_C=ClockSignal("sclk"),
                i_D=0,
                o_Q=spi_load,
                i_S=self.csn,
            )
        else:
            # this path gets you a "standards compliant" SPI interface, but it's slower and the timing is less reliable
            self.cipo_ts = TSTriple(1)
            self.specials += [self.cipo_ts.get_tristate(pads.cipo)]
            self.comb += [
                self.cipo_ts.oe.eq(self.oe),
                self.cipo_ts.o.eq(self.tx[15]),
            ]

            tx_staged = Signal(16)
            for bit in range(16):
                if bit != 0:
                    self.specials += Instance(
                        "SB_DFFS",
                        i_C=ClockSignal("sclk"),
                        i_D=self.tx[bit - 1],
                        o_Q=self.tx[bit],
                        i_S=tx_staged[bit] & self.csn,
                    )
                else:
                    self.specials += Instance(
                        "SB_DFFS",
                        i_C=ClockSignal("sclk"),
                        i_D=0,
                        o_Q=self.tx[bit],
                        i_S=tx_staged[bit] & self.csn,
                    )
            self.comb += [
                If(
                    self.tx_fifo.readable,
                    tx_staged.eq(self.tx_fifo.dout),
                ).Else(tx_staged.eq(0xDDDD), )
            ]
Beispiel #21
0
    def __init__(self, pads, fifo_depth=256):
        self.intro = ModuleDoc("""Intro

        I2S slave creates a slave audio interface instance. Tx and Rx interfaces are inferred based
        upon the presence or absence of the respective pins in the "pads" argument.

        The interface is I2S-like, but note the deviation that the bits are justified left without a
        1-bit pad after sync edges. This isn't a problem for talking to the LM49352 codec this was
        designed for, as the bit offset is programmable, but this will not work well if are talking
        to a CODEC without a programmable bit offset!

        System Interface
        ----------------

        Audio interchange is done with the system using 16-bit stereo samples, with the right channel
        mapped to the least significant word of a 32-bit word. Thus each 32-bit word is a single
        stereo sample. As this is a slave I2S interface, sampling rate and framing is set by the
        programming of the audio CODEC chip. A slave situation is preferred because this defers the
        generation of audio clocks to the CODEC, which has PLLs specialized to generate the correct
        frequencies for audio sampling rates.

        `fifo_depth` is the depth at which either a read interrupt is fired (guaranteeing at least
        `fifo_depth` stereo samples in the receive FIFO) or a write interrupt is fired (guaranteeing
        at least `fifo_depth` free space in the transmit FIFO). The maximum depth is 512.

        To receive audio data:

        - reset the Rx FIFO, to guarantee all pointers at zero
        - hook the Rx full interrupt with an interrupt handler (optional)
        - if the CODEC is not yet transmitting data, initiate data transmission
        - enable Rx FIFO to run
        - poll or wait for interrupt; upon interrupt, read `fifo_depth` words. Repeat.
        - to close the stream, simply clear the Rx FIFO enable bit. The next initiation should call a
          reset of the FIFO to ensure leftover previous data is cleared from the FIFO.

        To transmit audio data:

        - reset the Tx FIFO, to guarantee all pointers at zero
        - hook the Tx available interrupt with an interrupt handler (optional)
        - write 512 words of data into the Tx FIFO, filling it to the max
        - if the CODEC is not yet requesting data and unmuted, unmute and initiate reception
        - enable the Tx FIFO to run
        - poll or wait for interrupt; upon interrupt, write `fifo_depth` words. Repeat.
        - to close stream, mute the DAC and stop the request clock. Ideally, this can be completed
          before the FIFO is emptied, so there is no jarring pop or truncation of data
        - stop FIFO running. Next initiation should reset the FIFO to ensure leftover previous data
          in FIFO is cleared.

        CODEC Interface
        ---------------

        The interface assumes we have a sysclk domain running around 100MHz, and that our typical max
        audio rate is 44.1kHz * 24bits * 2channels = 2.1168MHz audio clock. Thus, the architecture
        treats the audio clock and data as asynchronous inputs that are MultiReg-syncd into the clock
        domain. Probably the slowest sysclk rate this might work with is around 20-25MHz (10x over
        sampling), but at 100MHz things will be quite comfortable.

        The upside of the fully asynchronous implementation is that we can leave the I/O unconstrained,
        giving the place/route more latitude to do its job.

        Here's the timing format targeted by this I2S interface:

            .. wavedrom::
                :caption: Timing format of the I2S interface

                { "signal" : [
                  { "name": "clk",         "wave": "n....|.......|......" },
                  { "name": "sync",        "wave": "1.0..|....1..|....0." },
                  { "name": "tx/rx",       "wave": ".====|==x.===|==x.=x", "data":
                  ["L15", "L14", "...", "L1", "L0", "R15", "R14", "...", "R1", "R0", "L15"] },
                ]}

        - Data is updated on the falling edge
        - Data is sampled on the rising edge
        - Words are MSB-to-LSB, left-justified (**NOTE: this is a deviation from strict I2S, which
          offsets by 1 from the left**)
        - Sync is an input (FPGA is slave, codec is master): low => left channel, high => right channel
        - Sync can be longer than the wordlen, extra bits are just ignored
        - Tx is data to the codec (SDI pin on LM49352)
        - Rx is data from the codec (SDO pin on LM49352)
        """)

        # One cache line is 8 32-bit words, need to always have enough space for one line or else
        # nothing works
        if fifo_depth > 504:
            fifo_depth = 504
            print(
                "I2S warning: fifo depth greater than 504 selected; truncating to 504"
            )
        if fifo_depth < 8:
            fifo_depth = 8
            print(
                "I2S warning: fifo depth less than 8 selected; truncating to 8"
            )

        # Connect pins, synchronizers, and edge detectors
        if hasattr(pads, 'tx'):
            tx_pin = Signal()
            self.comb += pads.tx.eq(tx_pin)
        if hasattr(pads, 'rx'):
            rx_pin = Signal()
            self.specials += MultiReg(pads.rx, rx_pin)
        sync_pin = Signal()
        self.specials += MultiReg(pads.sync, sync_pin)

        clk_pin = Signal()
        self.specials += MultiReg(pads.clk, clk_pin)
        clk_d = Signal()
        self.sync += clk_d.eq(clk_pin)
        rising_edge = Signal()
        falling_edge = Signal()
        self.comb += [
            rising_edge.eq(clk_pin & ~clk_d),
            falling_edge.eq(~clk_pin & clk_d)
        ]

        # Wishbone bus
        self.bus = bus = wishbone.Interface()
        rd_ack = Signal()
        wr_ack = Signal()
        self.comb += [
            If(
                bus.we,
                bus.ack.eq(wr_ack),
            ).Else(bus.ack.eq(rd_ack), )
        ]

        # Interrupts
        self.submodules.ev = EventManager()
        if hasattr(pads, 'rx'):
            self.ev.rx_ready = EventSourcePulse(
                description="Indicates FIFO is ready to read"
            )  # Rising edge triggered
            self.ev.rx_error = EventSourcePulse(
                description=
                "Indicates an Rx error has happened (over/underflow)")
        if hasattr(pads, 'tx'):
            self.ev.tx_ready = EventSourcePulse(
                description=
                "Indicates enough space available for next Tx quanta of {} words"
                .format(fifo_depth))
            self.ev.tx_error = EventSourcePulse(
                description="Indicates a Tx error has happened (over/underflow"
            )
        self.ev.finalize()

        # build the RX subsystem
        if hasattr(pads, 'rx'):
            rx_rd_d = Signal(32)
            rx_almostfull = Signal()
            rx_almostempty = Signal()
            rx_full = Signal()
            rx_empty = Signal()
            rx_rdcount = Signal(9)
            rx_rderr = Signal()
            rx_wrerr = Signal()
            rx_wrcount = Signal(9)
            rx_rden = Signal()
            rx_wr_d = Signal(32)
            rx_wren = Signal()

            self.rx_ctl = CSRStorage(
                description="Rx data path control",
                fields=[
                    CSRField("enable",
                             size=1,
                             description="Enable the receiving data"),
                    CSRField(
                        "reset",
                        size=1,
                        description=
                        "Writing `1` resets the FIFO. Reset happens regardless of enable state.",
                        pulse=1)
                ])
            self.rx_stat = CSRStatus(
                description="Rx data path status",
                fields=[
                    CSRField("overflow", size=1, description="Rx overflow"),
                    CSRField("underflow", size=1, description="Rx underflow"),
                    CSRField(
                        "dataready",
                        size=1,
                        description="{} words of data loaded and ready to read"
                        .format(fifo_depth)),
                    CSRField("empty",
                             size=1,
                             description="No data available in FIFO to read"
                             ),  # next flags probably never used
                    CSRField("wrcount", size=9, description="Write count"),
                    CSRField("rdcount", size=9, description="Read count"),
                    CSRField("fifo_depth",
                             size=9,
                             description="FIFO depth as synthesized")
                ])
            self.comb += self.rx_stat.fields.fifo_depth.eq(fifo_depth)

            rx_rst_cnt = Signal(3)
            rx_reset = Signal()
            self.sync += [
                If(
                    self.rx_ctl.fields.reset,
                    rx_rst_cnt.eq(5),  # 5 cycles reset required by design
                    rx_reset.eq(1)).Else(
                        If(rx_rst_cnt == 0,
                           rx_reset.eq(0)).Else(rx_rst_cnt.eq(rx_rst_cnt - 1),
                                                rx_reset.eq(1)))
            ]
            # At a width of 32 bits, an 18kiB fifo is 512 entries deep
            self.specials += Instance(
                "FIFO_SYNC_MACRO",
                p_DEVICE="7SERIES",
                p_FIFO_SIZE="18Kb",
                p_DATA_WIDTH=32,
                p_ALMOST_EMPTY_OFFSET=8,
                p_ALMOST_FULL_OFFSET=(512 - fifo_depth),
                p_DO_REG=0,
                i_CLK=ClockSignal(),
                i_RST=rx_reset,
                o_ALMOSTFULL=rx_almostfull,
                o_ALMOSTEMPTY=rx_almostempty,
                o_FULL=rx_full,
                o_EMPTY=rx_empty,
                i_WREN=rx_wren & ~rx_reset,
                i_DI=rx_wr_d,
                i_RDEN=rx_rden & ~rx_reset,
                o_DO=rx_rd_d,
                o_RDCOUNT=rx_rdcount,
                o_RDERR=rx_rderr,
                o_WRCOUNT=rx_wrcount,
                o_WRERR=rx_wrerr,
            )
            self.comb += [  # Wire up the status signals and interrupts
                self.rx_stat.fields.overflow.eq(rx_wrerr),
                self.rx_stat.fields.underflow.eq(rx_rderr),
                self.rx_stat.fields.dataready.eq(rx_almostfull),
                self.rx_stat.fields.wrcount.eq(rx_wrcount),
                self.rx_stat.fields.rdcount.eq(rx_rdcount),
                self.ev.rx_ready.trigger.eq(rx_almostfull),
                self.ev.rx_error.trigger.eq(rx_wrerr | rx_rderr),
            ]
            bus_read = Signal()
            bus_read_d = Signal()
            rd_ack_pipe = Signal()
            self.comb += bus_read.eq(bus.cyc & bus.stb & ~bus.we
                                     & (bus.cti == 0))
            self.sync += [  # This is the bus responder -- only works for uncached memory regions
                bus_read_d.eq(bus_read),
                If(
                    bus_read & ~bus_read_d,  # One response, one cycle
                    rd_ack_pipe.eq(1),
                    If(
                        ~rx_empty,
                        bus.dat_r.eq(rx_rd_d),
                        rx_rden.eq(1),
                    ).Else(
                        # Don't stall the bus indefinitely if we try to read from an empty fifo...just
                        # return garbage
                        bus.dat_r.eq(0xdeadbeef),
                        rx_rden.eq(0),
                    )).Else(
                        rx_rden.eq(0),
                        rd_ack_pipe.eq(0),
                    ),
                rd_ack.eq(rd_ack_pipe),
            ]

            rx_cnt = Signal(5)
            self.submodules.rxi2s = rxi2s = FSM(reset_state="IDLE")
            rxi2s.act(
                "IDLE",
                NextValue(rx_wr_d, 0),
                If(
                    self.rx_ctl.fields.enable,
                    # Wait_sync guarantees we start at the beginning of a left frame, and not in
                    # the middle
                    If(rising_edge & sync_pin, NextState("WAIT_SYNC")))),
            rxi2s.act(
                "WAIT_SYNC",
                If(rising_edge & ~sync_pin, NextState("LEFT"),
                   NextValue(rx_cnt, 16)),
            )
            rxi2s.act(
                "LEFT",
                If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else(
                    NextValue(rx_wr_d, Cat(rx_pin, rx_wr_d[:-1])),
                    NextValue(rx_cnt, rx_cnt - 1), NextState("LEFT_WAIT")))
            rxi2s.act(
                "LEFT_WAIT",
                If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else(
                    If(
                        rising_edge,
                        If((rx_cnt == 0) & sync_pin, NextValue(rx_cnt, 16),
                           NextState("RIGHT")).Elif(rx_cnt > 0,
                                                    NextState("LEFT")))))
            rxi2s.act(
                "RIGHT",
                If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else(
                    NextValue(rx_wr_d, Cat(rx_pin, rx_wr_d[:-1])),
                    NextValue(rx_cnt, rx_cnt - 1), NextState("RIGHT_WAIT")))
            rxi2s.act(
                "RIGHT_WAIT",
                If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else(
                    If(
                        rising_edge,
                        If(
                            (rx_cnt == 0) & ~sync_pin,
                            NextValue(rx_cnt, 16),
                            NextState("LEFT"),
                            rx_wren.eq(
                                1
                            )  # Pulse rx_wren to write the current data word
                        ).Elif(rx_cnt > 0, NextState("RIGHT")))))

        # Build the TX subsystem
        if hasattr(pads, 'tx'):
            tx_rd_d = Signal(32)
            tx_almostfull = Signal()
            tx_almostempty = Signal()
            tx_full = Signal()
            tx_empty = Signal()
            tx_rdcount = Signal(9)
            tx_rderr = Signal()
            tx_wrerr = Signal()
            tx_wrcount = Signal(9)
            tx_rden = Signal()
            tx_wr_d = Signal(32)
            tx_wren = Signal()

            self.tx_ctl = CSRStorage(
                description="Tx data path control",
                fields=[
                    CSRField("enable",
                             size=1,
                             description="Enable the transmission data"),
                    CSRField(
                        "reset",
                        size=1,
                        description=
                        "Writing `1` resets the FIFO. Reset happens regardless of enable state.",
                        pulse=1)
                ])
            self.tx_stat = CSRStatus(
                description="Tx data path status",
                fields=[
                    CSRField("overflow", size=1, description="Tx overflow"),
                    CSRField("underflow", size=1, description="Tx underflow"),
                    CSRField(
                        "free",
                        size=1,
                        description="At least {} words of space free".format(
                            fifo_depth)),
                    CSRField("almostfull",
                             size=1,
                             description="Less than 8 words space available"
                             ),  # the next few flags should be rarely used
                    CSRField("full",
                             size=1,
                             description="FIFO is full or overfull"),
                    CSRField("empty", size=1, description="FIFO is empty"),
                    CSRField("wrcount", size=9, description="Tx write count"),
                    CSRField("rdcount", size=9, description="Tx read count"),
                ])

            tx_rst_cnt = Signal(3)
            tx_reset = Signal()
            self.sync += [
                If(
                    self.tx_ctl.fields.reset,
                    tx_rst_cnt.eq(5),  # 5 cycles reset required by design
                    tx_reset.eq(1)).Else(
                        If(tx_rst_cnt == 0,
                           tx_reset.eq(0)).Else(tx_rst_cnt.eq(tx_rst_cnt - 1),
                                                tx_reset.eq(1)))
            ]
            # At a width of 32 bits, an 18kiB fifo is 512 entries deep
            self.specials += Instance(
                "FIFO_SYNC_MACRO",
                p_DEVICE="7SERIES",
                p_FIFO_SIZE="18Kb",
                p_DATA_WIDTH=32,
                p_ALMOST_EMPTY_OFFSET=fifo_depth,
                p_ALMOST_FULL_OFFSET=8,
                p_DO_REG=0,
                i_CLK=ClockSignal(),
                i_RST=tx_reset,
                o_ALMOSTFULL=tx_almostfull,
                o_ALMOSTEMPTY=tx_almostempty,
                o_FULL=tx_full,
                o_EMPTY=tx_empty,
                i_WREN=tx_wren & ~tx_reset,
                i_DI=tx_wr_d,
                i_RDEN=tx_rden & ~tx_reset,
                o_DO=tx_rd_d,
                o_RDCOUNT=tx_rdcount,
                o_RDERR=tx_rderr,
                o_WRCOUNT=tx_wrcount,
                o_WRERR=tx_wrerr,
            )

            self.comb += [  # Wire up the status signals and interrupts
                self.tx_stat.fields.overflow.eq(tx_wrerr),
                self.tx_stat.fields.underflow.eq(tx_rderr),
                self.tx_stat.fields.free.eq(tx_almostempty),
                self.tx_stat.fields.almostfull.eq(tx_almostfull),
                self.tx_stat.fields.full.eq(tx_full),
                self.tx_stat.fields.empty.eq(tx_empty),
                self.tx_stat.fields.rdcount.eq(tx_rdcount),
                self.tx_stat.fields.wrcount.eq(tx_wrcount),
                self.ev.tx_ready.trigger.eq(tx_almostempty),
                self.ev.tx_error.trigger.eq(tx_wrerr | tx_rderr),
            ]
            self.sync += [
                # This is the bus responder -- need to check how this interacts with uncached memory
                # region
                If(
                    bus.cyc & bus.stb & bus.we & ~bus.ack,
                    If(
                        ~tx_full,
                        tx_wr_d.eq(bus.dat_w),
                        tx_wren.eq(1),
                        wr_ack.eq(1),
                    ).Else(
                        tx_wren.eq(0),
                        wr_ack.eq(0),
                    )).Else(
                        tx_wren.eq(0),
                        wr_ack.eq(0),
                    )
            ]

            tx_cnt = Signal(5)
            tx_buf = Signal(32)
            self.submodules.txi2s = txi2s = FSM(reset_state="IDLE")
            txi2s.act(
                "IDLE",
                If(self.tx_ctl.fields.enable,
                   If(
                       falling_edge & sync_pin,
                       NextState("WAIT_SYNC"),
                   ))),
            txi2s.act(
                "WAIT_SYNC",
                If(falling_edge & ~sync_pin, NextState("LEFT"),
                   NextValue(tx_cnt, 16), NextValue(tx_buf, tx_rd_d),
                   tx_rden.eq(1)))
            txi2s.act(
                "LEFT",
                If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else(
                    NextValue(tx_pin, tx_buf[31]),
                    NextValue(tx_buf, Cat(0, tx_buf[:-1])),
                    NextValue(tx_cnt, tx_cnt - 1), NextState("LEFT_WAIT")))
            txi2s.act(
                "LEFT_WAIT",
                If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else(
                    If(
                        falling_edge,
                        If((tx_cnt == 0) & sync_pin, NextValue(tx_cnt, 16),
                           NextState("RIGHT")).Elif(tx_cnt > 0,
                                                    NextState("LEFT")))))
            txi2s.act(
                "RIGHT",
                If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else(
                    NextValue(tx_pin, tx_buf[31]),
                    NextValue(tx_buf, Cat(0, tx_buf[:-1])),
                    NextValue(tx_cnt, tx_cnt - 1), NextState("RIGHT_WAIT")))
            txi2s.act(
                "RIGHT_WAIT",
                If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else(
                    If(
                        falling_edge,
                        If((tx_cnt == 0) & ~sync_pin, NextValue(tx_cnt, 16),
                           NextState("LEFT"), NextValue(tx_buf, tx_rd_d),
                           tx_rden.eq(1)).Elif(tx_cnt > 0,
                                               NextState("RIGHT")))))
Beispiel #22
0
    def __init__(self,
                 pads,
                 fifo_depth=256,
                 controller=False,
                 master=False,
                 concatenate_channels=True,
                 sample_width=16,
                 frame_format=I2S_FORMAT.I2S_LEFT_JUSTIFIED,
                 lrck_ref_freq=100e6,
                 lrck_freq=44100,
                 bits_per_channel=28):
        if master == True:
            print(
                "Master/slave terminology deprecated, please use controller/peripheral. Please see http://oshwa.org/a-resolution-to-redefine-spi-signal-names."
            )
            controller = True

        self.intro = ModuleDoc("""Intro

        I2S controller/peripheral creates a controller/peripheral audio interface instance depending on a configured controller variable.
        Tx and Rx interfaces are inferred based upon the presence or absence of the respective pins in the "pads" argument.

        When device is configured as controller you can manipulate LRCK and SCLK signals using below variables.

        - lrck_ref_freq - is a reference signal that is required to achive desired LRCK and SCLK frequencies.
                         Have be the same as your sys_clk.
        - lrck_freq - this variable defines requested LRCK frequency. Mind you, that based on sys_clk frequency,
                         configured value will be more or less acurate.
        - bits_per_channel - defines SCLK frequency. Mind you, that based on sys_clk frequency,
                         the requested amount of bits per channel may vary from configured.

        When device is configured as peripheral I2S interface, sampling rate and framing is set by the
        programming of the audio CODEC chip. A peripheral configuration defers the
        generation of audio clocks to the CODEC, which has PLLs specialized to generate the correct
        frequencies for audio sampling rates.

        I2S core supports two formats: standard and left-justified.

        - Standard format requires a device to receive and send data with one bit offset for both channels.
            Left channel begins with low signal on LRCK.
        - Left justified format requires from device to receive and send data without any bit offset for both channels.
            Left channel begins with high signal on LRCK.

        Sample width can be any of 1 to 32 bits.

        When sample_width is less than or equal to 16-bit and concatenate_channels is enabled,
        sending and reciving channels is performed atomically. eg. both samples are transfered from/to fifo in single operation.

        System Interface
        ----------------

        `fifo_depth` is the depth at which either a read interrupt is fired (guaranteeing at least
        `fifo_depth` stereo samples in the receive FIFO) or a write interrupt is fired (guaranteeing
        at least `fifo_depth` free space in the transmit FIFO). The maximum depth is 512.

        To receive audio data:

        - reset the Rx FIFO, to guarantee all pointers at zero
        - hook the Rx full interrupt with an interrupt handler (optional)
        - if the CODEC is not yet transmitting data, initiate data transmission
        - enable Rx FIFO to run
        - poll or wait for interrupt; upon interrupt, read `fifo_depth` words. Repeat.
        - to close the stream, simply clear the Rx FIFO enable bit. The next initiation should call a
          reset of the FIFO to ensure leftover previous data is cleared from the FIFO.

        To transmit audio data:

        - reset the Tx FIFO, to guarantee all pointers at zero
        - hook the Tx available interrupt with an interrupt handler (optional)
        - write 512 words of data into the Tx FIFO, filling it to the max
        - if the CODEC is not yet requesting data and unmuted, unmute and initiate reception
        - enable the Tx FIFO to run
        - poll or wait for interrupt; upon interrupt, write `fifo_depth` words. Repeat.
        - to close stream, mute the DAC and stop the request clock. Ideally, this can be completed
          before the FIFO is emptied, so there is no jarring pop or truncation of data
        - stop FIFO running. Next initiation should reset the FIFO to ensure leftover previous data
          in FIFO is cleared.

        CODEC Interface
        ---------------

        The interface assumes we have a sysclk domain running around 100MHz, and that our typical max
        audio rate is 44.1kHz * 24bits * 2channels = 2.1168MHz audio clock. Thus, the architecture
        treats the audio clock and data as asynchronous inputs that are MultiReg-syncd into the clock
        domain. Probably the slowest sysclk rate this might work with is around 20-25MHz (10x over
        sampling), but at 100MHz things will be quite comfortable.

        The upside of the fully asynchronous implementation is that we can leave the I/O unconstrained,
        giving the place/route more latitude to do its job.

        Here's the timing format targeted by this I2S interface:

            .. wavedrom::
                :caption: Timing format of the I2S interface

                { "signal" : [
                  { "name": "clk",         "wave": "n....|.......|......" },
                  { "name": "sync",        "wave": "1.0..|....1..|....0." },
                  { "name": "tx/rx",       "wave": ".====|==x.===|==x.=x", "data":
                  ["L15", "L14", "...", "L1", "L0", "R15", "R14", "...", "R1", "R0", "L15"] },
                ]}

        - Data is updated on the falling edge
        - Data is sampled on the rising edge
        - Words are MSB-to-LSB,
        - Sync is an input or output based on configure mode,
        - Sync can be longer than the wordlen, extra bits are just ignored
        - Tx is data to the codec (SDI pin on LM49352)
        - Rx is data from the codec (SDO pin on LM49352)
        """)

        # One cache line is 8 32-bit words, need to always have enough space for one line or else
        # nothing works
        if fifo_depth > 504:
            fifo_depth = 504
            print(
                "I2S warning: fifo depth greater than 504 selected; truncating to 504"
            )
        if fifo_depth < 8:
            fifo_depth = 8
            print(
                "I2S warning: fifo depth less than 8 selected; truncating to 8"
            )
        if sample_width > 32:
            sample_width = 32
            print(
                "I2S warning: sample width greater than 32 bits. truncating to 32"
            )

        # Connect pins, synchronizers, and edge detectors
        if hasattr(pads, 'tx'):
            tx_pin = Signal()
            self.comb += pads.tx.eq(tx_pin)
        if hasattr(pads, 'rx'):
            rx_pin = Signal()
            self.specials += MultiReg(pads.rx, rx_pin)

        fifo_data_width = sample_width
        if concatenate_channels:
            if sample_width <= 16:
                fifo_data_width = sample_width * 2
            else:
                concatenate_channels = False
                print(
                    "I2S warning: sample width greater than 16 bits. your channels can't be glued"
                )

        sync_pin = Signal()
        self.specials += MultiReg(pads.sync, sync_pin)

        clk_pin = Signal()
        self.specials += MultiReg(pads.clk, clk_pin)
        clk_d = Signal()
        self.sync += clk_d.eq(clk_pin)
        rising_edge = Signal()
        falling_edge = Signal()
        self.comb += [
            rising_edge.eq(clk_pin & ~clk_d),
            falling_edge.eq(~clk_pin & clk_d)
        ]

        # Wishbone bus
        self.bus = bus = wishbone.Interface()
        rd_ack = Signal()
        wr_ack = Signal()
        self.comb += [
            If(
                bus.we,
                bus.ack.eq(wr_ack),
            ).Else(bus.ack.eq(rd_ack), )
        ]

        if controller == True:
            if bits_per_channel < sample_width and frame_format == I2S_FORMAT.I2S_STANDARD:
                bits_per_channel = sample_width + 1
                print(
                    "I2S warning: bits per channel can't be smaller than sample_width. Setting bits per channel to {}"
                    .format(sample_width + 1))
            # implementing LRCK signal
            lrck_period = int(lrck_ref_freq / (lrck_freq * 2))
            lrck_counter = Signal(16)
            self.sync += [
                If(
                    (lrck_counter == lrck_period),
                    lrck_counter.eq(0),
                    pads.sync.eq(~pads.sync),
                ).Else(lrck_counter.eq(lrck_counter + 1))
            ]
            # implementing SCLK signal
            sclk_period = int(lrck_period / (bits_per_channel * 2))
            sclk_counter = Signal(16)
            self.sync += [
                If(
                    (sclk_counter == sclk_period),
                    sclk_counter.eq(0),
                    pads.clk.eq(~pads.clk),
                ).Else(sclk_counter.eq(sclk_counter + 1))
            ]

        # Interrupts
        self.submodules.ev = EventManager()
        if hasattr(pads, 'rx'):
            self.ev.rx_ready = EventSourcePulse(
                description="Indicates FIFO is ready to read"
            )  # Rising edge triggered
            self.ev.rx_error = EventSourcePulse(
                description=
                "Indicates an Rx error has happened (over/underflow)")
        if hasattr(pads, 'tx'):
            self.ev.tx_ready = EventSourcePulse(
                description=
                "Indicates enough space available for next Tx quanta of {} words"
                .format(fifo_depth))
            self.ev.tx_error = EventSourcePulse(
                description="Indicates a Tx error has happened (over/underflow"
            )
        self.ev.finalize()

        # build the RX subsystem
        if hasattr(pads, 'rx'):
            rx_rd_d = Signal(fifo_data_width)
            rx_almostfull = Signal()
            rx_almostempty = Signal()
            rx_full = Signal()
            rx_empty = Signal()
            rx_rdcount = Signal(9)
            rx_rderr = Signal()
            rx_wrerr = Signal()
            rx_wrcount = Signal(9)
            rx_rden = Signal()
            rx_wr_d = Signal(fifo_data_width)
            rx_wren = Signal()

            self.rx_ctl = CSRStorage(
                description="Rx data path control",
                fields=[
                    CSRField("enable",
                             size=1,
                             description="Enable the receiving data"),
                    CSRField(
                        "reset",
                        size=1,
                        description=
                        "Writing `1` resets the FIFO. Reset happens regardless of enable state.",
                        pulse=1)
                ])
            self.rx_stat = CSRStatus(
                description="Rx data path status",
                fields=[
                    CSRField("overflow", size=1, description="Rx overflow"),
                    CSRField("underflow", size=1, description="Rx underflow"),
                    CSRField(
                        "dataready",
                        size=1,
                        description="{} words of data loaded and ready to read"
                        .format(fifo_depth)),
                    CSRField("empty",
                             size=1,
                             description="No data available in FIFO to read"
                             ),  # next flags probably never used
                    CSRField("wrcount", size=9, description="Write count"),
                    CSRField("rdcount", size=9, description="Read count"),
                    CSRField("fifo_depth",
                             size=9,
                             description="FIFO depth as synthesized"),
                    CSRField(
                        "concatenate_channels",
                        size=1,
                        reset=concatenate_channels,
                        description="Receive and send both channels atomically"
                    )
                ])
            self.rx_conf = CSRStatus(
                description="Rx configuration",
                fields=[
                    CSRField(
                        "format",
                        size=2,
                        reset=frame_format.value,
                        description=
                        "I2S sample format. {} is left-justified, {} is I2S standard"
                        .format(I2S_FORMAT.I2S_LEFT_JUSTIFIED,
                                I2S_FORMAT.I2S_STANDARD)),
                    CSRField("sample_width",
                             size=6,
                             reset=sample_width,
                             description="Single sample width"),
                    CSRField("lrck_freq",
                             size=24,
                             reset=lrck_freq,
                             description="Audio sampling rate frequency"),
                ])
            self.comb += self.rx_stat.fields.fifo_depth.eq(fifo_depth)

            rx_rst_cnt = Signal(3)
            rx_reset = Signal()
            self.sync += [
                If(
                    self.rx_ctl.fields.reset,
                    rx_rst_cnt.eq(5),  # 5 cycles reset required by design
                    rx_reset.eq(1)).Else(
                        If(rx_rst_cnt == 0,
                           rx_reset.eq(0)).Else(rx_rst_cnt.eq(rx_rst_cnt - 1),
                                                rx_reset.eq(1)))
            ]

            # At a width of 32 bits, an 18kiB fifo is 512 entries deep
            self.specials += Instance(
                "FIFO_SYNC_MACRO",
                p_DEVICE="7SERIES",
                p_FIFO_SIZE="18Kb",
                p_DATA_WIDTH=fifo_data_width,
                p_ALMOST_EMPTY_OFFSET=8,
                p_ALMOST_FULL_OFFSET=(512 - fifo_depth),
                p_DO_REG=0,
                i_CLK=ClockSignal(),
                i_RST=rx_reset,
                o_ALMOSTFULL=rx_almostfull,
                o_ALMOSTEMPTY=rx_almostempty,
                o_FULL=rx_full,
                o_EMPTY=rx_empty,
                i_WREN=rx_wren & ~rx_reset,
                i_DI=rx_wr_d,
                i_RDEN=rx_rden & ~rx_reset,
                o_DO=rx_rd_d,
                o_RDCOUNT=rx_rdcount,
                o_RDERR=rx_rderr,
                o_WRCOUNT=rx_wrcount,
                o_WRERR=rx_wrerr,
            )
            self.comb += [  # Wire up the status signals and interrupts
                self.rx_stat.fields.overflow.eq(rx_wrerr),
                self.rx_stat.fields.underflow.eq(rx_rderr),
                self.rx_stat.fields.dataready.eq(rx_almostfull),
                self.rx_stat.fields.wrcount.eq(rx_wrcount),
                self.rx_stat.fields.rdcount.eq(rx_rdcount),
                self.rx_stat.fields.empty.eq(rx_empty),
                self.ev.rx_ready.trigger.eq(rx_almostfull),
                self.ev.rx_error.trigger.eq(rx_wrerr | rx_rderr),
            ]
            bus_read = Signal()
            bus_read_d = Signal()
            rd_ack_pipe = Signal()
            self.comb += bus_read.eq(bus.cyc & bus.stb & ~bus.we
                                     & (bus.cti == 0))
            self.sync += [  # This is the bus responder -- only works for uncached memory regions
                bus_read_d.eq(bus_read),
                If(
                    bus_read & ~bus_read_d,  # One response, one cycle
                    rd_ack_pipe.eq(1),
                    If(
                        ~rx_empty,
                        bus.dat_r.eq(rx_rd_d),
                        rx_rden.eq(1),
                    ).Else(
                        # Don't stall the bus indefinitely if we try to read from an empty fifo...just
                        # return garbage
                        bus.dat_r.eq(0xdeadbeef),
                        rx_rden.eq(0),
                    )).Else(
                        rx_rden.eq(0),
                        rd_ack_pipe.eq(0),
                    ),
                rd_ack.eq(rd_ack_pipe),
            ]
            rx_cnt_width = math.ceil(math.log(fifo_data_width, 2))
            rx_cnt = Signal(rx_cnt_width)
            rx_delay_cnt = Signal()
            rx_delay_val = 1 if frame_format == I2S_FORMAT.I2S_STANDARD else 0

            self.submodules.rxi2s = rxi2s = FSM(reset_state="IDLE")
            rxi2s.act(
                "IDLE",
                NextValue(rx_wr_d, 0),
                If(
                    self.rx_ctl.fields.enable,
                    # Wait_sync guarantees we start at the beginning of a left frame, and not in
                    # the middle
                    If(
                        rising_edge &
                        (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD
                         else sync_pin), NextState("WAIT_SYNC"),
                        NextValue(rx_delay_cnt, rx_delay_val)))),
            rxi2s.act(
                "WAIT_SYNC",
                If(
                    rising_edge & (~sync_pin if frame_format
                                   == I2S_FORMAT.I2S_STANDARD else sync_pin),
                    If(rx_delay_cnt > 0,
                       NextValue(rx_delay_cnt, rx_delay_cnt - 1),
                       NextState("WAIT_SYNC")).Else(
                           NextState("LEFT"),
                           NextValue(rx_delay_cnt, rx_delay_val),
                           NextValue(rx_cnt, sample_width))),
            )
            rxi2s.act(
                "LEFT",
                If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else(
                    NextValue(rx_wr_d, Cat(rx_pin, rx_wr_d[:-1])),
                    NextValue(rx_cnt, rx_cnt - 1), NextState("LEFT_WAIT")))
            if concatenate_channels:
                rxi2s.act(
                    "LEFT_WAIT",
                    If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else(
                        If(
                            rising_edge,
                            If((rx_cnt == 0),
                               If((sync_pin if frame_format
                                   == I2S_FORMAT.I2S_STANDARD else ~sync_pin),
                                  If(
                                      rx_delay_cnt == 0,
                                      NextValue(rx_cnt, sample_width),
                                      NextValue(rx_delay_cnt, rx_delay_val),
                                      NextState("RIGHT"),
                                  ).Else(
                                      NextValue(rx_delay_cnt,
                                                rx_delay_cnt - 1),
                                      NextState("LEFT_WAIT"))).Else(
                                          NextState("LEFT_WAIT"))).Elif(
                                              rx_cnt > 0, NextState("LEFT")))))
            else:
                rxi2s.act(
                    "LEFT_WAIT",
                    If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else(
                        If(
                            rising_edge,
                            If(
                                (rx_cnt == 0),
                                If(
                                    (sync_pin
                                     if frame_format == I2S_FORMAT.I2S_STANDARD
                                     else ~sync_pin),
                                    If(
                                        rx_delay_cnt == 0,
                                        NextValue(rx_cnt, sample_width),
                                        NextValue(rx_delay_cnt, rx_delay_val),
                                        NextState("RIGHT"),
                                        rx_wren.eq(
                                            1
                                        )  # Pulse rx_wren to write the current data word
                                    ).Else(
                                        NextValue(rx_delay_cnt,
                                                  rx_delay_cnt - 1),
                                        NextState("LEFT_WAIT"))).Else(
                                            NextState("LEFT_WAIT"))).Elif(
                                                rx_cnt > 0,
                                                NextState("LEFT")))))

            rxi2s.act(
                "RIGHT",
                If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else(
                    NextValue(rx_wr_d, Cat(rx_pin, rx_wr_d[:-1])),
                    NextValue(rx_cnt, rx_cnt - 1), NextState("RIGHT_WAIT")))
            rxi2s.act(
                "RIGHT_WAIT",
                If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else(
                    If(
                        rising_edge,
                        If(
                            (rx_cnt == 0) &
                            (~sync_pin if frame_format
                             == I2S_FORMAT.I2S_STANDARD else sync_pin),
                            If(
                                rx_delay_cnt == 0,
                                NextValue(rx_cnt, sample_width),
                                NextValue(rx_delay_cnt, rx_delay_val),
                                NextState("LEFT"),
                                rx_wren.eq(
                                    1
                                )  # Pulse rx_wren to write the current data word
                            ).Else(NextValue(rx_delay_cnt, rx_delay_cnt - 1),
                                   NextState("RIGHT_WAIT"))).Elif(
                                       rx_cnt > 0, NextState("RIGHT")))))

        # Build the TX subsystem
        if hasattr(pads, 'tx'):
            tx_rd_d = Signal(fifo_data_width)
            tx_almostfull = Signal()
            tx_almostempty = Signal()
            tx_full = Signal()
            tx_empty = Signal()
            tx_rdcount = Signal(9)
            tx_rderr = Signal()
            tx_wrerr = Signal()
            tx_wrcount = Signal(9)
            tx_rden = Signal()
            tx_wr_d = Signal(fifo_data_width)
            tx_wren = Signal()

            self.tx_ctl = CSRStorage(
                description="Tx data path control",
                fields=[
                    CSRField("enable",
                             size=1,
                             description="Enable the transmission data"),
                    CSRField(
                        "reset",
                        size=1,
                        description=
                        "Writing `1` resets the FIFO. Reset happens regardless of enable state.",
                        pulse=1)
                ])
            self.tx_stat = CSRStatus(
                description="Tx data path status",
                fields=[
                    CSRField("overflow", size=1, description="Tx overflow"),
                    CSRField("underflow", size=1, description="Tx underflow"),
                    CSRField(
                        "free",
                        size=1,
                        description="At least {} words of space free".format(
                            fifo_depth)),
                    CSRField("almostfull",
                             size=1,
                             description="Less than 8 words space available"
                             ),  # the next few flags should be rarely used
                    CSRField("full",
                             size=1,
                             description="FIFO is full or overfull"),
                    CSRField("empty", size=1, description="FIFO is empty"),
                    CSRField("wrcount", size=9, description="Tx write count"),
                    CSRField("rdcount", size=9, description="Tx read count"),
                    CSRField(
                        "concatenate_channels",
                        size=1,
                        reset=concatenate_channels,
                        description="Receive and send both channels atomically"
                    )
                ])
            self.tx_conf = CSRStatus(
                description="TX configuration",
                fields=[
                    CSRField(
                        "format",
                        size=2,
                        reset=frame_format.value,
                        description=
                        "I2S sample format. {} is left-justified, {} is I2S standard"
                        .format(I2S_FORMAT.I2S_LEFT_JUSTIFIED,
                                I2S_FORMAT.I2S_STANDARD)),
                    CSRField("sample_width",
                             size=6,
                             reset=sample_width,
                             description="Single sample width"),
                    CSRField("lrck_freq",
                             size=24,
                             reset=lrck_freq,
                             description="Audio sampling rate frequency"),
                ])

            tx_rst_cnt = Signal(3)
            tx_reset = Signal()
            self.sync += [
                If(
                    self.tx_ctl.fields.reset,
                    tx_rst_cnt.eq(5),  # 5 cycles reset required by design
                    tx_reset.eq(1)).Else(
                        If(tx_rst_cnt == 0,
                           tx_reset.eq(0)).Else(tx_rst_cnt.eq(tx_rst_cnt - 1),
                                                tx_reset.eq(1)))
            ]

            # At a width of 32 bits, an 18kiB fifo is 512 entries deep
            self.specials += Instance(
                "FIFO_SYNC_MACRO",
                p_DEVICE="7SERIES",
                p_FIFO_SIZE="18Kb",
                p_DATA_WIDTH=fifo_data_width,
                p_ALMOST_EMPTY_OFFSET=(512 - fifo_depth),
                p_ALMOST_FULL_OFFSET=8,
                p_DO_REG=0,
                i_CLK=ClockSignal(),
                i_RST=tx_reset,
                o_ALMOSTFULL=tx_almostfull,
                o_ALMOSTEMPTY=tx_almostempty,
                o_FULL=tx_full,
                o_EMPTY=tx_empty,
                i_WREN=tx_wren & ~tx_reset,
                i_DI=tx_wr_d,
                i_RDEN=tx_rden & ~tx_reset,
                o_DO=tx_rd_d,
                o_RDCOUNT=tx_rdcount,
                o_RDERR=tx_rderr,
                o_WRCOUNT=tx_wrcount,
                o_WRERR=tx_wrerr,
            )

            self.comb += [  # Wire up the status signals and interrupts
                self.tx_stat.fields.underflow.eq(tx_rderr),
                self.tx_stat.fields.free.eq(tx_almostempty),
                self.tx_stat.fields.almostfull.eq(tx_almostfull),
                self.tx_stat.fields.full.eq(tx_full),
                self.tx_stat.fields.empty.eq(tx_empty),
                self.tx_stat.fields.rdcount.eq(tx_rdcount),
                self.tx_stat.fields.wrcount.eq(tx_wrcount),
                self.ev.tx_ready.trigger.eq(tx_almostempty),
                self.ev.tx_error.trigger.eq(tx_wrerr | tx_rderr),
            ]
            self.sync += [
                # This is the bus responder -- need to check how this interacts with uncached memory
                # region
                If(
                    bus.cyc & bus.stb & bus.we & ~bus.ack,
                    If(
                        ~tx_full,
                        tx_wr_d.eq(bus.dat_w),
                        tx_wren.eq(1),
                        wr_ack.eq(1),
                    ).Else(
                        tx_wren.eq(0),
                        wr_ack.eq(0),
                    )).Else(
                        tx_wren.eq(0),
                        wr_ack.eq(0),
                    )
            ]

            tx_buf_width = fifo_data_width + 1 if frame_format == I2S_FORMAT.I2S_STANDARD else fifo_data_width
            sample_width = sample_width + 1 if frame_format == I2S_FORMAT.I2S_STANDARD else sample_width
            offset = [0] if frame_format == I2S_FORMAT.I2S_STANDARD else []

            tx_cnt_width = math.ceil(math.log(fifo_data_width, 2))
            tx_cnt = Signal(tx_cnt_width)
            tx_buf = Signal(tx_buf_width)
            sample_msb = fifo_data_width - 1
            self.submodules.txi2s = txi2s = FSM(reset_state="IDLE")
            txi2s.act(
                "IDLE",
                If(
                    self.tx_ctl.fields.enable,
                    If(
                        falling_edge &
                        (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD
                         else sync_pin),
                        NextState("WAIT_SYNC"),
                    ))),
            txi2s.act(
                "WAIT_SYNC",
                If(
                    falling_edge & (~sync_pin if frame_format
                                    == I2S_FORMAT.I2S_STANDARD else sync_pin),
                    NextState("LEFT"),
                    NextValue(tx_cnt, sample_width),
                    NextValue(tx_buf, Cat(tx_rd_d, offset)),
                    tx_rden.eq(1),
                ))
            txi2s.act(
                "LEFT",
                If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else(
                    NextValue(tx_pin, tx_buf[sample_msb]),
                    NextValue(tx_buf, Cat(0, tx_buf[:-1])),
                    NextValue(tx_cnt, tx_cnt - 1), NextState("LEFT_WAIT")))
            if concatenate_channels:
                txi2s.act(
                    "LEFT_WAIT",
                    If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else(
                        If(
                            falling_edge,
                            If((tx_cnt == 0),
                               If(
                                   (sync_pin if frame_format
                                    == I2S_FORMAT.I2S_STANDARD else ~sync_pin),
                                   NextValue(tx_cnt, sample_width),
                                   NextState("RIGHT"),
                               ).Else(NextState("LEFT_WAIT"), )).Elif(
                                   tx_cnt > 0,
                                   NextState("LEFT"),
                               ))))
            else:
                txi2s.act(
                    "LEFT_WAIT",
                    If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else(
                        If(
                            falling_edge,
                            If((tx_cnt == 0),
                               If(
                                   (sync_pin if frame_format
                                    == I2S_FORMAT.I2S_STANDARD else ~sync_pin),
                                   NextValue(tx_cnt, sample_width),
                                   NextState("RIGHT"),
                                   NextValue(tx_buf, Cat(tx_rd_d, offset)),
                                   tx_rden.eq(1),
                               ).Else(NextState("LEFT_WAIT"), )).Elif(
                                   tx_cnt > 0,
                                   NextState("LEFT"),
                               ))))

            txi2s.act(
                "RIGHT",
                If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else(
                    NextValue(tx_pin, tx_buf[sample_msb]),
                    NextValue(tx_buf, Cat(0, tx_buf[:-1])),
                    NextValue(tx_cnt, tx_cnt - 1), NextState("RIGHT_WAIT")))
            txi2s.act(
                "RIGHT_WAIT",
                If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else(
                    If(
                        falling_edge,
                        If((tx_cnt == 0) &
                           (~sync_pin if frame_format
                            == I2S_FORMAT.I2S_STANDARD else sync_pin),
                           NextValue(tx_cnt, sample_width), NextState("LEFT"),
                           NextValue(tx_buf, Cat(tx_rd_d, offset)),
                           tx_rden.eq(1)).Elif(tx_cnt > 0,
                                               NextState("RIGHT")))))
Beispiel #23
0
    def __init__(self, pads, gpio_cs=False):
        self.intro = ModuleDoc(
            """Simple soft SPI controller module optimized for Betrusted applications

        Requires a clock domain 'spi', which runs at the speed of the SPI bus. 
        
        Simulation benchmarks 16.5us to transfer 16x16 bit words including setup overhead (sysclk=100MHz, spiclk=25MHz)
        which is about 15Mbps system-level performance, assuming the receiver can keep up.
        
        Note that for the ICE40 controller, timing simulations indicate the clock rate could go higher than 24MHz, although
        there is some question if setup/hold times to the external can be met after all delays are counted.
        
        The gpio_cs parameter when true turns CS into a GPIO to be managed by software; when false,
        it is automatically asserted/de-asserted by the SpiController machine.
        
        gpio_cs is {} in this instance. 
        """.format(gpio_cs))

        self.cipo = pads.cipo
        self.copi = pads.copi
        self.csn = pads.csn

        self.tx = CSRStorage(16,
                             name="tx",
                             description="""Tx data, for COPI""")
        self.rx = CSRStatus(16,
                            name="rx",
                            description="""Rx data, from CIPO""")
        if gpio_cs:
            self.cs = CSRStorage(fields=[
                CSRField(
                    "cs",
                    description=
                    "Writing `1` to this asserts cs_n, that is, brings it low; writing `0`, brings it high"
                )
            ])
        self.control = CSRStorage(fields=[
            CSRField(
                "go",
                description=
                "Initiate a SPI cycle by writing a `1`. Does not automatically clear."
            ),
        ])
        self.status = CSRStatus(fields=[
            CSRField("tip", description="Set when transaction is in progress"),
            CSRField("txfull", description="Set when Tx register is full"),
        ])
        self.wifi = CSRStorage(fields=[
            CSRField(
                "reset",
                description=
                "Write `1` to this register to reset the wifi chip; write `0` to enable normal operation",
                reset=1),
            CSRField(
                "pa_ena",
                description=
                "Mapped to PA_ENABLE for wifi (only useful if configured in wifi firmware)"
            ),
            CSRField("wakeup", description="Wakeup the wifi chip"),
        ])
        self.comb += [
            pads.pa_enable.eq(self.wifi.fields.pa_ena),
            pads.wakeup.eq(self.wifi.fields.wakeup),
            pads.res_n.eq(~self.wifi.fields.reset),
        ]

        self.submodules.ev = EventManager()
        # self.ev.spi_int = EventSourceProcess(description="Triggered on conclusion of each transaction")  # falling edge triggered
        self.ev.wirq = EventSourcePulse(
            description="Interrupt request from wifi chip"
        )  # rising edge triggered
        self.ev.finalize()
        # self.comb += self.ev.spi_int.trigger.eq(self.status.fields.tip)
        wirq_in = Signal()
        wirq_r = Signal()
        self.specials += MultiReg(pads.wirq, wirq_in)
        self.sync += wirq_r.eq(wirq_in)
        self.comb += self.ev.wirq.trigger.eq(wirq_in & ~wirq_r)

        tx_swab = Signal(16)
        self.comb += tx_swab.eq(Cat(self.tx.storage[8:], self.tx.storage[:8]))

        # Replica CSR into "spi" clock domain
        self.tx_r = Signal(16)
        self.rx_r = Signal(16)
        self.tip_r = Signal()
        self.txfull_r = Signal()
        self.go_r = Signal()
        self.tx_written = Signal()

        self.specials += MultiReg(self.tip_r, self.status.fields.tip)
        self.specials += MultiReg(self.txfull_r, self.status.fields.txfull)
        self.specials += MultiReg(self.control.fields.go, self.go_r, "spi")
        self.specials += MultiReg(self.tx.re, self.tx_written, "spi")
        # extract rising edge of go -- necessary in case of huge disparity in sysclk-to-spi clock domain
        self.go_d = Signal()
        self.go_edge = Signal()
        self.sync.spi += self.go_d.eq(self.go_r)
        self.comb += self.go_edge.eq(self.go_r & ~self.go_d)

        self.csn_r = Signal(reset=1)
        if gpio_cs:
            self.comb += self.csn.eq(~self.cs.fields.cs)
        else:
            self.comb += self.csn.eq(self.csn_r)
        self.comb += self.rx.status.eq(
            Cat(self.rx_r[8:],
                self.rx_r[:8]))  ## invalid while transaction is in progress
        fsm = FSM(reset_state="IDLE")
        fsm = ClockDomainsRenamer("spi")(fsm)
        self.submodules += fsm
        spicount = Signal(4)
        spiclk_run = Signal()
        fsm.act(
            "IDLE",
            If(
                self.go_edge,
                NextState("RUN"),
                NextValue(self.tx_r, Cat(0, tx_swab[:15])),
                # stability guaranteed so no synchronizer necessary
                NextValue(spicount, 15),
                NextValue(self.txfull_r, 0),
                NextValue(self.tip_r, 1),
                NextValue(self.csn_r, 0),
                NextValue(self.copi, tx_swab[15]),
                NextValue(self.rx_r, Cat(self.cipo, self.rx_r[:15])),
                NextValue(spiclk_run, 1),
            ).Else(
                NextValue(spiclk_run, 0),
                NextValue(self.tip_r, 0),
                NextValue(self.csn_r, 1),
                If(
                    self.tx_written,
                    NextValue(self.txfull_r, 1),
                ),
            ),
        )
        fsm.act(
            "RUN",
            If(
                spicount > 0,
                NextValue(spiclk_run, 1),
                NextValue(self.copi, self.tx_r[15]),
                NextValue(self.tx_r, Cat(0, self.tx_r[:15])),
                NextValue(spicount, spicount - 1),
                NextValue(self.rx_r, Cat(self.cipo, self.rx_r[:15])),
            ).Else(
                NextValue(spiclk_run, 0),
                NextValue(self.csn_r, 1),
                NextValue(self.tip_r, 0),
                NextState("IDLE"),
            ),
        )
        # gate the spi clock so it only runs during a SPI transaction -- requirement of the wf200 block
        self.specials += [
            Instance(
                "SB_IO",
                p_PIN_TYPE=0b100000,  # define a DDR output type
                p_IO_STANDARD="SB_LVCMOS",
                p_PULLUP=0,
                p_NEG_TRIGGER=0,
                io_PACKAGE_PIN=pads.sclk,
                i_LATCH_INPUT_VALUE=0,
                i_CLOCK_ENABLE=1,
                i_INPUT_CLK=0,
                i_OUTPUT_CLK=ClockSignal("spi"),
                i_OUTPUT_ENABLE=1,
                i_D_OUT_0=0,  # rising clock edge
                i_D_OUT_1=spiclk_run,  # falling clock edge
            )
        ]
Beispiel #24
0
    def __init__(self, mem_payload, mem_scratchpad, dfi, dfi_sel, *, nranks,
                 bankbits, rowbits, colbits, rdphase):
        self.description = ModuleDoc("""
        Executes the DRAM payload from memory

        {}
        """.format(Decoder.__doc__))

        self.start = Signal()
        self.ready = Signal()
        self.program_counter = Signal(max=mem_payload.depth - 1)
        self.loop_counter = Signal(Decoder.LOOP_COUNT)
        self.idle_counter = Signal(Decoder.TIMESLICE_NOOP)

        # Scratchpad
        self.submodules.scratchpad = Scratchpad(mem_scratchpad, dfi)

        # Fetcher
        # simple async reads, later we would probably want 1 cycle prefetch?
        instruction = Signal(Decoder.INSTRUCTION)
        payload_port = mem_payload.get_port(write_capable=False,
                                            async_read=True)
        self.specials += payload_port
        self.comb += [
            payload_port.adr.eq(self.program_counter),
            instruction.eq(payload_port.dat_r),
        ]

        # Decoder
        rankbits = log2_int(nranks)
        self.submodules.decoder = decoder = Decoder(instruction,
                                                    rankbits=rankbits,
                                                    bankbits=bankbits,
                                                    rowbits=rowbits,
                                                    colbits=colbits)
        self.submodules.rank_decoder = OneHotDecoder(nranks)
        if rankbits:
            self.comb += self.rank_decoder.i.eq(self.decoder.dfi_rank)

        # Executor
        self.submodules.dfi_executor = DFIExecutor(dfi, self.decoder,
                                                   self.rank_decoder)
        self.submodules.fsm = FSM()
        self.fsm.act(
            "READY", self.ready.eq(1), self.scratchpad.reset.eq(self.start),
            If(
                self.start,
                NextState("RUN"),
                NextValue(self.program_counter, 0),
            ))
        self.fsm.act(
            "RUN",
            dfi_sel.eq(1),
            # Always execute the whole program
            If(self.program_counter == mem_payload.depth - 1,
               NextState("READY")),
            # Execute instruction
            If(
                decoder.op_code == OpCode.LOOP,
                # If a loop instruction with count=0 is found it will be a NOOP
                If(
                    self.loop_counter != decoder.loop_count,
                    # Continue the loop
                    NextValue(self.program_counter,
                              self.program_counter - decoder.loop_jump),
                    NextValue(self.loop_counter, self.loop_counter + 1),
                ).Else(
                    # Finish the loop
                    # Set loop_counter to 0 so that next loop instruction will start properly
                    NextValue(self.program_counter, self.program_counter + 1),
                    NextValue(self.loop_counter, 0),
                ),
            ).Else(
                # DFI instruction
                If(decoder.timeslice == 0,
                   NextValue(self.program_counter,
                             self.program_counter + 1)).Else(
                                 # Wait in idle loop after sending the command
                                 NextValue(self.idle_counter,
                                           decoder.timeslice - 1),
                                 NextState("IDLE"),
                             ),
                # Send DFI command
                self.dfi_executor.exec.eq(1),
                If(
                    decoder.cas & ~decoder.ras & ~decoder.we,  # READ command
                    self.dfi_executor.phase.eq(rdphase),
                ).Else(self.dfi_executor.phase.eq(0), )),
        )
        self.fsm.act(
            "IDLE", dfi_sel.eq(1),
            If(
                self.idle_counter == 0,
                NextState("RUN"),
                NextValue(self.program_counter, self.program_counter + 1),
            ).Else(NextValue(self.idle_counter, self.idle_counter - 1), ))
Beispiel #25
0
    def __init__(self, pads, debounce_ms=5):
        self.background = ModuleDoc("""Matrix Keyboard Driver
A hardware key scanner that can run even when the CPU is powered down or stopped.

The hardware is assumed to be a matrix of switches, divided into rows and columns.
The number of rows and columns is inferred by the number of pins dedicated in
the `pads` record.

The rows are inputs, and require a `PULLDOWN` to be inferred on the pins, to prevent
them from floating when the keys are not pressed. Note that `PULLDOWN` is not offered
on all FPGA architectures, but at the very least is present on 7-Series FPGAs.

The columns are driven by tristate drivers. The state of the columns is either driven
high (`1`), or hi-Z (`Z`).

The KeyScan module also expects a `kbd` clock domain. The preferred implementation
makes this a 32.768KHz always-on clock input with no PLL. This allows the keyboard
module to continue scanning even if the CPU is powered down.

Columns are scanned sequentially using a `kbd`-domain state machine by driving each
column in order. When a column is driven, its electrical state goes from `hi-z` to `1`.
The rows are then sampled with each column scan state. Since each row has
a pulldown on it, if no keys are hit, the result is `0`. When a key is pressed, it
will short a row to a column, and the `1` driven on a column will flip the row
state to a `1`, thus registering a key press.

Columns are driven for a minimum period of time known as a `settling` period. The
settling time must be at least 2 because a Multireg (2-stage synchronizer) is used
to sample the data on the receiving side. In this module, settling time is fixed
to `4` cycles.

Thus a 1 in the `rowdat` registers indicate the column intersection that was active for
a given row in the matrix.

There is also a row shadow register that is maintained by the hardware. The row shadow
is used to maintain the previous state of the key matrix, so that a "key press change"
interrupt can be generated.

There is also a `keypressed` interrupt which fires every time there is a change in
the row registers.

`debounce` is the period in ms for the keyboard matrix to stabilize before triggering
an interrupt.
        """)
        rows_unsync = pads.row
        cols        = Signal(pads.col.nbits)

        self.uart_inject = Signal(8) # character to inject from the UART
        self.inject_strobe = Signal() # on rising edge, latch uart_inject and raise an interrupt
        self.uart_char = CSRStatus(9, fields = [
            CSRField("char", size=8, description="character value being injected"),
            CSRField("stb", size=1, description="FIFO has readable characters"),
        ])
        self.submodules.injectfifo = injectfifo = fifo.SyncFIFOBuffered(width=8, depth=16)
        self.comb += [
            injectfifo.din.eq(self.uart_inject),
            injectfifo.we.eq(self.inject_strobe),

            #self.uart_char.fields.char.eq(self.uart_inject),
            self.uart_char.fields.char.eq(injectfifo.dout),
            self.uart_char.fields.stb.eq(injectfifo.readable),
            injectfifo.re.eq(self.uart_char.we),
        ]
        for c in range(0, cols.nbits):
            cols_ts = TSTriple(1)
            self.specials += cols_ts.get_tristate(pads.col[c])
            self.comb += cols_ts.oe.eq(cols[c])
            self.comb += cols_ts.o.eq(1)

        # row and col are n-bit signals that correspond to the row and columns of the keyboard matrix
        # each row will generate a column register with the column result in it
        rows = Signal(rows_unsync.nbits)
        self.specials += MultiReg(rows_unsync, rows, "kbd")

        # setattr(self, name, object) is the same as self.name = object, except in this case "name" can be dynamically generated
        # this is necessary here because we CSRStatus is not iterable, so we have to manage the attributes manually
        for r in range(0, rows.nbits):
            setattr(self, "row" + str(r) + "dat", CSRStatus(cols.nbits, name="row" + str(r) + "dat", description="""Column data for the given row"""))

        settling = 4  # 4 cycles to settle: 2 cycles for MultiReg stabilization + slop. Must be > 2, and a power of 2
        colcount = Signal(max=(settling*cols.nbits+2))

        update_shadow = Signal()
        reset_scan    = Signal()
        scan_done     = Signal()
        col_r         = Signal(cols.nbits)
        scan_done_sys = Signal()
        self.specials += MultiReg(scan_done, scan_done_sys)
        for r in range(0, rows.nbits):
            row_scan = Signal(cols.nbits)
            row_scan_sys = Signal(cols.nbits)
            # below is in sysclock domain; row_scan is guaranteed stable by state machine sequencing when scan_done gating is enabled
            self.sync += [
                row_scan_sys.eq(row_scan), # loosen up the clock domain crossing timing
                If(scan_done_sys,
                    getattr(self, "row" + str(r) + "dat").status.eq(row_scan_sys)
                ).Else(
                    getattr(self, "row" + str(r) + "dat").status.eq(getattr(self, "row" + str(r) + "dat").status)
                )
            ]
            self.sync.kbd += [
                If(reset_scan,
                   row_scan.eq(0)
                ).Else(
                    If(rows[r] & (colcount[0:2] == 3),  # sample row only on the 4th cycle of colcount
                       row_scan.eq(row_scan | col_r)
                    ).Else(
                        row_scan.eq(row_scan)
                    )
                )
            ]

            rowshadow = Signal(cols.nbits)
            self.sync.kbd += If(update_shadow, rowshadow.eq(row_scan)).Else(rowshadow.eq(rowshadow))

            setattr(self, "row_scan" + str(r), row_scan)
            setattr(self, "rowshadow" + str(r), rowshadow)
            # create a simple, one-scan delayed version of row_scan for debouncing purposes
            row_debounce = Signal(cols.nbits)
            self.sync.kbd += [
                If(scan_done,
                    row_debounce.eq(row_scan),
                ).Else(
                    row_debounce.eq(row_debounce),
                )
            ]
            setattr(self, "row_debounce" + str(r), row_debounce)

        pending_kbd = Signal()
        pending_kbd_f = Signal()
        key_ack = Signal()
        self.sync.kbd += [
            pending_kbd_f.eq(pending_kbd),
            colcount.eq(colcount + 1),
            scan_done.eq(0),
            update_shadow.eq(0),
            reset_scan.eq(0),
            If(colcount == (settling*cols.nbits+2), colcount.eq(0)),
            If(colcount == (settling*cols.nbits), scan_done.eq(1)),
            # Only update the shadow if the pending bit has been cleared (e.g., CPU has acknowledged
            # it has fetched the current key state)
            If(~pending_kbd & pending_kbd_f,
                If(colcount == (settling * cols.nbits + 1),
                    update_shadow.eq(1),
                    key_ack.eq(0),
                ).Else(
                    key_ack.eq(1),
                )
            ).Else(
                If(colcount == (settling*cols.nbits+1),
                    update_shadow.eq(key_ack),
                    key_ack.eq(0),
                ).Else(
                    key_ack.eq(key_ack)
                )
            ),
            If(colcount == (settling*cols.nbits+2), reset_scan.eq(1)),
        ]

        # Drive the columns based on the colcount counter
        self.submodules.coldecoder = Decoder(cols.nbits)
        self.comb += [
            self.coldecoder.i.eq(colcount[log2_int(settling):]),
            self.coldecoder.n.eq(~(colcount < settling*cols.nbits)),
            cols.eq(self.coldecoder.o)
        ]
        self.sync.kbd += col_r.eq(self.coldecoder.o)

        self.submodules.ev = EventManager()
        self.ev.keypressed = EventSourcePulse(description="Triggered every time there is a difference in the row state") # Rising edge triggered
        self.ev.inject = EventSourcePulse(description="Key injection request received")
        self.ev.finalize()
        self.sync += [
            self.ev.inject.trigger.eq(self.inject_strobe)
        ]

        # zero state auto-clear: the "delta" methodology gets stuck if the initial sampling state of the keyboard matrix isn't 0. this fixes that.
        rows_nonzero = Signal(rows.nbits)
        col_zeros = Signal(cols.nbits) # form multi-bit zeros of the right width, otherwise we get 1'd0 as the rhs of equality statements
        row_zeros = Signal(rows.nbits)
        for r in range(0, rows.nbits):
            self.sync.kbd +=  rows_nonzero[r].eq(getattr(self, "row_scan" + str(r)) != col_zeros)
        all_zeros = Signal()
        clear_deltas = Signal()
        self.comb += all_zeros.eq(rows_nonzero == row_zeros)
        clear_kbd = Signal()
        self.submodules.clear_sync = BlindTransfer("sys", "kbd")
        self.comb += [
            self.clear_sync.i.eq(self.ev.keypressed.clear),
            clear_kbd.eq(self.clear_sync.o)
        ]

        # debounce timers and clocks
        debounce_clocks = int((debounce_ms * 0.001) * 32768.0)
        debounce_timer  = Signal(max=(debounce_clocks + 1))
        debounced = Signal()
        # Extract any changes just before the shadow takes its new values
        rowdiff = Signal(rows.nbits)
        for r in range(0, rows.nbits):
            self.sync.kbd += [
                If(clear_deltas,
                    rowdiff[r].eq(0),
                ).Elif(debounced, # was scan_done
                   rowdiff[r].eq( ~((getattr(self, "row_scan" + str(r)) ^ getattr(self, "rowshadow" + str(r))) == 0) )
                ).Else(
                    rowdiff[r].eq(rowdiff[r])
                )
            ]

        # debouncing:
        #   1. compute the delta of the current scan vs. previous scan
        #   2. if the delta is non-zero, start a debounce timer.
        #   3. Count down as long as the delta remains zero; if the delta is non-zero again, reset the timer.
        #   4. When the timer hits zero, latch the final value for sampling to the CPU
        rowchanging = Signal(rows.nbits)
        for r in range(0, rows.nbits):
            self.sync.kbd += [
                If(clear_deltas,
                    rowchanging[r].eq(0),
                ).Elif(scan_done,
                    rowchanging[r].eq( ~((getattr(self, "row_scan" + str(r)) ^ getattr(self, "row_debounce" + str(r))) == 0) )
                ).Else(
                    rowchanging[r].eq(rowchanging[r])
                )
            ]

        db_fsm = ClockDomainsRenamer("kbd")(FSM(reset_state="IDLE"))
        self.submodules += db_fsm
        db_fsm.act("IDLE",
            If(rowchanging != 0,
                NextValue(debounce_timer, debounce_clocks),
                NextState("DEBOUNCING")
            )
        )
        db_fsm.act("DEBOUNCING",
            If(rowchanging == 0,
                NextValue(debounce_timer, debounce_timer - 1),
                If(debounce_timer == 0,
                    NextState("DEBOUNCED")
                )
            ).Else(
                NextValue(debounce_timer, debounce_clocks),
                NextState("DEBOUNCING")
            )
        )
        db_fsm.act("DEBOUNCED",
            If(scan_done,
                debounced.eq(1),
                NextState("IDLE")
            )
        )
        self.comb += clear_deltas.eq(all_zeros & clear_kbd & db_fsm.ongoing("IDLE"))

        # Fire an interrupt during the reset_scan phase.
        changed  = Signal()
        changed_sys = Signal()
        kp_r  = Signal()
        changed_kbd = Signal()
        debounced_d = Signal()
        self.sync.kbd += debounced_d.eq(debounced)
        self.kbd_wakeup = Signal()
        self.comb += changed.eq(debounced_d & (rowdiff != 0))
        self.sync.kbd += [
            If(changed,
                changed_kbd.eq(1)
            ).Elif(key_ack,
                changed_kbd.eq(0)
            ).Else(
                changed_kbd.eq(changed_kbd)
            )
        ]
        self.comb += self.kbd_wakeup.eq(changed_kbd | changed)

        self.specials += MultiReg(changed_kbd, changed_sys)
        self.sync += kp_r.eq(changed_sys)
        self.comb += self.ev.keypressed.trigger.eq(changed_sys & ~kp_r)

        self.submodules.pending_sync = BlindTransfer("sys", "kbd")
        # pulse-convert pending into a single pulse on the falling edge -- otherwise we get a pulse train
        kp_pending_sys = Signal()
        kp_pending_sys_r = Signal()
        self.sync += [
            kp_pending_sys.eq(self.ev.keypressed.pending),
            kp_pending_sys_r.eq(kp_pending_sys),
        ]
        self.comb += [
            self.pending_sync.i.eq(~kp_pending_sys & kp_pending_sys_r),
            pending_kbd.eq(self.pending_sync.o),
        ]
Beispiel #26
0
    def __init__(self, clkspertick, clkfreq, bits=64):
        self.clkspertick = int(clkfreq / clkspertick)

        self.intro = ModuleDoc("""TickTimer: A practical systick timer.

        TIMER0 in the system gives a high-resolution, sysclk-speed timer which overflows
        very quickly and requires OS overhead to convert it into a practically usable time source
        which counts off in systicks, instead of sysclks.

        The hardware parameter to the block is the divisor of sysclk, and sysclk. So if
        the divisor is 1000, then the increment for a tick is 1ms. If the divisor is 2000,
        the increment for a tick is 0.5ms. 
        """)

        resolution_in_ms = 1000 * (self.clkspertick / clkfreq)
        self.note = ModuleDoc(
            title="Configuration",
            body=
            "This timer was configured with {} bits, which rolls over in {:.2f} years, with each bit giving {}ms resolution"
            .format(bits, (2**bits / (60 * 60 * 24 * 365)) *
                    (self.clkspertick / clkfreq), resolution_in_ms))

        prescaler = Signal(max=self.clkspertick, reset=self.clkspertick)
        timer = Signal(bits)

        self.control = CSRStorage(
            2,
            fields=[
                CSRField("reset",
                         description=
                         "Write a `1` to this bit to reset the count to 0",
                         pulse=True),
                CSRField(
                    "pause",
                    description=
                    "Write a `1` to this field to pause counting, 0 for free-run"
                )
            ])
        self.time = CSRStatus(bits,
                              name="time",
                              description="""Elapsed time in systicks""")

        self.sync += [
            If(
                self.control.fields.reset,
                timer.eq(0),
                prescaler.eq(self.clkspertick),
            ).Else(
                If(prescaler == 0, prescaler.eq(self.clkspertick),
                   If(
                       self.control.fields.pause == 0,
                       timer.eq(timer + 1),
                   )).Else(prescaler.eq(prescaler - 1), ))
        ]

        self.comb += self.time.status.eq(timer)

        self.msleep = ModuleDoc("""msleep extension
        
        The msleep extension is a Xous-specific add-on to aid the implementation of the msleep server.
        
        msleep fires an interrupt when the requested time is less than or equal to the current elapsed time in
        systicks. The interrupt remains active until a new target is set, or masked. 
        """)
        self.msleep_target = CSRStorage(
            size=bits,
            description="Target time in {}ms ticks".format(resolution_in_ms))
        self.submodules.ev = EventManager()
        alarm = Signal()
        self.ev.alarm = EventSourceLevel()
        self.comb += self.ev.alarm.trigger.eq(alarm)

        self.comb += alarm.eq(self.msleep_target.storage <= timer)
Beispiel #27
0
    def __init__(self, pads):
        self.background = ModuleDoc(
            """MemLCD: Driver for the SHARP Memory LCD model LS032B7DD02

        The LS032B7DD02 is a 336x536 pixel black and white memory LCD, with a 200ppi dot pitch.
        Memory LCDs can be thought of as 'fast E-ink displays that consume a tiny bit of standby
        power', as seen by these properties:

        * Extremely low standby power (30uW typ hold mode)
        * No special bias circuitry required to maintain image in hold mode
        * 120 degree viewing angle, 1:35 contrast ratio
        * All control logic fabricated on-glass using TFT devices that are auditable with
          a common 40x power desktop microscope and a bright backlight source

        This last property in particular makes the memory LCD extremely well suited for situations
        where absolute visibility into the construction of a secure enclave is desired.

        The memory organization of the LS032B7DD02 is simple: 536 lines of pixel data 336 wide.
        Each pixel is 1 bit (the display is black and white), and is fed into the module from
        left to right as pixels 1 through 336, inclusive. Lines are enumerated from top to bottom,
        from 1 to 536, inclusive.

        The LCD can only receive serial data. The protocol is a synchronous serial interface with
        an active high chip select. All data words are transmitted LSB first. A line transfer is
        initiated by sending a 6-bit mode selection, a 10-bit row address, and the subsequent 336
        pixels, followed by 16 dummy bits which transfer the data from the LCD holding register to
        the display itself.

            .. wavedrom::
                :caption: Single line data transfer to memory LCD

                { "signal": [
                    { "name": "SCLK", "wave": "0.P.......|......|...|..l." },
                    { "name": "SCS", "wave": "01........|......|...|.0.." },
                    { "name": "SI", "wave": "0===x..===|======|==x|....", "data": ["M0", "M1", "M2", "R0", "R1", " ", "R8", "R9", "D0", "D1", "D2", " ", "D334", "D335"] },
                    { "node": ".....................a.b..."},
                ],
                  "edge": ['a<->b 16 cycles']
                }

        Alternatively, one can send successive lines without dropping SCS by substituting the 16 dummy
        bits at the end with a 6-bit don't care preamble (where the mode bits would have been), 10 bits
        of row address, and then the pixel data.

            .. wavedrom::
              :caption: Multiple line data transfer to memory LCD

              { "signal": [
                    { "name": "SCLK", "wave": "0.P.......|......|...|....|....." },
                    { "name": "SCS", "wave": "01........|......|...|....|....." },
                    { "name": "SI", "wave": "0===x..===|======|==x|.===|=====", "data": ["M0", "M1", "M2", "R0", "R1", " ", "R8", "R9", "D0", "D1", "D2", " ", "D334", "D335", "R0", "R1", " ", "R8", "R9", "D0", "D1"] },
                    { "node": ".....................a.b..."},
              ],
                "edge": ['a<->b 6 cycles']
              }

        The very last line in the multiple line data transfer must terminate with 16 dummy cycles.

        Mode bits M0-M2 have the following meaning:
           M0: Set to 1 when transferring data lines. Set to 0 for hold mode, see below.
           M1: VCOM inversion flag. Ignore when hardware strap pin EXTMODE is high. Betrusted
               sets EXTMODE high, so the VCOM inversion is handled by low-power aux hardware.
               When EXTMODE is low, software must explicitly manage the VCOM inversion flag such the
               flag polarity changes once every second "as much as possible".
           M2: Normally set to 0. Set to 1 for all clear (see below)

        Data bit polarity:
           1 = White
           0 = Black

        For 'Hold mode' and 'All clear', a total of 16 cycles are sent, the first three being
        the mode bit and the last 13 being dummy cycles.

            .. wavedrom::
              :caption: Hold and all clear timings

              { "signal": [
                    { "name": "SCLK", "wave": "0.P...|.l" },
                    { "name": "SCS", "wave": "01....|0." },
                    { "name": "SI", "wave": "0===x...", "data": ["M0", "M1", "M2", "R0", "R1", " ", "R8", "R9", "D0", "D1", "D2", " ", "D334", "D335", "R0", "R1", " ", "R8", "R9", "D0", "D1"] },
                    { "node": ".....a.b..."},
              ],
                "edge": ['a<->b 13 cycles']
            }

        All signals are 3.0V compatible, 5V tolerant (VIH is 2.7V-VDD). The display itself requires
        a single 5V power supply (4.8-5.5V typ 5.8V abs max). In hold mode, typical power is 30uW, max 330uW;
        with data updating at 1Hz, power is 250uW, max 750uW (SCLK=1MHz, EXTCOMMIN=1Hz).

        * The maximum clock frequency for SCLK is 2MHz (typ 1MHz).
        * EXTCOMMIN frequency is 1-10Hz, 1Hz typ
        * EXTCOMMIN minimum high duration is 1us
        * All rise/fall times must be less than 50ns
        * SCS setup time is 3us, hold time is 1us. Minimum low duration is 1us,
          minimum high is 188us for a data update, 12 us for a hold mode operation.
        * SI setup time is 120ns, 190ns hold time.
        * Operating temperature is -20 to 70C, storage temperature -30 to 80C.
        """)

        self.interface = ModuleDoc("""Wishbone interface for MemLCD

        MemLCD maintains a local framebuffer for the LCD. The CPU can read and write
        to the frame buffer to update pixel data, and then request a screen update to
        commit the frame buffer to the LCD.

        Only full lines can be updated on a memory LCD; partial updates are not possible.
        In order to optimize the update process, MemLCD maintains a "dirty bit" associated
        with each line. Only lines with modified pixels are written to the screen after an
        update request.

        A line is 336 bits wide. When padded to 32-bit words, this yields a line width of
        44 bytes (0x2C, or 352 bits). In order to simplify math, the frame buffer rounds
        the line width up to the nearest power of two, or 64 bytes.

        The unused bits can be used as a "hint" to the MemLCD block as to which lines
        require updating. If the unused bits have any value other than 0, the MemLCD block
        will update those lines when an "UpdateDirty" command is triggered. It is up
        to the CPU to set and clear the dirty bits, they are not automatically cleared
        by the block upon update. Typically the clearing of the bits would be handled
        during the update-finished interrupt handling routine. If the dirty bits are
        not used, an "UpdateAll" command can be invoked, which will update every
        line of the LCD regardless of the contents of the dirty bits.

        The total depth of the memory is thus 44 bytes * 536 lines = 23,584 bytes or
        5,896 words.

        Pixels are stored with the left-most pixel in the MSB of each 32-bit word, with
        the left-most pixels occupying the lowest address in the line.

        Lines are stored with the bottom line of the screen at the lowest address.

        These parameters are chosen so that a 1-bit BMP file can be copied into the frame
        buffer and it will render directly to the screen with no further transformations
        required.

        The CPU is responsible for not writing data to the LCD while it is updating. Concurrent
        writes to the LCD during updates can lead to unpredictable behavior.
        """)
        data_width = 32
        width = 336
        height = 536
        bytes_per_line = 44

        self.fb_depth = fb_depth = height * bytes_per_line // (data_width // 8)
        pixdata = Signal(32)
        pixadr_rd = Signal(max=fb_depth)

        # 1 is white, which is the "off" state
        fb_init = [0xffffffff] * int(fb_depth)
        for i in range(fb_depth // 11):
            fb_init[i * 11 + 10] = 0xffff
        mem = Memory(
            32, fb_depth, init=fb_init
        )  # may need to round up to 8192 if a power of 2 is required by migen
        # read port for pixel data out
        self.specials.rdport = mem.get_port(
            write_capable=False,
            mode=READ_FIRST)  # READ_FIRST allows BRAM to be used
        self.comb += self.rdport.adr.eq(pixadr_rd)
        self.comb += pixdata.eq(self.rdport.dat_r)
        # implementation note: vivado will complain about being unable to merge an output register, leading to
        # non-optimal timing, but a check of the timing path shows that at 100MHz there is about 4-5ns of setup margin,
        # so the merge is unnecessary in this case. Ergo, prefer comb over sync to reduce latency.

        # memory-mapped write port to wishbone bus
        self.bus = wishbone.Interface()
        self.submodules.wb_sram_if = wishbone.SRAM(mem, read_only=False)
        decoder_offset = log2_int(fb_depth, need_pow2=False)

        def slave_filter(a):
            return a[decoder_offset:32 -
                     decoder_offset] == 0  # no aliasing in the block

        self.submodules.wb_con = wishbone.Decoder(
            self.bus, [(slave_filter, self.wb_sram_if.bus)], register=True)

        self.command = CSRStorage(
            2,
            fields=[
                CSRField(
                    "UpdateDirty",
                    description="Write a ``1`` to flush dirty lines to the LCD",
                    pulse=True),
                CSRField(
                    "UpdateAll",
                    description="Update full screen regardless of tag state",
                    pulse=True),
            ])

        self.busy = CSRStatus(
            1,
            name="Busy",
            description=
            """A ``1`` indicates that the block is currently updating the LCD"""
        )

        self.prescaler = CSRStorage(8,
                                    reset=99,
                                    name="prescaler",
                                    description="""
        Prescaler value. LCD clock is module (clock / (prescaler+1)). Reset value: 99, so
        for a default sysclk of 100MHz this yields an LCD SCLK of 1MHz""")

        self.submodules.ev = EventManager()
        self.ev.done = EventSourceProcess()
        self.ev.finalize()
        self.comb += self.ev.done.trigger.eq(
            self.busy.status)  # Fire an interupt when busy drops

        self.sclk = sclk = getattr(pads, "sclk")
        self.scs = scs = getattr(pads, "scs")
        self.si = si = getattr(pads, "si")
        self.sendline = sendline = Signal()
        self.linedone = linedone = Signal()
        updateall = Signal()
        fetch_dirty = Signal()
        update_line = Signal(
            max=height
        )  # Keep track of both line and address to avoid instantiating a multiplier
        update_addr = Signal(max=height * bytes_per_line)

        fsm_up = FSM(reset_state="IDLE")
        self.submodules += fsm_up

        fsm_up.act(
            "IDLE",
            If(
                self.command.fields.UpdateDirty
                | self.command.fields.UpdateAll,
                NextValue(self.busy.status, 1), NextValue(fetch_dirty, 1),
                If(self.command.fields.UpdateAll,
                   NextValue(updateall, 1)).Else(NextValue(updateall, 0)),
                NextState("START")).Else(NextValue(self.busy.status, 0)))
        fsm_up.act(
            "START",
            NextValue(update_line, height),
            NextValue(
                update_addr, (height - 1) * bytes_per_line
            ),  # Represents the byte address of the beginning of the last line
            NextState("FETCHDIRTY"))
        fsm_up.act(
            "FETCHDIRTY",  # Wait one cycle delay for the pixel data to be retrieved before evaluating it
            NextState("CHECKDIRTY"))
        fsm_up.act(
            "CHECKDIRTY",
            If(update_line == 0, NextState("IDLE")).Else(
                If(
                    (pixdata[16:] != 0) | updateall,
                    NextState("DIRTYLINE"),
                ).Else(NextValue(update_line, update_line - 1),
                       NextValue(update_addr, update_addr - bytes_per_line),
                       NextState("FETCHDIRTY"))))
        fsm_up.act("DIRTYLINE", NextValue(fetch_dirty, 0), sendline.eq(1),
                   NextState("WAITDONE"))
        fsm_up.act(
            "WAITDONE",
            If(linedone, NextValue(fetch_dirty, 1),
               NextValue(update_line, update_line - 1),
               NextValue(update_addr, update_addr - bytes_per_line),
               NextState("FETCHDIRTY")))

        modeshift = Signal(16)
        mode = Signal(6)
        pixshift = Signal(32)
        pixcount = Signal(max=width)
        bitreq = Signal()
        bitack = Signal()
        self.comb += mode.eq(
            1
        )  # Always in line write mode, not clearing, no vcom management necessary
        fsm_phy = FSM(reset_state="IDLE")
        self.submodules += fsm_phy
        # Update_addr units is in bytes. [2:] turns bytes to words
        # pixcount units are in pixels. [3:] turns pixels to bytes
        self.comb += [
            If(fetch_dirty, pixadr_rd.eq(
                (update_addr + bytes_per_line - 4)[2:])).Else(
                    pixadr_rd.eq((update_addr + pixcount[3:])[2:]))
        ]
        scs_cnt = Signal(max=200)
        fsm_phy.act(
            "IDLE",
            NextValue(si, 0),
            NextValue(linedone, 0),
            If(
                sendline,
                NextValue(scs, 1),
                NextValue(scs_cnt, 200),  # 2 us setup
                NextValue(pixcount, 16),
                NextValue(modeshift, Cat(mode, update_line)),
                NextState("SCS_SETUP")).Else(NextValue(scs, 0)))
        fsm_phy.act(
            "SCS_SETUP",
            If(scs_cnt > 0,
               NextValue(scs_cnt, scs_cnt - 1)).Else(NextState("MODELINE")))
        fsm_phy.act(
            "MODELINE",
            If(pixcount > 0, NextValue(modeshift, modeshift[1:]),
               NextValue(si, modeshift[0]), NextValue(pixcount, pixcount - 1),
               bitreq.eq(1),
               NextState("MODELINEWAIT")).Else(NextValue(pixcount, 1),
                                               NextValue(pixshift, pixdata),
                                               NextState("DATA")))
        fsm_phy.act("MODELINEWAIT", If(bitack, NextState("MODELINE")))
        fsm_phy.act(
            "DATA",
            If(
                pixcount < width + 17,
                If(
                    pixcount[0:5] == 0,
                    NextValue(pixshift, pixdata),
                ).Else(NextValue(pixshift, pixshift[1:]), ), NextValue(scs, 1),
                NextValue(si, pixshift[0]), NextValue(pixcount, pixcount + 1),
                bitreq.eq(1), NextState("DATAWAIT")).Else(
                    NextValue(si, 0),
                    NextValue(scs_cnt, 100),  # 1 us hold
                    NextState("SCS_HOLD")))
        fsm_phy.act(
            "SCS_HOLD",
            If(scs_cnt > 0, NextValue(scs_cnt, scs_cnt - 1)).Else(
                NextValue(scs, 0),
                NextValue(scs_cnt, 100),  # 1us minimum low time
                NextState("SCS_LOW")))
        fsm_phy.act(
            "SCS_LOW",
            If(scs_cnt > 0,
               NextValue(scs_cnt, scs_cnt - 1)).Else(NextValue(linedone, 1),
                                                     NextState("IDLE")))
        fsm_phy.act("DATAWAIT", If(bitack, NextState("DATA")))

        # This handles clock division
        fsm_bit = FSM(reset_state="IDLE")
        self.submodules += fsm_bit
        clkcnt = Signal(8)
        fsm_bit.act("IDLE", NextValue(sclk, 0),
                    NextValue(clkcnt, self.prescaler.storage),
                    If(bitreq, NextState("SCLK_LO")))
        fsm_bit.act(
            "SCLK_LO", NextValue(clkcnt, clkcnt - 1),
            If(clkcnt < self.prescaler.storage[1:], NextValue(sclk, 1),
               NextState("SCLK_HI")))
        fsm_bit.act(
            "SCLK_HI", NextValue(clkcnt, clkcnt - 1),
            If(clkcnt == 0, NextValue(sclk, 0), NextState("IDLE"),
               bitack.eq(1)))
Beispiel #28
0
    def __init__(self, pads):
        self.background = ModuleDoc("""Matrix Keyboard Driver
        A hardware key scanner that can run even when the CPU is powered down or stopped.
        
        The hardware is assumed to be a matrix of switches, divided into rows and columns. 
        The number of rows and columns is inferred by the number of pins dedicated in
        the `pads` record. 
        
        The rows are inputs, and require a `PULLDOWN` to be inferred on the pins, to prevent
        them from floating when the keys are not pressed. Note that `PULLDOWN` is not offered
        on all FPGA architectures, but at the very least is present on 7-Series FPGAs.
        
        The columns are driven by tristate drivers. The state of the columns is either driven
        high (`1`), or hi-Z (`Z`). 
        
        The KeyScan module also expects a `kbd` clock domain. The preferred implementation
        makes this a 32.768KHz always-on clock input with no PLL. This allows the keyboard 
        module to continue scanning even if the CPU is powered down.
        
        Columns are scanned sequentially using a `kbd`-domain state machine by driving each
        column in order. When a column is driven, its electrical state goes from `hi-z` to
         `1`. The rows are then sampled with each column scan state. Since each row has
         a pulldown on it, if no keys are hit, the result is `0`. When a key is pressed, it
         will short a row to a column, and the `1` driven on a column will flip the row
         state to a `1`, thus registering a key press. 
        
        Columns are driven for a minimum period of time known as a `settling` period. The
        settling time must be at least 2 because a Multireg (2-stage synchronizer) is used
        to sample the data on the receiving side. In this module, settling time is fixed
        to `4` cycles.
        
        Thus a 1 in the `rowdat` registers indicate the column intersection that was active for 
        a given row in the matrix.
        
        There is also a row shadow register that is maintained by the hardware. The row shadow
        is used to maintain the previous state of the key matrix, so that a "key press change"
        interrupt can be generated. The first change in the row registers is recorded in the
        `rowchange` status register. It does not update until it has been read. The idea of this
        register is that it can capture one key hit while the CPU is in standby, and the CPU
        should wake up in time to read it, before a new key hit is registered. The CPU can consult
        the `rowchange` register to read just the `rowdat` CSR with the key change, instead of
        having to iterate through the entire array to find the row that changed. 
        
        There is also a `keypressed` interrupt which fires every time there is a change in
        the row registers. 
        """)
        rows_unsync = pads.row
        cols = Signal(pads.col.nbits)

        for c in range(0, cols.nbits):
            cols_ts = TSTriple(1)
            self.specials += cols_ts.get_tristate(pads.col[c])
            self.comb += cols_ts.oe.eq(cols[c])
            self.comb += cols_ts.o.eq(1)

        # row and col are n-bit signals that correspond to the row and columns of the keyboard matrix
        # each row will generate a column register with the column result in it
        rows = Signal(rows_unsync.nbits)
        self.specials += MultiReg(rows_unsync, rows, "kbd")

        # setattr(self, name, object) is the same as self.name = object, except in this case "name" can be dynamically generated
        # this is necessary here because we CSRStatus is not iterable, so we have to manage the attributes manually
        for r in range(0, rows.nbits):
            setattr(
                self, "row" + str(r) + "dat",
                CSRStatus(cols.nbits,
                          name="row" + str(r) + "dat",
                          description="""Column data for the given row"""))

        settling = 4  # 4 cycles to settle: 2 cycles for MultiReg stabilization + slop. Must be > 2, and a power of 2
        colcount = Signal(max=(settling * cols.nbits + 2))

        update_shadow = Signal()
        reset_scan = Signal()
        scan_done = Signal()
        col_r = Signal(cols.nbits)
        scan_done_sys = Signal()
        self.specials += MultiReg(scan_done, scan_done_sys)
        for r in range(0, rows.nbits):
            row_scan = Signal(cols.nbits)
            # below is in sysclock domain; row_scan is guaranteed stable by state machine sequencing when scan_done gating is enabled
            self.sync += [
                If(scan_done_sys,
                   getattr(self,
                           "row" + str(r) + "dat").status.eq(row_scan)).Else(
                               getattr(self, "row" + str(r) + "dat").status.eq(
                                   getattr(self,
                                           "row" + str(r) + "dat").status))
            ]
            self.sync.kbd += [
                If(reset_scan, row_scan.eq(0)).Else(
                    If(
                        rows[r] &
                        (colcount[0:2]
                         == 3),  # sample row only on the 4th cycle of colcount
                        row_scan.eq(row_scan | col_r)).Else(
                            row_scan.eq(row_scan)))
            ]

            rowshadow = Signal(cols.nbits)
            self.sync.kbd += If(update_shadow, rowshadow.eq(row_scan)).Else(
                rowshadow.eq(rowshadow))

            setattr(self, "row_scan" + str(r), row_scan)
            setattr(self, "rowshadow" + str(r), rowshadow)

        pending_key = Signal()
        self.sync.kbd += [
            colcount.eq(colcount + 1),
            scan_done.eq(0),
            update_shadow.eq(0),
            reset_scan.eq(0),
            If(colcount == (settling * cols.nbits + 2), colcount.eq(0)),
            If(colcount == (settling * cols.nbits), scan_done.eq(1)),
            # Only update the shadow if the pending bit has been cleared (e.g., CPU has acknowledged
            # it has fetched the current key state)
            If(colcount == (settling * cols.nbits + 1),
               update_shadow.eq(~pending_key)),
            If(colcount == (settling * cols.nbits + 2), reset_scan.eq(1)),
        ]

        # Drive the columns based on the colcount counter
        self.submodules.coldecoder = Decoder(cols.nbits)
        self.comb += [
            self.coldecoder.i.eq(colcount[log2_int(settling):]),
            self.coldecoder.n.eq(~(colcount < settling * cols.nbits)),
            cols.eq(self.coldecoder.o)
        ]
        self.sync.kbd += col_r.eq(self.coldecoder.o)

        self.submodules.ev = EventManager()
        self.ev.keypressed = EventSourcePulse(
            description=
            "Triggered every time there is a difference in the row state"
        )  # Rising edge triggered
        self.ev.finalize()
        # Extract any changes just before the shadow takes its new values
        rowdiff = Signal(rows.nbits)
        for r in range(0, rows.nbits):
            self.sync.kbd += [
                If(
                    scan_done, rowdiff[r].eq(~(
                        (getattr(self, "row_scan" + str(r))
                         ^ getattr(self, "rowshadow" + str(r))) == 0))).Else(
                             rowdiff[r].eq(rowdiff[r]))
            ]
        # Fire an interrupt during the reset_scan phase. Delay by 2 cycles so that rowchange can pick
        # up a new value before the "pending" bit is set.
        kp_d = Signal()
        kp_d2 = Signal()
        kp_r = Signal()
        kp_r2 = Signal()
        self.sync.kbd += kp_d.eq(rowdiff != 0)
        self.sync.kbd += kp_d2.eq(kp_d)
        self.sync += kp_r.eq(kp_d2)
        self.sync += kp_r2.eq(kp_r)
        self.comb += self.ev.keypressed.trigger.eq(kp_r & ~kp_r2)

        self.rowchange = CSRStatus(
            rows.nbits,
            name="rowchange",
            description=
            """The rows that changed at the point of interrupt generation.
            Does not update again until the interrupt is serviced.""")
        reset_scan_sys = Signal()
        self.specials += MultiReg(reset_scan, reset_scan_sys)
        self.sync += [
            If(reset_scan_sys & ~self.ev.keypressed.pending,
               self.rowchange.status.eq(rowdiff)).Else(
                   self.rowchange.status.eq(self.rowchange.status))
        ]
        self.specials += MultiReg(self.ev.keypressed.pending, pending_key,
                                  "kbd")
Beispiel #29
0
    def __init__(self, model, models, seed):
        """Create a `Version` block.

        Arguments:

        model (int): The value of the :obj:`CSRStatus(model)` field.
        models: a list() of (value, "shortname", "description") tuples.
        """
        self.intro = ModuleDoc("""SoC Version Information

            This block contains various information about the state of the source code
            repository when this SoC was built.
            """)

        def makeint(i, base=10):
            try:
                return int(i, base=base)
            except:
                return 0

        def get_gitver():
            def decode_version(v):
                version = v.split(".")
                major = 0
                minor = 0
                rev = 0
                if len(version) >= 3:
                    rev = makeint(version[2])
                if len(version) >= 2:
                    minor = makeint(version[1])
                if len(version) >= 1:
                    major = makeint(version[0])
                return (major, minor, rev)

            git_rev_cmd = subprocess.Popen(
                ["git", "describe", "--tags", "--dirty=+"],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE)
            (git_stdout, _) = git_rev_cmd.communicate()
            if git_rev_cmd.wait() != 0:
                print('unable to get git version')
                return
            raw_git_rev = git_stdout.decode().strip()

            dirty = 0
            if raw_git_rev[-1] == "+":
                raw_git_rev = raw_git_rev[:-1]
                dirty = 1

            parts = raw_git_rev.split("-")
            major = 0
            minor = 0
            rev = 0
            gitrev = 0
            gitextra = 0

            if len(parts) >= 3:
                if parts[0].startswith("v"):
                    version = parts[0]
                    if version.startswith("v"):
                        version = parts[0][1:]
                    (major, minor, rev) = decode_version(version)
                gitextra = makeint(parts[1])
                if parts[2].startswith("g"):
                    gitrev = makeint(parts[2][1:], base=16)
            elif len(parts) >= 2:
                if parts[1].startswith("g"):
                    gitrev = makeint(parts[1][1:], base=16)
                version = parts[0]
                if version.startswith("v"):
                    version = parts[0][1:]
                (major, minor, rev) = decode_version(version)
            elif len(parts) >= 1:
                version = parts[0]
                if version.startswith("v"):
                    version = parts[0][1:]
                (major, minor, rev) = decode_version(version)

            return (major, minor, rev, gitrev, gitextra, dirty)

        (major, minor, rev, gitrev, gitextra, dirty) = get_gitver()

        self.major = CSRStatus(
            8,
            reset=major,
            description=
            "Major git tag version.  For example, this firmware was built from git tag ``v{}.{}.{}``, so this value is ``{}``."
            .format(major, minor, rev, major))
        self.minor = CSRStatus(
            8,
            reset=minor,
            description=
            "Minor git tag version.  For example, this firmware was built from git tag ``v{}.{}.{}``, so this value is ``{}``."
            .format(major, minor, rev, minor))
        self.revision = CSRStatus(
            8,
            reset=rev,
            description=
            "Revision git tag version.  For example, this firmware was built from git tag ``v{}.{}.{}``, so this value is ``{}``."
            .format(major, minor, rev, rev))
        self.gitrev = CSRStatus(
            32,
            reset=gitrev,
            description=
            "First 32-bits of the git revision.  This documentation was built from git rev ``{:08x}``, so this value is {}, which should be enough to check out the exact git version used to build this firmware."
            .format(gitrev, gitrev))
        self.gitextra = CSRStatus(
            10,
            reset=gitextra,
            description=
            "The number of additional commits beyond the git tag.  For example, if this value is ``1``, then the repository this was built from has one additional commit beyond the tag indicated in `MAJOR`, `MINOR`, and `REVISION`."
        )
        self.dirty = CSRStatus(fields=[
            CSRField(
                "dirty",
                reset=dirty,
                description=
                "Set to ``1`` if this device was built from a git repo with uncommitted modifications."
            )
        ])

        model_val = None
        for model_check in models:
            try:
                if int(model_check[0]) == int(model):
                    model_val = int(model_check[0])
            except:
                pass
            try:
                if model_check[1] == model:
                    model_val = int(model_check[0])
            except:
                pass
        if model_val is None:
            raise ValueError("Model not found in `models` list!")

        self.model = CSRStatus(fields=[
            CSRField(
                "model",
                reset=model_val,
                size=8,
                description=
                "Contains information on which model device this was built for.",
                values=models)
        ])
        self.seed = CSRStatus(
            32,
            reset=seed,
            description="32-bit seed used for the place-and-route.")

        self.comb += [
            self.major.status.eq(major),
            self.minor.status.eq(minor),
            self.revision.status.eq(rev),
            self.gitrev.status.eq(gitrev),
            self.gitextra.status.eq(gitextra),
            self.dirty.fields.dirty.eq(dirty),
            self.model.fields.model.eq(model_val),
            self.seed.status.eq(seed),
        ]
Beispiel #30
0
    def __init__(self, platform):
        self.intro = ModuleDoc(
            """Bitstream-patchable key ROM set for keys that are "baked in" to the FPGA image"""
        )
        platform.toolchain.attr_translate["KEEP"] = ("KEEP", "TRUE")
        platform.toolchain.attr_translate["DONT_TOUCH"] = ("DONT_TOUCH",
                                                           "TRUE")

        import binascii
        self.address = CSRStorage(8,
                                  name="address",
                                  description="address for ROM")
        self.data = CSRStatus(32, name="data", description="data from ROM")
        self.lockaddr = CSRStorage(
            8,
            name="lockaddr",
            description=
            "address of the word to lock. Address locked on write, and cannot be undone."
        )
        self.lockstat = CSRStatus(
            1,
            name="lockstat",
            description=
            "If set, the requested address word is locked and will always return 0."
        )

        lockmem = Memory(1, 256, init=[0] * 256)
        self.specials += lockmem
        self.specials.lockrd = lockmem.get_port(write_capable=False,
                                                mode=WRITE_FIRST,
                                                async_read=True)
        self.specials.lockwr = lockmem.get_port(write_capable=True,
                                                mode=WRITE_FIRST)
        self.comb += [
            self.lockwr.adr.eq(self.lockaddr.storage),
            self.lockwr.dat_w.eq(1),
            self.lockwr.we.eq(self.lockaddr.re),
        ]
        rawdata = Signal(32)

        rng = SystemRandom()
        with open("rom.db", "w") as f:
            for bit in range(0, 32):
                lutsel = Signal(4)
                for lut in range(4):
                    if lut == 0:
                        lutname = 'A'
                    elif lut == 1:
                        lutname = 'B'
                    elif lut == 2:
                        lutname = 'C'
                    else:
                        lutname = 'D'
                    romval = rng.getrandbits(64)
                    # print("rom bit ", str(bit), lutname, ": ", binascii.hexlify(romval.to_bytes(8, byteorder='big')))
                    rom_name = "KEYROM" + str(bit) + lutname
                    # X36Y99 and counting down
                    if bit % 2 == 0:
                        platform.toolchain.attr_translate[rom_name] = (
                            "LOC", "SLICE_X36Y" + str(50 + bit // 2))
                    else:
                        platform.toolchain.attr_translate[rom_name] = (
                            "LOC", "SLICE_X37Y" + str(50 + bit // 2))
                    platform.toolchain.attr_translate[rom_name +
                                                      'BEL'] = ("BEL",
                                                                lutname +
                                                                '6LUT')
                    platform.toolchain.attr_translate[rom_name + 'LOCK'] = (
                        "LOCK_PINS",
                        "I5:A6, I4:A5, I3:A4, I2:A3, I1:A2, I0:A1")
                    self.specials += [
                        Instance(
                            "LUT6",
                            name=rom_name,
                            # p_INIT=0x0000000000000000000000000000000000000000000000000000000000000000,
                            p_INIT=romval,
                            i_I0=self.address.storage[0],
                            i_I1=self.address.storage[1],
                            i_I2=self.address.storage[2],
                            i_I3=self.address.storage[3],
                            i_I4=self.address.storage[4],
                            i_I5=self.address.storage[5],
                            o_O=lutsel[lut],
                            attr=("KEEP", "DONT_TOUCH", rom_name,
                                  rom_name + 'BEL', rom_name + 'LOCK'))
                    ]
                    # record the ROM LUT locations in a DB and annotate the initial random value given
                    f.write("KEYROM " + str(bit) + ' ' + lutname + ' ' +
                            platform.toolchain.attr_translate[rom_name][1] +
                            ' ' + str(
                                binascii.hexlify(
                                    romval.to_bytes(8, byteorder='big'))) +
                            '\n')
                self.comb += [
                    If(self.address.storage[6:] == 0,
                       rawdata[bit].eq(lutsel[2])).Elif(
                           self.address.storage[6:] == 1,
                           rawdata[bit].eq(lutsel[3])).Elif(
                               self.address.storage[6:] == 2, rawdata[bit].eq(
                                   lutsel[0])).Else(rawdata[bit].eq(lutsel[1]))
                ]

        allow_read = Signal()
        self.comb += [
            allow_read.eq(~self.lockrd.dat_r),
            self.lockrd.adr.eq(self.address.storage),
        ]
        self.sync += [
            If(
                allow_read,
                self.data.status.eq(rawdata),
            ).Else(self.data.status.eq(0), ),
            self.lockstat.status.eq(allow_read)
        ]

        platform.add_platform_command("create_pblock keyrom")
        platform.add_platform_command(
            'resize_pblock [get_pblocks keyrom] -add ' +
            '{{SLICE_X36Y50:SLICE_X37Y65}}')
        #platform.add_platform_command("set_property CONTAIN_ROUTING true [get_pblocks keyrom]")  # should be fine to mingle the routing for this pblock
        platform.add_platform_command(
            "add_cells_to_pblock [get_pblocks keyrom] [get_cells KEYROM*]")