четвртак, 5. децембар 2019.

Bench power supply

I got inspired by this video:

The kit can be obtained for less than 10$, so you "only" need the rest of the stuff (case, better potentiometers, voltmeter, ampermeter, connectors, switches, etc.). I have purchased my kit from aliexpress (click on the image below):

I had a broken UPS, so I could reuse its case and transformer. I also had a cooler (heat sink with a fan) from my old processor, so I got this:

Here is a different point of view:

And, finally, here is the completed project (I have connected my Raspberry Pi 3):

For voltage regulation I have replaced the original potentiometer from the kit with the precise one, with multiple turns. That way I can set very precise voltage. For current limitation, I have left the original potentiometer. This power supply has the overcurrent protection and when it happens, the LED turns on. I have connected that LED connector to the UPS built-in LED.

Conclusion

This power supply is a linear one. It means that all the voltage difference between input and output  multiplied by current gets dissipated on a large transistor with the heat sink. It is not the most efficient way of regulating power, but for couple of bucks it is a nice project.

петак, 1. новембар 2019.

Fan control on the Raspberry Pi 4

I have recently purchased Raspberry Pi 4. It goes quite hot when working (even in idle), so I had to obtain a cooler. I have got one with big aluminum heat sink, and two small fans. They are meant to be connected to 5V and to work all the time. I didn't like that so I have decided to make a fan control.

The easiest way I could find was on this video:
https://www.youtube.com/watch?v=Pw1kSS_FIKk

The idea is quite simple: use one MOSFET to control the fan. I have connected the fan connector to the drain and GPIO pin to the gate and it looks like this:

The schematics of the circuit.

I have used IRF640N MOSFET, since I could not find the IRF530N, which was shown in the video. Here is the photo of the actual contraption:

MOSFET-based fan control

The software for the fan control is basic: turn on the fan if the temperature goes over the high threshold, and turn off the fan if the temperature goes lower than the low threshold.

#!/usr/bin/env python

import RPi.GPIO as GPIO
import time
import threading
import os
import sys

from subprocess import * 

# Fan control port (GPIO 23)
PORT_FAN = 23
HOT  = 55
COLD = 50
OFF = 0
ON  = 1
GPIO.setmode(GPIO.BCM)
GPIO.setup(PORT_FAN, GPIO.OUT)

GPIO.output(PORT_FAN, OFF)   # turn off the fan

def getTemp():
p = Popen("vcgencmd measure_temp|cut -c 6-7", shell=True, stdout=PIPE)
t = "" + p.communicate()[0]
t = t.strip()
t = int(t)
return t

while True:
temp = getTemp()
print temp
if temp > HOT:
GPIO.output(PORT_FAN, ON)   # turn on the fan
print "Turning the fan ON"
if temp < COLD:
GPIO.output(PORT_FAN, OFF)   # turn off the fan
print "Turning the fan OFF"
time.sleep(0.1)

GPIO.cleanup() 

 As you can see, the software is simple. If the temperature goes over 55 degrees of Celsius, the fans are turned on. When the CPU temperature goes below 50 degrees, the fans are turned off.

With this big aluminum heat sink, the idle CPU gets between 48 and 50 degrees.

One note: you can see on the photo above that I have connected +5V and GND of the power supply directly on the GPIO pins for +5V and GND, instead of using USB-C power cord. I have done that because I have quite decent 5V power supply which is not USB-C, so if I connect the USB-C cable to it, the additional voltage drop happens across that cable. Lower the quality of the cable, bigger voltage drop becomes.



субота, 5. октобар 2019.

Flashing DE0-NANO FPGA board and using DEV_CLRn reset functionality

In this post I am going to talk about programming DE0-NANO FPGA board two ways:
1. temporary programming, meaning that the design will not survive powering off, and
2. permanently storing (flashing) the design, so it will survive power off.

I will also address the idea of a mega-reset using the DEV_CLRn feature at the bottom of this post.

Disclamer: You are doing all of this at your own risk. I am not responsible for any problem caused by these examples. To prevent problems, check the documentation of your DE0-NANO to see if you have the same type of Programmer and EEPROM chips.

1. Temporary programming

Whenever you compile your design at the Quartus II IDE, you can send the design to the FPGA board via Programmer:

1. double click on the Program Device in the table:

2. When the Programmer opens, look for the USB blaster right to the Hardware Setup... button:


3. If you see the "No Hardware" text, click on the Hardware Setup... button. That would open the Hardware Setup dialog:

4. Double click the USB Blaster and Close. The Programmer should look like this:


5. Now the USB Blaster is present right to the Hardware Setup... button.

6. Click on the Start button to send the design to the FPGA board.

All this is temporary, meaning that the design will be erased when you power off the board.

Alternative way of temporary programming

An alternative way of doing this is by executing the following program:

C:\altera\13.0\quartus\bin\quartus_pgm.exe

You need to supply that program with the following command line parameters:

-c usb-blaster -m jtag -o P;<path_to_the_SOF_file>

That could be typed like this:


C:\altera\13.0\quartus\bin\quartus_pgm.exe -c usb-blaster -m jtag -o P;<path_to_the_SOF_file>

I have done that in my FPGARaspbootin program (the Run FPGA manually button, and Auto Run FPGA check box):


2. Permanent programming (flashing)

To flash the DE0-NANO device, you need to:

1. convert the SOF file into the JTAG Indirect Configuration File (*.jic file), by choosing File -> Convert Programming Files... menu option. That would open the Convert Programming File Dialog.

2. Choose the Programming file type to:
JTAG Indirect Configuration File (*.jic file)

3. Open the Configuration device combo box and choose: EPCS64

4. Click on the Flash Loader in the bottom table and click on the Add Device... button. That would open the Select Devices dialog. Choose Cyclone IV E and EP4CE22:


5. Click on the SOF Data in the table and click on the Add File... button. Choose your SOF file:

6. Click on the chosen SOF file in the table (in my example, the computer.sof file) and click on the Properties. Turn on the compression:

7. After that start the Programmer, delete the SOF file (if existed), and add the JIC file (click on the Add File... button). Make sure that both Program/Configure checkboxes are turned on:

8. Click on the Start button and wait for the 100% at the progress bar (upper right corner).

9. You can power off and then power on the FPGA and it will still have the design in it.

Reset all registers with the Reset button (DEV_CLRn option)

I have experienced some strange behavior regarding resetting my design. I have made my KEY[0] clear all of my registers using the Verilog code. That design, however, failed somehow to completely reset my board. Simply, after the reset, the design would behave unreliable. I have managed to solve this problem by introducing the mega-reset feature. Here is the explanation:

In DE0-NANO, the KEY[0] is connected to the PIN_J15, which is in turn connected to the DEV_CLRn pin. This can be used to reset all registers in the FPGA when you press the KEY[0] key on the DE0-NANO board. That is a kind of mega-reset. However, this feature is turned off by default. You need to enable it. Here is the procedure:

1. right click on the Cyclone IV... in the Project Navigator and choose the Device... option:

2. click on the Device and Pin Options... button:

3. Find and enable the Enable device-wide reset (DEV_CLRn) check box:

4. Open the Pin Planner dialog (Assignments -> Pin Planner menu option), and change the KEY[0] from PIN_J15 to something unassigned, like PIN_G5 (it is unused in my project):

After that, whenever you press the KEY[0] key, you will reset your entire board.

среда, 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

субота, 17. август 2019.

The ultimate read-only file system on the SD card

This is a kind of follow-up of my previous post:
https://mvidakovic.blogspot.com/2019/01/read-only-file-system-on-raspberry-pi.html

I have recently stumbled upon this tool:
https://github.com/BertoldVdb/sdtool

I immediately forked it:
https://github.com/milanvidakovic/sdtool

The main idea behind this tool is to set one of the following command bits of the CSD register (very low level communication) of the SD card (via SPI interface):

  • TMP_WRITE_PROTECT - temporarily enable/disable write protection for the SD card,
  • PERM_WRITE_PROTECT - permanently enable write protection for the SD card (cannot be reversed).
The behavior of the temporary write protection is quite interesting: when you enable it, it will silently fail when writing to the SD card. In reality, it means that when you try to write to a file (or create a new file), you will not get any error, and you might even be able to read that file (probably because it was placed in the cache of the file system), but as soon as you reboot, you will see that there have not been any changes.

How can this be used? First of all, you should set up your Raspberry Pi file system to read-only (for example, you can use this), so it would be normal not to have writes to the SD card. Then you can use the sdtool to enable the write protection. With those two steps, you should get the ultimate protection of your SD card.

Why is this important? I have couple of RPIs which work constantly for years. My experience with SD cards and RPIs is that they eventually corrupt the SD card. Sooner or later it will happen. If you just enable the read-only file system, it seems that the SD card still gets corrupted. I don't know why, but it happened to me several times. I have changed several SD cards (different vendors, different models), and several power adapters, and still got SD cards corrupted at some point. I thought that having the file system set to read-only would not corrupt the SD card during power loss (unattended shutdown), but, unfortunately, the SD card would get corrupted anyway. 

Even if the RPi is on the UPS (doesn't get turned off improperly), it would still corrupt the SD card sooner or later. It happened too many times with my RPIs (and the Odroid, too). 

One way to fix this is to boot the device from the USB hard disk instead of SD card, but then the same problem can occur with the hard disk (although, not that bad as it occurs with the SD card). As a matter of fact, it happened once with my Odroid and once with my RPi 3 (to have the USB hard disk corrupted). In addition, why would you put the USB hard disk on your RPI 0? It is supposed to be a lightweight system, not having a hard disk that consumes more power than RPI 0.

I have found an information on some forums that a special (quite expensive) SD card types do not get corrupted at all: aMLC and SLC type. Both are significantly more expensive than the usual cards, but people claim that they simply do not get corrupted.

One funny thing - my RPI 1 has corrupted the SD card only once in almost five years, while RPI Zero and RPI 2 and 3 can corrupt the SD card in a matter of months (or even sooner). Does it have to do with the speed of the system? Is the SD card overwhelmed with data and then it goes corrupt? Why is it corrupted in the read-only mode, then? I don't know answers to all those questions. What I do know, however, is the fact that the SD card would eventually get corrupted and that is the main reason for this post.

I am now testing this temporary write protection in conjunction with the read-only file system on my RPIs, and I will update this post with my experience.

At the end, just to repeat the fact that the contacts on the SD card are actually the SPI interface contacts, meaning that all the communication with the SD card goes over the SPI interface:




среда, 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.


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

ENC28J60 with 12-pin connector

I have recently purchased this module, and it has some strange pin names:

As you can see, some pins have wrong names:
LNT is actually INT
ST is actually SI (MOSI)
Q3 is actually 3.3V power
SCK is sometimes referred to SCLK (don't use the CLK for the SPI clock)

I am posting this to help people who, just like me, purchased this strange module.

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

Two-way AC control

How to control the AC two way using Raspberry Pi

In my previous posts, I have described my small Home Automation system. It consists of various sensors, connected to several Raspberry Pi computers. The control was initially one-way, meaning that I could only watch sensor data on the Home Automation web site. Then I have added AC remote control support to the system. I have used the lirc library which enables user to record IR commands and then to reproduce them. The initial setup enabled users to remotely control the AC from the Home web site. However, there was no way of knowing the actual AC status (if the AC already works or not). This means that the Home web site could not display the current AC status and instead of turning the AC on, we could turn it off. That is the topic of this post - how to add the support to the Home Automation system, which can read AC status.

By default, the AC is controlled using the IR remote. I have rather old AC, which is not connected to the WiFi, so there is only one way of knowing if the AC is working or not - the status LED at the AC device. I have figured out a way to read the LED status and to feed that information to the RPI. The Idea is quite simple: glue the photo sensitive resistor to the AC status LED, and connect that resistor to the simple circuit which gives an information to the RPI if the AC is working.

This is the photo sensitive resistor - photoresistor:


Here is the circuit:

The photo resistor decreases the resistance when exposed to the light. If there is no light, the resistance is couple of hundred kilo Ohms. When lit by the AC LED, the resistance drops to the approx. 20 kilo Ohms. Since the circuit is actually a voltage divider, when LED is not lit, the voltage at the GPIO port is high, meaning logical 1. If the LED is turned on, the resistance drops, the voltage at the divider drops, and the GPIO port reads 0.

The Python code which reacts to the change of LED operation is here:

PORT_AC = 21
GPIO.setmode(GPIO.BCM)
GPIO.setup(PORT_AC, GPIO.IN)

acStatus = not GPIO.input(PORT_AC)
print 'AC STATUS: ', acStatus

def ac_edge_detected(channel):
  time.sleep(0.25) // give some time to properly read GPIO port
  print 'AC EDGE detected, channel is ', channel
  global acStatus
  acStatus = not GPIO.input(PORT_AC)
  print 'AC STATUS: ', acStatus
  requests.get('http://' + STATUS_HOST + ':8080/setAc/' + str(acStatus))

GPIO.add_event_detect(PORT_AC, GPIO.BOTH, callback=ac_edge_detected, bouncetime=50)

Here is how it looks implemented:

And here is the AC part of the Home Web application:

Conclusion

Initial AC solution provided me with the option of turning on/off AC remotely. However, the system could not know the initial and current AC status, making turning on/off unreliable. This contraption is capable of determining the AC status by observing the AC LED status. It does so by having the voltage over the photo resistor dropping when the LED is turned on.

понедељак, 17. јун 2019.

Digital Wrist Watch using AtTiny85

I have recently found a great tutorial how to make a wrist watch using AtTiny85 microcontroller here: https://www.instructables.com/id/ATtiny85-Ring-Watch/

That was an inspiration to make my own wrist watch using the same IC. Here is it:

The watch consists of four elements:
1. AtTiny85 MCU
2. OLED display with  SSD1306 controller, 128 x 64 pixels
3. Two buttons, and
4. CR 2025 3V lithium battery.

Here is the schematics:


I have used the original plastic box for the OLED display to put all the components inside. Here is the bottom view of the same watch:
At the bottom, there is a battery, then the wrist belt, and then comes the MCU and the display at the top:

As you can see from the image above, I haven't soldered the MCU to the wires; instead I have soldered a socket (two halves, actually), so I could remove the MCU from the watch in order to program it.

Programming AtTiny85

Regarding programming, here is the link to the github repo (my own rework of the original code):
https://github.com/milanvidakovic/AtTiny85Watch

When speaking of programming AtTiny85, I have followed instructions from the https://www.instructables.com/id/ATtiny85-Ring-Watch/ site to install the support for the AtTiny85 into the Arduino IDE.

I have also decided to use my Arduino as a programmer for the AtTiny85. It is not necessary to use the Arduino, but I decided to do so, because it looked like the easier solution.

Here are the instructions:
1. Install the Arduino IDE.
2. Install the ArduinoTiny add-on from the: https://github.com/milanvidakovic/ArduinoTiny
    Unzip ArduinoTiny-master.zip
    Copy the tiny folder to the hardware folder under Arduino path
3. Add EEPROM support:
    In Arduino path, copy hardware\arduino\avr\libraries\EEPROM\EEPROM.h to 
    hardware\tiny\avr\cores\tiny folder
4. Install TinyWireM Library:
    Run Arduino
    Select Sketch Menu -> Include Library sub-menu -> Manage Libraries...
    Search TinyWireM
    Select TinyWireM and click Install button
    Close Library Manager and close Arduino
5. Install the Arduino as ISP project on the Arduino board
6. Use the Arduino as ISP to burn Bootloader to the AtTiny85 MCU
7. Use the Arduino as ISP to send the AtTiny85Watch project to the AtTiny85 MCU

Arduino as ISP

If we want to program AtTiny85 using Arduino, then we need to install the Arduino as ISP sketch on the Arduino:
1. Open File -> Examples -> ArduinoISP -> ArduinoISP
2. If you use SPI on expansion port, not on the 6-pin connector at the top of the Arduino, then you need to uncomment the following line:
// Uncomment following line to use the old Uno style wiring
// (using pin 11, 12 and 13 instead of the SPI header) Nano, Due...
#define USE_OLD_STYLE_WIRING
3. Click on the Upload button to make your Arduino an AtTiny85 programmer.

Use Arduino as ISP for programming AtTiny85

To use the Arduino as a programmer for the AtTiny85, you need to connect AtTiny85 to the Arduino like this:


Before uploading any program to the AtTiny85 using Arduino, it is necessary to burn the Bootloader on the AtTiny85:
1. Plug the Arduino to the USB port of your PC
2. Connect the AtTiny85 to the Arduino as shown on the image below
3. Choose Tools -> Board -> AtTiny85 as a Board
4. Choose Tools -> Programmer -> Arduino as ISP
5. Choose Tools -> Clock -> 8 MHz (Internal oscillator...)
6. Choose Tools -> Burn Bootloader

After this, you are ready to Upload your programs on the AtTiny85 using the Arduino as ISP. Just open the sketch, and then click on the Upload button. One note: for the AtTiny85Watch, it is necessary to choose Tools -> Clock -> 1 MHz (Internal oscillator...).

Conclusion

Programming AtTiny85 is perhaps the easiest if you are able to do everything in the Arduino IDE. Also, it is easier to use the Arduino as a programmer, instead of some proprietary hardware. AtTiny85 is quite interesting MCU. It is powerful enough to be used for the project like this.

уторак, 14. мај 2019.

32-bit FPGA-based computer

Going 32-bit

There are follow-ups:
- implemented BLIT instruction,
- adding SPI interface to my FPGA computer,
- making BASIC interpreter for my FPGA platform,
- using GCC on my FPGA platform,
- added cache controller,
- new VGA display mode,
- booting from the SD card.


I have upgraded my FPGA-based computer from 16-bit to 32-bit. It now has 16 registers, each 32-bit. It uses 32MB SDRAM which exists on the DE0-NANO board, but it also uses static RAM for the video memory (frame buffer), for both text and graphics mode. It is approx. 40 KB of static RAM.

FPGA Computer Schematics

Memory management

If was quite painful to make the computer work with the SDRAM. The 32MB SDRAM needs a special controller to be used. I have found one useful controller on the github:

Since there are two types of memory in this computer (dynamic and static), I had to make a decision how to layout the memory. First 40KBs are used for the static RAM (all interrupt vectors, text and graphics video RAM and sprite definition memory). After that, the rest of the memory is in the SDRAM (up until 32MB).

If there is a need to read from the memory, this is how it is done. Let's suppose that we need to read 16 bits from the PC + 2 address:

addr <= (pc + 2) >> 1;
next_state <= EXECUTE;
state <= READ_DATA;

We need to set the next_state register to the state to which we want to return, when the read is done. Then, the CPU goes to the READ_DATA state.

READ_DATA: begin
if (addr >= SDRAM_START_ADDR) begin
waiting_sdram <= 1;
addr_o <= addr;
rd_enable_o <= 1'b1;
if (busy_i) begin
state <= READ_WAIT;
end
end
else begin
memrd <= 1'b1;
memwr <= 1'b0;
state <= READ_WAIT;
end
end

In this READ_DATA state, the CPU puts the address to the SDRAM address bus (addr_o), and sets the rd_enable to 1. Then it waits until the SDRAM is ready to read (busy_i is 1). When the SDRAM controller starts reading, the CPU goes to the READ_WAIT state. 

READ_WAIT: begin
if (addr >= SDRAM_START_ADDR) begin
rd_enable_o <= 1'b0;
if (rd_ready_i) begin
waiting_sdram <= 0;
data_r <= rd_data_i;
state <= next_state;
end
end
else begin
memrd <= 1'b0;
memwr <= 1'b0;
data_r <= data;
state <= next_state;
end
end

The READ_WAIT state finishes when the data is obtained from the memory (the actual data is in the data_r register).  It takes approx. 6 cycles (at 100 MHz) to fully obtain data from the memory (from READ_DATA to READ_WAIT, both to be finished). Then, the CPU goes to the next_state, as being set before this reading operation has been started.

Regarding writing to the SDRAM memory, let's suppose that we want to put something on the stack:

addr <= (regs[SP] - 2'd2) >> 1;
data_to_write <= regs[ir[11:8]][15:0];
// move sp to the next location
regs[SP] <= regs[SP] - 2'd2;
next_state <= EXECUTE;
state <= WRITE_DATA;

We need to set the next_state register to the state to which we want to return, when the write is done. Then, the CPU goes to the WRITE_DATA state.

WRITE_DATA: begin
if (addr >= SDRAM_START_ADDR) begin
waiting_sdram <= 1;
addr_o <= addr;
wr_data_o <= data_to_write;
wr_enable_o <= 1'b1;
if (busy_i)
state <= WRITE_WAIT;
end
else begin
memrd <= 1'b0;
memwr <= 1'b1;
state <= WRITE_WAIT;
end
end

In the WRITE_DATA state, the CPU would set the address to be written (addr_o), data to be written (wr_data_o), and would set the wr_enable_o to 1. Then it would wait for the controller to notify that it is ready to write (busy_i is 1). Then the CPU goes to the WRITE_WAIT state.

WRITE_WAIT: begin
if (addr >= SDRAM_START_ADDR) begin
wr_enable_o <= 1'b0;
if (~busy_i) begin
waiting_sdram <= 0;
state <= next_state;
end
end
else begin
memrd <= 1'b0;
memwr <= 1'b0;
state <= next_state;
end
end

The WRITE_WAIT state finishes when the data is saved to the memory.  It takes approx. 6 cycles (at 100 MHz) to fully write data to the memory (from WRITE_DATA to WRITE_WAIT, both to be finished). Then, the CPU goes to the next_state, as being set before this writing operation has been started.

CPU redesign

The CPU itself was redesigned, too. It now has quite rich instruction set, 32-bit, 16-bit and 8-bit instructions, floating point (32-bit, single precision), and three interrupts:
- IRQ0 is the timer interrupt (triggered when a given number of milliseconds have been counted),
- IRQ1 is the UART interrupt (triggered when a byte has arrived), and
- IRQ2 is the PS/2 interrupt (triggered, whenever a key is pressed on the PS/2 keyboard).

The timer IRQ was made this way: there is a counter which is incremented every millisecond. There is a timer port which initially holds zero. The programmer needs to set the number of milliseconds to be counted after which the interrupt would occur. It is done using the OUT instruction:

mov.s r0, 0x0001 ; JUMP opcode
mov.s r1, TIMER_HANDLER_ADDR ; timer vector address
st.s [r1], r0
mov.w r0, timer_triggered
mov.s r1, TIMER_HANDLER_ADDR + 2
st.w [r1], r0 ; the timer IRQ handler has been set


move.w r0, 50  ; set the timer interrupt for every 50 milliseconds
out 129, r0

The assembler code above would set the internal timer register to the given value (50). Every millisecond the CPU would increase another internal register, named timer_counter, and when the timer_counter reaches the timer, that would trigger the timer interrupt:

if (timer && (timer_counter < timer)) begin
timer_counter <= timer_counter + 1'b1;
end
else if (timer && (timer_counter == timer)) begin
irq[0] <= 1;
timer_counter <= 0;
end 

At the end of each instruction execution, there is a check for the interrupts:

if (irq_r[0]) begin
// timer
pc <= 16'd8;
addr <= 16'd4;
irq_r[0] <= 0;
end 

If there is a timer interrupt, the CPU would jump to the TIMER_HANDLER_ADDR, which is 8.

FPGA Raspbootin loader

I have modified the FPGA Raspbootin loader so it would now load the FPGA itself, instead of relying on the Quartus II studio for that. This means that I can now control the Computer from a single application - FPGA Raspbootin:


The loader now first loads the design into the FPGA (unless it is flashed - then no loading the design file is needed), and then it loads the selected binary into the computer. Here is the Java code for loading the design into the FPGA (by starting the quartus_pgm.exe program):

public static void runFpga() {
Process process;
try {
process = new ProcessBuilder(qpfPath,
"-c", "usb-blaster",
"-m", "jtag",
"-o", "P;" + sofPath).start();
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
  System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}

The qpfPath points to the quartus_pgm.exe file, which acutally loads the design into the FPGA. Usually it is something like: C:\altera\13.0\quartus\bin\quartus_pgm.exe

The design file has the *.sof extension, and it is loaded into the FPGA using the quartus_pgm.exe program. The *.sof file is built during the compilation of the design inside the Quartus II studio. In my program, the path to the *.sof file is in the sofPath variable.

More details about loading FPGA design on the DE0-NANO FPGA board can be found here:
https://mvidakovic.blogspot.com/2019/10/flashing-de0-nano-fpga-board.html

Conclusion

The 32-bit rework took more time than I expected, mainly because I wanted to use the built-in 32MB SDRAM. Then I added the floating-point instructions and now it looks quite stable. I have used about 80% of the FPGA, so I could try to do something more later.

The CPU is on the github:
https://github.com/milanvidakovic/FPGAComputer32

The assembler examples are on the github:
https://github.com/milanvidakovic/Assembler32

The Raspbootin64 boot loader is on the github:
https://github.com/milanvidakovic/FPGARaspbootin64Client

The Emulator is on the github:
https://github.com/milanvidakovic/FPGAEmulator32