Skip to content
Jeff Bush edited this page Sep 18, 2016 · 28 revisions

Years ago, a co-worker of mine at Be posted an interesting bit of sample code called Whack. The original article is here:

Issue3-51.html

And source code is here:

Whack.zip

The app allowed the user to enter a formula that would compute the color value per pixel to display patterns on the screen. I've attempted to re-create the application on FPGA.

The compiler reads a C expression and outputs instructions with the following format:

+---------+---------+---------+----------+------+-------------+
| dest(3) | srca(3) | srcb(3) |   op (4) | c(1) |   imm (32)  |
+---------+---------+---------+----------+------+-------------+
  • op Which arithmetic operation to perform, one of:

    opcode operation
    0 or
    1 xor
    2 and
    3 equal
    4 not equal
    5 greater
    6 less than
    7 greater or equal
    8 less than or equal
    9 shift left
    10 shift right
    11 add
    12 subtract
    13 multiply
  • dest Destination register to store result

  • srca first source operand register

  • srcb second source operand register

  • c If true, the second operand will use the constant value instead of a register value

  • imm If c is set, use this constant value as the second operand.

An instruction that is all zeroes is no operation (it corresponds to 'and r0, r0, r0', which has no effect).

A pixel processor has 8 registers:

index usage
0 general purpose scratchpad
1 general purpose scratchpad
2 general purpose scratchpad
3 general purpose scratchpad
4 current x position of pixel (read only)
5 current y position of pixel (read only)
6 frame number (read only)
7 color value (write only)

The hardware latches values written to r7 and displays them on the screen when the program is finished. The processor does support memory accesses or control flow.

The compiler accepts C style expressions. The result of the expression is treated as a 24 bit packed RGB color value.

This system does not have a framebuffer and computes the pixel values on the fly. The FPGA runs at 50 Mhz, but the output VGA clock is 25 Mhz, which means it must generate a pixel every other cycle. Two cycles aren't enough to do anything interesting. The solution is to process eight pixels in parallel. When it finishes computing colors for a batch of pixels, it copies them in parallel into a FIFO, and starts processing the next batch of pixels. The FIFO feeds a pixel to the VGA R2R DAC every other cycle. This means it will execute 16 instructions per pixel. The pixel processor executes 16 instructions from program memory in a repeating loop and doesn't have a notion of program length; the compiler pads the program out with NOPs.

Example test images

Colorbars

Input Expression:

(((x >> 6) & 1) * 0xff) | (((x >> 7) & 1) * 0xff00) | (((x >> 8) & 1) * 0xff0000)

Compiled Code:

shr r0, r4, #6
and r0, r0, #1
mul r0, r0, #255
shr r1, r4, #7
and r1, r1, #1
mul r1, r1, #65280
or r0, r0, r1
shr r1, r4, #8
and r1, r1, #1
mul r1, r1, #16711680
or r0, r0, r1
and r7, r0, r0

Image:
colorbars

Checkerboard:

Input Expression:

(((x >> 4) & 1) ^ ((y >> 4) & 1)) * 0xffffff

Compiled Code:

shr r0, r4, #4
and r0, r0, #1
shr r1, r5, #4
and r1, r1, #1
xor r0, r0, r1
mul r0, r0, #16777215
and r7, r0, r0

Image:
checkerboard

Grid:

Input Expression:

(((x & 0x1f) == 0) | ((y & 0x1f) == 0)) * 0xffffff

Compiled Code:

and r0, r4, #31
eq r0, r0, #0
and r1, r5, #31
eq r1, r1, #0
or r0, r0, r1
mul r0, r0, #16777215
and r7, r0, r0

Image:
grid

Half plane:

Input Expression:

((x - y) > 0) * 0xffffff

Compiled Code:

sub r0, r4, r5
gt r0, r0, #0
mul r0, r0, #16777215
and r7, r0, r0

Image:
halfplane

An interesting pattern from the original article:

Input Expression:

(((x+y)^x)+f)

Compiled Code:

add r0, r4, r5
xor r0, r0, r4
add r0, r0, r6
and r7, r0, r0

Image:
http://www.youtube.com/watch?v=1tVbqoH7bzc&feature=youtu.be