Wednesday, September 12, 2012

Communicating with the Basys2 using the Adept SDK

Writing code for development boards is fun: LEDs flash, respond to button presses, etc. Eventually though, we would like to be able to communicate with our boards using a PC. Many boards include a RS-232 (serial) port for just this purpose. RS-232 has a few drawbacks though, most notably that if your computer was purchased this millennium, you will probably have to use a USB-serial converter. These are cheap and prevalent, but it is just another thing to clutter up your workspace. Also, transferring large amounts of non-ASCII data (i.e. raw hex) can be challenging and time consuming. Since legacy ports are going the way of the Dodo bird, many developers are omitting the serial port from their development boards, and instead including a USB controller. The Basys2 from Digilent is one such board. This leaves us in a lurch, unless we can find a way to interact with the device through its USB controller. And that's were the Adept SDK comes in.

Instead of having to look up the specifics of the USB chip on the board, the Adept SDK provides APIs to interact with the board. In this post, I'll cover just 2 of the libraries: the device access management library (DMGR) and the asynchronous parallel port library (DEPP). Note that DEPP is so named for the similarity to the Enhanced Parallel Port (EPP) protocol used by parallel ports.

The Adept SDK provides code samples for each of the libraries. For the DMGR library, there are 2 examples: EnumDemo and GetInfoDemo. To get started, download the Adept SDK and Adept Runtime from Digilent. Install the runtime, and decompress the SDK somewhere you can find it. The SDK contains:
  • Documentation: PDF files describing how to use the libraries in the doc folder
  • Header Files: The include folder contains header files
  • Static Libraries: The APIs are provided as precompiled libraries
  • Code Samples: Example code for each library is included in the samples folders

After downloading the SDK for Windows, I set about to compiling some of the samples. There are introductions on how to do this in the samples directory, but they are specific to Visual Studio. In my case, I am using MinGW's toolchain. The linkers in the two toolchains expect library files to be named differently. Visual Studio expects a library file named <library name>.lib, and this is how the libraries are provided by Digilent. MinGW's linker, ld, only accepts library files in the format lib<library name>.a. Similarly, ld running under Linux expects a file lib<library name>.so. So, in order to use the Windows libraries provided by Digilent, I had to rename all of the files. dmgr.lib had to be renamed libdmgr.a, for example. The Windows version of the SDK contains the libraries in the lib or lib64 directories. The Linux version installs the libraries with the runtime.

In order to get g++ to compile the examples, I had to specify the include directory using -I, specify the library directory using -L, and specify which libraries to use AFTER the filename. For example, a project that uses only the DMGR library would have -ldmgr after the file name.

I compiled and executed the DMGR examples EnumDemo and GetInfoDemo. These confirmed that there was in fact a Basys2 attached to my system, and that it supported the DEPP library. I then instantiated a DEPP interface on the FPGA that included 2 operand registers and a result register. The operand registers can be written by the PC, and the result register can be read. Actually creating those structures on the FPGA will be covered in another post, but project files can be found here.

I wanted to write some code to write values to the operand registers, then read the result out. Here's what it looks like (full file here):
 #if defined(WIN32)
/* Include Windows specific headers here.*/
#include <windows.h>
#endif
This makes the necessary types available on a Windows.
#include <stdio.h>
#include "dpcdecl.h"
#include "dmgr.h"
#include "depp.h"
All of the header files needed for this project.
#define OP1ADDR         0x00
#define OP2ADDR         0x01
#define RESADDR         0x00
I chose to use text substitutions to define the register locations, to make my code easier to read. And then we start with main():
int main()
{
        HIF deviceHandle;
        int status = fTrue;
        char deviceName[32] = "Basys2";
        unsigned char result;
The type HIF is defined in dpcdecl.h, and is a structure that holds a handle to a device. This handle is used to interact with the Basys2. The status variable will be used for error detection/error handling. The device name is the UserName of the device. I found the UserName using EnumDemo. result will hold the result of the arithmetic that we read back from the device.
//Open a handle to the device
status = DmgrOpen(&deviceHandle,deviceName);
if (status)
printf("Successfully opened a handle to %s\n", deviceName);
else
{
status = DmgrGetLastError();
printf("Error code: %d\n", status);
}
 The first step in interacting with the device is to open a handle to it. Note that the API functions return FALSE if there is an error, and TRUE if they are successful.
//Enable the default port (Port 0) on the device
status = DeppEnable(deviceHandle);
if (status)
printf("Successfully enabled Port 0\n");
else
{
status = DmgrGetLastError();
printf("Error code: %d\n", status);
}
The EPP port must be enabled before reading or writing.
//Do some math
DeppPutReg(deviceHandle, OP1ADDR, 0x00, fFalse);
DeppPutReg(deviceHandle, OP2ADDR, 0x00, fFalse);
DeppGetReg(deviceHandle, RESADDR, &result, fFalse);
printf("0x00 + 0x00 = 0x%02X\n", result);
//...
DeppPutReg(deviceHandle, OP1ADDR, 0xFF, fFalse);
DeppPutReg(deviceHandle, OP2ADDR, 0x01, fFalse);
DeppGetReg(deviceHandle, RESADDR, &result, fFalse);
printf("0xFF + 0x01 = 0x%02X\n", result);
Here's where I actually transfer some data to and from the device. I write to the operand registers using DeppPutReg(), and then read the result register using DeppGetReg(). I ran through several scenarios to make sure that the adder was working properly.
//Disable the active port on the device
status = DeppDisable(deviceHandle);
if (status)
printf("Successfully disabled DEPP port\n");
else
{
status = DmgrGetLastError();
printf("Error code: %d\n", status);
}

//Close our handle to the devicestatus = DmgrClose(deviceHandle);
if (status)
printf("Successfully closed device handle\n");
else
{
status = DmgrGetLastError();
printf("Error code: %d\n", status);
} 
This is all cleanup to make sure that I gracefully release the device.

When it was all said and done, the output looked like this:
Successfully opened a handle to Basys2
Successfully enabled Port 0
0x00 + 0x00 = 0x00
0x01 + 0x00 = 0x01
0x01 + 0x01 = 0x02
0xA5 + 0x5A = 0xFF
0xFF + 0x01 = 0x00
Successfully disabled DEPP port
Successfully closed device handle

And so, I successfully moved raw data to the device, and read a result. While an unsigned 8-bit adder is trivial, this gives me all of the building blocks to do something more interesting, like cryptographic work.

16 comments:

  1. Hello, Camdon!

    You did a great job here!I want to ask if it's possible to extend a litle bit the discussion about the gcc compiler. I have a Windows XP x86 machine and I want to compile the Adept sources with g++!I renamed all the libraries as you suggested but I have a lot of errors telling me that some headers couldn't be found. Can you share the steps to follow in order to compile Adept sources with g++?Hope I'm not disturbing you but I see that you worked with this stuff and maybe you can give some hint!Thank you!

    ReplyDelete
    Replies
    1. Angela, what sorts of errors are you seeing? Could you be a bit more specific, maybe include a brief dump? It sounds like you're likely missing some path info for the -L and/or -I options of gcc. Perhaps you need to find the directory which has the .h files which are "missing" and include the -I "c:\whatever_the_path_is" in the command line. Similarly the -L option should have the path to the lib directories. For the -L option it may be necessary that it come *after* any .c files are specified for compiling.

      If you don't mind I'm adding your on Google+ as well in case you'd like to do a "hangout" for any further assistance in solving your issues - please let us know if you figure it out what the solution was! [Edits: because I'm not feeling well and I thought my original reply didn't make much sense.]

      Delete
    2. Angela,

      Can you post the specific text of some of the errors (starting with the first one)?

      I agree with Ken that it is probably a path issue. Are you using Cygwin, MinGW, or something else?

      Delete
  2. Thank you both for responding...Ken was right, it was a mistake in the command line!
    I figure out what I was doing wrong, so practically, it was the way the included directory and library directory were specified.I was calling them before the .cpp filename(a generic example of command line, which works is: g++ -o test test.cpp -I /../../../include -L /../../../lib -ldepp -ldmgr).

    ReplyDelete
  3. Hi!

    Thank you for this tutorial! I am curious about how you handle all this on the FPGA side. Care to show some VHDL? Thank you.

    ReplyDelete
    Replies
    1. Part 1:
      http://sadgeeksinsnow.blogspot.com/2012/09/implementing-depp-interface-on-basys2.html

      Part 2:
      http://sadgeeksinsnow.blogspot.com/2012/09/implementing-depp-interface-on-basys2_12.html

      Cheers

      Delete
  4. Good day, I was trying your design on my Basys 2 and, so far, it doesn't seem to work for me.

    I created an Adder8Bit module and simulated the behavior of the whole design, checking it works. However, I cannot modify the registers using the C++ program you provided.

    ReplyDelete
    Replies
    1. Jack, as you may be able to tell we like to help as much as possible. I appreciate you going through the process of putting this together (I know it is no simple task!) And with that said, I hope we can get you up and working ASAP.

      By chance do you have any error messages that may be of use? I believe we have some full source files around here somewhere... lemme dig them up so you can compare/contrast what Camdon did. Additionally, I'll ensure he is informed he's kinda my FGPA whiz kid ^_^.

      So yeah, maybe a copy of your terminal output when running the C program?
      Thanks!

      Delete
    2. Following up, here are all of the files we used. Additionally there is a zip - it's just the other files (but just 1 download).

      https://drive.google.com/folderview?id=0ByjYpdaRCAj-SUlVYjJ5TG13c2c&usp=sharing

      For your compare/contrast, if you wanted to see what's different.

      Delete
    3. Hi Ken,

      I appreciate your quick response and I do apologize if my first post sounded brash and vague.

      I assembled your files and added a makeshift Adder8Bit module to add the values of both operand registers and send the result to the third register. The problem seems to be that no matter what value I send to the registers, they seem to have a will of its own.

      I have tested this in three different boxes (all 32 bit), with Ubuntu 12.04, Fedora 18 and Windows 7. Only Win7 gives me the correct output, while the other two output something similar to this:

      Successfully opened a handle to Basys2
      Successfully enabled port 0
      0xAF + 0x04 = 0x08
      0xAF + 0x04 = 0x08
      0xAF + 0x04 = 0x08
      0xAF + 0x04 = 0x08
      0xAF + 0x04 = 0x08
      Successfully disabled DEPP port
      Successfully closed device handle

      I am beginning to think that this is a problem with the Linux ports of the SDK. Have installed all required libraries (libusb-1.0+ and ftdi driver) and even checked the shared libraries that the program uses with the ldd command, all seems normal.

      Delete
    4. It does sound like a problem of some sort with the drivers. A couple questions:

      1. Are you re-programming the FPGA each time you connect to a different system, or do you write the bitstream to the flash and load it from there?

      2. Assuming that you are programming the FPGA each time, have you written some bitstream that you know works to the device? Something like setting the LEDs so a specific configuration so you know the bitstream took?

      3. Have you checked the jumper that controls where the FPGA gets the bitstream from? Having that jumper in the wrong position essentially leads to undefined behavior, and I wouldn't be surprised if the behavior varied from system to system.

      4. Are any of the systems you are using virtual machines, not running directly on the hardware? (Using VMWare, VirtualBox, QEMU, etc)

      Delete
    5. 1.- Yes, I do re-program the FPGA each time.

      2.- Yes, I did. A small 2 bit adder with led output using the switches, and it works mint.

      3.- No, I haven't checked this one yet. I'll keep you posted.

      4.- No, installed and running navitely on each box.

      Delete
    6. Jack, yeah when I first did this #3 bit me too! The jumper next to the VGA port, labeled JP3, Mode, PC | ROM.

      Set it so it's on the PC side if you're loading the bit stream to the FPGA directly (e.g. the "Device 0: XC3S250E")

      Otherwise, if you're loading to the ROM be sure you've set the jumper.

      Let us know the results!

      Delete
    7. I suppose we can take that the silence on the wire means the solution was achieved? Opa!

      Delete