Digilent EPP Performance
From Hamsterworks Wiki!
This FPGA project was completed November 2012.
Anthony Hernandez wrote::
| I have a question concerning the Spartan-3E Xilinx transfer rate that you said was possible (in your Introducing the Spartan 3E FPGA and VHDL).
The ebook says that 11MB/s should be an attainable transfer rate from the xilinx board, but it is taking me 30 seconds to read 5MB from the board when I try it. Do you have any idea as to why I might be experiencing such low transfer speeds? I used the DeppDemo in the Linux adept SDK. |
This was odd, so I performed some testing. Oh gosh! How wrong could I be!
My original board was the Nexys2, and that is where I originally played with the EPP code (reading and writing the flash chip. I just verified that it worked the same on the Basys2. I didn't known that there is a 30x performance difference - the Basys2 doesn't have any Flash to program or read back, so I never tried it!
I ran the same code (except changing the board name) to the same project (except changing the UCF file). It transfers 10,000 byte blocks for 10 seconds.
Contents |
Performance on Basys2 vs Nexys2
The application below send bytes out to EPP port 10, the LEDs, on both boards.
Basys 2
This is the Basys2, 170,000 bytes per second:
C:\Users\Hamster\Documents\Visual Studio 2010\Projects\throughput\Debug>throughput.exe Opening Enabling Outputting Stream to register completed 1700000 bytes!
Nexys2
This is the Nexys2, 4,984,000 bytes per second
C:\Users\Hamster\Documents\Visual Studio 2010\Projects\throughput\Debug>throughput.exe Opening Enabling Outputting Stream to register completed 49840000 bytes!
Improvements
When I added a DCM to run the design at 160Mhz, slightly reorganized the code and then increased the buffer to 64kB I found that I ran out of CPU on my low-end AMD laptop. Moving to an old Core i3 M520 (2.4GHz) I managed to transfer 1024MB in 100 seconds - just over 10MB/sec, or fifty times what the Basys2 is capable of.
Performance was much the same with either GetReg() or PutReg().
App Source Code
Just change the board name passed to DmgrOpen() for the different boards
#include <StdAfx.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "dpcdecl.h"
#include "depp.h"
#include "dmgr.h"
static HIF hif = hifInvalid;
static void DoPutRegRepeat() {
BYTE data[10000];
int i = 0;
int start = time(NULL);
memset(data,0xff,sizeof(data));
while(time(NULL) - start < 10)
{
if(!DeppPutRegRepeat(hif, 10, data, sizeof(data), fFalse)){
printf("DeppPutRegRepeat failed.\n");
exit(3);
}
i++;
}
printf("Stream to register completed %i bytes!\n",(int)(sizeof(data)*i));
}
int main(int cszArg, char * rgszArg[]) {
fprintf(stderr,"Opening\n");
if(!DmgrOpen(&hif, "Nexys2")) { // Change to Basys2 for the other board.
printf("DmgrOpen failed (check the device name you provided)\n");
return 0;
}
fprintf(stderr,"Enabling\n");
if(!DeppEnable(hif)) {
printf("DeppEnable failed\n");
return 0;
}
fprintf(stderr,"Outputting\n");
DoPutRegRepeat();
if( hif != hifInvalid ) {
DeppDisable(hif);
DmgrClose(hif);
}
return 0;
}
The design for the FPGA
This is nearly all from the Adept SDK example - I've put it here for completeness.
----------------------------------------------------------------------------
-- DPIMREF.VHD -- Digilent Parallel Interface Module Reference Design
----------------------------------------------------------------------------
-- Author: Gene Apperson
-- Copyright 2004 Digilent, Inc.
----------------------------------------------------------------------------
-- IMPORTANT NOTE ABOUT BUILDING THIS LOGIC IN ISE
--
-- Before building the Dpimref logic in ISE:
-- 1. In Project Navigator, right-click on "Synthesize-XST"
-- (in the Process View Tab) and select "Properties"
-- 2. Click the "HDL Options" tab
-- 3. Set the "FSM Encoding Algorithm" to "None"
----------------------------------------------------------------------------
--
----------------------------------------------------------------------------
-- This module contains an example implementation of Digilent Parallel
-- Interface Module logic. This interface is used in conjunction with the
-- DPCUTIL DLL and a Digilent Communications Module (USB, EtherNet, Serial)
-- to exchange data with an application running on a host PC and the logic
-- implemented in a gate array.
--
-- See the Digilent document, Digilent Parallel Interface Model Reference
-- Manual (doc # 560-000) for a description of the interface.
--
-- This design uses a state machine implementation to respond to transfer
-- cycles. It implements an address register, 8 internal data registers
-- that merely hold a value written, and interface registers to communicate
-- with a Digilent DIO4 board. There is an LED output register whose value
-- drives the 8 discrete leds on the DIO4. There are two input registers.
-- One reads the switches on the DIO4 and the other reads the buttons.
--
-- Interface signals used in top level entity port:
-- mclk - master clock, generally 50Mhz osc on system board
-- pdb - port data bus
-- astb - address strobe
-- dstb - data strobe
-- pwr - data direction (described in reference manual as WRITE)
-- pwait - transfer synchronization (described in reference manual
-- as WAIT)
-- rgLed - LED outputs to the DIO4
-- rgSwt - switch inputs from the DIO4
-- ldb - led gate signal for the DIO4
-- rgBtn - button inputs from the DIO4
-- btn - button on system board (D2SB or D2FT)
-- led - led on the system board
--
----------------------------------------------------------------------------
-- Revision History:
-- 06/09/2004(GeneA): created
-- 08/10/2004(GeneA): initial public release
-- 04/25/2006(JoshP): comment addition
----------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity dpimref is
Port (
mclk : in std_logic;
pdb : inout std_logic_vector(7 downto 0);
astb : in std_logic;
dstb : in std_logic;
pwr : in std_logic;
pwait : out std_logic;
rgLed : out std_logic_vector(7 downto 0);
rgSwt : in std_logic_vector(7 downto 0);
rgBtn : in std_logic_vector(4 downto 0)
);
end dpimref;
architecture Behavioral of dpimref is
------------------------------------------------------------------------
-- Component Declarations
------------------------------------------------------------------------
------------------------------------------------------------------------
-- Local Type Declarations
------------------------------------------------------------------------
------------------------------------------------------------------------
-- Constant Declarations
------------------------------------------------------------------------
-- The following constants define state codes for the EPP port interface
-- state machine. The high order bits of the state number give a unique
-- state identifier. The low order bits are the state machine outputs for
-- that state. This type of state machine implementation uses no
-- combination logic to generate outputs which should produce glitch
-- free outputs.
constant stEppReady : std_logic_vector(7 downto 0) := "0000" & "0000";
constant stEppAwrA : std_logic_vector(7 downto 0) := "0001" & "0100";
constant stEppAwrB : std_logic_vector(7 downto 0) := "0010" & "0001";
constant stEppArdA : std_logic_vector(7 downto 0) := "0011" & "0010";
constant stEppArdB : std_logic_vector(7 downto 0) := "0100" & "0011";
constant stEppDwrA : std_logic_vector(7 downto 0) := "0101" & "1000";
constant stEppDwrB : std_logic_vector(7 downto 0) := "0110" & "0001";
constant stEppDrdA : std_logic_vector(7 downto 0) := "0111" & "0010";
constant stEppDrdB : std_logic_vector(7 downto 0) := "1000" & "0011";
------------------------------------------------------------------------
-- Signal Declarations
------------------------------------------------------------------------
-- State machine current state register
signal stEppCur : std_logic_vector(7 downto 0) := stEppReady;
signal stEppNext : std_logic_vector(7 downto 0);
signal clkMain : std_logic;
-- Internal control signales
signal ctlEppWait : std_logic;
signal ctlEppAstb : std_logic;
signal ctlEppDstb : std_logic;
signal ctlEppDir : std_logic;
signal ctlEppWr : std_logic;
signal ctlEppAwr : std_logic;
signal ctlEppDwr : std_logic;
signal busEppOut : std_logic_vector(7 downto 0);
signal busEppIn : std_logic_vector(7 downto 0);
signal busEppData : std_logic_vector(7 downto 0);
-- Registers
signal regEppAdr : std_logic_vector(3 downto 0);
signal regData0 : std_logic_vector(7 downto 0);
signal regData1 : std_logic_vector(7 downto 0);
signal regData2 : std_logic_vector(7 downto 0);
signal regData3 : std_logic_vector(7 downto 0);
signal regData4 : std_logic_vector(7 downto 0);
signal regData5 : std_logic_vector(7 downto 0);
signal regData6 : std_logic_vector(7 downto 0);
signal regData7 : std_logic_vector(7 downto 0);
signal regLed : std_logic_vector(7 downto 0);
signal cntr : std_logic_vector(23 downto 0);
------------------------------------------------------------------------
-- Module Implementation
------------------------------------------------------------------------
begin
------------------------------------------------------------------------
-- Map basic status and control signals
------------------------------------------------------------------------
clkMain <= mclk;
ctlEppAstb <= astb;
ctlEppDstb <= dstb;
ctlEppWr <= pwr;
pwait <= ctlEppWait; -- drive WAIT from state machine output
-- Data bus direction control. The internal input data bus always
-- gets the port data bus. The port data bus drives the internal
-- output data bus onto the pins when the interface says we are doing
-- a read cycle and we are in one of the read cycles states in the
-- state machine.
busEppIn <= pdb;
pdb <= busEppOut when ctlEppWr = '1' and ctlEppDir = '1' else "ZZZZZZZZ";
-- Select either address or data onto the internal output data bus.
busEppOut <= "0000" & regEppAdr when ctlEppAstb = '0' else busEppData;
rgLed <= regLed;
-- Decode the address register and select the appropriate data register
busEppData <= regData0 when regEppAdr = "0000" else
regData1 when regEppAdr = "0001" else
regData2 when regEppAdr = "0010" else
regData3 when regEppAdr = "0011" else
regData4 when regEppAdr = "0100" else
regData5 when regEppAdr = "0101" else
regData6 when regEppAdr = "0110" else
regData7 when regEppAdr = "0111" else
rgSwt when regEppAdr = "1000" else
"000" & rgBtn when regEppAdr = "1001" else
"00000000";
------------------------------------------------------------------------
-- EPP Interface Control State Machine
------------------------------------------------------------------------
-- Map control signals from the current state
ctlEppWait <= stEppCur(0);
ctlEppDir <= stEppCur(1);
ctlEppAwr <= stEppCur(2);
ctlEppDwr <= stEppCur(3);
-- This process moves the state machine to the next state
-- on each clock cycle
process (clkMain)
begin
if clkMain = '1' and clkMain'Event then
stEppCur <= stEppNext;
end if;
end process;
-- This process determines the next state machine state based
-- on the current state and the state machine inputs.
process (stEppCur, stEppNext, ctlEppAstb, ctlEppDstb, ctlEppWr)
begin
case stEppCur is
-- Idle state waiting for the beginning of an EPP cycle
when stEppReady =>
if ctlEppAstb = '0' then
-- Address read or write cycle
if ctlEppWr = '0' then
stEppNext <= stEppAwrA;
else
stEppNext <= stEppArdA;
end if;
elsif ctlEppDstb = '0' then
-- Data read or write cycle
if ctlEppWr = '0' then
stEppNext <= stEppDwrA;
else
stEppNext <= stEppDrdA;
end if;
else
-- Remain in ready state
stEppNext <= stEppReady;
end if;
-- Write address register
when stEppAwrA =>
stEppNext <= stEppAwrB;
when stEppAwrB =>
if ctlEppAstb = '0' then
stEppNext <= stEppAwrB;
else
stEppNext <= stEppReady;
end if;
-- Read address register
when stEppArdA =>
stEppNext <= stEppArdB;
when stEppArdB =>
if ctlEppAstb = '0' then
stEppNext <= stEppArdB;
else
stEppNext <= stEppReady;
end if;
-- Write data register
when stEppDwrA =>
stEppNext <= stEppDwrB;
when stEppDwrB =>
if ctlEppDstb = '0' then
stEppNext <= stEppDwrB;
else
stEppNext <= stEppReady;
end if;
-- Read data register
when stEppDrdA =>
stEppNext <= stEppDrdB;
when stEppDrdB =>
if ctlEppDstb = '0' then
stEppNext <= stEppDrdB;
else
stEppNext <= stEppReady;
end if;
-- Some unknown state
when others =>
stEppNext <= stEppReady;
end case;
end process;
------------------------------------------------------------------------
-- EPP Address register
------------------------------------------------------------------------
process (clkMain, ctlEppAwr)
begin
if clkMain = '1' and clkMain'Event then
if ctlEppAwr = '1' then
regEppAdr <= busEppIn(3 downto 0);
end if;
end if;
end process;
------------------------------------------------------------------------
-- EPP Data registers
------------------------------------------------------------------------
-- The following processes implement the interface registers. These
-- registers just hold the value written so that it can be read back.
-- In a real design, the contents of these registers would drive additional
-- logic.
-- The ctlEppDwr signal is an output from the state machine that says
-- we are in a 'write data register' state. This is combined with the
-- address in the address register to determine which register to write.
process (clkMain, regEppAdr, ctlEppDwr, busEppIn)
begin
if clkMain = '1' and clkMain'Event then
if ctlEppDwr = '1' and regEppAdr = "0000" then
regData0 <= busEppIn;
end if;
end if;
end process;
process (clkMain, regEppAdr, ctlEppDwr, busEppIn)
begin
if clkMain = '1' and clkMain'Event then
if ctlEppDwr = '1' and regEppAdr = "0001" then
regData1 <= busEppIn;
end if;
end if;
end process;
process (clkMain, regEppAdr, ctlEppDwr, busEppIn)
begin
if clkMain = '1' and clkMain'Event then
if ctlEppDwr = '1' and regEppAdr = "0010" then
regData2 <= busEppIn;
end if;
end if;
end process;
process (clkMain, regEppAdr, ctlEppDwr, busEppIn)
begin
if clkMain = '1' and clkMain'Event then
if ctlEppDwr = '1' and regEppAdr = "0011" then
regData3 <= busEppIn;
end if;
end if;
end process;
process (clkMain, regEppAdr, ctlEppDwr, busEppIn)
begin
if clkMain = '1' and clkMain'Event then
if ctlEppDwr = '1' and regEppAdr = "0100" then
regData4 <= busEppIn;
end if;
end if;
end process;
process (clkMain, regEppAdr, ctlEppDwr, busEppIn)
begin
if clkMain = '1' and clkMain'Event then
if ctlEppDwr = '1' and regEppAdr = "0101" then
regData5 <= busEppIn;
end if;
end if;
end process;
process (clkMain, regEppAdr, ctlEppDwr, busEppIn)
begin
if clkMain = '1' and clkMain'Event then
if ctlEppDwr = '1' and regEppAdr = "0110" then
regData6 <= busEppIn;
end if;
end if;
end process;
process (clkMain, regEppAdr, ctlEppDwr, busEppIn)
begin
if clkMain = '1' and clkMain'Event then
if ctlEppDwr = '1' and regEppAdr = "0111" then
regData7 <= busEppIn;
end if;
end if;
end process;
process (clkMain, regEppAdr, ctlEppDwr, busEppIn)
begin
if clkMain = '1' and clkMain'Event then
if ctlEppDwr = '1' and regEppAdr = "1010" then
regLed <= busEppIn;
end if;
end if;
end process;
process (clkMain)
begin
if clkMain = '1' and clkMain'Event then
cntr <= cntr + 1;
end if;
end process;
end Behavioral;
UCF file for NEXYS2
NET "mclk" LOC = "B8"; NET "astb" LOC = "V14"; NET "dstb" LOC = "U14"; NET "pwait" LOC = "N9"; NET "pwr" LOC = "V16"; NET "pdb<0>" LOC = "R14"; NET "pdb<1>" LOC = "R13"; NET "pdb<2>" LOC = "P13"; NET "pdb<3>" LOC = "T12"; NET "pdb<4>" LOC = "N11"; NET "pdb<5>" LOC = "R11"; NET "pdb<6>" LOC = "P10"; NET "pdb<7>" LOC = "R10"; NET "rgLed<0>" LOC = "J14"; NET "rgLed<1>" LOC = "J15"; NET "rgLed<2>" LOC = "K15"; NET "rgLed<3>" LOC = "K14"; NET "rgLed<4>" LOC = "E17"; NET "rgLed<5>" LOC = "P15"; NET "rgLed<6>" LOC = "F4"; NET "rgLed<7>" LOC = "R4"; NET "rgSwt<0>" LOC = "G18"; NET "rgSwt<1>" LOC = "H18"; NET "rgSwt<2>" LOC = "K18"; NET "rgSwt<3>" LOC = "K17"; NET "rgSwt<4>" LOC = "L14"; NET "rgSwt<5>" LOC = "L13"; NET "rgSwt<6>" LOC = "N17"; NET "rgSwt<7>" LOC = "R17"; NET "rgbtn<0>" LOC = "B18"; NET "rgbtn<1>" LOC = "D18"; NET "rgbtn<2>" LOC = "E18"; NET "rgbtn<3>" LOC = "H13";
UCF file for BASYS2
NET "mclk" LOC = "B8"; NET "mclk" CLOCK_DEDICATED_ROUTE = FALSE; NET "astb" LOC = "F2"; NET "dstb" LOC = "F1"; NET "pwr" LOC = "C2"; NET "pwait" LOC = "D2"; NET "pdb(0)" LOC = "N2"; NET "pdb(1)" LOC = "M2"; NET "pdb(2)" LOC = "M1"; NET "pdb(3)" LOC = "L1"; NET "pdb(4)" LOC = "L2"; NET "pdb(5)" LOC = "H2"; NET "pdb(6)" LOC = "H1"; NET "pdb(7)" LOC = "H3"; NET "rgLed(7)" LOC = "G1" ; NET "rgLed(6)" LOC = "P4" ; NET "rgLed(5)" LOC = "N4" ; NET "rgLed(4)" LOC = "N5" ; NET "rgLed(3)" LOC = "P6" ; NET "rgLed(2)" LOC = "P7" ; NET "rgLed(1)" LOC = "M11" ; NET "rgLed(0)" LOC = "M5" ; NET "rgSwt(7)" LOC = "N3"; NET "rgSwt(6)" LOC = "E2"; NET "rgSwt(5)" LOC = "F3"; NET "rgSwt(4)" LOC = "G3"; NET "rgSwt(3)" LOC = "B4"; NET "rgSwt(2)" LOC = "K3"; NET "rgSwt(1)" LOC = "L3"; NET "rgSwt(0)" LOC = "P11"; NET "rgBtn(3)" LOC = "A7"; NET "rgBtn(2)" LOC = "M4"; NET "rgBtn(1)" LOC = "C11"; NET "rgBtn(0)" LOC = "G12"; # Bank = 0, Signal name = BTN0