Proteus Kintex 7 FPGA Development Module

Simple USB 3.1 Gen 1 Interfacing with Host Systems Using Proteus

98 views June 29, 2021 admin 0

Introduction

USB 3.0 has revolutionized the world of desktops and mobile devices by bringing much higher bandwidth and better power delivery compared to its predecessor USB 2.0. USB 3.0 (later renamed as USB 3.1 Gen 1) offers a data rate of 5Gb/s or a whopping theoretical bandwidth of 625MB/s. This is more than 10 times the theoretical maximum bandwidth of USB 2.0. While practically available bandwidth for end-user applications can be lower depending on the hardware and firmware solutions chosen, USB 3.0 offers a great value proposition that cannot be ignored. In this article, we will use:

Proteus Kintex 7 USB 3.1 Development Board

Proteus is an easy to use FPGA Development board featuring the Xilinx Kintex-7 FPGA with 4GB DDR3L SDRAM. This board contains the Xilinx XC7K160T– FBG676 FPGA (other FPGA configurations are available at request). The high-speed USB 3.1 interface (USB-C connector) provides a fast and easy configuration download to the onboard SPI flash.

Tools and Prerequisites

Hardware

  1. Proteus Kintex 7 USB 3.1 Development Board
  2. Xilinx Platform Cable USB II.
  3. USB A to USB Type C cable

Software

  1. FT_Prog tool for configuring on-board FT2232H USB Serial converter (download and install from FTDI website).
  2. Python 3.5 or higher
  3. Xilinx Vivado Design Suite 2018.2 or higher
  4. MinGW

Implementation of  FIFO Master Interface with FT600/FT601

In this article, the implementation of the USB 3.1 Interfacing with Host System is implemented using LiteX and Migen. Hence, install Migen, if not installed already. The below code is the complete implementation of FIFO Master Interface in Migen.

class FT601_245(Module):

    def __init__(self, io, width=32):
        # Shared control
        # Data bus is driven by both the FPGA and the FTDI. We use tristate.
        io.oe_n.reset = 1
        io.rd_n.reset = 1
        io.wr_n.reset = 1

        self.data_tri = data_tri = TSTriple(width)
        data_tri.oe.reset = 1
        assert io.data.nbits == width
        self.specials += data_tri.get_tristate(io.data)

        self.be_tri = be_tri = TSTriple(width // 8)
        be_tri.oe.reset = 1
        assert io.be.nbits == width // 8
        self.specials += be_tri.get_tristate(io.be)

        self.comb += [
            be_tri.o.eq(0xf),
            be_tri.oe.eq(1),
            data_tri.oe.eq(1),
            io.oe_n.eq(1)
        ]

        txe_n_d4 = self.txe_n_d4 = Signal()
        txe_n_d5 = self.txe_n_d5 = Signal()
        wr_n_d1 = self.wr_n_d1 = Signal(reset=1)
        wr_n_d0 = self.wr_n_d0 = Signal(reset=1)
        data_d0 = self.data_d0 = Signal(width, reset=0)
        data_d1 = self.data_d1 = Signal(width, reset=0)
        self.specials += MultiReg(io.txe_n, txe_n_d4, n=txe_delay, reset=1)

        self.comb += [
            io.wr_n.eq(wr_n_d1),
            data_tri.o.eq(data_d1),
        ]

        prefetch_counter = Signal(4, reset=0)
        counter = self.counter = Signal(width, reset=0)
        self.sync += [
            txe_n_d5.eq(txe_n_d4),
            data_d1.eq(data_d0),
            wr_n_d1.eq(wr_n_d0),

            data_d0.eq(counter),
            If((txe_n_d4 == 0) & (txe_n_d5 == 0),
                wr_n_d0.eq(0),
                If(prefetch_counter < txe_delay + 2,
                    prefetch_counter.eq(prefetch_counter + 1)
                ).Else(
                    prefetch_counter.eq(prefetch_counter)
                ),
                counter.eq(counter + 1)
            ).Elif(txe_n_d4 == 1,
                wr_n_d0.eq(1),
                prefetch_counter.eq(0),
                counter.eq(counter - prefetch_counter)
            )
        ]

class MultiRegImpl(Module):
    def __init__(self, i, o, odomain, n, reset=0):
        self.i = i
        self.o = o
        self.odomain = odomain

        w, signed = value_bits_sign(self.i)
        self.regs = [Signal((w, signed), reset=reset, reset_less=True)
                for i in range(n)]

        ###

        sd = getattr(self.sync, self.odomain)
        src = self.i
        for reg in self.regs:
            sd += reg.eq(src)
            src = reg
        self.comb += self.o.eq(src)
        for reg in self.regs:
            reg.attr.add("no_retiming")


class MultiReg(Special):
    def __init__(self, i, o, odomain="sys", n=2, reset=0):
        Special.__init__(self)
        self.i = wrap(i)
        self.o = wrap(o)
        self.odomain = odomain
        self.n = n
        self.reset = reset

    def iter_expressions(self):
        yield self, "i", SPECIAL_INPUT
        yield self, "o", SPECIAL_OUTPUT

    def rename_clock_domain(self, old, new):
        Special.rename_clock_domain(self, old, new)
        if self.odomain == old:
            self.odomain = new

    def list_clock_domains(self):
        r = Special.list_clock_domains(self)
        r.add(self.odomain)
        return r

    @staticmethod
    def lower(dr):
        return MultiRegImpl(dr.i, dr.o, dr.odomain, dr.n, dr.reset)

class FT601SourceTop(Module):
    def __init__(self, platform):
        usb_fifo = platform.request("usb_fifo")

        crg = CRG(platform.request(platform.default_clk_name))
        self.submodules.crg = crg

        usb_phy = FT601_245(usb_fifo)
        self.submodules.usb_phy = usb_phy

These are constraints which are required for the design.

_io = [
    ("clk100",  0, Pins("E10"), IOStandard("LVCMOS25")),

    ("usb_fifo", 0,
        Subsignal("data", Pins("J8 H8 F8 D8 A8 H9 G9 C9 B9 A9 J10 G10 F10 B10 A10 J11 H11 G11 E11 C11 B11 H12 G12 C12 B12 A12 E12 J13 H13 D13 C13 A13")),
        Subsignal("be", Pins("J14 H14 G14 F14")),
        #Subsignal("clk", Pins("E10")),  # Used as main system clock clk100
        Subsignal("txe_n", Pins("D14")),
        Subsignal("rxf_n", Pins("A14")),
        Subsignal("wr_n", Pins("B14")),
        Subsignal("rd_n", Pins("C14")),
        Subsignal("oe_n", Pins("A15")),
        Subsignal("wakeup_n", Pins("B15")),
        IOStandard("LVCMOS25"),
    ),
]

The below module will convert the code into Verilog and generates bitstream for the given FPGA part number using Vivado toolchain.

class ProteusK7Platform(XilinxPlatform):
    default_clk_name = "clk100"
    default_clk_period = 10.000

    def __init__(self, programmer="xc3sprog"):
        self.programmer = programmer
        XilinxPlatform.__init__(self, "xc7k160tfbg676-2", _io, [], toolchain="vivado")

    def create_programmer(self):
        if self.programmer == "xc3sprog":
            return XC3SProg("jtaghs1")
        else:
            raise ValueError("{} programmer is not supported".format(self.programmer))


default_subtarget = FT601SourceTop
default_platform = ProteusK7Platform

if __name__ == '__main__':
    p = ProteusK7Platform()
    m = FT601SourceTop(p)
    # out = verilog.convert(m, ios=set()))# m.get_ios()))
    p.build(m, build_name="ft601_proteus_k7_160t", build_dir="build_proteus_k7_160t")

Interfacing the USB 3.1 Gen 1 with Host System using Proteus

Once Migen is installed, download the Migen code from here and save it as a Python file in a convenient location. Open the Command Prompt in the directory where this Python file is saved and run the Python file using the following command

python ft601.py

After running the code, in the directory where the Python file is saved, you will find a folder named “build_proteus_k7_160t”. In this folder, you will get a .bit, TCL script, Verilog file and XDC file.

Now, download the required FTD3XX Host files from here and extract them to any convenient directory in your PC. This  d3xx-host folder contains the main.cpp file, FTD3XX Library files and other files required to generate the Executable file that tests the project.

To generate the executable file, browse to the directory where the d3xx-host folder was extracted and open Command Prompt in that folder and run the following command:

make -f Makefile.mingw32 build

This will create the executable file required to test the project. Now, program Proteus Kintex 7 USB 3.1 Development Board with the bitstream (available in “build_proteus_k7_160t” folder), that was generated by running the Python file, using the Tenagra application (refer the Tenagra article to program the Proteus). After the board is programmed, browse to the directory in which the executable file is present and open Command Prompt and run the executable file. (Refer the image below)

Congratulations! You have successfully interfaced USB 3.1 Gen 1 with the host system using Proteus Kintex 7 USB 3.1 Development Board.

Was this helpful?

Leave A Comment
*
*