Added graphics mode in the FPGA computer

Added graphics mode to the FPGA computer

This is another follow-up of the original FPGA Computer post.

I have added the graphics mode to the FPGA computer - 320x240 pixels, 8 colors for each pixel. Framebuffer starts at the same address as the text mode one (2400 decimal), but it now displays pixels, instead of characters.

Each pixel can have one of eight colors. There are two pixels per byte in the framebuffer:

7 6 5 4 3 2 1 0
x r g b x r g b

For example, if you want to draw two red pixels at the (0, 0) coordinates (top left corner), you need to put the following byte into the location 2400:

7 6 5 4 3 2 1 0
0 1 0 0 0 1 0 0

Or, 0x44 in hex.

Since the default mode is text mode, I have devised a system to switch video modes:
mov r1, 1
out [128], r1

This code will switch to the graphics mode of 320x240. To switch back to the text mode, you need to execute the following code:
mov r1, 0
out [128], r1



So, number 1 at the port 128 sets the video mode to 320x240, while 0 sets to the text mode.

The implementation in Verilog was not complicated compared to the text mode. First of all, I had to decide which resolution to implement. I have chosen 320x240 with 8 colors, because it consumes 38400 bytes, which is the least amount of memory with the decent resolution and number of colors. I could not have more pixels, since that would consume more RAM than the computer has (64KB).

Even this mode consumes more than half of the available memory, so I can always make other modes not so demanding in memory (reduce the number of colors). For example, having the same 320x240 black and white framebuffer would consume 9600 bytes, or approx. 9KB.

Next, the implementation has two pixels per byte of the framebuffer. So, I have recycled the text mode vga module and used the same two variables: x and y to go through all the pixels of the screen. Then I had to fetch in advance the next word (two bytes - remember, memory is organized in 32KW, having data bus 16 bits wide) containing next four pixels. So the algorithm was simple:


- during the visible scanline processing, the video module fetches next four pixels when displaying the third pixel of the current word in a row
else if (x < 640 && !mem_read) begin
if ((x & 7) == 7)  begin
// when we are finishing current word, 
// containing four pixels, 
// we need to fetch in advance 
// the next word (x+1, y)
// (at the last pixel of the current character,
// let's fetch next)
rd <= 1'b1;
wr <= 1'b0;
addr <= VIDEO_MEM_ADDR + ((xx >> 2)+(yy * 80) + 1);
mem_read <= 1'b1;
end

end 
- during the horizontal blanking, the video module fetches first four pixels at the beginning of the next row
else if ((x >= 640) && (y < 480)) begin
// when we start the horizontal blanking, 
// and we need to go to the next line, 
// we need to fetch in advance the first word 
// in the next line (0, y+1)
rd <= 1'b1;
wr <= 1'b0;
mem_read <= 1'b1;
if ((y & 1) == 1) begin
addr <= VIDEO_MEM_ADDR + ((yy + 1) * 80);
end
else begin
addr <= VIDEO_MEM_ADDR + ((yy) * 80);
end
end
- during the vertical blanking, the video module fetches the first four pixels at the top left corner of the screen (start of the video memory - address 2400 decimal).
if ((x >= 640) && (y >= 480)) begin
// when we start the vertical blanking, 
// we need to fetch in advance the first word at (0, 0)
rd <= 1'b1;
wr <= 1'b0;
mem_read <= 1'b1;
addr <= VIDEO_MEM_ADDR + 0;
end


When we set the addres bus to the address of the word (containing pixels) to be fetched, then we receive that word using the following code:
if (mem_read) begin
pixels <= data;
rd <= 1'bz;
wr <= 1'bz;
mem_read <= 1'b0;
end

Received pixels are stored in the pixels register.

The actual output of the pixels register to the r, g, and b wires of the vga connector is then simple:
if (valid) begin
r <= pixels[12 - ((xx & 3) << 2) + 0] == 1'b1;
g <= pixels[12 - ((xx & 3) << 2) + 1] == 1'b1;
b <= pixels[12 - ((xx & 3) << 2) + 2] == 1'b1;
end
else begin
// blanking -> no pixels
r <= 1'b0;
g <= 1'b0;
b <= 1'b0;
end

The xx and yy variables contain actual x and y positions divided by two. x and y iterate in the 640x480 range, while xx and yy iterate in the range of 320x200:
assign xx = x >> 1;
assign yy = y >> 1;

Assembler example

Assembler examples can be found here.

Here is the assembler example which draws two pixels, three lines and a circle on the screen:

mov r0, 1
out [128], r0  ; set the video mode to graphics

mov r0, 0 ; x = 0
mov r1, 100         ; y = 100
mov r2, 7 ; white color (0111)
call pixel
inc r0 ; x = 1
mov r2, 4 ; red color (0100)
call pixel

mov r2, 4 ; red color (0100)
mov r0, 50 ; A.x = 50
mov r1, 50 ; A.y = 50
mov r3, 150 ; B.x = 150
mov r4, 150 ; B.y = 150
call line

mov r2, 2 ; green color (0010)
mov r0, 50 ; A.x = 50
mov r1, 50 ; A.y = 50
mov r3, 150 ; B.x = 150
mov r4, 50 ; B.y = 50
call line

mov r2, 1 ; blue color (0001)
mov r0, 150 ; A.x = 150
mov r1, 50 ; A.y = 50
mov r3, 150 ; B.x = 150
mov r4, 150 ; B.y = 150
call line

mov r2, 7 ; white color (0111)
mov r0, 150 ; x = 150
mov r1, 150 ; y = 150
mov r3, 50 ; r = 50
call circle

First we switch to the graphics mode (out instruction). Then we draw two pixels. The pixel subroutine has three parameters: r0 (x-coordinate), r1(y-coordinate) and r2 (color). The color is determined by the content that is put in the r2 register. It is 0x7, which means that all three bits of a pixel are set to 1, having the white color. 

Three lines are drawn using the line subroutine. It has five parameters: r0 (x1-coordinate), r1 (y1-coordinate), r2 (color), r3 (x2-coordinate), and r4 (y2-coordinate). The circle subroutine has four parameters: r0 (x-coordinate), r1 (y-coordinate), r2 (color), and r3 (radius).

Lines and circles are created using Bresenham's line and circle algorithms



Here is the snapshot of the emulator:


Conclusion

Adding graphics mode was not that complicated. I have decided to have the 320x240 resolution having each pixel independent of the other (no attributes). That approach consumed quite a lot of memory, but this is not important since this computer will be comparable to the vintage platforms of the 70s and 80s.

The graphics module is on the github.


Comments

Comments powered by Disqus