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

среда, 31. јул 2019.

FPGA computer boots from the SD card

FPGA computer boots now from the SD card

This is a follow-up of my first text about 32-bit FPGA computer.

The 32-bit FPGA computer now has a kind of a hard disk. ESP32 is used as a hard disk controller (I have designed the board to work with the Arduino as well - you can see the empty socket for the Arduino to the right of the ESP32), while the SD card is used as a kind of a hard disk.


The FPGA computer with the ESP32-Arduino board is on the images above and below.


Initially, it was just the Arduino:



Since the FPGA computer is designed to boot from the serial port, I have created a kind of a hard disk controller, which consists of an ESP32 and SD card reader. When started, ESP32 lists files in the root folder of the SD card, and if it finds the file "BOOT.BIN", it reads it and sends the content to the FPGA computer over the serial port, using the Raspbootin protocol (as described in one of my previous posts).


The video above shows the boot from the powerup. Next video shows boot from the reset.


The code is available at the github.

This is the schematics:

The main idea is that the controller behaves just like the PC from which the FPGA computer initally gets its boot code. During the powerup, the FPGA computer will try to load the boot code from the serial interface, using a modified Raspbootin protocol. On the other side of the serial cable, there can be a PC with the corresponding Raspbootin client, or, just like in this case, the microcontroller, which has the following functionality:
  1. wait for the Raspbootin start sequence,
  2. look for the BOOT.BIN file in the SD card, and
  3. send that file content over serial cable, using the Raspbootin protocol.
The FPGA computer would then boot from the loaded BOOT.BIN file. The BOOT.BIN file is the assembled file obtained using the custom assembler also available on my github page.

Conclusion

With this board, the FPGA-based computer can now boot from the SD card. Next step would be to extend the code so it would be used as a real SD controller, allowing the FPGA computer to open files, read, write, delete, etc.


субота, 24. новембар 2018.

Hardware sprites on the FPGA computer

Adding hardware sprites

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

I have added hardware sprites to the graphic mode of my FPGA computer. It now supports up to 16 sprites, each one being 16x16pixels in size. Here is how it looks on the monitor:
In emulator, it looks the same:



Each sprite is defined by the 8-byte structure:
  • sprite definition data address (2 bytes)
  • x coordinate (2 bytes)
  • y coordinate (2 bytes)
  • transparent color (2 bytes).
The sprite structure for the first sprite starts at address of 56 decimal. Each next sprite structure starts 8 bytes later. 

Sprite definition data consists of 16 lines, each line described by 16 pixels, each pixel defined by 4 bits: xrgb. This means that one sprite line consists of 8 bytes (two pixels per byte), so total bytes needed for the sprite definition is 8x16 bytes == 128 bytes.

Here is the example of showing one sprite at (25, 25) in assembler language:

  mov r0, sprite_def
  mov r1, 56
  st [r1], r0  ; sprite definition is at sprite_def address
  mov r0, 25
  st [r1 + 2], r0  ; x = 25  at addr 58
  mov r0, 25
  st [r1 + 4], r0  ; y = 25  at addr 60
  mov r0, 0
  st [r1 + 6], r0  ; transparent color is black (0) at addr 62
  ; sprite definition
sprite_def:
  #d16 0x0000, 0x0000, 0x0000, 0x0000  ; 0
  #d16 0x0000, 0x000f, 0xf000, 0x0000  ; 1
  #d16 0x0000, 0x000f, 0xf000, 0x0000  ; 2
  #d16 0x0000, 0x000f, 0xf000, 0x0000  ; 3
  #d16 0x0000, 0x004f, 0xf400, 0x0000  ; 4
  #d16 0x0000, 0x004f, 0xf400, 0x0000  ; 5
  #d16 0x0000, 0x044f, 0xf440, 0x0000  ; 6
  #d16 0x0000, 0x444f, 0xf444, 0x0000  ; 7
  #d16 0x0004, 0x444f, 0xf444, 0x4000  ; 8
  #d16 0x0044, 0x444f, 0xf444, 0x4400  ; 9
  #d16 0x0400, 0x004f, 0xf400, 0x0040  ; 10
  #d16 0x0000, 0x004f, 0xf400, 0x0000  ; 11
  #d16 0x0000, 0x004f, 0xf400, 0x0000  ; 12
  #d16 0x0000, 0x041f, 0xf140, 0x0000  ; 13
  #d16 0x0000, 0x4111, 0x1114, 0x0000  ; 14
  #d16 0x0004, 0x4444, 0x4444, 0x4000  ; 15

How this stuff works? First of all, I had to decide how to implement sprites. I have decided to fetch all sprite data during the vertical blanking interval (VBI). During VBI, the video subsystem starts fetching sprite data by reading the 8-byte sprite structure starting from the address of 56 decimal (the address and data bus are 16-bit, so the computer is word-oriented (reads two bytes at the same time), and the actual address is set to
56 >> 1 == 28):

if ((x >= 640) && (y == 479) && (state == IN_LINE)) begin
  // when we start the vertical blanking, 
  // we need to fetch in advance the first sprite data
  state <= READ_SPRITES;
  sprite_counter <= 4'b0;
  rd <= 1'b1;
  wr <= 1'b0;
  mem_read <= 1'b1;
  addr <= 16'd28;    // prepare to read sprite definition address
end

In the next clock cycle, the system is in the READ_SPRITES state. The first thing that we do in the READ_SPRITES state is fetching the sprite definition address which is present at the data bus, since we have initiated a memory read from within the previous state.

Then we need to prepare the address bus for the next state in which we will fetch the x coordinate of the sprite. We do that by setting the address bus to (58 + (sprite_counter << 3)) for all sprites, having the sprite_counter iterating from 0 to 15:

READ_SPRITES: begin
  sprite_addr[sprite_counter] <= data;
  state <= READ_SPRITE_X;
  rd <= 1'b1;
  wr <= 1'b0;
  mem_read <= 1'b1;
  // prepare to read x coordinate of the sprite
  addr <= (16'd58 + (sprite_counter << 3)) >> 1;    
end

In the READ_SPRITE_X state, we fetch the x coordinate of the sprite which was ready at the data bus, and then we prepare to read the y coordinate in the next state:

READ_SPRITE_Y: begin
  sprite_y[sprite_counter] <= data;
  state <= READ_SPRITE_TRANSPARENT_COLOR;
  rd <= 1'b1;
  wr <= 1'b0;
  mem_read <= 1'b1;
  // prepare to read transparent color of the sprite  
  addr <= (16'd62 + (sprite_counter << 3)) >> 1;    
end

In the READ_SPRITE_Y state, we fetch the y coordinate of the sprite which was ready at the data bus, and then we prepare to read the sprite transparent color in the next state:

READ_SPRITE_TRANSPARENT_COLOR: begin
  sprite_transparent_color[sprite_counter] <= data[3:0];
  state <= READ_SPRITE_DATA;
  rd <= 1'b1;
  wr <= 1'b0;
  mem_read <= 1'b1;
  line_counter <= 16'b0;
  word_counter <= 4'b0;
  // read sprite definition bytes
  addr <= sprite_addr[sprite_counter] >> 1;    
end

In the READ_SPRITE_TRANSPARENT_COLOR state, we fetch the transparent color of the sprite, and then put the address of the sprite definition to the address bus so we can fetch it in the next state:

READ_SPRITE_DATA: begin
  if (line_counter < 16) begin
    case (word_counter) 
    0:  sprite_pixels[sprite_counter][line_counter][63:48] <= data;
    1:  sprite_pixels[sprite_counter][line_counter][47:32] <= data;
    2:  sprite_pixels[sprite_counter][line_counter][31:16] <= data;
    3:  sprite_pixels[sprite_counter][line_counter][15:0]  <= data;
    endcase
    state <= READ_SPRITE_DATA;
    rd <= 1'b1;
    wr <= 1'b0;
    mem_read <= 1'b1;
    if (word_counter < 3) begin
      word_counter = word_counter + 1'b1;
    end
    else begin
      word_counter = 1'b0;
      line_counter = line_counter + 16'b1;
    end
    // read sprite definition bytes
    addr = (sprite_addr[sprite_counter] + ((word_counter +
           (line_counter << 2)) << 1) ) >> 1;    
  end
  else 
  begin
    if (sprite_counter < SPRITE_NUM) begin
      sprite_counter = sprite_counter + 1'b1;
      state <= READ_SPRITES;
      rd <= 1'b1;
      wr <= 1'b0;
      mem_read <= 1'b1;
      // read next sprite definition address
      addr <= (16'd56 + (sprite_counter << 3)) >> 1;   
    end
    else begin
      sprite_counter <= 4'b0;
      rd <= 1'b1;
      wr <= 1'b0;
      mem_read <= 1'b1;
      addr <= VIDEO_MEM_ADDR + 0;
      state <= V_BLANK;
    end
  end
end

In the READ_SPRITE_DATA state we start reading sprite definition from the memory. We do it for each line of the sprite (16 lines per sprite), and within the line, for each word containing four pixels of the sprite line definition.

When we finish loading all sprite definition data for the current sprite, then we do the same for other sprite until we read all sprite definition data. Then we then set the address bus to load the pixel data at the (0, 0) position on the screen, and move to the V_BLANK state:

V_BLANK: begin
  pixels <= data;
  state <= SCAN_IDLE;
  rd <= 1'bz;
  wr <= 1'bz;
  mem_read <= 1'b0;
end

In the V_BLANK state we read the pixels of the frame buffer at the (0, 0) coordinate, and then set the all the control signals to high impedance and set the state to SCAN_IDLE. We will leave the SCAN_IDLE state when the the time comes to start displaying pixels starting from the (0, 0) coordinate.

Displaying sprite data

During the scanline processing, we need to display both original pixels from the frame buffer as well as the sprite data, and we need to make sure that the original pixels must be displayed through the transparent sprite color.

This is done in the following code:

if (valid) begin
  for (i = 0; i < SPRITE_NUM; i = i+1) begin
    if ((sprite_addr[i] != 16'b0) &&
       (xx >= sprite_x[i]) &&
       (xx < (sprite_x[i] + 16)) &&
       (yy >= sprite_y[i]) &&
       (yy < (sprite_y[i] + 16))) begin

      sprite_found = 1'b1;
      if (
        sprite_pixels[i][yy - sprite_y[i]][60-(((xx - sprite_x[i]) << 2) ) + 0] != sprite_transparent_color[i][0] ||
        sprite_pixels[i][yy - sprite_y[i]][60-(((xx - sprite_x[i]) << 2) ) + 1] != sprite_transparent_color[i][1] ||
        sprite_pixels[i][yy - sprite_y[i]][60-(((xx - sprite_x[i]) << 2) ) + 2] != sprite_transparent_color[i][2]
      ) begin
        r <= sprite_pixels[i][yy - sprite_y[i]][60-(((xx - sprite_x[i]) << 2) ) + 0] == 1'b1;
        g <= sprite_pixels[i][yy - sprite_y[i]][60-(((xx - sprite_x[i]) << 2) ) + 1] == 1'b1;
        b <= sprite_pixels[i][yy - sprite_y[i]][60-(((xx - sprite_x[i]) << 2) ) + 2] == 1'b1;
      end 
      else 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
    end 
  end
  if (!sprite_found) 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
    sprite_found = 1'b0;
  end
end
else begin
  // blanking -> no pixels
  r <= 1'b0;
  g <= 1'b0;
  b <= 1'b0;
end
end

The most interesting thing is the "for loop". It is not a loop - it actually repeats the Verilog code SPRITE_NUM times. That is the most important thing to understand about "loops". You don't have the linear code to be executed multiple times. Instead, everything is a giant state machine that pulses with the clock signals and the "for loop" just unwraps the code multiple times, and all that unwrapped code "works" at the same time.

So, when we have this Verilog code:
 for (i = 0; i < SPRITE_NUM; i = i+1) begin
    if ((sprite_addr[i] != 16'b0) &&
       (xx >= sprite_x[i]) &&
       (xx < (sprite_x[i] + 16)) &&
       (yy >= sprite_y[i]) &&
       (yy < (sprite_y[i] + 16))) begin

It actually does this:
    if ((sprite_addr[0] != 16'b0) &&
       (xx >= sprite_x[0]) &&
       (xx < (sprite_x[0] + 16)) &&
       (yy >= sprite_y[0]) &&
       (yy < (sprite_y[0] + 16))) begin

...
    end
    if ((sprite_addr[1] != 16'b0) &&
       (xx >= sprite_x[1]) && 
       (xx < (sprite_x[1] + 16)) && 
       (yy >= sprite_y[1]) && 
       (yy < (sprite_y[1] + 16))) begin
...
    end
...


The code with the "for loop" does the same thing for all sprites:
  1. if the spite definition address is not zero, and current x and y coordinates of the scanline are within sprite coordinates, then we put the current sprite pixel color to the output r, g and b signals, or we put the original frame buffer pixel colors, if the current sprite pixel is transparent one (the color of the current sprite pixel is the transparent color).
  2. else, if the current x and y coordinates of the scanline are outside of the sprite coordinates, we put the frame buffer pixel data to the r, g and b output signals.
  3. else, it must be blanking interval, so put zeros to r, g and b to output signals.

Conclusion

This implementation of sprites requires that the vga module has its own internal memory which is filled with the sprite data from the main memory. Then, during the scanline processing, sprite pixels are combined with frame buffer pixels in a way that sprite pixels are placed "over" the frame buffer pixels, unless the current sprite pixel is the transparent one. If that is the case, then the frame buffer pixel is "shown" through the sprite.

The great thing about hardware sprites is that they do not consume processor time at all. Everything is done in hardware and showing sprites actually requires just to have the sprite definition address set to non-zero value.

петак, 7. септембар 2018.

Snakes!

The first game on my FPGA platform

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

I have decided to make a game for the FPGA Computer. My friend gave me his Pascal implementation of the Snakes game and I have ported it into the FPGA Assembler. Not an easy task, but it works now:


The game was made to work in the video text mode. The frame is constructed using '-', '+', and '|' characters. The head of the snake is '@', the body is 'O', the star is '*', and when the snake hits the wall or its tail, we get the 'X' character at the crash scene.

In the emulator it works the same way:

The game is placed on the github.

Milliseconds counter register

During the game development, it occured to me that I need to implement the milliseconds counter register in order to implement the delay function. In the cpu.v file, I have created two additional registers:

reg [N-1:0] millis_counter;

reg [15:0] clock_counter;


The millis_counter register will hold the number of milliseconds counted so far. Incrementing this register is done whenever clock_counter reaches 50000 (clock is 50MHz, which means that when the clock_counter reaches 50000, one millisecond has elapsed):

always @ (posedge CLOCK_50) begin
if (clock_counter < 50000) begin
clock_counter <= clock_counter + 1'b1;
end
else begin
clock_counter <= 0;
millis_counter <= millis_counter + 1'b1;
end
...

To read the millis_counter register, I have introduced another port address for the IN instruction:

4'b0011: begin
  // IN reg, [xx]
  `ifdef DEBUG
  $display("%2x: IN r%-d, [%4d]",ir[3:0], (ir[11:8]), data);
  `endif
  case (mc_count)
0: begin
mbr <= data;  // remember the address of IO port
mc_count <= 1;
pc <= pc + 2'd2;  // move to the next instruction
end
1: begin
    case (mbr)
64: begin    // UART RX DATA
regs[ir[11:8]] <= {8'b0, rx_data_r};
end
65: begin   // UART TX BUSY
regs[ir[11:8]] <= tx_busy;
end
68: begin    // keyboard data
regs[ir[11:8]] <= {8'b0, ps2_data_r};
end
69: begin // milliseconds counted so far
regs[ir[11:8]] <= millis_counter;
end
  endcase // end of case(mbr)
  ir <= 0;      // initiate fetch
  addr <= pc >> 1;
end
default: begin
end
  endcase  // end of case (mc_count)
end // end of IN reg, [xx]

The example of the usage can be found in the snakes.asm file:

; ################################################################
; function delay(r0)
; waits for the r0 milliseconds
; ################################################################
delay:
push r1
push r2
delay_loop2:
in r1, [PORT_MILLIS] ; port 69
delay_loop1:
in r2, [PORT_MILLIS] ; port 69
sub r2, r1
jz delay_loop1 ; one millisecond elapsed here
dec r0
jnz delay_loop2
pop r2
pop r1
ret

Conclusion

This was the first game made for the FPGA Computer. The game is quite simple and uses video text mode. I had to implement a milliseconds counter register and the corresponding IN port to read it. It was used to implement the delay function. 




субота, 25. август 2018.

UART Loader

FPGA Computer UART Loader

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

I have developed the UART Loader for the FPGA Computer to be able to send programs to it. It is based on the UART module developed in Verilog, for the FPGA Computer. This module provides both sending and receiving bytes, using 115200 bauds, 8 bits, 1 start, 1 stop bit, no parity. The serial port of the FPGA computer is connected to the TTL SerialToUSB dongle, which is then connected to the USB port of the computer:

When I initially created the FPGA Computer, I was able to store just one program in it, by hardcoding it in the RAM memory. Here is the part of the RAM.v Verilog module that includes the program in the RAM:

// Declare the RAM variable
reg [N-1:0] ram[32767:0];

initial
begin
  $readmemh("program.hex", ram);
end

The problem with this approach is that it is very slow. This program has to be embedded into the computer during the building of the computer, which can last several minutes. That is why I have devised the Loader. It is hardcoded in the RAM module, and when the computer powers on, it jumps to the address 0x0000, where I have placed a JUMP instruction to go to the Loader:

; ########################################################
; RESET CODE (4 bytes max)
; ########################################################
#addr 0x0000
j start

When started, Loader sends an initialisation sequence of bytes to the PC, via UART:

; send raspbootin boot char sequence
mov r0, 77 ; "M" character
call uart_send
mov r0, 13 ; \n character
call uart_send
mov r0, 10 ; \r character
call uart_send
mov r0, 3
call uart_send
mov r0, 3
call uart_send
mov r0, 3
call uart_send

This sequence is inherited from the original Raspbootin protocol for which I have made a Java implementation. This version is similar, but I have added a checksum at the end (more about this below).

The Loader then fetches the number of bytes to be received:

first_byte:
in r1, [64] ; get the char from the uart
st [size], r1 ; store the lowest byte to the size variable
inc [state] ; next state -> 1 (second byte)
j skip ; return from interrupt
second_byte:
in r1, [64] ; get the char from the uart (8 upper bits)
ld r2, [size] ; get the lower 8 bits (received earlier)
shl r1, 8 ; shift the received byte 8 bits
or r1, r2 ; put together lower and upper 8 bits
st [size], r1 ; store the calculated size
inc [state] ; next state 
j skip ; return from interrupt

After that, the Loader returns back the received size (just to make sure that it received the correct number of bytes):

; this is 16-bit cpu, so we don't load code bigger than 65535 bytes
; send confirmation that the code has been loaded
ld r0, [size]
and r0, 255
call uart_send
ld r0, [size]
shr r0, 8
call uart_send
inc [state] ; next state ->  (code arrives)

After that, all incoming bytes are loaded into the memory, starting from the 0x400 address:

in r1, [64] ; get the byte from the uart into r1

mov r2, r1
ld r0, [sum_all]
add r0, r2
st [sum_all], r0 ; primitive checksum - sum of all bytes
; at this moment, r1 holds the received byte
ld r2, [current_addr]
st.b [r2], r1 ; store the received byte into the memory
inc r2 ; move to the next location in memory
st [current_addr], r2   ; save the incremented value of the address

ld r2, [current_size]   ; increment the byte counter
inc r2
st [current_size], r2
cmp r2, [size] ; did we receive all?
jz all_arrived
j skip

When all bytes are received, the Loader sends back the primitive checksum, so the PC can check if everything is OK:

all_arrived:
; send the sum of all bytes
ld r0, [sum_all]
and r0, 255
call uart_send
ld r0, [sum_all]
shr r0, 8
call uart_send

mov r0, 1; signal to the main program ->loader has received all
st [loaded], r0

After that, the Loader jumps to the 0x400 address:

not_loaded:
ld r0, [loaded]
cmp r0, 1
jz 0x400
nop
j not_loaded

For the PC, I have modified the Raspbootin Loader, originally used in the Raspberry Pi bare metal programming, and it is also stored on the github.

Conclusion

When I tried Raspberry Pi bare metal programming, I immediately had the problem of transferring programs from the PC to the RPI. Usually, there is no network (it is bare metal platform with almost none of the I/O libraries) and the only other way is by transferring programs via micro SD cards (card dance). You would cross-compile the program on the PC, save it to the SD card, eject it, put it in the RPI, and reset the RPI. And then again, and again...

That was a motivation for the programmers to develop some kind of a loader for the RPI. One of those loaders is the Raspbootin. It is fairly simple. I re-used it for the exaclty same purpose - to load programs on my FPGA Computer from the PC. The only problematic part of this development was debugging the Loader. It could be only done on the FPGA, with those couple-of-minutes compiling. When I survived that, I was able to cross-assemble programs on my PC and send them to the board via Loader.


уторак, 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.