RGB LED Panel Driver Tutorial
RGB LED Panel Driver Tutorial
http://bikerglen.com/projects/lighting/led-panel-1up/#Afterword
Contents
1 Introduction
2 Required Hardware
3 Required Software
4 Theory of Operation
4.1 The LED Panel
4.1.1 LED Panel Hardware
4.1.2 Driving the Panel
4.2 The FPGA
4.2.1 GPMC Interface
4.2.2 Register Interface
4.2.3 Display Buffers
4.2.4 Display Driver
4.3 The Software
5 Connecting the Hardware
5.1 Display Data Connections
5.2 Display Power Supply Connection
6 Downloading and Compiling the Software
7 Running the Panel Using the Stock Bit File
7.1 Device Tree Configuration
7.2 FPGA Configuration
7.3 Running a Pattern
7.4 Modifying an Existing Pattern
8 Rebuilding the Bit File
9 Simulating the Design
10 Creating a New Pattern Subclass
11 Ideas for Improvement
12 References
13 Afterword
Introduction
In this project, we interface a SparkFun or Adafruit 32x32 RGB LED panel to a BeagleBone Black board using the Xilinx Spartan 6 LX9 FPGA on the LogiBone FPGA board. The hardware for this project is relatively easy to
constructjust 16 data signals connect the LED panel to the LogiBone FPGA board. The complexity of this project lies mostly in the RTL and software.
A demonstration of the project is available on YouTube.
A demonstration of the project expanded to six panels is also available on YouTube.
Figure 1. RGB LED panel with a random twinkling pattern connected to the LogiBone FPGA board and some other sample panel images.
Required Hardware
The following hardware items are required:
1 trong 14
4/20/2015 3:06 PM
http://bikerglen.com/projects/lighting/led-panel-1up/#Afterword
Required Software
Stock ValentFX LogiBone Ubuntu build w/ the LogiBone logibone_<revision>_dm.ko kernel module and logi_loader
Download and follow the instructions here to install the default LogiBone Ubuntu image on an SD card.
Xilinx ISE WebPack Software
If you want to build the FPGA bit file yourself or customize the Verilog to drive more panels or add other custom functionality (such as a coprocessor to help compute difficult pixel patterns), youll need to download and
install the Xilinx ISE WebPack software. Instructions are here. If you only want to use the default FPGA bit file, you can skip installing the Xilinx ISE WebPack software.
Glens LED panel GIT repository
Finally, youll need to clone my GIT repository at http://github.com/bikerglen/beagle to your BeagleBone Black. This repository contains the Verilog source code for the FPGA, a prebuilt bit file, and C++ source code for
displaying some demonstration patterns on the panel. Instructions for downloading or cloning and using the repository are presented later.
Theory of Operation
This system has three major components: the LED panel, the FPGA code, and the C++ code. Lets examine each of these three major components in detail.
The panel contains six sets of column drivers; three for the top half of the display and three for the bottom. Each driver has 32 outputs. The three drivers for the top of the display drive the red, green, and blue chips in each of
the 32 columns of LEDs in rows 0 to 15 of the panel. The three drivers for the bottom of the display drive the red, green, and blue chips in each of the 32 columns of LEDs in rows 16 to 31 of the panel.
Each of the drivers has a serial data input, a blanking input, a shift register, and a parallel output register as shown below in Figure 3. The data present on the serial data input is shifted into the shift register using the SCLK
signal. After an entire row of data has been shifted in to the shift register, the LATCH signal is used to transfer the row of pixel data from the shift register into the parallel output register. If a bit in the output register is a 1 and
the blanking input is deasserted, the driver for that column will be enabled; otherwise, the driver will be turned off. Data is shifted from the right edge of the display to the left edge of the display. In other words, the first bit
shifted in will be displayed on the left edge of the display and the last bit shifted in will be displayed on the right.
Figure 3. Column
driver operation for the R0 data input and top-half red columns outputs. There are two more of these shift registers at the top of the display for the top-half green and blue columns and three more
at the bottom for the bottom half red, green, and blue columns.
The red, green, and blue column drivers for the top half of the display are attached respectively to the R0, G0, and B0 data inputs. The red, green, and blue column drivers for the bottom half of the display are attached
respectively to the R1, G1, and B1 data inputs. All six of the 32-bit drivers share common SCLK, LATCH, and BLANK signals.
The rows are driven using four address bits and an address decoder. The four-bit address input to the row drivers is decoded and the two row drivers corresponding to that address will be turned on. When A[3:0] is 0, rows 0
and 16 of the display are turned on. When A[3:0] is 1, rows 1 and 17 of the display are turned on. This pattern continues until A[3:0] is 15 and rows 15 and 31 are turned on.
In addition to the row and column logic and drivers, the display has a blanking input. This input is most likely connected to the column drivers. When the blanking signal is asserted, all of the pixels are turned off and the
display will be black. When the blanking signal is deasserted, the addressed rows and columns will be driven and the corresponding pixels illuminated. To display an image without flickering and ghosting, all of these signals
must be used and properly sequenced when driving the panel.
2 trong 14
4/20/2015 3:06 PM
http://bikerglen.com/projects/lighting/led-panel-1up/#Afterword
The display is multiplexed and has a 1/16th duty cycle. This means that no more than one row out of the 16 in the top half of the display and one row out of the 16 in the bottom half of the display are ever illuminated at once.
Furthermore, an LED can only be on or off. If both the row and column for an LED are turned on, the LED will be illuminated; otherwise, the LED will be off.
To display an image, the entire LED panel must be scanned fast enough so that it appears to display a continuous image without flickering. To display different colors and different brightness levels, the brightness of the red,
green, and blue LED chips within each LED package must be adjusted by varying the amount of time that each LED chip is on or off within a single refresh cycle.
The basic process used to refresh the display when using three bits-per-pixel color (one bit for red; one bit for green; and one bit for blue) is the following:
1. Shift the pixel data for row 0 into the top column drivers and the pixel data for row 16 into the bottom column drivers using the R0, G0, B0, R1, G1, and B1 data inputs and the SCLK shift clock signal.
2. Assert the blanking signal to blank the display.
3. Set the address input to 0.
4. Latch the contents of the column drivers shift registers into the column drivers output registers using the LATCH signal.
5. Deassert the blanking signal to display rows 0 and 16.
6. Wait some fixed amount of time.
7. Repeat the process for each of the pairs of rows in the display.
8. Repeat the entire process at least 100 to 200 times per second to prevent flicker.
The above process uses one bit per LED color. This will give you eight possible colors: black; the primary colors red, green, and blue; the secondary colors cyan, magenta, and yellow; and white.
To display more colors and brightness levels the above technique is modified to use binary coded modulation. In binary coded modulation, each pixel is controlled using more than a single bit per color per pixel. The amount of
time each red, green, and blue LED chip is on is then varied proportionally to the pixels red, green, and blue values.
In binary coded modulation, the following process is performed to refresh the display:
1. Shift bit zero of each pixels red, green, and blue values for rows 0 and 16 into the column drivers.
2. Assert the blanking signal to blank the display.
3. Set the address input to 0.
4. Latch the contents of the column drivers shift registers into the column drivers output registers using the LATCH signal.
5. Deassert the blanking signal to display rows 0 and 16.
6. Wait some amount of time, N.
7. Repeat the above process for the next higher order bit of color data in the same row. In step 6, wait two times the previous delay time. Repeat this process for each bit of color data, doubling the delay time after displaying
each successive bit.
8. Repeat the above process for each of the pairs of rows in the display.
9. Repeat the entire process at least 100 to 200 times per second to prevent flicker.
Note that in actual implementations, the process of shifting the pixel data into the shift registers in Step 1 is usually done during the wait time in Step 6.
Global display dimming can be performed by varying the amount of time the blanking signal is asserted or deasserted within the wait time period, N. For example, asserting the blanking signal 25% early will result in a display
with a brightness of 75% instead of 100%. Note that during global dimming, the wait time itself is not shortened or lengthened; only the blanking signal is modified to be asserted earlier than it normally would be.
The FPGA
The FPGA interfaces the C++ pattern generation software running on the BeagleBone Black CPU to the LED panel. The FPGA does the heavy lifting required to refresh the entire LED panel about 200 times per second. This
leaves the BeagleBone Black CPU free to generate the patterns and perform other tasks.
Figure 4.
Block diagram of the system including a block diagram of the FPGAs major functional blocks.
As shown in Figure 4 above, software running on the BeagleBone Black generates patterns. These patterns are fed to the FPGA on the LogiBone board using the TI SOCs GPMC bus. These patterns are written to a dual-port
memory that serves as a display buffer. Finally a display controller reads the patterns out of the dual port memory, shifts the data into the display, and enables the row drivers as needed to display the image. The entire process is
repeated about 200 times per second and generates a 32 x 32 RGB image with 12-bit color without any interaction from the BeagleBone Blacks CPU.
GPMC Interface
The TI SOC has a programmable memory interface called the general-purpose memory controller (GPMC). This interface is extremely flexible. It can operate in both synchronous and asynchronous modes and the bus timing is
programmable in 10ns increments. The GPMC bus will be used to transfer pixel data from the software on the BeagleBone Black to the FPGA on the LogiBone board.
In our system, the GPMC is configured to operate in its asynchronous, multiplexed address/data mode. In this mode, both the address and data buses are 16 bits wide. This permits an entire 12-bit pixel to be transferred from
the CPU on the BBB to the FPGA on the LogiBone board in a single write operation. For more information on the GPMCs asynchronous, multiplexed mode of operation, see sections 7.1.3.3.10.1.1 of the AM335x ARM
Cortex-A8 Microprocessors Technical Reference Manual.
Im using a slightly different circuit in the FPGA to interface to the GPMC bus than the stock LogiBone projects. Its a bit slower than the stock VHDL circuit, but guarantees that each write from the CPU over the GPMC bus
creates exactly one write strobe pulse to the register interface inside the FPGA. Because its slightly slower than the stock circuit, it requires modified bus timing and thus a custom device tree setup file. Figure 5 below shows
the bus timing using the modified GPMC interface to perform a write to the FPGA. Figure 6 below shows the bus timing using the modified GPMC interface to perform a read from the FPGA.
Figure 5. Simulation of a write to the GPMC target using the modified bus timings.
3 trong 14
4/20/2015 3:06 PM
http://bikerglen.com/projects/lighting/led-panel-1up/#Afterword
Figure 6. Simulation of a read from the GPMC target using the modified bus timings.
The read or write address is latched into a temporary holding register on the rising edge of the GPMC_ADVN signal and the write data is latached into its own temporary holding register on the falling edge of the
GPMC_WEN signal. This requires using the GPMC_ADVN and an inverted version of the GPMC_WEN data signals as clocks. Technically, using data signals as clocks is gross. Its actually so gross, the Xilinx tools will
generate an error for this condition. But you can set an exception in the UCF file for the affected nets and force synthesis to continue. It would be much better to use the GPMC in its synchronous mode, but this technique is
good enough for an FPGA until I have time to build a synchronous version of the interface, a synchronous GPMC bus model for simulation, and learn how to modify the device tree further.
In addition to latching the address and write data values into holding registers, the GPMC_CSN, GPMC_WEN, and GPMC_OEN controls signals are registered and brought into the FPGAs 100MHz clock domain. Once in the
FPGAs clock domain, the WEN and OEN signals are gated with the CSN signal and edge detected to detect writes to the GPCM target and reads from the GPMC target. When a read or write is detected, the contents of the
address and write data holding registers are captured into registers in the FPGAs 100MHz clock domain.
The primary reason to slow down the GPMC bus versus the stock device tree setup file was to stretch the time that each of these control signals is low or high to at least 30ns to guarantee that the edges of the signals could be
detected in the FPGAs 100MHz clock domain. This also guaranteed that the address and data would be stable in their own holding registers before moving the contents of those registers into the address and data registers that
are clocked in the FPGAs 100MHz clock domain.
The outputs of the GPMC target are a bus that Im calling the slow bus. The slow bus connects the GPMC target to the FPGAs register interface. Figure 7 shows an example slow bus write operation. Figure 8 shows an
example slow bus read operation.
sb_addr, sb_wr, and sb_wr_data will be valid for exactly a single 100MHz clock pulse every time a write occurs on the GPMC bus. When the register interface sees sb_wr asserted, it writes sb_wr_data into the register at
sb_addr.
sb_addr and sb_rd will be valid for exactly a single 100MHz clock pulse every time a read occurs on the GPMC bus. The register interface sees sb_rd asserted then must return the value of the register at the address sb_addr on
the sb_rd_data bus on the very next clock cycle.
Register Interface
The register interface is implemented in the top level of the FPGA Verilog. The register interface defines the view the software has of the FPGA. Table 1 below lists the registers in the FPGA.
FPGA
Address
BBB SOC
Address
Name
Description
0x0000
0x0000
0x0001
0x0002
0x0002
0x0004
4 trong 14
4/20/2015 3:06 PM
http://bikerglen.com/projects/lighting/led-panel-1up/#Afterword
0x0003
0x0006
0x0004
0x0008
0x0005
0x000a
0x0006
0x000c
0x0007
0x000e
0x0008
0x0010
0x0009
0x0012
0x000a
0x0014
Display Buffers
The display buffers are implemented usinx Xilinx Block RAMs configured as dual-port memories with asynchronous read and write ports. The first RAM contains display buffers 0 and 1 for the top half of the display. The
second RAM contains display buffers 0 and 1 for the bottom half of the display. Structuring the memories to contain half the display each permits the pixels in rows 0 to 15 to be read from memory on the exact same clock that
the pixels in rows 16 to 31 are read from memory.
Display buffer 0 is located at address 0x0000. Display buffer 1 is located at address 0x0400. Each display buffer contains 1024 12-bit RGB values arranged as 32 rows of 32 columns. Within each display buffer, the top-left
pixel is stored at offset 0, the bottom-right pixel is stored at offset 0x3ff. Bits 4 to 0 of the pixel offset are 0x00 for pixels in the leftmost column on the display; bits 4 to 0 of the pixel offset are 0x1F for pixels in the rightmost
column.
Pixels are stored in the memory as 12-bit RGB values. These values are stored right-justiified. Bits 11 to 8 are the red pixel level, bits 7 to 4 are the green level, and bits 3 to 0 are the blue level.
Display Driver
The display driver reads pixel values from memory, shifts those values to the display, and cycles through the rows of the display as required to implement binary coded modulation as described in the theory of operation section
of this document. The display driver is implemented as a state machine. Each state implements a step in the refresh process. When that step is complete, the state machine moves to the next step in the process.
Figure 9 below shows simulation waveforms for the control and data outputs for three rows worth of display data. The basic process is to blank the display, latch in the previously shifted data, update the row selects, unblank
the display, shift in the next set of pixel data, and then wait for an update timer to expire. This is repeated four times for each row. If you examine the blanking output, youll notice that its low period doubles three times within
the output period for each display row. This is the result of using binary coded modulation to vary the intensity of each pixel.
The Software
The demonstration software uses the /dev/logibone_mem device to communicate with the FPGA. The driver for this device is part of the stock LogiBone Ubuntu image and its loadable kernel module is installed by the
modified device tree setup shell script thats included in the GitHub repository for the LED panel. (More on this subject in a later section.) This driver maps the registers in the FPGA to a portion of the BBB CPUs address
space using the GPMC. The GPMC normally maps memory into the CPUs address space. Because our FPGA looks like a memory to the GPMC bus, its registers can be mapped into the CPU address space too. Pretty cool. No
SPI, I2C, etc.; just fast parallel accesses between the CPU and the FPGA. This memory-mapped space can then be accessed by opening the /dev/logbone_mem device using the C library open function call and reads and writes
to a register in the FPGA can be performed using the pread and pwrite C library function calls.
Figure 10 below is a block diagram of the demonstration software stack. In the demonstration software, main opens the /dev/logibone_mem device, fills the global buffer memory, gLevels, with all black, and then calls
WriteLevels to write the global buffer to the display and clear the display. Once the display is cleared, the main function instantiates a pattern / animation subclass such as a radiating circle, perlin noise, or colorwash subclass.
5 trong 14
4/20/2015 3:06 PM
http://bikerglen.com/projects/lighting/led-panel-1up/#Afterword
Figure 11. PMOD connector pin outs, connections between the PMOD connectors and the display input connector, and the display connector pin out.
6 trong 14
4/20/2015 3:06 PM
http://bikerglen.com/projects/lighting/led-panel-1up/#Afterword
FPGA board connected to RGB LED panel using the PMOD-to-display adapter board.
the power supply to the RGB LED panel using a female DC barrel jack adapter.
The 'cd' command without any arguments changes to your home directory. The git clone command copies the repository from the specified URL to the 'led-panel' directory. If you want to use a different name for the directory,
feel free but you'll need to change the path the in the rest of the commands that follow too.
Now change directories to the 'software' directory and execute the 'make' command to build the code:
ubuntu@arm:$ cd led-panel/projects/led-panel-v01/software/
ubuntu@arm:$ make
g++ -c runcircle.cpp
g++ -c pattern.cpp
g++ -c circle.cpp
g++ -o runcircle runcircle.o pattern.o circle.o
g++ -c runperlin.cpp
g++ -c perlin.cpp
g++ -o runperlin runperlin.o pattern.o perlin.o
g++ -c runwash.cpp
g++ -c wash.cpp
g++ -o runwash runwash.o pattern.o wash.o
g++ -c runtwinkle.cpp
g++ -c twinkle.cpp
g++ -o runtwinkle runtwinkle.o pattern.o twinkle.o
g++ -c runwipe.cpp
g++ -c wipe.cpp
g++ -o runwipe runwipe.o pattern.o wipe.o
g++ -o blank blank.cpp
g++ -o picture picture.cpp
7 trong 14
4/20/2015 3:06 PM
http://bikerglen.com/projects/lighting/led-panel-1up/#Afterword
ubuntu@arm:$
Congratulations! All the code is now compiled. This builds the runcircle, runperlin, runwash, runtwinkle, runwipe, blank, and picture commands. Each of the 'run' commands produces a different animated pattern on the
display. The blank command clears the display. The picture command displays an image on the panel. The filename of the image is supplied as the first argument to the command. The image must be a 32x32 24-bit color image
in interleaved RGB format.
Now that the shell script and device tree configuration file have been copied to the home directory, lets run them:
ubuntu@arm:$ cd
ubuntu@arm:$ sudo ./setup_device-tree-slow.sh
rm: cannot remove `/lib/firmware/BB-BONE-LOGIBONE-SLOW.dtbo': No such file or directory
0: 54:PF--1: 55:PF--2: 56:PF--3: 57:PF--7: ff:P-O-L Override Board Name,SLOW,Override Manuf,BB-BONE-LOGIBONE
ubuntu@arm:$
The first time the shell script is executed, the .dtbo file does not exist so youll get an error message that you can safely ignore. Also note that you may be prompted to enter your user password in order to authenticate yourself
to the sudo command and run the setup shell script as root.
The device tree is now configured and the LogiBone boards kernel level device driver has been loaded! Time to configure the FPGA.
FPGA Configuration
The next step is to load the configuration bit file into the FPGA. The configuration bit file is generated by the Xilinx ISE tools and contains our complete synthesized and routed design. Without the configuration file, the FPGA
is nothing but a bunch of disconnected gates, memories, lookup tables, and routing resources. The configuration file connects the various resources together in the FPGA in such a way as to implement the logic we specified in
our source code. The process of turning the source code into elements that exist in the FPGA is called synthesis. Xilinx divides the synthesis process into two stages: translation, where the code is compiled into primitive gates,
and mapping, where the gates are arranged into CLBs and LUTs. The process of determining exactly which elements in the FPGA are used and how theyre connected using the available gates and routing resources is called
place and route. After synthesis and place and route, a third process called static timing analysis runs to ensure that the design operates at the requested speed.
The LogiBone boards stock Ubuntu image includes a utility called logi_loader in /usr/local/bin. The logi_loader utility loads a bit file specifed on the command line into the FPGA. The repository cloned onto the BBB earlier
contains a pre-built configuration bit file so we can get started quickly with the FPGA and the display. Lets give our FPGA some intelligence by loading the pre-built bit file into it using the logi_loader command:
ubuntu@arm:$ cd
ubuntu@arm:$ sudo logi_loader led-panel/projects/led-panel-v01/fpga/bitfiles/beagle01.bit
0+1 records in
0+1 records out
340714 bytes (341 kB) copied, 0.963579 s, 354 kB/s
ubuntu@arm:$
If all is well, the two user LEDs on the LogiBone FPGA should be blinking at slightly different rates. At this point you can connect power to the display. The display should be blank. (During the FPGA design phase, I set the
defaults contents of the memories in the design to be all zero. A welcome message would have been cool too.)
Running a Pattern
Now lets run one of the patterns we compiled earlier on the display. The Perlin noise pattern is pretty cool. Change to the software directory and run the runperlin executable as root:
ubuntu@arm:$ cd
ubuntu@arm:$ cd led-panel/projects/led-panel-v01/software/
ubuntu@arm:$ sudo ./runperlin
^Cubuntu@arm:$
Hopefully, the display is showing a mesmerizing and constantly changing pseudorandom pattern now! If not, make sure you included sudo in front of ./runperlin to run the command as root. The /dev/logibone_mem devce is
only accessible by root. If the colors seem odd, check that the signals to the column drivers are connected correctly. If rows are swapped, check the signals to the row drivers. If the display looks like the top and bottom are
swapped, check that R0,G0,B0 are not swapped with R1,G1,B1. If that fails, use a logic analyzer or scope to examine the signals between the display and the FPGA and ensure they look like their counter parts in the simulation
of the FPGA.
Once you have everything working and have had enough of the Perlin noise, type ctrl-c to exit the runperlin program. Experiment with running some of the other patterns. The runwash and runcircle animations are pretty cool.
There are also a biker and a beagle raw image files for use with the picture command in the repository. If you have the display sitting between you and your monitor and its starting to burn your eyes, use the blank command to
clear the display back to all black.
As you can see above, the fourth argument to the constructor is xy_scale and it is of type float. That is the parameter we want to change.
runperlin.cpp declares gPattern of type Perlin and then instantiates an instance of that class using the new operator. Find the gPattern = new Perlin (); in runperlin.cpp:
8 trong 14
4/20/2015 3:06 PM
http://bikerglen.com/projects/lighting/led-panel-1up/#Afterword
The fourth argument to the constructor is 8.0/64.0. Lets change that to 4.0/64.0:
// create a new pattern object -- perlin noise, mode 2 long repeating
gPattern = new Perlin (DISPLAY_WIDTH, DISPLAY_HEIGHT, 2, 4.0/64.0, 0.0125, 512.0, 0.005);
Save the file and exit the editor. Now recompile everything and run the runperlin command again as root:
ubuntu@arm:$ make
g++ -c runperlin.cpp
g++ -o runperlin runperlin.o pattern.o perlin.o
ubuntu@arm:$ sudo ./runperlin
ubuntu@arm:$
Notice the different size of the blobs compared to the first time you executed the runperlin command?
Extra credit: Make the runperlin script listen for UDP packets containing new xy_scale and z_step parameters and change those parameters using the setter functions of the Perlin class as new UDP packets arrive. Use an
Arduino, an Ethernet shield, and two potentiometers to generate the UDP packets.
Once the tool has launched, close any projects that opened when the the tool launched using the File menu then the Close Project option. Now click the Open Project button as shown below in Figure 16.
Figure 16. Open project button when no other projects are opened.
The ISE GUI will display a standard file chooser dialog bog as shown below in Figure 17. Navigate to the build directory inside the fpga directory you copied to the build machine earlier. Select the beagle01.xise project file
from this directory and click open.
The tool will open the beagle01 project and do some initial processing on the design. If the tool cannot find any files in the design, it will alert you to the missing files and give you the opportunity to locate those files now.
Once everything has opened successfully, the screen will look similar to that in Figure 18. To rebuild the design from the original Verilog, click Implementation and click beagle01 in the hierarchy pane. Finally right click
9 trong 14
4/20/2015 3:06 PM
http://bikerglen.com/projects/lighting/led-panel-1up/#Afterword
Generate Programming File and select Rerun All. These sections of the screen are highlighted in red in Figure 18 below.
Once you click Rerun All, the tool will re-run the entire flow, starting from the source code, through synthesis, translation, mapping, place and route, and static timing analysis. If all of these processes run successfully, the tool
will generate a programming file called beagle01.bit in the build directory. After generating the programming file successfully, the screen will look something like Figure 19.
synthesis, mapping, translation, place and route, and programming file generation.
Once the bit file has been generated, the bit file can be copied back to the BeagleBone Black using scp or an FTP client such as Filezilla (again use port 22 for SFTP). Once on the BeagleBone Black, the design can be loaded
into the FPGA and tested using the logi_loader command.
10 trong 14
4/20/2015 3:06 PM
http://bikerglen.com/projects/lighting/led-panel-1up/#Afterword
example, a test script used to run a series of steps in a test plan against the FPGA RTL). Additionally, the testbench may contain clock generators to generate the clocks used by the system and scripts to automate checking the
results of the simulation.
Our testbench module is called testbench and is in the file testbench.v inside the fpga/test directory. In our testbench, we have a model of the GPMC host running inside the CPU on the BeagleBone Black, an instance of the
beagle01 top-level module for our FPGA, a clock generator, a test script that writes data into the FPGA using the GPMC bus, and some code to monitor the column outputs from the FPGA,
Lets run a quick simulation of the LED panel FPGA using the isim simulator built into the Xilinx ISE WebPack software and our testbench. Launch the Xilinx ISE Project Navigator and open the beagle01.xise project. Once
the project is open, click Simulation and click on testbench (testbench.v) in the Hierarchy panel. Expand the ISim Simulator process, right click on Simulate Behavioral Model and select Rerun All. These sections of the screen
are highlighted in red in Figure 20 below.
Once Rerun All is selected, the ISim simulator will launch. The source code for the testbench, the models, the FPGA, and any Xilinx IP core generator modules will be compiled and elaborated. Once elaborated, the simulation
will begin running. Our testbench uses the $finish system task to stop the simulator once the test has completed. Once the simulation has run to completion, the screen will look like that in Figure 21 showing the line of code
containing the $finish system task that caused the simulation to stop. Click the little X highlighted by the red box in the Figure 21 to close the testbench.v file.
Figure 21. Simulation complete. Close the text editor to view the waveforms.
Once the testbench source code window is closed, the screen will show some waveforms for the simulation as shown below in Figure 22.
11 trong 14
4/20/2015 3:06 PM
http://bikerglen.com/projects/lighting/led-panel-1up/#Afterword
At this point you can use the mouse and menu items to zoom into and out of the waveforms. You can also dig into the hierachy of the testbench and the design using the left pane and add signals to the waveform display by
dragging signals from the middle pane to the waveform display on the right side of the window. To update the waveforms to contain values for the newly added signals, re-run the simulation using the Rerun item under the
Simulation menu. If you modified the test bench or the FPGA source code, youll need to relaunch the simulator using the Relaunch item under the Simulation menu. Relaunch will recompile and re-elaborate the testbench,
modules, and cores in the design. Be sure to save the wcfg file if you want to have the same signals displayed in the waveforms pane after the simulator has relaunched.
#endif
Listing 1. Example pattern subclass C++ header file.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "globals.h"
#include "pattern.h"
#include "mypattern.h"
//--------------------------------------------------------------------------------------------// constructors
//
MyPattern::MyPattern (const int32_t width, const int32_t height) : Pattern (width, height)
{
}
//--------------------------------------------------------------------------------------------// destructor
//
MyPattern::~MyPattern (void)
12 trong 14
4/20/2015 3:06 PM
http://bikerglen.com/projects/lighting/led-panel-1up/#Afterword
{
}
//--------------------------------------------------------------------------------------------// init -- reset to first frame in animation
//
void MyPattern::init (void)
{
// code to reset to first frame in animation
}
//--------------------------------------------------------------------------------------------// next -- calculate next frame in animation
//
bool MyPattern::next (void)
{
// code to generate next frame in animation
}
Listing 2. Example pattern subclass C++ source file.
Becase the init and next methods are defined as pure virtual functions in the parent Pattern class, your new subclass must define the init and next methods.
The init method must contain code to reset the animation to the first frame of the animation. If you were creating a Pattern subclass to display an animated GIF on the LED panel, your init method, might open the GIF file,
parse the frames in the file, store those frames in local memory, set the frame pointer to the first frame of the GIF, and set the frame delay timer to 0. The init method should not modify gLevels or call WriteLevels.
The next method must contain code to generate a frame of the animation, store the created frame in gLevels, and advance to the next frame of the animation. In our example subclass to display an animated GIF, when the frame
delay timer expires, wed copy the frame of the animation pointed to by the frame pointer to the gLevels buffer, advance the frame pointer to the next frame, and set the frame delay timer to the delay between frames of the GIF
animation. If the timer had not expired, wed decrement the timer and return without doing anything else.
The next method returns a boolean value to indicate when a convenient stopping point in the animation has been reached. In the GIF example, the next method might usually return false but would return true after the last frame
of the animated GIF has been displayed for the amount of time indicated by the frame delay timer. This boolean value indicates to the calling function that now would be a good time to either increment a count of how many
times the animation has been displayed or that it is a good time to switch to displaying a different pattern or animation. The next method should not call WriteLevels; only the caller of next should call WriteLevels.
After coding your new pattern subclass, youll need to add a few lines to the Makefile to compile the pattern subclass. Youll also need to copy, modify, and compile one of the run<pattern>.cpp source code files to instantiate
and run the pattern.
Extra credit: create a program to display mulitple patterns in a sequence. The patterns should change after each animation has run for one complete cycle. Use the the return value of the next method of each pattern subclass to
determine when an animation sequence has completed.
References
The following references were used during construction of this project.
Adafruits RGB LED Panel Library on GitHub
TI E2E RGB LED Coffee Table
Ken Perlins Original Perline Noise Source Code
Casey Duncans Python Noise Library on GitHub
Matt Zuckers Perlin Noise Math and Seamless Tiling FAQ
Cliff Cummings Paper on Clock Domain Crossing Design and Verification Techniques
LED Dimming Using Binary Code Modulation
Afterword
I eventually created a new version of the project to drive six panels. Files related to the 6up version of the project can be found in the /beagle/projects/led-panel-6up directory of my github repository. Unlike the v01 project
in the github repository and described in this tutorial, the 6up project is not 100% ready-to-run out of the box. For example, theres no step-by-step guide, youll need to build your own bitfile, and youll need to compile the
software. The new Verilog does, however, includes global dimming support, uses only two clocks per 6 bits of RGB data to shift data into the panels, and supports up to six panels daisy chained together.
Here are a few photos of some of the projects built using the 6up version of the project:
Figure 23. Five-sided cube built with five panels and the 6up Verilog.
13 trong 14
4/20/2015 3:06 PM
http://bikerglen.com/projects/lighting/led-panel-1up/#Afterword
14 trong 14
4/20/2015 3:06 PM