pynq.iop.devmode — Python productivity for Zynq (Pynq) v1.0

# Source code for pynq.iop.devmode

    #   Copyright (c) 2016, Xilinx, Inc.
    #   All rights reserved.
    #
    #   Redistribution and use in source and binary forms, with or without
    #   modification, are permitted provided that the following conditions are met:
    #
    #   1.  Redistributions of source code must retain the above copyright notice,
    #       this list of conditions and the following disclaimer.
    #
    #   2.  Redistributions in binary form must reproduce the above copyright
    #       notice, this list of conditions and the following disclaimer in the
    #       documentation and/or other materials provided with the distribution.
    #
    #   3.  Neither the name of the copyright holder nor the names of its
    #       contributors may be used to endorse or promote products derived from
    #       this software without specific prior written permission.
    #
    #   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    #   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
    #   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
    #   PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
    #   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    #   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    #   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
    #   OR BUSINESS INTERRUPTION). HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
    #   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
    #   OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
    #   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

    __author__      = "Graham Schelle, Giuseppe Natale, Yun Rock Qu"
    __copyright__   = "Copyright 2016, Xilinx"
    __email__       = "pynq_support@xilinx.com"

    import time
    from pynq import MMIO
    from pynq.iop import request_iop
    from pynq.iop import iop_const
    from pynq.iop import PMODA
    from pynq.iop import PMODB
    from pynq.iop import ARDUINO

    class DevMode(object):
        """Control an IO processor running the developer mode program.

        This class will wait for Python to send commands to Pmod / Arduino IO,
        IIC, or SPI.

        Attributes
        ----------
        if_id : int
            The interface ID (1,2,3) corresponding to (PMODA,PMODB,ARDUINO).
        iop : _IOP
            IO processor instance used by DevMode.
        iop_switch_config :list
            IO processor switch configuration (8 or 19 integers).
        mmio : MMIO
            Memory-mapped IO instance to read and write instructions and data.

        """
        def __init__(self, if_id, switch_config):
            """Return a new instance of a DevMode object.

            Parameters
            ----------
            if_id : int
                The interface ID (1,2,3) corresponding to (PMODA,PMODB,ARDUINO).
            switch_config : list
                IO Processor switch configuration (8 or 19 integers).

            """
            if not if_id in [PMODA, PMODB, ARDUINO]:
                raise ValueError("No such IOP for DevMode.")

            self.if_id = if_id
            self.iop = request_iop(if_id, iop_const.MAILBOX_PROGRAM)
            self.iop_switch_config = list(switch_config)
            self.mmio = MMIO(self.iop.mmio.base_addr + iop_const.MAILBOX_OFFSET, \
                             iop_const.MAILBOX_SIZE)

        def start(self):
            """Start the IO Processor.

            The IOP instance will start automatically after instantiation.

            This method will:
            1. zero out mailbox CMD register;
            2. load switch config;
            3. set IOP status as "RUNNING".

            """
            self.iop.start()
            self.mmio.write(iop_const.MAILBOX_PY2IOP_CMD_OFFSET, 0)
            self.load_switch_config(self.iop_switch_config)

        def stop(self):
            """Put the IO Processor into Reset.

            This method will set IOP status as "STOPPED".

            """
            self.iop.stop()

        def load_switch_config(self, config=None):
            """Load the IO processor's switch configuration.

            This method will update switch config.

            Parameters
            ----------
            config: list
                A switch configuration list of integers.

            Raises
            ----------
            TypeError
                If the config argument is not of the correct type.

            """
            if self.if_id in [PMODA, PMODB]:
                if config == None:
                    config = iop_const.PMOD_SWCFG_DIOALL
                elif not len(config) == iop_const.PMOD_SWITCHCONFIG_NUMREGS:
                    raise TypeError('Invalid switch config {}.'.format(config))

                # Build switch config word
                self.iop_switch_config = config
                sw_config_word = 0
                for ix, cfg in enumerate(self.iop_switch_config):
                    sw_config_word |= (cfg << ix*4)

                # Disable, configure, enable switch
                self.write_cmd(iop_const.PMOD_SWITCHCONFIG_BASEADDR + 4, 0)
                self.write_cmd(iop_const.PMOD_SWITCHCONFIG_BASEADDR, \
                               sw_config_word)
                self.write_cmd(iop_const.PMOD_SWITCHCONFIG_BASEADDR + 7, \
                               0x80, dWidth=1)

            elif self.if_id in [ARDUINO]:
                if config == None:
                    config = iop_const.ARDUINO_SWCFG_DIOALL
                elif not len(config) == iop_const.ARDUINO_SWITCHCONFIG_NUMREGS:
                    raise TypeError('Invalid switch config {}.'.format(config))

                # Build switch config word
                self.iop_switch_config = config
                sw_config_words = [0, 0, 0, 0]
                for ix, cfg in enumerate(self.iop_switch_config):
                    if ix < 6:
                        sw_config_words[0] |= (cfg << ix*2)
                    elif ix == 6:
                        sw_config_words[0] |= (cfg << 31)
                    elif 7 <= ix < 11:
                        sw_config_words[1] |= (cfg << (ix-7)*4)
                    elif 11 <= ix < 15:
                        sw_config_words[2] |= (cfg << (ix-11)*4)
                    else:
                        sw_config_words[3] |= (cfg << (ix-15)*4)

                    # Configure switch
                    for i in range(4):
                        self.write_cmd(iop_const.ARDUINO_SWITCHCONFIG_BASEADDR + \
                                       4*i, sw_config_words[i])

            else:
                raise ValueError("Cannot load switch for unknown IOP.")

        def status(self):
            """Returns the status of the IO processor.

            Parameters
            ----------
            None

            Returns
            -------
            str
                The IOP status ("IDLE", "RUNNING", or "STOPPED").

            """
            return self.iop.state

        def write_cmd(self, address, data, dWidth=4, dLength=1, timeout=10):
            """Send a write command to the mailbox.

            Parameters
            ----------
            address : int
                The address tied to IO processor's memory map.
            data : int
                32-bit value to be written (None for read).
            dWidth : int
                Command data width.
            dLength : int
                Command burst length (currently only supporting dLength 1).
            timeout : int
                Time in milliseconds before function exits with warning.

            Returns
            -------
            None

            """
            return self._send_cmd(iop_const.WRITE_CMD, address, data,
                                    dWidth=dWidth, timeout=timeout)

        def read_cmd(self, address, dWidth=4, dLength=1, timeout=10):
            """Send a read command to the mailbox.

            Parameters
            ----------
            address : int
                The address tied to IO processor's memory map.
            dWidth : int
                Command data width.
            dLength : int
                Command burst length (currently only supporting dLength 1).
            timeout : int
                Time in milliseconds before function exits with warning.

            Returns
            -------
            list
                A list of data returned by MMIO read.

            """
            return self._send_cmd(iop_const.READ_CMD, address, None,
                                    dWidth=dWidth, timeout=timeout)

        def is_cmd_mailbox_idle(self):
            """Check whether the IOP command mailbox is idle.

            Parameters
            ----------
            None

            Returns
            -------
            bool
                True if IOP command mailbox idle.

            """
            mb_cmd_word = self.mmio.read(iop_const.MAILBOX_PY2IOP_CMD_OFFSET)
            return (mb_cmd_word & 0x1) == 0

        def get_cmd_word(self, cmd, dWidth, dLength):
            """Build the command word.

            Note
            ----
            The returned command word has the following format:
            Bit [0]     : valid bit.
            Bit [2:1]   : command data width.
            Bit [3]     : command type (read or write).
            Bit [15:8]  : command burst length.
            Bit [31:16] : unused.

            Parameters
            ----------
            cmd : int
                Either 1 (read IOP register) or 0 (write IOP register).
            dWidth : int
                Command data width.
            dLength : int
                Command burst length (currently only supporting dLength 1).

            Returns
            -------
            int
                The command word following a specific format.

            """
            word = 0x1                    # cmd valid
            word = word | (dWidth-1) << 1 # cmd dataWidth    (3->4B, 1->2B, 0->1B)
            word = word | (cmd) << 3      # cmd type         (1->RD, 0->WR)
            word = word | (dLength) << 8  # cmd burst length (1->1 word)
            word = word | (0) << 16       # unused

            return word

        def _send_cmd(self, cmd, address, data, dWidth=4, dLength=1, timeout=10):
            """Send a command to the IO processor via mailbox.

            Note
            ----
            User should avoid to call this method directly.
            Use the read_cmd() or write_cmd() instead.

            Example:
                >>> _send_cmd(1, 4, None)  # Read address 4.

            Parameters
            ----------
            cmd : int
                Either 1 (read IOP Reg) or 0 (write IOP Reg).
            address : int
                The address tied to IO processor's memory map.
            data : int
                32-bit value to be written (None for read).
            dWidth : int
                Command data width.
            dLength : int
                Command burst length (currently only supporting dLength 1).
            timeout : int
                Time in milliseconds before function exits with warning.

            Raises
            ------
            LookupError
                If it takes too long to receive the ACK from the IOP.

            """
            self.mmio.write(iop_const.MAILBOX_PY2IOP_ADDR_OFFSET, address)
            if data != None:
                self.mmio.write(iop_const.MAILBOX_PY2IOP_DATA_OFFSET, data)

            # Build the write command
            cmd_word = self.get_cmd_word(cmd, dWidth, dLength)
            self.mmio.write(iop_const.MAILBOX_PY2IOP_CMD_OFFSET, cmd_word)

            # Wait for ACK in steps of 1ms
            cntdown = timeout
            while not self.is_cmd_mailbox_idle() and cntdown > 0:
                time.sleep(0.001)
                cntdown -= 1

            # If ACK is not received, alert users.
            if cntdown == 0:
                raise LookupError("DevMode _send_cmd() not acknowledged.")

            # Return data if expected from read, otherwise return None
            if cmd == iop_const.WRITE_CMD:
                return None
            else:
                return self.mmio.read(iop_const.MAILBOX_PY2IOP_DATA_OFFSET)