Wednesday, 25 January 2017

A high speed external interface

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.

The Digilent Parallel Interface

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.

The FPGA side of the interface

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

Read Transaction

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

Write 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

FSM diagram


Constraints for the BASYS2 board

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

VHDL for the FPGA interface

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;

The PC side of the interface

Header files and libraries

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 to a device

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.

Connecting to the EPP port of that device

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

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 register

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;
}

Closing the EPP port

One function is used to close the EPP port:
• DpcCloseData()

DpcCloseData(hif, &erc);
if (erc == ercNoError)
return b;

Closing the interface

It is always good to clean up after yourself. Use the following function to do so:
• DpcTerm()
DpcTerm();

Project - Using the PC end of the interface

• 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!

Project - Implementing the FPGA end of the interface

• 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

No comments:

Post a Comment