Приказивање постова са ознаком video. Прикажи све постове
Приказивање постова са ознаком video. Прикажи све постове

среда, 18. септембар 2019.

Added new VGA graphics mode

This is a follow-up of my original FPGA computer post.

FPGA computer has got a new VGA mode: 640x480 in two colors. One byte of the video memory holds 8 pixels, each being 1 or 0 (white or black):

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

If you want to put four white and four black pixels at the top left corner of the screen (from the (0,0) to the (7,0) coordinates), you need to type:

mov.w r0, 0xF0
st.b [1024], r0

This mode is made out of existing VGA text mode, since it does almost all the job. The text mode shows characters made of 8x8 pixels on the 640x480 VGA screen. I have inserted additional Verilog code inside the text mode module, in a way that when the 640x480x2 mode is set, it shows the pixels, not the characters.

First of all, the programmer needs to set the display mode to 640x480x2:

mov.w r0, 2
out [128], r0

The VGA module detects this and changes the signal generation on the VGA connector:

if (valid) begin
  if (vga_mode == 0)  begin
    r <= inverse ^ (pixels[7 - (x & 7)] ? !curr_char[6+8] : curr_char[2+8]);
    g <= inverse ^ (pixels[7 - (x & 7)] ? !curr_char[5+8] : curr_char[1+8]);
    b <= inverse ^ (pixels[7 - (x & 7)] ? !curr_char[4+8] : curr_char[0+8]);
  end
  else if (vga_mode == 2) begin
    r <= inverse ^ (curr_char[15 - (x & 15)]);
    g <= inverse ^ (curr_char[15 - (x & 15)]);
    b <= inverse ^ (curr_char[15 - (x & 15)]);
  end
end 

What we see above is the Verilog code that sets the R, G and B wires of the VGA connector to the corresponding values, depending of the display mode. If the mode is text (vga_mode == 0), it outputs the font pixels of the character that was found in the video memory (pixels module returns actual pixels of the current_char register). However, if the mode is graphics (vga_mode == 2), then the actual byte found in the video memory is outputted to the wires (actual bits of the byte are pixels).

All this means is that current_char register holds two bytes from the video memory, and it is periodically loaded from the video memory. At the beginning, it is loaded from the very first word of the video memory, and after that VGA module loads two bytes of the video memory periodically:
- at the end of each character in text mode, it fetches the next character,
- at the end of each 16 pixels of the graphics mode, it fetches next 16 bits (pixels),
- at the end of each scan line it fetches the content of the beginning of the next scan line,
- at the lower right corner scanline end, it fetches the content of the top left corner.

This is the photo of the actual monitor:

And, this is the screenshot of the emulator:


Conclusion

Before this feature was introduced, the FPGA computer had two video modes:
- text 80x60 mode characters text mode (made of 640x480 pixels and each character is 8x8 pixels in size).
- graphics 320x240 mode, each pixel being in one of 8 colors.

This new mode is added to the existing VGA text module (80x60 characters) since that module already works with 640x480 pixels. The only additional thing was to show pixels, not characters. So, the computer now has one more mode: 640x480 in two colors (black and white). You can look at the Verilog code:

https://github.com/milanvidakovic/FPGAComputer32/blob/master/vga_module.v

And, you can look at the assembly code which draws everything here:

https://github.com/milanvidakovic/Assembler32/blob/master/raspbootin/graphics640.asm

уторак, 21. август 2018.

Text mode in the FPGA computer

How text mode works


This is a follow-up of the FPGA computer post. 

In this post I will give more details about the text mode of the FPGA computer. The text mode is the default mode for the computer. When the computers powers up, this is the default mode.

Text mode is 80x60 characters, occupying 4800 words, or 9600 bytes, starting from the address of 2400

Lower byte is the ASCII code of a character, while the upper byte contains the attributes:

7

6

5

4

3

2

1

0

Foreground color, inverted

Background color

x

r

g

b

x

r

g

b


The foreground color is inverted so zero values (default) would mean white color. That way, you don't need to set the foreground color to white, and by default (0, 0, 0), it is white. The default background color is black (0, 0, 0). This means that if the upper (Attribute) byte is zero (0x00), the background color is black, and the foreground color is white.

I have used Ken Shirriff's blog post FizzBuzz a lot for this implementation. I highly recommend his posts!

Verilog implementation relies on the character ROM. Character ROM is implemented as a separate Verilog module, and is used like this:
// Character generator
chars chars_1(
  .char(curr_char[7:0]),
  .rownum(y[2:0]),
  .pixels(pixels)
); 

Current character (which is read from the address of 2400, up to the 2400+9600) is received in the curr_char register. This register is wired to the chars module, together with two additional parameters: rownum (wired to the y register - the y coordinate) and the pixels output register (this register will hold the pixels of the current character, for the current y coordinate).

The chars module itself is a giant switch statement:
always @(*)
  case ({char, rownum})

    11'b00110000000: pixels = 8'b01111100; //  XXXXX  
    11'b00110000001: pixels = 8'b11000110; // XX   XX 
    11'b00110000010: pixels = 8'b11001110; // XX  XXX 
    11'b00110000011: pixels = 8'b11011110; // XX XXXX 
    11'b00110000100: pixels = 8'b11110110; // XXXX XX 
    11'b00110000101: pixels = 8'b11100110; // XXX  XX 
    11'b00110000110: pixels = 8'b01111100; //  XXXXX  
    11'b00110000111: pixels = 8'b00000000; //         

    11'b00110001000: pixels = 8'b00110000; //   XX    
    11'b00110001001: pixels = 8'b01110000; //  XXX    
    11'b00110001010: pixels = 8'b00110000; //   XX    
    11'b00110001011: pixels = 8'b00110000; //   XX    
    11'b00110001100: pixels = 8'b00110000; //   XX    
    11'b00110001101: pixels = 8'b00110000; //   XX    
    11'b00110001110: pixels = 8'b11111100; // XXXXXX  
    11'b00110001111: pixels = 8'b00000000; //       

As you can see, the input character and the y coordinate are concatenated to determine which row of pixels will be returned to the vga text module.

How is the current_char obtained? There are three distinctive situations when this byte is obtained:
1. during the visible scanline processing. During this case, we wait for the last column (pixel) of the current character to be displayed, and then we fetch the next character:
else if (x < 640 && !mem_read) begin
 if ((x & 7) == 7) begin
  // when we are finishing current character, 
  // we need to fetch in advance 
  // the next character (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 + ((x >> 3) + (y >> 3)*80 + 1);
  mem_read <= 1'b1;
 end

end 
2. during the horizontal blanking. During this case, we need to obtain either the current character (we haven't finished the current row yet), or the next character in the next row:


else if ((x >= 640) && ((y & 7) < 7)) begin
// when we start the horizontal blanking, 
// and still displaying character in the current row,
// we need to fetch in advance 
// the first character in the current row (0, row)
rd <= 1'b1;
wr <= 1'b0;
addr <= VIDEO_MEM_ADDR + ((y >> 3)*80);
mem_read <= 1'b1;
end
else if ((x >= 640) && ((y & 7) == 7)) begin
// when we start the horizontal blanking, 
// and we need to go to the next line, 
// we need to fetch in advance the first character in next row (0, row+1)
rd <= 1'b1;
wr <= 1'b0;
addr <= VIDEO_MEM_ADDR + (((y >> 3) + 1)*80);
mem_read <= 1'b1;
end



3. during the vertical blanking. In this case, we need to fetch the first character, at the beginning of the frame buffer:

if ((x >= 640) && (y >= 480)) begin
// when we start the vertical blanking, 
// we need to fetch in advance the first character (0, 0)
rd <= 1'b1;
wr <= 1'b0;
addr <= VIDEO_MEM_ADDR + 0;
mem_read <= 1'b1;
end

The code above sets the address bus and control lines. The character is then fetched from the data bus:
if (mem_read) begin
curr_char <= data;
rd <= 1'bz;
wr <= 1'bz;
mem_read <= 1'b0;
end

The character is wired to the character ROM, and the output is placed in the pixels register. From that point, the pixels are shifted bit by bit to the r, g, and b wires of VGA connector:
if (valid) begin
r <= pixels[7 - (x & 7)] ? !curr_char[6+8] : curr_char[2+8];
g <= pixels[7 - (x & 7)] ? !curr_char[5+8] : curr_char[1+8];
b <= pixels[7 - (x & 7)] ? !curr_char[4+8] : curr_char[0+8];
end 
else begin
// blanking -> no pixels
r <= 1'b0;
g <= 1'b0;
b <= 1'b0;
end

It is interesting how horizontal and vertical sync pulses are generated:
assign hs = x < (640 + 16) || x >= (640 + 16 + 96);
assign vs = y < (480 + 10) || y >= (480 + 10 + 2);
assign valid = (x < 640) && (y < 480);

Just  by wiring hs an vs one-bit registers to the VGA connector and by assigning to them expressions above, horizontal and vertical sync pulses are generated according to the current state of the x and y counters. When the x counter reaches 640 + 10, it is the end of the current scanline and the hs pulse is low (inverted logic). Similarly, the vs pulse is low when the y counter (the line counter) reaches 480 + 10.

If you look at the value range of the x and y registers, you will see that the x goes from 0 to 799, while y goes from 0 to 524. This means that the actual resolution of the VGA 640x480 mode is 800x525. However, during the horizontal and vertical blanking some of those pixels (and also lines) are not visible, so the actual visible pixels are from the 640x480 range. That is detected in the "assign valid =..." line of the code above. 

Programming in assembler

Assembler examples can be found here.

Following assembler code writes a string with color attributes.
mov r1, hello  ; r1 holds the address of the "Hello World!" string
mov r2, 0      ; r2 is the index
mov r3, 0      ; r3 has the attribute
again:
ld.b r0, [r1]  ; load r0 with the content of the memory location to which r1 points (current character)
cmp r0, 0      ; if the current character is 0 (string terminator),
jz end         ; go out of this loop 
st.b [r2 + VIDEO_1], r3; store the attribute
inc r2        ; move to the character location
st.b [r2 + VIDEO_1], r0; store the character at the VIDEO_0 + r2 
inc r1        ; move to the next character in the string
inc r2         ; move to the next location (attribute) in video memory
inc r3         ; change the attribute of the current character
j again        ; continue with the loop
end:
halt


hello:

#str "Hello World!\0"



The result is on the image below.


In the emulator, it looks like this:


Conclusion

The text mode is implemented by reading the character from the framebuffer, and then by obtaining its pixels from the character ROM. When those pixels are obtained, they are shifted one by one to the VGA connector, to the corresponding r, g and b wires. That way, the character is shown on the screen. I have implemented the text mode first, and then l have implemented the graphics mode. Both modes are surprisingly simple to be implemented in Verilog.

Text mode module is on the github.


четвртак, 10. мај 2018.

Generating VGA video signals using Raspberry Pi and then FPGA

I have recently found couple of posts (for example, this and this) on the net about generating VGA signals using just CPU. All those posts talk about generating 640x480 VGA video signals using Arduino.
I have decided to try to generate VGA signals using Raspberry PI 3. I have read that you can actually get the VGA output from the RPI without much problems, since it has that feature built in the Broadcom SoC. However, I didn't want to use the built in feature; I wanted to generate signals myself.
It proved to be quite difficult. First I tried the lowest possible level library, under the Raspbian OS:

// Set GPIO pins to output
INP_GPIO(VIDEO); // must use INP_GPIO before we can use OUT_GPIO
OUT_GPIO(VIDEO);
INP_GPIO(H_SYNC); // must use INP_GPIO before we can use OUT_GPIO
OUT_GPIO(H_SYNC);
INP_GPIO(V_SYNC); // must use INP_GPIO before we can use OUT_GPIO
OUT_GPIO(V_SYNC);

vSyncLow();
hSyncLow();
vgaOff();
nsleep(99999999);
while(1) {
line = 0;
vSyncLow();

while(line < 600){
    //2.2uS Back Porch
    delayMicroseconds(2);  
    
    //20uS Color Data
    vgaOn();  //High
    delayMicroseconds(10); // 1uS        
    //Red Color Low
    vgaOff();  //Low
    delayMicroseconds(10); // 1uS        
    
    //1uS Front Porch
    delayMicroseconds(1); // 1uS 
    line++;
    
    //3.2uS Horizontal Sync
    hSyncHigh();  //HSYNC High
    delayMicroseconds(3);
    hSyncLow();  //HSYNC Low
    
    //26.4uS Total
  }
  //Clear the counter
  line=0; 
  //VSYNC High
  vSyncHigh();
  //4 Lines Of VSYNC   
  while(line < 4){         
    //2.2uS Back Porch    
    delayMicroseconds(2);
    
    //20 uS Of Color Data
    delayMicroseconds(20);// 20uS
    
    //1uS Front Porch
    delayMicroseconds(1); // 1uS
    line++;
    
    //HSYNC for 3.2uS
    hSyncHigh();  //High
    delayMicroseconds(3);
    hSyncLow();  //Low  
    
    //26.4uS Total
  }
  
  //Clear the counter
  line = 0;
  //VSYNC Low
  vSyncLow();
  //22 Lines Of Vertical Back Porch + 1 Line Of Front Porch
  while(line < 22){
      //2.2uS Back Porch
      delayMicroseconds(2);

      //20uS Color Data
      delayMicroseconds(20);// 20uS
        
      //1uS Front Porch
      delayMicroseconds(1); // 1uS
      line++;
      
      //HSYNC for 3.2uS
      hSyncHigh();  //High
      delayMicroseconds(3);
      hSyncLow();  //Low  

      //26.4uS Total
  }     
}

The result was this:


The monitor has some physical damage, but that is not the problem. The problem is the bad synchronization. The timing is critical. Since the pixel frequency is approx. 25MHz, the time for a single pixel is 40 nanoseconds. In the picture, it is obvious that the lines do not start at the same time, which means that the horizontal sync pulses do not start at the precise time. They miss their time for couple of hundred of nano seconds.

OK, this could be due to the multitasking in the Linux OS. So, I moved to the bare metal programming. I have found a nice bare metal library on the github, here:


Author did a great job of making nice, readable examples. I have modified one of his examples and made a VGA signal generator using the built in interrupt generator, which is triggered every 3 microseconds:

// clear pending irq
*ARMTIMER_ACQ = 1;
//*TIMER_BASE = 2;
//printf("Inside dbg_main, counter: %d ", counter);
if (counter == 0) {
GPIO_SET(V_SYNC);
GPIO_SET(H_SYNC);
  GPIO_SET(VIDEO);
} else  
if (counter == 1) {
GPIO_CLR(H_SYNC);
  GPIO_CLR(VIDEO);
} else 

if ((counter % 8) == 0) {
GPIO_SET(H_SYNC);
  GPIO_SET(VIDEO);
} else 
if ((counter % 8) == 1) {
  GPIO_CLR(VIDEO);
GPIO_CLR(H_SYNC);

if (counter == 17) {
GPIO_CLR(V_SYNC);
}

counter++;
if (counter == 4700) {
counter = 0;
}

However, the picture was not much better:


The timing is a bit better, but still horizontal sync pulses manage to miss the right time to fire.

I did all of this while waiting for the DE0-NANO FPGA board, which I choose to play with in order to generate VGA signals. When it finally arrived, I was able to properly generate VGA signals:


Then I have added the color and some text (top left corner):


Damage on the LCD is visible here, but it is OK for the development.

Here is the Altera DE0-NANO FPGA board:


I have purchased a female VGA connector and connected GPIO pins from the board to the connector:
  • GPIO_R -> 68Ohm -> VGA_R
  • GPIO_G -> 68Ohm -> VGA_G
  • GPIO_B -> 68Ohm -> VGA_B
  • GPIO_HS -> 470Ohm -> VGA_HORIZONTAL_SYNC
  • GPIO_VS -> 470Ohm -> VGA_VERTICAL_SYNC

I have found various values for the resistors on various sites (from direct connections, to 68 Ohms, 100Ohms, 500 Ohms, etc.), but this schematics works for me.

The Verilog code is quite simple. I have recycled the FizzBuzz example made by Ken Shirriff:

module vga(
//////////// CLOCK //////////
input CLOCK_50,  // this is 50MHz clock
//////////// KEY //////////
input KEY,             // reset key (one of two onboard keys)
//////////// GPIO //////////
output reg r,
output reg g,
output reg b,
output wire hs,
output wire vs
);

//=======================================================
//  REG/WIRE declarations
//=======================================================
reg clk25; // 25MHz signal (clk divided by 2)
reg newframe;
reg newline;

reg [9:0] x;
reg [9:0] y;
wire valid;

reg [7:0] xx;
reg [7:0] yy;

reg [7:0] framebuffer [9:0]; // 10  bytes text-based framebuffer
wire [6:0] counter;
wire [7:0] pixels; // Pixels making up one row of the character
//////////// GPIO //////////
output reg r,
output reg g,
output reg b,
output wire hs,
output wire vs
);
//=======================================================
//  REG/WIRE declarations
//=======================================================
reg clk25; // 25MHz signal (clk divided by 2)
reg newframe;
reg newline;

reg [9:0] x;
reg [9:0] y;
wire valid;

reg [7:0] xx;
reg [7:0] yy;

reg [7:0] framebuffer [9:0];
wire [6:0] counter;
wire [7:0] pixels; // Pixels making up one row of the character

//=======================================================
//  Structural coding
//=======================================================
initial begin
framebuffer[0] = "0";
framebuffer[1] = "1";
framebuffer[2] = "2";
framebuffer[3] = "3";
framebuffer[4] = "A";
framebuffer[5] = "a";
framebuffer[6] = "B";
framebuffer[7] = "b";
framebuffer[8] = "8";
framebuffer[9] = "9";
end
// Character generator

chars chars_1(
  .char(framebuffer[counter]),
  .rownum(y[2:0]),
  .pixels(pixels)
  );

assign hs = x < (640 + 16) || x >= (640 + 16 + 96);
assign vs = y < (480 + 10) || y >= (480 + 10 + 2);
assign valid = (x < 640) && (y < 480);
assign counter = (valid)?(x >> 3):0;

always @(posedge CLOCK_50) begin
newframe <= 0;
newline <= 0;
if (!KEY) begin
x <= 10'b0;
y <= 10'b0;
clk25 <= 1'b0;
newframe <= 1;
newline <= 1;
end
else begin
clk25 <= ~clk25;
if (clk25 == 1'b1) begin
if (x < 10'd799) begin
x <= x + 1'b1;
end
else begin
x <= 10'b0;
newline <= 1;
if (y < 10'd524) begin
y <= y + 1'b1;
end
else begin
y <= 10'b0;
newframe <= 1;
end
end
end
end

if (valid) begin

if (x < 80 && y < 8) begin
r <= pixels[7 - (x & 7)];
g <= pixels[7 - (x & 7)];
b <= pixels[7 - (x & 7)];
end
else begin
r <= (x < 213) ? 1 : 0;
g <= (x >= 213 && x < 426) ? 1 : 0;
b <= (x >= 426) ? 1 : 0;
end
end
else begin
// blanking -> no pixels
r <= 0;
g <= 0;
b <= 0;
end
end
endmodule

Verilog programming is not simple. It has a steep learning curve. The other problem can be a long compile time in the Quartus II IDE. For a bit more complex code than this VGA project, the compile time easily exceeds couple of minutes. I have solved this problem by installing the Icarus Verilog software, which compiles the Verilog code in a fraction of a second. This is due to the fact that the Icarus Verilog is not intended to deploy the Verilog code to the actual hardware - instead, it is intended for the simulation only. This way, I am able to produce the running and correct code quickly, and then I can copy that code into the Quartus II IDE, build the project, and deploy it to the real hardware.