A RISC-like (Reduced Instruction Set Computer) emulator that runs a simple Assembly-like machine language.
Commit message emoji meanings can be found here. This repository's URL can be found here.
Rafael Almeida de Bem (website).
Implement a virtual machine that contains a CPU and Memory modules and runs a simplified version of the Assembly programming language. University project.
This project was implemented with scalability and ease of use in mind. Adding and modifying commands is very easy and requires very little knowledge of the system as a whole. Since mutable objects were used, some confusion might arise. However, they were treated similarly to C++'s pointers, so programmers with low-level language knowledge should feel right at home.
Since the sixth release of this system, the VM supports simple concurrent mechanisms such as blocking processes that are waiting for an IO operation using an IO handler thread. On the seventh release of the system, a simple shell socket was added to concurrently load new processes into the memory or to shutdown the system.
Most of the initial code was heavily inspired by the teacher's example, written in Java. The command structure is the same, albeit with different parameters and language-specific improvements (see Command_SWAP).
The project has two threads: the Virtual Machine thread, and the "main" thread. This allows the program to have a responsive GUI without having to stop and wait for the GUI to update.
Every CPU cycle the GUI updates and sleeps for half a second. Without a Tk object on the VM, there is no sleep()
.
The VM contains both the CPU and the Memory instances. First, the CPU is instantiated. Secondly, the memory. Lastly, the Tk object is checked and, if it is defined, some properties are set - such as disabling the capture of keystrokes, and some tags for coloring the current command (light red), and the command that set an interruption (dark red).
Upon initialization, the memory is populated with 512 Command_EMPTY
objects.
Afterwards, the IVirtualMachine.load_from_file()
method is called, which reads a file from the disk and converts the
lines to Command objects.
With the loading of the program into the memory done, the CPU can execute its loop on the IVirtualMachine.start()
(which, in turn, will call IVirtualMachine.run()
) method.
Interruptions and traps are also implemented, both using Exceptions.
Interruptions and traps are implemented using a Queue that holds the Exception classes that correspond to each interruption type. Custom exception classes were used in this case to emphasize that an interruption needs to be handled. A command can set a trap by doing the following:
# Types of each of the referenced arguments:
self: IBaseCommand = ...
self.interruption_queue: Queue = ...
self.func: Callable = ...
# Set a trap
self.interruption_queue.put(ETrap(self.func))
When an error occurs, such as an IndexError
on a memory access attempt, an interruption is set and executed after the
command has ended. The interruption classes are as follows:
Exception | Meaning |
---|---|
EInvalidCommand |
An invalid command string has been inputted |
ETrap |
A trap has been set by the user program |
EInvalidAddress |
There was an attempt to access an illegal memory area |
EProgramEnd |
A STOP command was issued |
EMathOverflowError |
An overflow has occurred |
EShutdown |
End the CPU loop, no more processes are available |
ESignalVirtualAlarm |
SIGVTALRM clock interruption |
EIOOperationComplete |
An IO request has been fulfilled |
This project requires Python 3.8 or higher to run. Older versions will not work as expected and have not been tested.
First, install the required files:
python3 -m pip install -r requirements.txt
Then, simply run the program. This will open a new Tkinter window with the current command being executed, as well as the rest of the memory. When no argument is passed, a default example program is loaded and executed.
python3 main.py
Alternatively, you can also pass a .asm
program file to be executed by the VM:
python3 main.py foo.asm
This will load the program foo.asm
into the virtual memory and evaluate the program execution step by step.
If you do not wish to see the step-by-step evaluation of the program, open main.py
and remove the #
(comment) sign
from text = None
. This will stop Tkinter from opening.The Tkinter interface will be removed in a future release. The Tkinter interface has been removed.
This VM interacts with a so-called DeBem Shell or DBSH, for short.
The shell (client) will try to connect to localhost:8899
via binary socket to communicate with the VM (server), which has bound that address and port upon creation.
Running the shell is as simple as:
python3 source/user/shell.py
After running the command and assuming the connection was successful, you will be greeted by a welcome dialog:
88888888ba, 88888888ba ad88888ba 88 88
88 `"8b 88 "8b d8" "8b 88 88
88 `8b 88 ,8P Y8, 88 88
88 88 88aaaaaa8P' `Y8aaaaa, 88aaaaaaaa88
88 88 88""""""8b, `"""""8b, 88""""""""88
88 8P 88 `8b `8b 88 88
88 .a8P 88 a8P Y8a a8P 88 88
88888888Y"' 88888888P" "Y88888P" 88 88
Welcome to DBSH - DeBem Shell
Type 'help' to get started
$
Then you can interact with the interface with the keyboard as you would a normal shell.
The DBSH is very bare-bones, and you can list all of the available commands by entering help
.
If you type help
followed by a command, the shell will print out that command's documentation.
To load a program into the memory, enter the following:
$ load example_programs/p2.asm
The program will then be loaded onto the memory of the VM and will be added to the process queue. The PID of the new process will also be printed to the screen.
Playing around in the shell is also encouraged.
This repository contains extensive tests for the virtual machine system.
Most programs in the example_programs/
folder have tests testing their functionality.
The tests file is called asm_test.py
and is contained in the tests/
folder.
To run all tests:
python3 -m pytest
To run a specific test (from this repository's root directory):
python3 -m pytest test/asm_test.py::AssemblyTest::{{ desired test name goes here }}
# For example, to run the P2 file test:
python3 -m pytest test/asm_test.py::AssemblyTest::test_p2
The "desired test name" is the name of the function defined in the asm_test.py
script.