Almost all of my embedded development and debugging makes heavy use of JTAG, both for loading new bitstreams/firmware images and for interacting with on-chip debug systems.
When I first got into FPGA development I used the
Xilinx Platform Cable USB II, which sells for $258.75 on Digikey as of this writing. It integrated nicely with the Xilinx IDE but I quickly grew frustrated. I wanted to use the BSCAN_SPARTAN6 primitive in the FPGA to move debug data on and off the FPGA using JTAG, but Xilinx does not provide any sort of API for scripting the platform cable. Although iMPACT allows manual bit twiddling in the chain as well as executing pre-made SVF files, there is no way to do interactive testing with it
My first step in deciding how to proceed was to see what made their adapter tick. I would have opened up the adapter to see what was inside, but Bunnie saved me the trouble by posting
pictures a while ago as the Name That Ware for March 2011.
|
Xilinx Platform Cable USB II (image courtesy of Bunnie) |
The vast majority of the footprints on the board aren't even populated... one can only guess what additional functionality may have been planned at one point. There's an XC3S200A FPGA, a Cypress USB MCU, USB descriptor EEPROM, flash for the FPGA, and then a bunch of passives for power regulation and level shifting. Overall, the design is quite simple and certainly not worth $250.
After browsing for something cheaper and based on a well-known chipset, I found the
Digilent HS1, a $54.99 FT2232-based adapter which is supported by Digilent's documented JTAG API and integrated nicely with the Xilinx IDE. In addition, since it's a standard FTDI chipset it would be possible to interact with it at a lower level using libftd2xx. (I also built a custom FT232H-based programmer that I have half a dozen of around my lab, but I wanted a known-good design to verify my software on first.)
The HS1 worked quite well using the Xilinx tools, but I still needed JTAG code to talk to it and interact with the FPGAs for scripted tests. I looked at a couple of popular options and rejected each of them:
- OpenOCD (GNU GPL, incompatible with the BSD license used by my work)
- xc3sprog (GPL, standalone tool with no API, includes programming algorithms that can run directly off a .bit file)
- urjtag (GPL, has a socket-based JTAG server under development but not released yet)
It looked like I was going to have to write my own software, so I sat down and did just that. The result was a C++ library I call libjtaghal (JTAG hardware abstraction layer). It will be released publicly under the 3-clause BSD license once I've cleaned it up a bit; in the meantime if anyone wants a raw code drop with no documentation and a not-quite-finished build system leave a comment and I'll post something.
The basic structure of libjtaghal is built around two core object types: interfaces and devices. A JtagInterface represents a connection to a single JTAG adapter. As of now I support:
- FT*232H MPSSE (assumes ADBUS7 is the output enable, an option to configure this is planned for the future)
- Digilent API (for HS1 and integrated programmers on the Atlys etc)
- Generic socket-based protocol for talking to remote libjtaghal servers
My custom 8-port JTAG system (more to follow in a future post) will use my socket-based protocol and show up as 8 separate interfaces which can each be controlled independently (potentially from 8 separate client PCs).
A JtagDevice represents a single chip in a scan chain. Support for multi-device scan chains needs a bit more work; this is one of the reasons I haven't released it yet.
A given JtagDevice may implement one or more additional interfaces. Some of these are:
- CPLD (generic complex programmable logic device)
- FPGA (generic FPGA device)
- ProgrammableDevice (any device which accepts firmware of some sort, including CPLDs, FPGAs, MCUs, and JTAG-capable ROMs)
- RPCNetworkInterface (a device which supports sending RPC messages over JTAG)
- DMANetworkInterface (a device which supports sending DMA messages over JTAG)
- RPCAndDMANetworkInterface (implements RPCNetworkInterface, DMANetworkInterface, and some logic to connect the two protocols)
This design allows several very handy design abstractions. For example, the below code is the sum total of the "program" mode for my "jtagclient" command-line application. It takes a JtagInterface object "iface" and programs the device at chain index "devnum" with the firmware image "bitfile". Note the complete lack of any device- or interface-specific code. The same function can configure a CoolRunner-II via one of my custom FTDI programmers or a Spartan-6 using the integrated Digilent programmer on a dev board without changing anything.
JtagDevice* device = iface.GetDevice(devnum);
if(device == NULL)
{
throw JtagExceptionWrapper(
"Device is null, cannot continue",
"",
JtagException::EXCEPTION_TYPE_BOARD_FAULT);
}
//Make sure it's a programmable device
ProgrammableDevice* pdev = dynamic_cast(device);
if(pdev == NULL)
{
throw JtagExceptionWrapper(
"Device is not a programmable device, cannot continue",
"",
JtagException::EXCEPTION_TYPE_BOARD_FAULT);
}
//Load the firmware image and program the device
printf("Loading firmware image...\n");
FirmwareImage* img = pdev->LoadFirmwareImage(bitfile);
printf("Programming device...\n");
pdev->Program(img);
printf("Configuration successful\n");
delete img;
This is part of the test case for my gigabit Ethernet MAC, allocating a page of memory on the device under test by talking to the RAM controller at NoC address "raddr" via the RPCNetworkInterface "iface". (Details on how this is implemented will be coming in a few posts.)
printf("Allocating memory...\n");
iface.RPCFunctionCall(raddr, RAM_ALLOCATE, 0, 0, 0, rxm);
uint32_t txptr = rxm.data[1];
printf(" Transmit buffer is at 0x%08x\n", txptr);
There's a lot more to the system than this but I'll save the rest for my next post :)