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
- Proteus Kintex 7 USB 3.1 Development Board
- Xilinx Platform Cable USB II.
- USB A to USB Type C cable
Software
- FT_Prog tool for configuring on-board FT2232H USB Serial converter (download and install from FTDI website).
- Python 3.5 or higher
- Xilinx Vivado Design Suite 2018.2 or higher
- 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.