Building a graphic VGA controller with XGA+ resolution

112 views
Skip to first unread message

Jo mo

unread,
Jan 30, 2025, 11:09:47 PMJan 30
to FPGAwars: explorando el lado libre

Hello guys,

These days I started working on a graphic VGA controller. My initial specifications are:

  • Display resolution: 1024x768 (XGA+). My screen can go up to 1280x1024 (SVGA), but it requires about 100 MHz of pixel rate. However, I am getting a bit of flickering on the screen with my RGB hardware, so I decided to stay with a pixel rate of about 62 MHz.

  • 4-bit color graphics: My FPGA board outputs RGB444 (12 bits). To decrease the size of the future necessary video RAM and also the FPGA LUT used, I decided to lower the 12 bits to only 4 bits. This limits the number of colors to 16, but to have more choices of colors, I decided to use palettes of colors. This means that I am using palettes of any 16 colors chosen from the 4096 (12 bits) possible colors.

Remark: I made this choice because in my application I do not really need to display realistic images (photos). For now, I just want to be able to display computer GUI-like pictures.

In my actual design, I am displaying:

  • A background color, which can be chosen from the entire RGB444 palette (4096 colors).

  • Above, on layer 0, a rectangle with the 16 colors of 4 different color palettes, with a palette change every ~3 seconds.

  • Above, on layer 1, I superimposed an image of one of our Jedi masters.

  • Above, on layer 2, I superimposed a higher resolution, dithered image of the same master. I also put on the same layer the same image but moving!

For that image of Obi-Juan, I used MATLAB to convert a colored PNG file into a monochrome-dithered image file, and then used a Python script to process it. The Python script (written by ChatGPT ;P ) removes all the line returns and adds a space between each bit, which gives a good binary format for IceStudio!

You can see a video and  image of my actual results here.  and i am joining the .ice file

Jedi_master on XGA.JPG

@Obijuan : I hope you are fine my friend !, and sorry for using your picture without asking you the rigths! ;-))

As you can see on the video, i have a bit of flickering mostly visible on the high-res pictures. maybe a syncing problem or an electrical noise noise (62mhz on 2layers pcb) ?

Currently, the resource usage is 731 LUTs on ECP5. We also need 5 x 16kbit BRAM blocks (2 for the first image, 3 for the second higher-resolution and bigger images). So it should barely fit on the Alhambra 2. If you have problems loading it on this board, just disconnect one of the 2 picture layers.

Next, I will try to work on:

  • Generating the contents of a layer with the help of a processor core (and its assembler program). Big Task !!!

  • Maybe using a simple software compression algorithm (like RLE) to compress (in the computer) a simple monochrome images. like :

              wrIlBg6e.bmp

         This decrease in size will make it easier to fit more stuff in our BRAM. Then I will use a decompression fpga circuit to get back a usable image (on my layers system).

  • Then I will see if using the SRAM (of my Colorlight ECP5 board) can help (video ram buffer) !

  • Remark:  the storage of those image is not simple, if Bram is not inferred, they are stored in about  3000 LUTS (for my two monochrome images), so better use Bram. But as this one is also very limited, the use of external Ram should be investigated !

So still a long way to go... ;-)

Remark: To run it on an ICE40 board you will need to:

- Make your own ICE40 PLL (SB_PLL40_CORE). because In this design, I am using an ECP5 PLL primitive to generate the 62 MHz.

- Of course, you will also need a RGB444 VGA output hardware.

Big hug guys!

test vga palette 4 vga1024x768-juan_bram-alphalayers.ice

charli va

unread,
Jan 31, 2025, 1:31:48 AMJan 31
to fpga-wars-explora...@googlegroups.com
Hi Joaquim!!! great work!!! i want to replicate it but give me some days to it.

Thanks for share your great work!

--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.
Para ver este debate, visita https://groups.google.com/d/msgid/fpga-wars-explorando-el-lado-libre/090e8de2-e11e-4362-9713-d85310176772n%40googlegroups.com.

Obijuan

unread,
Jan 31, 2025, 4:00:55 AMJan 31
to FPGAwars: explorando el lado libre
Amazing work Joaquim! Thanks for sharing!

@Obijuan : I hope you are fine my friend !, and sorry for using your picture without asking you the rigths! ;-))


Everything is fine.  No problem with the image. All the stuff I publish is always under open licenses and can be used, modified and shared by anyone without asking for permission  😉


Democrito

unread,
Jan 31, 2025, 5:53:02 AMJan 31
to FPGAwars: explorando el lado libre
Great job Joaquim,

I have no idea about VGA stuff, but I've seen your circuit and it seems very well structured, and people with knowledge could easily modify it for their needs.

Thanks for this great gift and a big hug!

Jesus Arias

unread,
Jan 31, 2025, 4:51:38 PMJan 31
to FPGAwars: explorando el lado libre
Hi, Joaquim
I tough the pixel rate for the XGA mode was 65 MHz, not 62. Your clock is about a 5% slower. Could this have something to do about the flicker?
Another possible cause is the stability of the clock. Internal oscillators have a lot of phase noise and are ill suited for analog video. Maybe the ECP5 is using some of these? (I mean the reference clock, not the PLL)
Nice demo, anyway ;)

Jo mo

unread,
Jan 31, 2025, 7:31:47 PMJan 31
to FPGAwars: explorando el lado libre
Hola Jesus,

Yes you are right the exact frequency for XGA is 65 mhz
But as the closest frequency that clarity designer(Lattice software) proposed me when i asked for 65mhz was 62.5mhz. 
And as in this calculator i got frequencies were between (51.27mhz  to  68 Mhz)

Capture.JPG

So wentwith that 62.5 mhz. remark: my monitor aspect ratio is 5:4

But just now looking more carefully, if i enter 66mhz in clarity designer it give me a pll configuration of 66.6Mhz (so 0.8mhz closer to the 65 Mhz target)
So i used this new 65 mhz pll block and used the syncing parameters from here

And now i am getting about 50% less flickering!
So it almost perfect, the remaining flickering is almost only visible on the "high" resolution Obijuan dithered monochrome image. 
It is really only an horizontal flickering, when two following pixels have two different colors to display .

For the internal clock stability, in this circuit i used only the pll output.
And the PLL himself should be supplied with the external 25mhz on the colorligth i5/i9 board !?

I will play a bit more with this !

Thanks for your inputs, they are always great ;-)

Big hug

charli va

unread,
Jan 31, 2025, 8:10:58 PMJan 31
to fpga-wars-explora...@googlegroups.com
Great calculator! maybe we could embed in icestudio ;)

Jesus you are "the dart in the circuit" ;)

--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Jesus Arias

unread,
Feb 1, 2025, 4:19:52 AMFeb 1
to FPGAwars: explorando el lado libre
>>It is really only an horizontal flickering, when two following pixels have two different colors to display .
It looks to me like sampling artifacts. Are you using the number of pixels from tinyVGA (HTOTAL=1344 pixels) ? For LCD screens it is a good idea to use exactly these numbers even if the clock frequency is a little different because the monitor has to sample the video signal exactly in the center of the pixels, and, if the total number of pixels is different it can sample the pixel boundaries. (For the screen the line has to have 1344 pixels, no matter what the time the line takes (+-5% only). This would be no problem for a true analog CRT monitor)

Have a nice day

Jo mo

unread,
Feb 26, 2025, 2:24:53 PMFeb 26
to FPGAwars: explorando el lado libre
Hello guys,

Some news on my VGA/XGA journey!

I have no flickering at all when i use 50mhz, but in that case it is not possible to go up to the XGA resolution, and we have to stick to 800x600.

Nevertheless, by using the next frequency available on my ECP5 PLL (58mhz), and adjusting  the sync values "accordingly" ! To get the 1024x768 resolution. I managed to bring down (almost fully) the flickering.
It works fine on two of my LCD  screens ( 19 inches,5:4 ratio). i need this format for my project!

Maybe using an external clock oscillator wit a frequency between 50mhz and 58mhz, i could find a sweeter spot! but for now i will stay with that!

But for other use cases (different screens, different fpga (and pll frequencies), different PCB routings of the vga circuit),...), Has wrote Jesus, it is safer to stick to the values found on tinyfpga website.

In the joined ice file, i updated:
- i updated the sync values to my use case
- changed most of the code block to icestudio blocks ( it helps for a faster icestudio navigation as there are less recalculations of graphics)
- PLL block with a selectable pll frequency
- Only on image block which is made to load in BRAM and handle monochrome image data generated by the python code image_to_monochrome.py (se details bellow)

With this new python code/ image block combo, we have the use of an automatic RLE convertion of the images in the cases where this conversion is more efficient in terms of size!
Here are 3 uses cases. of the python script

Source: bmp image(RGB888) 907x 538pixels (RGB888)
A -source 1: "obijuan" high-details colored rgba 8x8x8x8, png(compressed format) picture, (63kB),   image size 220x180 pixels= 39'600 pixels
python image_to_monochrome.py obijuan2.png
Figure_A.png

B -source: "octopus-big" image: almost monochrome rgba 8x8x8 mid-details bmp picture (1.5 MB)   image size 907x538 pixels= 487'966 pixels
python image_to_monochrome.py poulpegro.bmp
Figure_gros poulpe.png

C -source: "octopus-small" image:  monochrome rgb 8x8x8 low-details bmp picture (787 kB)   image size 512x512 pixels= 262'144 pixels
python image_to_monochrome.py poulpeSml.bmp
Figure_smal poulpe.png


Conclusions:

For high details colored source images, 
  - the dithering give better image quality 
  - RLE compression is very bad in terms of final data size!
For simple monochrome images: 
  - The dithering is not very interesting!
  - The RLE compression reduces the size of data in Bram  by a factor of 3    ( the theoretical absolut maximum factor for this 8 bits rle compression is around 15.8  ( for very-very simple images (eg: all pixels white or all black) )


Remark 1: The size of the .txt files are much bigger then the final BRAM usage the reason is that readmemh takes as input an ascii file with spaces characters added. And in our case, if using RLE compression, we put hexadecimal value in the .txt file, instead of binary. So my advice, do not try comparing at all the sizes of .txt files
That is why i added the BRAM usages info in the plots generated python script and in the names of .txt generated

Remark 2: For now, we need to update manually in our design the size of Bram to be allocated for a picture (in Max_size parameter input). I wanted to update the amount of allocated BRAM automatically at build time by reading the size of the file stored inside the file itself but unfortunately, i could not managed to do it ;-(.  
If someone knows how we can allocate a Bram size at build-time with a value stored in a file! Just let me know and i will update the python script for generating that.

Remark 3: The python code was 99% generated with the assistance of an AI language model. and my human oversight was applied to verify and refine the outputs, ensuring it meets the specific requirements.  Yes, i prefer to concentrate on the creative/ideas part of the think ;-)


In this  Xga_1024x768_58mhz_Lab_1.ice  file i am loading 3 different images in Bram it uses 82% of the Colorligth i5 Bram
To use it in in Alhambra, i made the Xga_1024x768_58mhz_Lab_1-for Alhambra.ice, Where i just reduced the allocation of Bram for the 2 octopus images, resulting in cropped&tilled images. AND again, you need to Make your own ICE40 PLL (SB_PLL40_CORE). because In this design, I am using an ECP5 PLL primitive to generate the 58 MHz.

I joined in the zip file, all you need to play with this.

Here is a little video of this "Lab_1".   Sorry for the bad footage, i am not ready for an Hollywood career ;-)

Todo:
  -  use sdram (like rob did ;-)  and sdcard  to store the images datas)
  - "16 colors images" block (with some type of RLE compressions).
  -  image rotation (with sprites technique or rotation calculation logic)
  - ...
 
  The list is increasing.... As always, more you discover, more it remains to be discovered ;-)

Big hug guys!
Xga_1024x768_58mhz_Lab_1.zip

charli va

unread,
Feb 26, 2025, 3:08:26 PMFeb 26
to fpga-wars-explora...@googlegroups.com
Wow Joaquim!!! great progress!! Congratulations!!

And thanks for the nice and detailed explanation of the work, I love the details! and the video shows very smooth!

About dithering color images and RLE... they are opposites!  It's like you want to mix water and oil.  

In case someone does not know RLE or dithering and is interested, I will try to explain why it gave you these results. 

Dithering scatter dots attempt to generate a "visual illusion" to hack your brain , transforming a color in a dense map of points, depending the color (darker more joined points, ligher, more scattered or with defined patterns). There are a lot of dithering algorithms ( i think for the kind of points you are using floid-steinberg)  including this awesome 3d dithering:


In other hand RLE packs equal contiguous colors (for example, if you have 9 red pixels together, instead of saving 9 pixels, for example, each pixel has 8 bits in a 256-color palette, it saves 72 bits, but with rle it saves only 1 byte for the pixel and 1 byte to store how many times this color repeats, in this case it only saves 2 bytes. In that case, it optimizes a 72-bit register to 16.

For color images or grayscale image (basically is an image with a gray color palette) dithering generates a non contiguous same color points. For this  RLE compresses very bad, because there are very few contiguous same color points.

 In other hand RLE works very well when there are similar contiguous areas for example in black&white images, because there are many identical points in a row.

Sorry for the pain! XD

Thanks again and let us know, your journey is amazing!


--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Jo mo

unread,
Feb 27, 2025, 4:09:04 AMFeb 27
to fpga-wars-explora...@googlegroups.com
Hola Charli,

Thanks a lot for the theoretical details you added! They are very useful for a good understanding of what is going on here! I was a bit stingy on that matter. :-)
Yes, as you wrote, dithering and RLE are quite "opposite," and that Python script is aimed at helping the user to find the right trade-off between "perceived quality" and resource cost !

Thanks also for the link to that very interesting video. Hopefully, on my side, I will not need to go to 3D+motion, so the simplicity and resource cost of the design will stay reasonable!

Have a great day guys!

charli va

unread,
Feb 27, 2025, 4:36:01 AMFeb 27
to fpga-wars-explora...@googlegroups.com
Thanks for your Python scripts are very clear and is awesome that you share your path , we learn all together.

And dont worry the fractal dithering vídeo was to show what cool things we could do with this apparent old algorithms.

And about your footage… its awesome!! Underground at all!! I love it. 

Good day!!!

Reply all
Reply to author
Forward
0 new messages