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

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.


петак, 3. август 2018.

Raspberry PI stuff

Various stuff about Raspberry Pi



Installation

You need to download the OS image from the official Raspberry PI site:


I prefer Raspbian with desktop.

Then you need to download the Etcher software for writing the OS image to the micro SD card:


Put the micro SD card in your computer, start the Etcher, choose the image file and write.

When everything is done, remove the micro SD card safely from the PC, put it in the Raspberry PI, connect HDMI cable (in case of Zero, mini HDMI cable) from RPI to the monitor (or TV), and connect the keyboard to one of the USB ports (in case of RPI Zero, you need to connect your USB keyboard via adapter to the micro USB port). Connect the power cable. RPI will boot for the first time.

Default username/password is pi/raspberry.

Upon login, start the raspi-config by typing:

sudo raspi-config

This will start the configuration utility for the RPI. I use it to set up the new password, host name of the RPI and to turn on almost all interfacing options. When setting the interfacing options, I turn on the SSH, I2C, SPI and 1-wire. 

When exiting, the raspi-config will reboot the machine.

I prefer to set up the static IP to my RPIs, so here are some combinations:
1. Set up RPI 3 with the static IP on Ethernet,
2. Setup RPI Zero with the static IP on wireless,
3. Set up RPI Zero with the Ethernet support (needs additional ENC28J60 module to be connected to the RPI Zero).

Setting up RPI 3 with the static IP on Ethernet (and WiFi)

Before booting, connect the Ethernet cable from your router to the RPI 3, and connect the power. You can then log on. From that moment, you can set up the static IP address. Before that, you can check if the networking works. First of all, you can type:

ifconfig

This will write your IP address, which your RPI obtained from the router (via DHCP). If the IP address of the RPI begins, for example, with 192.168.1, then the static IP address will need to start the same way (remember first three numbers of the IP address). 

Here we have two branches:
1. from stretch, on with the buster builds of the Raspbian
2. before stretch build.

Stretch, buster, and newer builds

To set up the static IP address, you need to edit the /etc/network/interfaces file:

sudo nano /etc/network/interfaces

The nano editor will open the interfaces file. You can then put the following content:

# interfaces(5) file used by ifup(8) and ifdown(8)

# Please note that this file is written to be used with dhcpcd
# For static IP, consult /etc/dhcpcd.conf and 'man dhcpcd.conf'

# Include files from /etc/network/interfaces.d:
source-directory /etc/network/interfaces.d

auto lo
iface lo inet loopback

auto eth0
allow-hotplug eth0
iface eth0 inet manual

auto wlan0
allow-hotplug wlan0
iface wlan0 inet manual
wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf

Both eth0 and wlan0 (I have decided to assign my wlan0 static address, too) are set to manual. In case of wlan0, you need to edit the /etc/wpa_supplicant/wpa_supplicant.conf file to the basic content:

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
   ssid="xxxx"
   psk="yyyy"
}

Then you need to add the following code to the end of the /etc/dhcpcd.conf file:

# Static eth0 IP configuration
interface eth0
static ip_address=192.168.1.207/24
static routers=192.168.1.1
static domain_name_servers=192.168.1.1 8.8.8.8
# Static wlan0 IP configuration
interface wlan0
static ip_address=192.168.1.217/24
static routers=192.168.1.1
static domain_name_servers=192.168.1.1 8.8.8.8

Before stretch (or buster) builds

To set up the static IP address, you need to edit the /etc/network/interfaces file:

sudo nano /etc/network/interfaces

The nano editor will open the interfaces file. You can then put the following content:

# interfaces(5) file used by ifup(8) and ifdown(8)

# Please note that this file is written to be used with dhcpcd
# For static IP, consult /etc/dhcpcd.conf and 'man dhcpcd.conf'

# Include files from /etc/network/interfaces.d:
source-directory /etc/network/interfaces.d

auto lo
iface lo inet loopback

allow-hotplug eth0
iface eth0 inet static
address 192.168.1.200
netmask 255.255.255.0
gateway 192.168.1.1

The address set in this example is 192.168.1.200. After that, you can restart the networking by typing:

sudo service networking restart

Or, you can reboot the RPI by typing:

sudo reboot


Setting up RPI Zero with the static IP on Wireless

RPI Zero W already has the wireless, while RPI Zero does not. In case of having the RPI Zero, you need to obtain WiFi dongle and some adapter to connect it to the micro USB port. After that, the procedure is the same for both RPI Zero W and RPI Zero.

Here too, we have two branches:
1. stretch/buster builds.
2. pre-stretch(or buster) builds

Stretch, buster, and newer builds

Just look above at the same title.

Before stretch (or buster) builds

You need to edit the /etc/network/interfaces by typing:

sudo nano /etc/network/interfaces

In the nano editor, change the interfaces file to:

# interfaces(5) file used by ifup(8) and ifdown(8)

# Please note that this file is written to be used with dhcpcd
# For static IP, consult /etc/dhcpcd.conf and 'man dhcpcd.conf'

# Include files from /etc/network/interfaces.d:
source-directory /etc/network/interfaces.d

auto lo
iface lo inet loopback

allow-hotplug wlan0
iface wlan0 inet static
#    wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf
        wpa-ssid "MySSID"
        wpa-psk "xxxxxx"
address 192.168.1.201
netmask 255.255.255.0
gateway 192.168.1.1

The address set in this example is 192.168.1.201. The MySSID is the SSID of your WiFi network. You must enter the SSID and the password with the quotes (").


Setting up RPI Zero for the Ethernet support

RPI Zero supports the ENC28J60 Ethernet module out of box.

ENC28J60 Ethernet module

This module needs to be connected to the RPI Zero via SPI interface. Don't forget to enable the SPI from the raspi-config tool (look above). After that, you need to do the following:

1. Connect the ENC28J60 module to the RPI using the following pin scheme:

Pi            PinNo ENC28J60     
---------------------------------
+3V3          17 VCC          
GPIO10/MOSI    19 SI           
GPIO9/MISO    21 SO           
GPIO11/SCLK    23 SCK          
GND            20 GND          

GPIO25        22 INT          
CE0#/GPIO8    24 CS           

2. Enable the ENC28j60 module at the end of your /boot/config.txt file by typing:

sudo nano /boot/config.txt

This will open the nano editor. Go to the end of the file and enter the following text:

dtoverlay=enc28j60

3. Reboot (sudo reboot)

From this moment on, you can work with the Ethernet as eth0 device.


Having static IP on both Ethernet and WiFi

The text below is for the pre-stretch/buster builds. For having both ethernet and WiFi static, look above, at the "Setting up RPI 3 with the static IP on Ethernet (and WiFi)" title.

If you want to have the static IP on both Ethernet port and WiFi, you need to edit the /etc/network/interfaces file and put the following text:

# interfaces(5) file used by ifup(8) and ifdown(8)

# Please note that this file is written to be used with dhcpcd
# For static IP, consult /etc/dhcpcd.conf and 'man dhcpcd.conf'

# Include files from /etc/network/interfaces.d:
source-directory /etc/network/interfaces.d

auto lo
iface lo inet loopback

#allow-hotplug eth0
iface eth0 inet static
address 192.168.1.202
netmask 255.255.255.0
gateway 192.168.1.1

auto wlan0
#allow-hotplug wlan0
iface wlan0 inet static
#    wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf
        wpa-ssid "MySSID"
        wpa-psk "xxxxxxx"
address 192.168.1.212
netmask 255.255.255.0
gateway 192.168.1.1

The address set in this example for the Ethernet is 192.168.1.202 and for the WiFi is 192.168.1.212. 

Installing Java8 on your RPI

Type the following in your console:

sudo aptitude install oracle-java8-jdk

This will install the Java8 installer and would run it. 


Samba support

Samba allows you to share a part of your RPI disk to the network, for other machines and users. It also allows you to access other samba shares on the network. We will focus on the sharing of our disk on the network.

Install Samba via apt-get:

sudo apt-get install samba samba-common-bin

Edit the smb.conf file using nano:

sudo nano /etc/samba/smb.conf

Find the entries for workgroup and wins support, and set them up as follows:

workgroup = your_workgroup_name
wins support = yes

You also need to add the following section to end of the smb.conf to add share:

[pihome]
   comment= Pi Home
   path=/home/pi
   browseable=Yes
   writeable=Yes
   only guest=no
   create mask=0777
   directory mask=0777
   public=no

This will add the Samba share named "pihome" on your RPI, so it will be accessible from other machines.

At the end, we need to add the current user to the Samba:

sudo smbpasswd -a pi

After that, just restart the smbd daemon:

sudo systemctl restart smbd


петак, 13. јул 2018.

FPGA Computer Assembler

This is the second follow-up of my initial text about the FPGA Computer.

I use a fork of the customasm project for my FPGA-based CPU. It is on the github here:

https://github.com/milanvidakovic/FPGAcustomasm

This 16-bit CPU has 8 general-purpose registers (r0 – r7), pc (program counter), sp (stack pointer), ir (instruction register), and h (higher word when multiplying, or remainder when dividing). Each register is 16-bits wide.

The address bus is 16 bits wide, addressing 65536 addresses. Data bus is also 16 bits wide, but all the addresses are 8-bit aligned. 

There are eleven groups of instructions:


Group number
Group name
Group members
Group description
0
NOP/MOV/
IN/OUT/PUSH/
POP/RET/IRET/
HALT/SWAP
nop
mov reg, xx
mov reg, reg
in reg, [xx]
out [xx], reg
push reg
push xx
pop reg
ret
iret
swap
halt
The most general group. Deals with putting values into registers, exchanging values between registers, I/O operations, stack operations, returning from subroutines, and register content swapping. NOP and HALT are also in this group.
1
JUMP
j xx
jc xx
jnc xx
jz xx
jnz xx
jo xx
jno xx
jp xx
jnp xx
jg xx
jge xx
js xx
jse xx
Jump to the given location.

2
CALL
call xx
callc xx
callnc xx
callz xx
callnz xx
callo xx
callno xx
callp xx
callnp xx
callg xx
callge xx
calls xx
callse xx
Calling subroutine. Puts the return address on the stack before jumping to the subroutine. Needs to call RET when returning from the subroutine.
3
LOAD/STORE
ld reg, [xx]
ld reg, [reg]
ld reg, [reg + xx]
ld.b reg, [xx]
ld.b reg, [reg]
ld.b reg, [reg + xx]
st [xx], reg
st [reg], reg
st [reg + xx], reg
st.b [xx], reg
st.b [reg], reg
st.b [reg + xx], reg
Load from memory into the register
destination: register
source: memory address given by the number, or by the register, or by the register+number.
Store the given register into the memory location
destination: memory location given by the number, or by the register, or by the register+number.
4
ADD/SUB
add reg, reg
add reg, xx
add reg, [reg]
add reg, [xx]
add reg, [reg + xx]
add.b reg, [reg]
add.b reg, [xx]
add.b reg, [reg + xx]
sub reg, reg
sub reg, xx
sub reg, [reg]
sub reg, [xx]
sub reg, [reg + xx]
sub.b reg, [reg]
sub.b reg, [xx]
sub.b reg, [reg + xx]
 Add and sub group.
5
AND/OR
and reg, reg
and reg, xx
and reg, [reg]
and reg, [xx]
and reg, [reg + xx]
and.b reg, [reg]
and.b reg, [xx]
and.b reg, [reg + xx]
or reg, reg
or reg, xx
or reg, [reg]
or reg, [xx]
or reg, [reg + xx]
or.b reg, [reg]
or.b reg, [xx]
or.b reg, [reg + xx]
 And / or group.
6
XOR
xor reg, reg
xor reg, xx
xor reg, [reg]
xor reg, [xx]
xor reg, [reg + xx]
xor.b reg, [reg]
xor.b reg, [xx]
xor.b reg, [reg + xx]
 Xor group.
7
SHL/SHR
shl reg, reg
shl reg, xx
shl reg, [reg]
shl reg, [xx]
shl reg, [reg + xx]
shl.b reg, [reg]
shl.b reg, [xx]
shl.b reg, [reg + xx]
shr reg, reg
shr reg, xx
shr reg, [reg]
shr reg, [xx]
shr reg, [reg + xx]
shr.b reg, [reg]
shr.b reg, [xx]
shr.b reg, [reg + xx]
 Shift group.
8
MUL/DIV
mul reg, reg
mul reg, xx
mul reg, [reg]
mul reg, [xx]
mul reg, [reg + xx]
mul.b reg, [reg]
mul.b reg, [xx]
mul.b reg, [reg + xx]
div reg, reg
div reg, xx
div reg, [reg]
div reg, [xx]
div reg, [reg + xx]
div.b reg, [reg]
div.b reg, [xx]
div.b reg, [reg + xx]
Multiply / divide group.
9
INC/DEC
inc reg
inc [reg]
inc [xx]
inc [reg + xx]
inc.b [reg]
inc.b [xx]
inc.b [reg + xx]
dec reg
dec [reg]
dec [xx]
dec [reg + xx]
dec.b [reg]
dec.b [xx]
dec.b [reg + xx]
Increment and decrement group.
10
CMP/NEG
cmp reg, reg
cmp reg, xx
cmp reg, [reg]
cmp reg, [xx]
cmp reg, [reg + xx]
cmp.b reg, [reg]
cmp.b reg, [xx]
cmp.b reg, [reg + xx]
neg reg
neg [reg]
neg [xx]
neg [reg + xx]
neg.b [reg]
neg.b [xx]
neg.b [reg + xx]
 Compare / negate group.

All the instructions are two or four bytes long. Since the data bus is 16-bits wide, the complete instruction is fetched in either one or two memory reads. This means that, since the SRAM is used, the complete instruction is fetched, decoded, and executed in three or more clock cycles.

All the instructions have the similar format:


from
to
what
group
bbbb
0-7: r0-r7
8-sp
9-h
bbbb
0-7: r0-r7
8-sp
9-h
0000
0=>mov regx, regy
0000

The first byte has lower four bits used to designate the destination register (to), while upper four bits  are used for the source register (from) identification. The second byte has lower four bits for the instruction group identification (group) and upper four bits for the type of the instruction in that group (what).

For example, the  mov r2, r1  instruction is encoded as:
binary: 0001 0010 0000 0000
hex: 12 00

The Source is r1 (0001), the Destination is r2 (0010), the group is 0 (0000) and the type is move regx, regy (0000).

Second example is the  mov r1, 0x0f  instruction:
binary: 0000 0001 0010 0000, 0000 0000 0000 1111
hex: 01 20, 00 0f


The Load instructions are used to load the value from the memory into the register. The Store instructions store the value of the register into the given memory location. Memory location is given as number (ld  r1, [0x0a] - load the content of the 0x0a location into the r1 register), or as a value of a register (ld  r1, [r2] - load the content of the memory location to which r2 points), or as a sum of number and register (ld  r1, [0x0f + r2]). 

ld r1, [0x0a] loads two bytes from the 0x0a location. The address (0x0a) must be even if we work with 16-bit values.

If we want to load a byte from a location, we need to use the ".b" suffix:
ld.b r1, [0x0a]

The code above will load a byte from the 0x0a location into the r1 register.

Hello World example


Let's look at the Hello World example:

; this program will print HELLO WORLD
#addr 0x400
VIDEO_0 = 2400 ; beginning of the text frame buffer

mov r2, 0      ; r1 is the index
mov r1, hello  ; r1 holds the address of the "HELLO WORLD" string

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 [r2 + VIDEO_0], r0  ; store the character at the VIDEO_0 + r2 
inc r1                 ; move to the next character
add r2, 2              ; move to the next location in the video memory
j again                ; continue with the loop

end:
halt
hello:

#str "HELLO WORLD!\0"

First we define the constant VIDEO_0 with the valuer of 2400. This is the address of the text-based frame buffer. It points to the first character in the video memory.

Then we set the r2 to 0 and r1 to the address of the hello string. Note that the mov instruction is used to move the number into the register (for example, mov r2, 0), or to move a value of the source register to the destination register (for example, mov r1, r2).

Next, we enter the loop. The loop starts with the again label, and in the loop we load the byte value from the current address (starts with the first character of the hello string), then we compare that byte with the zero (checking the end of the string), and then we store that byte in the current address of the video memory.

When all the characters are printed on the screen, the CPU halts (halt instruction).


Interrupts


Let's look at the UART echo demo. This demo waits for the character to arrive via serial UART (115200 baud, one start bit, one stop bit, no partiy), then prints that character on the screen, and finally, echoes that character back to the UART:

#addr 0x400
; ########################################################
; REAL START OF THE PROGRAM
; ########################################################
mov sp, 1000

mov r0, 14
st [cursor], r0

; set the IRQ handler for UART to our own IRQ handler
mov r0, 1
mov r1, 16
st [r1], r0
mov r0, irq_triggered
mov r1, 18
st [r1], r0

halt

The code above sets the interrupt handling routine (irq_triggered) for the UART. This is the IRQ1 and its handling routine is at the address 16 (0x0010). This means that whenever the serial  UART subsystem receives a byte, the CPU will jump to the 0x0010 address. At that address, we have placed the JUMP instruction (j irq_triggered), having at the address 0x0010 value of 0x0001 (the JUMP instruction opcode - 0x0001) and at the address 0x0012 the address of the irq_triggered routine (st [r1], irq_triggered).

That way, we have prepared the UART interrupt routine and the main program halts. The rest of the program is in the interrupt routine. Let's look at the interrupt routine:

; ##################################################################
; Subroutine which is called whenever some byte arrives at the UART
; ##################################################################
irq_triggered:
push r0
push r1
push r2   
push r5
push r6

in r1, [64] ; r1 holds now received byte from the UART (address 64 decimal)
ld r6, [cursor]
st [r6 + VIDEO_0], r1    ; store the UART character at the VIDEO_0 + r2 
add r6, 2       ; move to the next location in the video memory
st [cursor], r6

loop2:
in r5, [65]   ; tx busy in r5
cmp r5, 0     
jz not_busy   ; if not busy, send back the received character 
j loop2
not_busy:
out [66], r1  ; send the received character to the UART
skip:
pop r6
pop r5
pop r2
pop r1                 
pop r0
iret
When the interrupt happens, the irq_triggered routine first pushes some registers on the stack, obtains the received byte from the UART (in r1, [64]), prints it on the screen, and then sends back that character through UART (out [66], r1). If the UART is busy sending some character, the in r5, [65] will have r5 set to 1; otherwise, the r5 will have 0. Finally, the routine pops the registers from the stack and returns (iret instruction). 

The difference between iret and ret is that ret pops the return address from the stack and jumps to the obtained address (return from the call subroutine), while the iret pops the return address, pops the flags, and then jumps to the obtained address (interrupt routine might have changed flags,so they need to be saved before interrupt routine is invoked, and restored during the iret execution).

All the examples are stored in the FPGACustomasm project on the github:
https://github.com/milanvidakovic/FPGAcustomasm/tree/master/examples/FPGA/raspbootin