Ludde's FPGA NES: The world's most compact HQ2X in Verilog?
The world's most compact HQ2X in Verilog?
HQ2X is a pretty amazing algorithm that can be used to upsample images from the NES's pretty mediocre resolution of 256x240 into the double, 512x480. It translates one single pixel into a block of 2x2 pixels, by looking at the surrounding pixels and interpolating.
I really wanted to have support for HQ2X in my FPGA NES, so I had to write the algorithm in Verilog. I found some info about HQ2X on the nesdev forums, and it turns out there exists some symmetry in the HQ2X algorithm so that it can be represented pretty compactly in C++. Rewriting it in Verilog while still conserving FPGA resources was a fun but challenging problem!
My VGA core is still clocked at 21.4772Mhz. This means that I need to output one pixel every clock cycle.
I have 2 x 256 pixels of blockram for the input pixels from the PPU, i.e. the two most recent lines seen. I call those Prev and Curr.
the left, and fill C, F, I with the new inputs from PPU or Prev/Curr. Note: You need to apply some special treatment on the edges of the screen, so you never try to read pixels outside of the visible screen area.
HQ2X contains a function diff(), used to compare if two pixels are similar enough. It compares each surrounding pixel against E, and this results in 8 similarity values. These are all packed into one byte called 'pattern'.
(Pipelined, so that when Clock 4 starts, Clock 0 will start processing the next set of input).
I use a 4-line buffer for the output, so while HQ2X writes to row a,b the vga module reads from row c,d and vice versa.
Where Mul1 is a 3-bit value, and Mul2 and Mul3 are 2-bit values. This means I needed only two 2x5 bit multipliers and one 3x5 bit multiplier. Those were cheap enough to implement on LUTs, so I didn't even need to use FPGA DSP units.
which behaves very similar to the HQ2X linked above. (Note: That implementation also differs from the original HQ2X due to the optimizations, so exact accuracy here is not really a must).
The pipelining means that every single resource is used during every clock cycle. The only time they are idling is during the horizontal and vertical blanking periods when no pixels need to be outputted.
And the result on the NES looks fantastic!
The amount of resources utilized is impressive... very compact... A lot smaller than my implementation, it seems that you're indeed a very skillfull programmer.
This is awesome. I hope this goes places.
Hi, thanks for the post this blog it is very helpfull for me. Can you send to me verilog sorce of this project.
Programmer from Gothenburg, Sweden that likes reverse engineering and low level hacking. Some previous programs of mine are µTorrent, ScummVM, OpenTTD.