| F# to FPGA tutorial on Euclid's Greatest Common Divisor algorithm - Part II |
|
|
| Written by Stephen |
| Monday, 12 January 2009 14:22 |
|
In the first part of the tutorial on using Avalda FPGA Developer to compile Euclid's GCD algorithm to an FPGA we started with Euclid's GCD algorithm as a quoted F# program, and ended up transforming it to an RTL verilog description. In this part I'll walk through using ISE 10.1 to transform that RTL verilog description to a bitstream file which we can then load into Xess's XSA-3S1000 board to be run on its Xilinx Spartan XC3S1000 FPGA. The bitstream file contains all the info needed by the board to program the FPGA. Xilinx's Spartan line of FPGAs provides a good balance of low cost and performance to enable its use in a wide variety of consumer electronic applications. It's basic architecture consists of a large number of "configuration logic blocks" (CLBs), each of which contains look up tables (LUTs) that can implement any kind of basic digital logic function, along with RAM and registers. And the best thing is that these CLBs and LUTs run in parallel! The connections between the LUTs and CLBs and the functions the LUTs implement can all be programmed, even while the chip is still operating, and there are Input/Output blocks for the chip to perform I/O. Simply said, these chips are AWESOME. Many hardware engineers are ditching the usual long-winded and expensive ASIC design process in favour of FPGAs. If you're a software engineer doing DSP it's very easy to hit the performance limit of a microprocessor. And who are you going to call when you do? FPGA! Launch ISE 10.1 if you've already installed it. On the File menu select "New Project" and choose a project name (eg, "gcdtutorial") and project location on the New Project Wizard gui. Leave the Top-level source type set to HDL. Press the Next button and verify the Device Properties on the next screen of the New Project Wizard (see below).
Press Next three times to skip the source adding screens and press the Finish button on the Project Summary gui. Instead of adding the verilog files generated in the first part of the tutorial using the avaldac compiler of Avalda FPGA Developer, we need to first add three vhdl files needed for the interface to the SDRAM on the Xess 3S1000 board. These files can be obtained by first downloading and unzipping the Dualport module zip file in the tutorials section of Xess's Web Site. Grab "common.vhdl" and "sdramcntl.vhd" from the unzipped files' XS_LIB folder, and get the "xsasdramcntl.vhd" file from the XSA_LIB folder. Since they are vhdl files, the order in which they are added to the project matters. Go to the Project menu of ISE and use either the "Add Source..." or "Add Copy of Source..." menu items to add those vhdl files to your project in the order listed above. Make sure the associations of each file you add are set to "All" so that they show up whether you are compiling for simulation only or implementation. The top level sdram controller module for that project is in the "xsasdramcntl.vhd" file and you need to make sure some of its parameters are set properly so double click on the XSASDRAMCntl component from that file on ISE's Sources pane and set the generic parameters to the following:
FREQ : natural := 50_000; CLK_DIV : real := 2.0; PIPE_EN : boolean := false; MAX_NOP : natural := 10000; MULTIPLE_ACTIVE_ROWS : boolean := false; DATA_WIDTH : natural := 16; NROWS : natural := 8192; NCOLS : natural := 512; HADDR_WIDTH : natural := 24; SADDR_WIDTH : natural := 13
Then use "Add Source..." or "Add Copy of Source..." to add the gcd verilog files produced by the avaldac compiler from part I, which can all be added at once by selecting them in the folder browser window. The file order does not matter for verilog and ISE will usually automatically figure out the top module. Make sure the top of the left project browser window has the "Sources for:" selection set to "Implementation".
Why did we need those three SDRAM controller related vhdl files to begin with? The simple answer is I/O. We will be mapping our gcd program to an FPGA but we need a way of giving it some input to run and reading out the result. We like to do things from first principles and so going through the basic steps of loading data in and out the fpga via the SDRAM helps one understand better what is going on. You can even play with the gcd_xess_interface.v file to learn more about how hdl's like verilog work, or even replace it with a vhdl file if you would rather work with that language. There are quite enough quirks with hdls that it is worth the effort to go through some basic design steps if you are interested enough. Even our choice of the gcd program as a first tutorial is intended to keep things relatively simple so you get your basics right first (although if we show you the verilog simulator in which you actually see how the computation progresses, the program may not look as simple!). I learned this important learning habit from my math professors at Princeton who would often begin exploring a complicated theorem or topic with one of the simplest objects that has the properties you are interested in - like a little 2 by 2 matrix of just 1s and 0s say, or a tiny algebraic group. You can uncover many basic flaws in your understanding this way and then leap ahead to more complicated examples. There are many FPGA boards that come with sophisticated software interfaces to make it easy to map soft processors onto the FPGA and then use that to easily do I/O and communicate with the rest of the FPGA design from your PC. We will get to those in good time, but for the Xess board we will simply use their tools to load the board's SDRAM with our input data, then read that data back out after our gcd program has finished running using a small finite state machine (FSM) "wrapper" interface to the gcd module, the SDRAM controller module, and the rest of the board. We'll also drive an 8-segment led to test if our answer is what we expect. Hence, those SDRAM controller files use part of our FPGA as an interface which allows us to easily communicate with the external SDRAM chip. Xess has plenty of documentation on the SDRAM controller and example uses. Now the FSM "wrapper" will act as our very top module for the whole project and as mentioned above this file will instantiate both our gcd module and the XSASDRAMCntl component, and be responsible for connecting to all the important external signals of the clock, SDRAM (most of which just get passed to the XSASDRAMCntl module), and the leds. Its FSM is the key part that will coordinate the tasks of reading the data input from the SDRAM, setting it up for the gcd module and firing up the gcd module, then writing the gcd result back to the SDRAM. This gcd project top module is called "gcd_xess_interface" and you can download it here. The SDRAM stores 16 bits in each address, and the address size is 24 bits. To make things easy, we will store our data input in address 0x000000 and also load the result at the same address. Since the gcd algorithm takes two numbers, we have hard-wired one of the inputs to "6", and we will pass a value of "4" through the SDRAM as the other input to the gcd module. Ok, we're being lazy here but you can make an interesting exercise for yourself to expand the gcd_xess_interface to read and store stuff on different addresses and to even store 32 bit numbers as two 16 bit numbers (which will be particularly helpful when using floating number formats). Hence, we will be calculating the gcd of 6 and 4 which is 2. The leds test checks to see if the result from the gcd module is 2, and outputs a "1" if it is, and a "0" otherwise. Next, we'll add the ucf file for the project (user constraint file). This is a very important file because it tells ISE the mapping you want between the pins on the actual Spartan fpga and the wires in your module or in some of the external devices on your board. So don't mess it up! Here is where it can sometimes be handy to look at the actual data sheet for the Spartan XC3s1000 chip from Xilinx's Web Site. It has a nice picture of all the pins on the chip and how they are named. You would also consult the schematics from your board vendor to figure out where it expects certain signals to be connected to on a particular fpga device. For example, your board vendor may want the clock connecting to a particular pin of the fpga. In this tutorial, you can use the "gcd_xess_interface.ucf" found here and add it to your project. It connects the clk signal to the P8 location representing the 50 Mhz clock. Alternatively, the gcd_xess_interface.ucf file can also be created manually from scratch by selecting "New Source..." from the Project menu. This will open a New Source wizard gui where you can give it a name of "gcd_xess_interface" and select the "Implementations Constraint File" source type before pressing the Next and Finish buttons. A "gcd_xess_interface.ucf" file would then be created. Select the gcd_xess_interface object representing the top module in the Sources pane, then go to the Processes pane and double click on "Floorplan IO - Pre-Synthesis" under the User Constraints process node. This will open up the Xilinx PACE window for "gcd_xess_interface.ucf" where you can manually assign pins to the gcd_xess_interface input and output ports. At this point we have almost finished getting our gcd_xess_interface top module ready for synthesis and implementation etc. One key file needs to be added to the project for implementing the modulus operator using a Xilinx Coregen module. Recall from part I of the tutorial that the gcd algorithm uses this operator. Select "New Source" from the Project menu which will open up the New Source Wizard gui. Select "IP (CORE Generator & Architecture Wizard)" for the source type and give it a name of "signedintmod". Click the Next button and expand the Math Functions node on the next wizard page and select "Pipelined Divider v3.0" under the Dividers node (we will use the remainder output port of "remd" which will give us our modulus result). Click Next then Finish. Xilinx IP Core generator will start running and a new window will open where you can customize the Pipelined Divider core. Enter 32 for both the Dividend and Divisor bus widths; select the "Signed" radio button for the sign; select the "Integer" radio button for the Remainder parameter; select the "1 Clock" radio button for the Clocks Per Division parameter; and check the "CE" box in the Optional Pin area (see below).
Then press the "Generate" button and ISE will begin producing some files related to the signedintmod core. The information window at the bottom of ISE will let you know when the core has been successfully generated. Go to the project folder and open the "signedintmod.veo" file and follow the instructions in it to cut out the module instantiation section, then open the signedintmod_stub file produced from the avaldac compiler (the name has some additonal characters appended to it and the file should have been added to the project earlier so you can use ISE to edit it from within your project) and paste in the module instantiation at the area indicated. Replace "YourInstanceName" with an instance name like "inst", then save the signedintmod_stub file. Although the "quot" and "rfd" ports are not used, you can leave those net names there. If you expand the signedintmod_stub file in the Sources pane you should see a Coregen indicator beneath it with the instance you created. We are now ready to synthesize the design. Click on the "gcd_xess_interface" top wrapper module in the Sources pane then double click on "Synthesize - XST" in the Processes pane. This process will run for a while and then print out some statistics such as the maximum allowed frequency. The frequency of the clock driving your design should be less than this maximum frequency. If the synthesis step finished with no errors, double click on "Implement Design" in the Processes pane and wait until it finishes. Assuming the Implement stage completed successfully, right click on "Generate Programming File" in the Processes pane, select "Properties", then "Configuration Options", and then switch all the "Pull Up" resistor settings to "Float" as advised in Xess's documentation. Then double click on "Generate Programming File" on the Processes pane. After the Generate Programming step completes successfully, a file named "gcd_xess_interface.bit" is produced in your project folder. This is the bitstream file that contains the information that can program the FPGA to be configured to running your design. Actually, the Xess board has a smaller programmable logic device chip called a Complex Programmable Logic Device (CPLD) which helps configure the Spartan FPGA with the bitstream - a Xilinx XC9500XL CPLD. Make sure you are handling your Xess board in a static-free environment (eg, grounded), and connect it to your PC using either the DB25 male to male cable via the parallel port or the XSUSB interface board via a USB cable. Apply power to your board and follow the usual procedures for testing it (eg, run GXSTEST making sure that the port selection is set correctly depending on whether you are using the USB cable or not). If the test passes successfully, prepare a text file with a .XES extension and add the following line which will be used by the GXSLOAD utility to load data into the SDRAM: = 02 000000 00 04
The .XES file uses Xess's simple format for encoding data intended for the SDRAM. The second hex code is the SDRAM address (hex for 0x000000), and the next two numbers correspond to two 8 bit hex codes for the data. The .XES format uses 8 bit data encoding and the SDRAM data is 16 bits wide, so two 8 bit numbers are needed in the .XES encoding for one SDRAM data number. In this case, the number input is 4 (0x0004 in 16 bit hexadecimal encoding). Double click on the GXSLOAD utility and drag the .XES file to the input textbox under the RAM column. Make sure the upload format under that column is set to XESS-24 and that the "Download RAM/Flash Interface" box is checked. Then press the "Load" button and wait until the SDRAM data is loaded. GXSLOAD will upload a bit file called ramintfc.bit (which comes with the XS tools) and the fpga is programmed to create an interface between the parallel port and the SDRAM, then the data is loaded to the SDRAM. Then drag the gcd_xess_interface.bit file created above to the textbox under the FPGA/CPLD column on GXSLOAD and press the "Load" button again. The fpga is now configured with our gcd interface/gcd module program bitstream and it runs very quickly so you should immediately see the result on the leds display. If all goes well, a "1" should appear on the leds indicating that the data returned from the gcd module matched the answer "2" that we were expecting - i.e, the greatest common divisor of 6 and 4. But the leds are used as just a diagnostic test. The real proof that it worked will be found by reading the result back from the SDRAM since our gcd_xess_interface module wrote it there. Hence, enter 0x000001 for the High Address textfield under the RAM column of GXSLOAD, and 0x000000 for the Low Address textfield, then drag the small folder icon at the lower left of that column to some place on your pc. This action will reconfigure the fpga as an interface between the parallel port and the SDRAM and then read the data from the SDRAM at the address range specified, and output the specified SDRAM data on the RAMUPLD.XES file that appears when you drag that little folder icon out. If you now open the RAMUPLD.XES file you should see the following line: = 02 000000 00 02
|
| Last Updated ( Monday, 12 January 2009 16:01 ) |