Monday, September 4, 2023

VHDL Bitmap Rendering


This is a brief overview of using VHDL to render bitmap images on a VGA display. It is far from being a complete tutorial on working with VHDL. The presentation was developed using the Xilinx Vivado suite, targeting the Arctix A7 FPGA on the Digilent Basys 3 development board.
 

My first experiment with rendering a bitmap image from an FPGA device was based on using the builtin file I/O operations available in VHDL. In VHDL it is possible to read the content of a bitmap image from a file, parse the bitmap header to obtain the layout of the image (height, width, color encoding etc.), and then populate a ROM entity in VHDL with the bitmap data, and of course build a VHDL circuit around that ROM to render the image to a VGA display. Turns out that this method really slows down the synthesis operation, and even the small image shown above requires several minutes to generate a bitstream for programming the FPGA. However it is quite interesting to see the capability of VHDL code being used not just to describe digital logic circuits, but for "mundane" operations such as file I/O!

For the purpose of designing microprocessor based systems in VHDL, one will no doubt want to write a program for the target CPU, and then have a means to infer a ROM in the VHDL design containing the image of the compiled code. For example, the t80 distribution has a utility program provided by the author called hex2rom that can take a source hex or binary image and generate a suitable VHDL entity. hex2rom works just as well to convert a bitmap file to VHDL and provides options to specify the basic necessary attributes such as endianness, address width and so on. 

First significant problem encountered has to do with using hex2rom to generate ROM in synchronized mode. In many cases, the sync ROM is more desirable as it is more likely to infer a Block RAM. However, hex2rom registers the ROM address internally, which causes the ROM to output a pixel data that is 1 clock behind the screen row/column address from which the pixel address input to the ROM was calculated. Before:


After many days, finally realized from running the simulation what was going on. The fix is to add 1 to the allowed column range of the image enable signal in each scan-line, forcing the image display logic to sync with the actual pixel data coming out of the ROM.  


Hard to explain in words, more details to come. And that's not the last problem to be solved. We also find that Vivado is spewing a lot of warnings related to the BRAM that the synthesis tool has inferred.


In relatively small and simple circuits these warnings can possibly be ignored and the circuit ends up working as intended, but the cause should be understood and corrected. In this case, the tool doesn't think that the address inputs to the inferred BRAM are properly synchronized (the Xilinx Synthesis Tool guide or similar documentation provide a lot of information on how to setup your design to infer properly working BRAMs, without incurring a bunch of DRC warnings!).