This chapter is only applicable to Basys2 board - The Papilio board only has a serial port. It also assumes that you are using the Windows OS - but I’m sure that only minor changes are needed for it all to work under Linux too.
Digilent FPGA boards have a port of the USB interface wired to the FPGA. I’ve used this to transfer data at up to 11 megabytes per second (but only on a Nexys2 - the interface Basys2 is much slower!). The supplied documentation is pretty terse, so here is a quick start guide.
The interface implements the long obsolete EPP protocol that was traditionally used to talk to parallel port scanners. It allows the connected device to address up to 256 8-bit registers that can be implemented within the FPGA.
These registers can either be read by the host PC one byte at a time, or a "Repeat" function can be called to read multiple bytes from the same register.
The "make or break" shortcoming of this interface is that there is no interrupt signal going back to the host which would allow the FPGA get its attention. Unlike when using RS-232 this forces the host software to poll the FPGA at regular intervals - which is not ideal for responsiveness or CPU usage.
-
http://www.digilentinc.com/data/software/adept/dpimref%20programmers%20manual.pdf documents the FPGA side of the interface
-
http://digilentinc.com/Data/Products/ADEPT/DPCUTIL%20Programmers%20%20Reference%20Manual.pdf documents the host side of the interface
-
http://www.digilentinc.com/Products/Detail.cfm?Prod=ADEPT2 for the latest Adept SDK
The following signals make up the interface:
Name |
Type |
Description |
DB(7 downto 0) |
INOUT |
Data bus |
WRITE |
IN |
Write enable (active low) - data will be written from the host during this cycle |
ASTB |
IN |
Address strobe (active low) - data bus will be captured into the address register |
DSTB |
IN |
Data strobe (active low) - the bus will be captured into the currently selected data register |
WAIT |
OUT |
Asserted when FPGA is ready to accept data |
INT |
OUT |
Interrupt request - not used |
RESET |
IN |
Reset - not used |
The steps in a read transaction are:
-
Host lowers ASTB or DSTB to commence read of either the address register or the selected data register
-
FPGA presents data on data bus
-
FPGA raises WAIT indicating that the data is valid
-
Host captures the data
-
Host raises ASTB or DSTB
-
FPGA removes the data from the data bus
-
FPGA lowers WAIT to finish transaction
The steps in a write transaction are:
-
Host presents data on the data bus
-
Host lowers write wnable to 0
-
Host lowers either ASTB or DSTB to commence write of either the address register or the selected data register
-
FPGA raises WAIT once data is captured
-
Host raises ASTB or DSTB, removes data from bus and raises write enable
-
FPGA lowers WAIT to finish transaction
The constraints required to implement the interface are:
NET "EppAstb" LOC = "F2"; # Bank = 3 NET "EppDstb" LOC = "F1"; # Bank = 3 NET "EppWR" LOC = "C2"; # Bank = 3 NET "EppWait" LOC = "D2"; # Bank = 3 NET "EppDB<0>" LOC = "N2"; # Bank = 2 NET "EppDB<1>" LOC = "M2"; # Bank = 2 NET "EppDB<2>" LOC = "M1"; # Bank = 3 NET "EppDB<3>" LOC = "L1"; # Bank = 3 NET "EppDB<4>" LOC = "L2"; # Bank = 3 NET "EppDB<5>" LOC = "H2"; # Bank = 3 NET "EppDB<6>" LOC = "H1"; # Bank = 3 NET "EppDB<7>" LOC = "H3"; # Bank = 3
This source allows you to set the LEDs and read the switches from the PC. It has a few VHDL features that you won’t have seen up to now:
-
The EppDB (EPP Data Bus) is INOUT - a tri-state bidirectional bus. When you assign "ZZZZZZZZ" (high impedance) to the signal it will then \'read' as the input from the outside world. This is only really useful on I/O pins - within the FPGA all tri-state logic is implemented using multiplexers.
-
It uses an enumerated type to hold the FSM 'state'. This is only really useful if you don’t want to use individual bits within the state value to drive logic (which is usually a good way to get glitch free outputs)
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity epp_interface is
port (Clk : in std_logic;
-- EPP interface
EppAstb : in std_logic;
EppDstb : in std_logic;
EppWR : in std_logic;
EppWait : out std_logic;
EppDB : inout std_logic_vector(7 downto 0);
-- Feedback
switches: in std_logic_vector(7 downto 0);
leds : out std_logic_vector(7 downto 0)
);
end epp_interface;
architecture Behavioral of epp_interface is
type epp_state is (idle, data_read, data_write, addr_read, addr_write);
signal state : epp_state := idle;
signal address : std_logic_vector(7 downto 0) := (others => '0');
signal port0data : std_logic_vector(7 downto 0) := (others => '0');
begin
process(clk)
begin
if rising_edge(clk) then
case state is
when data_read =>
EppWait <= '1';
case address is
when "00000000" =>
EppDB <= not port0data;
when "00000001" =>
EppDB <= switches;
when others =>
end case;
if EppDstb = '1' then
state <= idle;
end if;
when data_write =>
EppWait <= '1';
case address is
when "00000000" =>
port0data <= EppDB;
when "00000001" =>
leds <= EppDB;
when others =>
end case;
if EppDstb = '1' then
state <= idle;
end if;
when addr_read =>
EppWait <= '1';
EppDB <= address;
if EppAstb = '1' then
state <= idle;
end if;
when addr_write =>
EppWait <= '1';
address <= eppDB;
if EppAstb = '1' then
state <= idle;
end if;
when others =>
EppWait <= '0';
EppDB <= "ZZZZZZZZ";
if EppWr = '0' then
if EppAstb = '0' then
state <= addr_write;
elsif EppDstb = '0' then
state <= data_write;
end if;
else
if EppDstb = '0' then
state <= data_read;
elsif EppAstb = '0' then
state <= addr_read;
end if;
end if;
end case;
end if;
end process;
end Behavioral;
These are in the Adept SDK, which can be downloaded from http://www.digilentinc.com/Products/Detail.cfm?Prod=ADEPT2
The zip file includes all the files you need, including documentation, libraries and examples.
The following header files are needed in your C code:
-
gendefs.h
-
dpcdefs.h
-
dpcutil.h
You will also need to add the path to the libraries into your project’s linking settings.
Connecting isn’t that simple, but it’s not that hard either. Three functions are needed:
-
DpcInit()
-
DvmgGetDefaultDev()
-
DvmgGetDevName()
if (!DpcInit(&erc)) {
printf("Unable to initialise\n");
return 0;
}
id = DvmgGetDefaultDev(&erc);
if (id == -1) {
printf("No default device\n");
goto error;
}
if(!DvmgGetDevName(id, device, &erc)) {
printf("No device name\n");
goto error;
}
The first time you make use of the interface you may need to call one more
function only once to present a dialogue box allowing you to select which FPGA board will be your default device:
-
DvmgStartConfigureDevices()
Once used, the settings will be saved in the registry and will persist.
One function is used to connect to the device (vs connecting to the JTAG port):
-
DpcOpenData()
if (!DpcOpenData(&hif, device, &erc, NULL)) {
goto fail;
}
Reading a port is achieved with either of these functions:
-
DpcGetReg() - Read a single byte from a register
-
DpcGetRegRepeat() - Read multiple bytes from a register
Here’s an example function that opens the EPP port and reads a single register:
static int GetReg(unsigned char r) {
unsigned char b;
ERC erc;
HANDLE hif;
if (!DpcOpenData(&hif, device, &erc, NULL)) {
goto fail;
}
if (!DpcGetReg(hif, r, &b, &erc, NULL)) {
DpcCloseData(hif,&erc);
goto fail;
}
erc = DpcGetFirstError(hif);
DpcCloseData(hif, &erc);
if (erc == ercNoError)
return b;
fail:
return -1;
}
Writing to a port is achieved with either of these functions:
-
DpcPutReg() - Read a single byte from a register
-
DpcPutRegRepeat() - Read multiple bytes from a register
Here’s an example function that opens the EPP port and writes to a single register
static int PutReg(unsigned char r, unsigned char b) {
ERC erc;
HANDLE hif;
printf("Put %i %i\n",r,b);
if (!DpcOpenData(&hif, device, &erc, NULL)) {
goto fail;
}
if(!DpcPutReg(hif, r, b, &erc, NULL)) {
DpcCloseData(hif,&erc);
goto fail;
}
erc = DpcGetFirstError(hif);
DpcCloseData(hif, &erc);
if (erc == ercNoError)
return 0;
fail:
return -1;
}
One function is used to close the EPP port:
-
DpcCloseData()
DpcCloseData(hif, &erc);
if (erc == ercNoError)
return b;
It is always good to clean up after yourself. Use the following function to do so:
-
DpcTerm()
DpcTerm();
-
Download and configure your board with the "Adept I/O expansion reference design" project from http://www.digilentinc.com/Products/Detail.cfm?Prod=BASYS2
-
Check that the Adept I/O expansion tab responds to changes in the switches
-
Create a C program that opens the interface and reads a single byte from registers 5 and 6 and displays the value to the screen
-
Close off Adept and check that your C program also shows the state of the switches on the Basys2
-
Expand your C program to write to the value of the switches to register 1 - this is the LEDs
You now have the host side of bidirectional communication sorted!
-
Create a new FPGA project
-
Create a module that implements the EPP protocol - or use the one of Digilent’s reference designs if you want
-
Connect writes of register 1 to the LEDs
-
Connect reads of register 5 or 6 to the switches
-
Test that your design works just as well with your program as Digilent’s reference design