コード例 #1
0
ファイル: test-common.py プロジェクト: seanreichle/valentyusb
    def __init__(self, dut):
        self.dut = dut
        self.csrs = dict()
        with open("csr.csv", newline='') as csr_csv_file:
            csr_csv = csv.reader(csr_csv_file)
            # csr_register format: csr_register, name, address, size, rw/ro
            for row in csr_csv:
                if row[0] == 'csr_register':
                    self.csrs[row[1]] = int(row[2], base=0)
        cocotb.fork(Clock(dut.clk48, 20800, 'ps').start())
        self.wb = WishboneMaster(dut, "wishbone", dut.clk12, timeout=20)

        # Set the signal "test_name" to match this test
        import inspect
        tn = cocotb.binary.BinaryValue(value=None, n_bits=4096)
        tn.buff = inspect.stack()[1][3]
        self.dut.test_name = tn
コード例 #2
0
class HaddecksTest:
    def __init__(self, dut, test_name):
        self.dut = dut
        self.test_name = test_name
        self.csrs = dict()
        self.dut.reset = 1
        with open("../build/csr.csv", newline='') as csr_csv_file:
            csr_csv = csv.reader(csr_csv_file)
            # csr_register format: csr_register, name, address, size, rw/ro
            for row in csr_csv:
                if row[0] == 'csr_register':
                    exec("self.{} = {}".format(row[1].upper(),
                                               int(row[2], base=0)))
        cocotb.fork(Clock(dut.clk48, 20800 // 4, 'ps').start())
        self.wb = WishboneMaster(dut, "wishbone", dut.clk48, timeout=20)

        # Set the signal "test_name" to match this test, so that we can
        # tell from gtkwave which test we're in.
        tn = cocotb.binary.BinaryValue(value=None, n_bits=4096)
        tn.buff = test_name
        self.dut.test_name = tn

    @cocotb.coroutine
    def write(self, addr, val):
        yield self.wb.write(addr, val)

    @cocotb.coroutine
    def read(self, addr):
        value = yield self.wb.read(addr)
        raise ReturnValue(value)

    @cocotb.coroutine
    def reset(self):
        self.dut.reset = 1
        yield RisingEdge(self.dut.clk48)
        yield RisingEdge(self.dut.clk48)
        yield RisingEdge(self.dut.clk48)
        yield RisingEdge(self.dut.clk48)
        self.dut.reset = 0
        yield RisingEdge(self.dut.clk48)
        yield RisingEdge(self.dut.clk48)
        yield RisingEdge(self.dut.clk48)
        yield RisingEdge(self.dut.clk48)
コード例 #3
0
    def __init__(self, dut, test_name):
        if "WIRES" in os.environ:
            self.wires = int(os.environ["WIRES"])
        else:
            self.wires = 4
        self.dut = dut
        self.test_name = test_name
        self.csrs = dict()
        self.dut.reset = 1
        with open("csr.csv", newline='') as csr_csv_file:
            csr_csv = csv.reader(csr_csv_file)
            # csr_register format: csr_register, name, address, size, rw/ro
            for row in csr_csv:
                if row[0] == 'csr_register':
                    exec("self.{} = {}".format(row[1].upper(),
                                               int(row[2], base=0)))
        cocotb.fork(Clock(dut.clk48, 20800, 'ps').start())
        self.wb = WishboneMaster(dut, "wishbone", dut.clk12, timeout=20)

        # Set the signal "test_name" to match this test, so that we can
        # tell from gtkwave which test we're in.
        tn = cocotb.binary.BinaryValue(value=None, n_bits=4096)
        tn.buff = test_name
        self.dut.test_name = tn
コード例 #4
0
ファイル: test-common.py プロジェクト: seanreichle/valentyusb
class UsbTest:
    def __init__(self, dut):
        self.dut = dut
        self.csrs = dict()
        with open("csr.csv", newline='') as csr_csv_file:
            csr_csv = csv.reader(csr_csv_file)
            # csr_register format: csr_register, name, address, size, rw/ro
            for row in csr_csv:
                if row[0] == 'csr_register':
                    self.csrs[row[1]] = int(row[2], base=0)
        cocotb.fork(Clock(dut.clk48, 20800, 'ps').start())
        self.wb = WishboneMaster(dut, "wishbone", dut.clk12, timeout=20)

        # Set the signal "test_name" to match this test
        import inspect
        tn = cocotb.binary.BinaryValue(value=None, n_bits=4096)
        tn.buff = inspect.stack()[1][3]
        self.dut.test_name = tn

    @cocotb.coroutine
    def reset(self):

        self.dut.reset = 1
        yield RisingEdge(self.dut.clk12)
        self.dut.reset = 0
        yield RisingEdge(self.dut.clk12)

        self.dut.usb_d_p = 1
        self.dut.usb_d_n = 0

        yield self.disconnect()

        # Enable endpoint 0
        yield self.write(self.csrs['usb_enable_out0'], 0xff)
        yield self.write(self.csrs['usb_enable_out1'], 0xff)
        yield self.write(self.csrs['usb_enable_in0'], 0xff)
        yield self.write(self.csrs['usb_enable_in1'], 0xff)

    @cocotb.coroutine
    def write(self, addr, val):
        yield self.wb.write(addr, val)

    @cocotb.coroutine
    def read(self, addr):
        value = yield self.wb.read(addr)
        raise ReturnValue(value)

    @cocotb.coroutine
    def connect(self):
        USB_PULLUP_OUT = self.csrs['usb_pullup_out']
        yield self.write(USB_PULLUP_OUT, 1)

    @cocotb.coroutine
    def clear_pending(self, _ep):
        yield Timer(0)

    @cocotb.coroutine
    def disconnect(self):
        USB_PULLUP_OUT = self.csrs['usb_pullup_out']
        yield self.write(USB_PULLUP_OUT, 0)

    def assertEqual(self, a, b, msg):
        if a != b:
            raise TestFailure("{} != {} - {}".format(a, b, msg))

    def assertSequenceEqual(self, a, b, msg):
        if a != b:
            raise TestFailure("{} vs {} - {}".format(a, b, msg))

    def print_ep(self, epaddr, msg, *args):
        self.dut._log.info("ep(%i, %s): %s" % (
            EndpointType.epnum(epaddr),
            EndpointType.epdir(epaddr).name,
            msg) % args)

    # Host->Device
    @cocotb.coroutine
    def _host_send_packet(self, packet):
        """Send a USB packet."""

        # Packet gets multiplied by 4x so we can send using the
        # usb48 clock instead of the usb12 clock.
        packet = 'JJJJJJJJ' + wrap_packet(packet)
        self.assertEqual('J', packet[-1], "Packet didn't end in J: "+packet)

        for v in packet:
            if v == '0' or v == '_':
                # SE0 - both lines pulled low
                self.dut.usb_d_p <= 0
                self.dut.usb_d_n <= 0
            elif v == '1':
                # SE1 - illegal, should never occur
                self.dut.usb_d_p <= 1
                self.dut.usb_d_n <= 1
            elif v == '-' or v == 'I':
                # Idle
                self.dut.usb_d_p <= 1
                self.dut.usb_d_n <= 0
            elif v == 'J':
                self.dut.usb_d_p <= 1
                self.dut.usb_d_n <= 0
            elif v == 'K':
                self.dut.usb_d_p <= 0
                self.dut.usb_d_n <= 1
            else:
                raise TestFailure("Unknown value: %s" % v)
            yield RisingEdge(self.dut.clk48)

    @cocotb.coroutine
    def host_send_token_packet(self, pid, addr, ep):
        epnum = EndpointType.epnum(ep)
        yield self._host_send_packet(token_packet(pid, addr, epnum))

    @cocotb.coroutine
    def host_send_data_packet(self, pid, data):
        assert pid in (PID.DATA0, PID.DATA1), pid
        yield self._host_send_packet(data_packet(pid, data))

    @cocotb.coroutine
    def host_send_sof(self, time):
        yield self._host_send_packet(sof_packet(time))

    @cocotb.coroutine
    def host_send_ack(self):
        yield self._host_send_packet(handshake_packet(PID.ACK))

    @cocotb.coroutine
    def host_send(self, data01, addr, epnum, data, expected=PID.ACK):
        """Send data out the virtual USB connection, including an OUT token"""
        yield self.host_send_token_packet(PID.OUT, addr, epnum)
        yield self.host_send_data_packet(data01, data)
        yield self.host_expect_packet(handshake_packet(expected), "Expected {} packet.".format(expected))


    @cocotb.coroutine
    def host_setup(self, addr, epnum, data):
        """Send data out the virtual USB connection, including a SETUP token"""
        yield self.host_send_token_packet(PID.SETUP, addr, epnum)
        yield self.host_send_data_packet(PID.DATA0, data)
        yield self.host_expect_ack()

    @cocotb.coroutine
    def host_recv(self, data01, addr, epnum, data):
        """Send data out the virtual USB connection, including an IN token"""
        yield self.host_send_token_packet(PID.IN, addr, epnum)
        yield self.host_expect_data_packet(data01, data)
        yield self.host_send_ack()

    # Device->Host
    @cocotb.coroutine
    def host_expect_packet(self, packet, msg=None):
        """Except to receive the following USB packet."""

        def current():
            values = (self.dut.usb_d_p, self.dut.usb_d_n)

            if values == (0, 0):
                return '_'
            elif values == (1, 1):
                return '1'
            elif values == (1, 0):
                return 'J'
            elif values == (0, 1):
                return 'K'
            else:
                raise TestFailure("Unrecognized dut values: {}".format(values))

        # Wait for transmission to start
        tx = 0
        bit_times = 0
        for i in range(0, 100):
            tx = self.dut.usb_tx_en
            if tx == 1:
                break
            yield RisingEdge(self.dut.clk48)
            bit_times = bit_times + 1
        if tx != 1:
            raise TestFailure("No packet started, " + msg)

        # # USB specifies that the turn-around time is 7.5 bit times for the device
        bit_time_max = 12.5
        bit_time_acceptable = 7.5
        if (bit_times/4.0) > bit_time_max:
            raise TestFailure("Response came after {} bit times, which is more than {}".format(bit_times / 4.0, bit_time_max))
        if (bit_times/4.0) > bit_time_acceptable:
            self.dut._log.warn("Response came after {} bit times (> {})".format(bit_times / 4.0, bit_time_acceptable))
        else:
            self.dut._log.info("Response came after {} bit times".format(bit_times / 4.0))

        # Read in the transmission data
        result = ""
        for i in range(0, 1024):
            result += current()
            yield RisingEdge(self.dut.clk48)
            if self.dut.usb_tx_en != 1:
                break
        if tx == 1:
            raise TestFailure("Packet didn't finish, " + msg)
        self.dut.usb_d_p = 1
        self.dut.usb_d_n = 0

        # Check the packet received matches
        expected = pp_packet(wrap_packet(packet))
        actual = pp_packet(result)
        self.assertSequenceEqual(expected, actual, msg)

    @cocotb.coroutine
    def host_expect_ack(self):
        yield self.host_expect_packet(handshake_packet(PID.ACK), "Expected ACK packet.")

    @cocotb.coroutine
    def host_expect_nak(self):
        yield self.host_expect_packet(handshake_packet(PID.NAK), "Expected NAK packet.")

    @cocotb.coroutine
    def host_expect_stall(self):
        yield self.host_expect_packet(handshake_packet(PID.STALL), "Expected STALL packet.")

    @cocotb.coroutine
    def host_expect_data_packet(self, pid, data):
        assert pid in (PID.DATA0, PID.DATA1), pid
        yield self.host_expect_packet(data_packet(pid, data), "Expected %s packet with %r" % (pid.name, data))

    @cocotb.coroutine
    def pending(self, ep):
        if EndpointType.epdir(ep) == EndpointType.IN:
            val = yield self.read(self.csrs['usb_epin_status'])
        else:
            val = yield self.read(self.csrs['usb_epout_status'])
        raise ReturnValue(val & 1)

    @cocotb.coroutine
    def expect_setup(self, epaddr, expected_data):
        actual_data = []
        # wait for data to appear
        for i in range(128):
            self.dut._log.debug("Prime loop {}".format(i))
            status = yield self.read(self.csrs['usb_setup_status'])
            have = status & 1
            if have:
                break
            yield RisingEdge(self.dut.clk12)

        for i in range(48):
            self.dut._log.debug("Read loop {}".format(i))
            status = yield self.read(self.csrs['usb_setup_status'])
            have = status & 1
            if not have:
                break
            v = yield self.read(self.csrs['usb_setup_data'])
            yield self.write(self.csrs['usb_setup_ctrl'], 1)
            actual_data.append(v)
            yield RisingEdge(self.dut.clk12)

        if len(actual_data) < 2:
            raise TestFailure("data was short (got {}, expected {})".format(expected_data, actual_data))
        actual_data, actual_crc16 = actual_data[:-2], actual_data[-2:]

        self.print_ep(epaddr, "Got: %r (expected: %r)", actual_data, expected_data)
        self.assertSequenceEqual(expected_data, actual_data, "SETUP packet not received")
        self.assertSequenceEqual(crc16(expected_data), actual_crc16, "CRC16 not valid")

    @cocotb.coroutine
    def expect_data(self, epaddr, expected_data, expected):
        actual_data = []
        # wait for data to appear
        for i in range(128):
            self.dut._log.debug("Prime loop {}".format(i))
            status = yield self.read(self.csrs['usb_epout_status'])
            have = status & 1
            if have:
                break
            yield RisingEdge(self.dut.clk12)

        for i in range(256):
            self.dut._log.debug("Read loop {}".format(i))
            status = yield self.read(self.csrs['usb_epout_status'])
            have = status & 1
            if not have:
                break
            v = yield self.read(self.csrs['usb_epout_data'])
            yield self.write(self.csrs['usb_epout_ctrl'], 3)
            actual_data.append(v)
            yield RisingEdge(self.dut.clk12)

        if expected == PID.ACK:
            if len(actual_data) < 2:
                raise TestFailure("data {} was short".format(actual_data))
            actual_data, actual_crc16 = actual_data[:-2], actual_data[-2:]

            self.print_ep(epaddr, "Got: %r (expected: %r)", actual_data, expected_data)
            self.assertSequenceEqual(expected_data, actual_data, "DATA packet not correctly received")
            self.assertSequenceEqual(crc16(expected_data), actual_crc16, "CRC16 not valid")

    @cocotb.coroutine
    def set_response(self, ep, response):
        if EndpointType.epdir(ep) == EndpointType.IN and response == EndpointResponse.ACK:
            yield self.write(self.csrs['usb_epin_epno'], EndpointType.epnum(ep))

    @cocotb.coroutine
    def send_data(self, token, ep, data):
        for b in data:
            yield self.write(self.csrs['usb_epin_data'], b)
        yield self.write(self.csrs['usb_epin_epno'], ep)

    @cocotb.coroutine
    def transaction_setup(self, addr, data, epnum=0):
        epaddr_out = EndpointType.epaddr(0, EndpointType.OUT)
        epaddr_in = EndpointType.epaddr(0, EndpointType.IN)

        xmit = cocotb.fork(self.host_setup(addr, epnum, data))
        yield self.expect_setup(epaddr_out, data)
        yield xmit.join()

    @cocotb.coroutine
    def transaction_data_out(self, addr, ep, data, chunk_size=64, expected=PID.ACK):
        epnum = EndpointType.epnum(ep)
        datax = PID.DATA1

        # # Set it up so we ACK the final IN packet
        # yield self.write(self.csrs['usb_epin_epno'], 0)
        for _i, chunk in enumerate(grouper_tofit(chunk_size, data)):
            self.dut._log.warning("Sening {} bytes to host".format(len(chunk)))
            # Enable receiving data
            yield self.write(self.csrs['usb_epout_ctrl'], (1 << 1))
            xmit = cocotb.fork(self.host_send(datax, addr, epnum, chunk, expected))
            yield self.expect_data(epnum, list(chunk), expected)
            yield xmit.join()

            if datax == PID.DATA0:
                datax = PID.DATA1
            else:
                datax = PID.DATA0

    @cocotb.coroutine
    def transaction_data_in(self, addr, ep, data, chunk_size=64):
        epnum = EndpointType.epnum(ep)
        datax = PID.DATA1
        sent_data = 0
        for i, chunk in enumerate(grouper_tofit(chunk_size, data)):
            sent_data = 1
            self.dut._log.debug("Actual data we're expecting: {}".format(chunk))
            for b in chunk:
                yield self.write(self.csrs['usb_epin_data'], b)
            yield self.write(self.csrs['usb_epin_epno'], epnum)
            recv = cocotb.fork(self.host_recv(datax, addr, epnum, chunk))
            yield recv.join()

            if datax == PID.DATA0:
                datax = PID.DATA1
            else:
                datax = PID.DATA0
        if not sent_data:
            yield self.write(self.csrs['usb_epin_epno'], epnum)
            recv = cocotb.fork(self.host_recv(datax, addr, epnum, []))
            yield self.send_data(datax, epnum, data)
            yield recv.join()

    @cocotb.coroutine
    def set_data(self, ep, data):
        _epnum = EndpointType.epnum(ep)
        for b in data:
            yield self.write(self.csrs['usb_epin_data'], b)

    @cocotb.coroutine
    def transaction_status_in(self, addr, ep):
        epnum = EndpointType.epnum(ep)
        assert EndpointType.epdir(ep) == EndpointType.IN
        xmit = cocotb.fork(self.host_recv(PID.DATA1, addr, epnum, []))
        yield xmit.join()

    @cocotb.coroutine
    def transaction_status_out(self, addr, ep):
        epnum = EndpointType.epnum(ep)
        assert EndpointType.epdir(ep) == EndpointType.OUT
        xmit = cocotb.fork(self.host_send(PID.DATA1, addr, epnum, []))
        yield xmit.join()

    @cocotb.coroutine
    def control_transfer_out(self, addr, setup_data, descriptor_data=None):
        epaddr_out = EndpointType.epaddr(0, EndpointType.OUT)
        epaddr_in = EndpointType.epaddr(0, EndpointType.IN)

        if (setup_data[0] & 0x80) == 0x80:
            raise Exception("setup_data indicated an IN transfer, but you requested an OUT transfer")

        # Setup stage
        self.dut._log.info("setup stage")
        yield self.transaction_setup(addr, setup_data)

        # Data stage
        if (setup_data[7] != 0 or setup_data[6] != 0) and descriptor_data is None:
            raise Exception("setup_data indicates data, but no descriptor data was specified")
        if (setup_data[7] == 0 and setup_data[6] == 0) and descriptor_data is not None:
            raise Exception("setup_data indicates no data, but descriptor data was specified")
        if descriptor_data is not None:
            self.dut._log.info("data stage")
            yield self.transaction_data_out(addr, epaddr_out, descriptor_data)

        # Status stage
        self.dut._log.info("status stage")
        # yield self.set_response(epaddr_in, EndpointResponse.ACK)
        yield self.transaction_status_in(addr, epaddr_in)

    @cocotb.coroutine
    def control_transfer_in(self, addr, setup_data, descriptor_data=None):
        epaddr_out = EndpointType.epaddr(0, EndpointType.OUT)
        epaddr_in = EndpointType.epaddr(0, EndpointType.IN)

        if (setup_data[0] & 0x80) == 0x00:
            raise Exception("setup_data indicated an OUT transfer, but you requested an IN transfer")

        # Setup stage
        self.dut._log.info("setup stage")
        yield self.transaction_setup(addr, setup_data)

        # Data stage
        # Data stage
        if (setup_data[7] != 0 or setup_data[6] != 0) and descriptor_data is None:
            raise Exception("setup_data indicates data, but no descriptor data was specified")
        if (setup_data[7] == 0 and setup_data[6] == 0) and descriptor_data is not None:
            raise Exception("setup_data indicates no data, but descriptor data was specified")
        if descriptor_data is not None:
            self.dut._log.info("data stage")
            yield self.transaction_data_in(addr, epaddr_in, descriptor_data)

        # Status stage
        self.dut._log.info("status stage")
        # yield self.set_response(epaddr_in, EndpointResponse.ACK)
        yield self.transaction_status_out(addr, epaddr_out)
コード例 #5
0
class SpiboneTest:
    def __init__(self, dut, test_name):
        if "WIRES" in os.environ:
            self.wires = int(os.environ["WIRES"])
        else:
            self.wires = 4
        self.dut = dut
        self.test_name = test_name
        self.csrs = dict()
        self.dut.reset = 1
        with open("csr.csv", newline='') as csr_csv_file:
            csr_csv = csv.reader(csr_csv_file)
            # csr_register format: csr_register, name, address, size, rw/ro
            for row in csr_csv:
                if row[0] == 'csr_register':
                    exec("self.{} = {}".format(row[1].upper(),
                                               int(row[2], base=0)))
        cocotb.fork(Clock(dut.clk48, 20800, 'ps').start())
        self.wb = WishboneMaster(dut, "wishbone", dut.clk12, timeout=20)

        # Set the signal "test_name" to match this test, so that we can
        # tell from gtkwave which test we're in.
        tn = cocotb.binary.BinaryValue(value=None, n_bits=4096)
        tn.buff = test_name
        self.dut.test_name = tn

    @cocotb.coroutine
    # Validate that the MOSI line doesn't change while CLK is 1
    def validate_mosi_stability(self, test_name):
        previous_clk = self.dut.spi_clk
        previous_val = self.dut.spi_mosi
        while True:
            v = self.dut.spi_cs_n.value
            v_str = "{}".format(v)
            # self.dut._log.error("{} - CS_N value: {}  Reset value: {}".format(test_name, v_str, self.dut.reset))
            if v_str == "0" and self.dut.reset == 0:
                if self.dut.spi_clk:
                    # Clock went high, so capture value
                    if previous_clk == 0:
                        previous_val = self.dut.spi_mosi
                    else:
                        if int(self.dut.spi_mosi) != int(previous_val):
                            raise TestFailure(
                                "spi.mosi changed while clk was high (was {}, now {})"
                                .format(previous_val, self.dut.spi_mosi))
                previous_clk = self.dut.spi_clk
            # else:
            #     self.dut._log.error("CS_N is Z")
            yield Edge(self.dut.clk48)

    @cocotb.coroutine
    # Validate that the MOSI and MISO line don't change while CLK is 1
    def validate_mosi_miso_stability(self, test_name):
        previous_clk = self.dut.spi_clk
        previous_mosi = self.dut.spi_mosi
        if self.wires == 4:
            previous_miso = self.dut.spi_miso
        while True:
            v = self.dut.spi_cs_n.value
            v_str = "{}".format(v)
            # self.dut._log.error("{} - CS_N value: {}  Reset value: {}".format(test_name, v_str, self.dut.reset))
            if v_str == "0" and self.dut.reset == 0:
                if self.dut.spi_clk:
                    # Clock went high, so capture value
                    if previous_clk == 0:
                        previous_mosi = self.dut.spi_mosi
                        if self.wires == 4:
                            previous_miso = self.dut.spi_miso
                    else:
                        if int(self.dut.spi_mosi) != int(previous_mosi):
                            raise TestFailure(
                                "spi.mosi changed while clk was high (was {}, now {})"
                                .format(previous_mosi, self.dut.spi_mosi))
                        if self.wires == 4:
                            if int(self.dut.spi_miso) != int(previous_miso):
                                raise TestFailure(
                                    "spi.mosi changed while clk was high (was {}, now {})"
                                    .format(previous_miso, self.dut.spi_miso))
                previous_clk = self.dut.spi_clk
            # else:
            #     self.dut._log.error("CS_N is Z")
            yield Edge(self.dut.clk48)

    @cocotb.coroutine
    def write(self, addr, val):
        yield self.wb.write(addr, val)

    @cocotb.coroutine
    def read(self, addr):
        value = yield self.wb.read(addr)
        raise ReturnValue(value)

    @cocotb.coroutine
    def reset(self):
        self.dut.reset = 1
        self.dut.spi_cs_n = 1
        self.dut.spi_mosi = 0
        self.dut.spi_clk = 0
        yield RisingEdge(self.dut.clk12)
        yield RisingEdge(self.dut.clk12)
        cocotb.fork(self.validate_mosi_miso_stability(self.test_name))
        self.dut.reset = 0
        self.dut.spi_mosi = 0
        self.dut.spi_clk = 0
        self.dut.spi_cs_n = 1
        yield RisingEdge(self.dut.clk12)
        yield RisingEdge(self.dut.clk12)

    @cocotb.coroutine
    def host_spi_tick(self):
        self.dut.spi_clk = 0
        yield RisingEdge(self.dut.clk12)
        self.dut.spi_clk = 1
        yield RisingEdge(self.dut.clk12)

    @cocotb.coroutine
    def host_spi_write_byte(self, val):
        for shift in range(7, -1, -1):
            self.dut.spi_mosi = (val >> shift) & 1
            yield self.host_spi_tick()

    @cocotb.coroutine
    def host_spi_read_byte(self):
        val = 0
        for shift in range(7, -1, -1):
            yield self.host_spi_tick()
            if self.wires == 3 or self.wires == 2:
                val = val | (int(self.dut.spi_mosi) << shift)
            elif self.wires == 4:
                val = val | (int(self.dut.spi_miso) << shift)
        raise ReturnValue(val)

    @cocotb.coroutine
    def host_start(self):
        if self.wires == 3 or self.wires == 4:
            self.dut.spi_cs_n = 0
            self.dut.spi_mosi = 0
        elif self.wires == 2:
            yield self.host_spi_write_byte(0xab)
        if False:
            yield

    @cocotb.coroutine
    def host_finish(self):
        if self.wires == 3 or self.wires == 4:
            self.dut.spi_cs_n = 1
        self.dut.spi_mosi = 0
        yield self.host_spi_tick()
        self.dut.spi_mosi = 0
        yield self.host_spi_tick()
        self.dut.spi_mosi = 0
        yield self.host_spi_tick()
        self.dut.spi_mosi = 0
        yield self.host_spi_tick()
        self.dut.spi_mosi = 0
        yield self.host_spi_tick()
        if False:
            yield

    @cocotb.coroutine
    def host_spi_write(self, addr, val):
        yield self.host_start()

        # Header
        # 0: Write
        # 1: Read
        yield self.host_spi_write_byte(0)

        # Address
        for shift in [24, 16, 8, 0]:
            yield self.host_spi_write_byte(addr >> shift)

        # Value
        for shift in [24, 16, 8, 0]:
            yield self.host_spi_write_byte(val >> shift)

        # Wait for response
        timeout_counter = 0
        while True:
            val = yield self.host_spi_read_byte()
            if val != 0xff:
                if val == 0:
                    break
                raise TestFailure(
                    "response byte was 0x{:02x}, not 0x00".format(val))
            timeout_counter = timeout_counter + 1
            if timeout_counter > 20:
                raise TestFailure("timed out waiting for response")
        yield self.host_finish()

    @cocotb.coroutine
    def host_spi_read(self, addr):
        yield self.host_start()

        # Header
        # 0: Write
        # 1: Read
        yield self.host_spi_write_byte(1)

        # Address
        for shift in [24, 16, 8, 0]:
            yield self.host_spi_write_byte(addr >> shift)

        # Wait for response
        timeout_counter = 0
        while True:
            val = yield self.host_spi_read_byte()
            if val != 0xff:
                if val == 1:
                    break
                raise TestFailure(
                    "response byte was 0x{:02x}, not 0x01".format(val))
            timeout_counter = timeout_counter + 1
            if timeout_counter > 20:
                raise TestFailure("timed out waiting for response")

        # Value
        val = 0
        for shift in [24, 16, 8, 0]:
            addon = yield self.host_spi_read_byte()
            val = val | (addon << shift)

        self.dut.spi_cs_n = 1
        yield self.host_finish()
        raise ReturnValue(val)