Communicating with an FPGA via a FT2232 Chip and the VCP Driver #
Communicating with an FPGA through an FTDI chip is a common requirement in many projects. FTDI provides two different ways for communicating with the FTDI chip: the Virtual Com Port (VCP) and the D2XX library. In the previous tutorial we explored how to use the D2XX library. Now we will use the VCP Drivers.
Prerequisites #
The following tutorial is design for the Digilent CMOD-A7 Development Board that features a Xilinx xc7a35tlcpg236 FPGA. If you are using a different board, adjust the constraints and clock frequency accordingly. By default, the Digilent boards come configured for use with the VCP drivers so there is no need to re-program the EEPROM unless it had previously been swapped to D2XX (see the previous tutorial).
Create a Vivado project as described previously for your specific development board. It is recommended to read the D2XX tutorial first for background.
Install VCP Drivers #
If you are running Windows, install the VCP drivers from the FTDI website: [FTDI VCP Drivers](https://ftdichip.com/drivers/vcp-drivers/. If you are on Linux newer than sometime in 2009, it should already have the drivers needed.
FPGA Design #
For this tutorial, we will make a basic echo state machine that receives one ASCII character and echo’s it back.
Verilog #
Create a top.v
file in your Vivado project with the following contents.
`timescale 1ns / 1ps
module top_cmod #(
parameter integer CLK_HZ = 12_000_000,
parameter integer BAUD = 9600
) (
input wire clk,
input wire uart_txd_in,
output wire uart_rxd_out
);
// UART TX and RX wire and regs
wire [7:0] rx_data;
wire rx_done;
reg [7:0] tx_data = 8'b0;
reg tx_start = 1'b0;
wire tx_busy;
reg reset = 1'b0;
reg bytes_sent = 1'b0;
// State
reg [1:0] state = 2'b00;
uart_rx #(
.CLK_HZ( CLK_HZ ), // in Hertz
.BAUD( BAUD ) // max. BAUD is CLK_HZ / 2
) uart_rx_inst0 (
.clk( clk ),
.nrst( ~reset ),
.rx_data( rx_data ),
.rx_done( rx_done ),
.rxd( uart_txd_in )
);
uart_tx #(
.CLK_HZ( CLK_HZ ), // in Hertz
.BAUD( BAUD ) // max. BAUD is CLK_HZ / 2
) uart_tx_inst0 (
.clk( clk ),
.nrst( ~reset ),
//.tx_do_sample( ),
.tx_data( tx_data ),
.tx_start( tx_start ),
.tx_busy( tx_busy ),
.txd( uart_rxd_out )
);
// Codes
localparam integer C0 = 8'h00; // reset
localparam integer C2 = 8'h02; // ready receive
// State
localparam integer S0 = 2'b00; // reset
localparam integer S1 = 2'b01; // ready
localparam integer S2 = 2'b10; // reading
localparam integer S3 = 2'b11; // writing
always @(posedge clk)
begin
case(state)
S0: // Reset
begin
reset <= 1;
// rx_data <= 0;
tx_data <= 0;
tx_start <= 0;
bytes_sent <= 0;
state <= S1;
end
S1: // ready
begin
reset <= 0;
if (rx_done)
begin
if (rx_data == C0)
state <= S0; // reset
else if (rx_data == C2)
state <= S2; // ready to receive
end
end
S2: // reading
begin
if (rx_done)
begin
tx_data <= rx_data;
state <= S3;
end
end
S3: // writing
begin
if (~tx_busy & ~bytes_sent & ~tx_start)
begin
tx_start <= 1;
bytes_sent <= 1;
end
else if (~tx_busy & ~tx_start)
begin
state <= S0;
bytes_sent <= 0;
end
else if (tx_busy & tx_start)
begin
tx_start <= 0;
end
end
// Default
default: state <= S0;
endcase
end
endmodule
Note: if you are using a different development board, the CLK_HZ
parameter will likely need to be updated to the frequency of the oscillator on the development board. For example, for an Arty board, the input clock oscillates at 100MHz
To keep this tutorial simple, the UART modules are from the open source repo: https://github.com/pConst/basic_verilog. Download the following files and add them to the project:
uart_tx.sv
uart_rx.sv
delay.sv
edge_detect.sv
Constraints #
Add the following constraints file to the project.
## Clock signal
set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { clk }]; #IO_L12P_T1_MRCC_35 Sch=gclk[100]
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports { clk }];
## USB-UART Interface
set_property -dict { PACKAGE_PIN D10 IOSTANDARD LVCMOS33 } [get_ports { uart_rxd_out }]; #IO_L19N_T3_VREF_16 Sch=uart_rxd_out
set_property -dict { PACKAGE_PIN A9 IOSTANDARD LVCMOS33 } [get_ports { uart_txd_in }]; #IO_L14N_T2_SRCC_16 Sch=uart_txd_in
These constraints simply create a clock and add the appropriate connections to the FTDI chip.
Design Operation #
The finite state machine has a simple state diagram. The reset state resets all registers and then moves to the ready state. In the ready state, if a C0=0x00
code is received, then a reset is performed. If the code C2=0x02
is received, then the state waits to receive the data char. Once the data char is received, then state machine goes to the write state and then resets.
stateDiagram-v2 S0: S0 - Reset S1: S1 - Ready S2: S2 - Read Char S3: S3 - Write Char S0 --> S1 S1 --> S0 : C0 S1 --> S2 : C2 S2 --> S3 : rx_done S3 --> S0
That should be everything required on the FPGA side. Now, simply synthesize, run implementation and boot it onto the bitstream FPGA.
Matlab Control #
Arbitrarily we will use Matlab to talk to the com port. Just about every language has com port support so one doesn’t need to use Matlab.
First, we must find the correct Com port. Running the following command in Matlab will return a list of available serial ports:
>> serialportlist
ans =
"/dev/ttyUSB1"
In this case on Linux, the com port is named /dev/ttyUSB1
. If there are two options like USB0
and USB1
, the correct option will be the highest number. The lower one is the A
interface in the FT2232 used for JTAG programming the FPGA.
With the name of the serial port, consider the following script
clear
% Open the com port
sp = serialport("/dev/ttyUSB1", 9600);
% Perform Reset
write(sp, 0, "uint8")
% Ready to send
write(sp, 2, "uint8")
% Send char 'a'
data = 97;
write(sp, data, "uint8")
% Read the response
resp = read(sp, 1, "uint8");
fprintf("sent = %s\n", data)
fprintf("response = %s\n", resp)
% Disconnect from the serial port
clear sp
First, the com port is opened. Then we reset, the state machine, prepare it to read a character and send the ASCII character ‘a’ which is 97 in decimal. Then we read the character back and print the results. If it all works the script should return
sent = a
response = a
Conclusion #
If speed and control are not required by the application, then using VCP drivers is a great quick and dirty method for communicating with an FPGA through an FTDI chip. The upper bound on baud rate for the VCP drivers are a little loose, but from experience 4 Mega Baud is certainly the highest one should go and likely, 1 Mega Buad is pushing it. Further, there is less control over the USB timing. So if higher throughput or more control is required, then the D2XX lib is much better.