LoginLogin

Mock computer - A computer in a computer

Root / Talk About Programs / [.]

spaceturtlesCreated:
Instead of making a mock OS why not a mock computer? My take on this is a low level machine sim functional like a virtual machine with low-level features; you could be able to code an operating system instead of a mock OS utility program right down to the pixel. Progress will start and be posted on here after I have released v1 of project 2, a sandbox space survival game. Features: Planned: Basic mathematical functions Basic logic functions Basic data management Basic graphical functions Current: Dev notes: 2/12/18 -When I say "basic" I may or may not mean that. 2/15/18 -I've started work on it yesterday and got 4/5ths of one function done. -I'm thinking of calling it V-MAHrS (Virtual Machine Assembler Hardware Sim). I might need a better name. -I wasn't thinking much (or I thought too much) when I said that my program won't include the small bits like the bus. It obviously wouldn't work without one.

This would really be an emulator for a nonexistant computer. Like the ones in 0x10C

This would really be an emulator for a nonexistant computer. Like the ones in 0x10C
Pretty much. I might even turn it into a bit of a coding game.

If you build a virtual machine right you should be able to run your programs to any platform you want (Windows, Mac, Linux, SmileBASIC, etc) without needing to rewrite a single line of code. This is essentially how Java works. I've created my own awhile ago, although I've not worked out all the bugs so I never released it (and frankly stopped caring about SmileBASIC since it's stuck in Japan), but you can see the benefits of a virtual machine pretty clearly from the screenshots. It allowed me to write programs in an assembler that would then compile to a program that could run on my PC, my Raspberry Pi, in SmileBASIC, and I even was working on TI-84+CE support. I'll just write up some information what I learned from this to see if any of it will help you. Assembler It will be hard to code anything for your virtual machine if you lack an assembler to go with it. An assembler compiles assembly code written in ASCII text into the numerical machine code that the machine can understand. Basically to make an assembler you need three things: 1. A table that matches instructions as ASCII text to their numerical machine code equivalent. This is called an "opcode reference chart". For example, see the Z80 opcode reference chart. 2. Your assembler will pass through your code twice. On the "first pass" it builds what's called a "symbol table". Unless you're building an incredibly primitive assembler like from the 80s, most modern assemblers don't require the user to know anything about RAM addresses when coding. If you store data in RAM, you should be able to put a symbol tag before that data and simply refer to the symbol tag when you need its address, and the assembler will fill it in for you. For example, in my virtual machine when I wanted to change the Window's title, I'd do something like this:
	;Set title
	MVU $0
	MOV $5
	EXP B
	GET appTitle
	MVU $0
	MOV $8
	SYSCALL
Notice how I reference "GET appTitle" on its own. If I were to scroll down to the very bottom of the code, I'd find:
appTitle:
	$"Snake"
"appTitle" here is called a "symbol". It is tagging the memory address of the string "Snake". When you pass through your code the first time, you don't compile anything, you just keep track of how far down in the code you are and then when you hit a symbol tag such as "appTitle", you know exactly where that symbol will exist in memory when the program is ran. What you then do is add both the symbol and its memory address to your "symbol table". You pass through the entire code building a symbol table from every symbol tag. 3. The second pass actually compiles your code. Just run through the code and for every line, you can directly compile it based on the structure of your opcode reference chart. Whenever you see a symbol, such as the line "GET appTitle", you can simply replace "appTitle" with its actual value based on your symbol table you built in the first pass. There are other things you may consider including in your assembler, such as macros. Virtual Machine Building the actual virtual machine should be a bit easier. Essentially this is all you need for any virtual machine...
;Pseudocode

var RAM = load("program.bin")
var pc = 0
var register1, register2, register3, etc
var flag1, flag2, flag3, etc

var halted = false;
while (!halted) {
      switch (RAM[pc]) {
            case RAM[pc] == 0:
                  ...
                  break;
            case RAM[pc] == 1:
                  ...
                  break;
            ...
            etc
      }
      pc++;
}
So first you need to load your program into the virtual machine's RAM somehow. Your RAM's size can either be static (either the user defines how much RAM the program has explicitly or implicitly based on the program's size) or it can be dynamic (resizes itself if the program needs more RAM during runtime). "Registers" are piece of hardware inside the CPU that computers use to temporarily keep track of data. They can usually store between 8 bits to 64 bits, but that's completely your choice. Intel 4004 was a 4-bit CPU and fully functional. Z80 had 16-bit registers, and eZ80 had 24-bit registers. Modern x86_64 computers by Intel and AMD go up to 64-bits. Your "pc" is your Program Counter, it's the most crucial register for any computer. It tells the computer which memory address to read the next instruction from. Hence why every loop we increment it by 1, because we are incrementing through our code. The "switch" statement just says to do something specific depending on what the instruction is. Typically CPUs also have "flags", which are a special type of 1 bit register. For example, if register1 is holding the value "2" and I call an instruction to compare it to register2 which holds the value "3", they are not equal. But where is the result of the comparison stored? Typically in a flag. So you might have a flag called "equal" and it will be set to 1 if they are equal and 0 if they're not. Then later, you might have a jump instruction that changes the program counter but only if the equal flag is true, that way you can have sub routines only happen under certain conditions. That's basically all a CPU is. It looks at a number (your instruction), manipulates some memory based on the number, and then goes to the next number. Virtual Kernel Notice in my virtual machine I use an instruction called "SYSCALL". All modern operating systems have something called a "system call". For example, in Linux operating systems on x86_64 machines, if you want to exit a program, you'd use this system call:
mov rax, 60
mov rdi, 0
syscall
In this case, "rax" and "rdi" are registers and "mov" is an instruction to move data from the second operand into the first. In this case, "syscall" takes two inputs: the ID of the system call (60 being the "end program" syscall) and the error code (0 being "no error"). This system call is sent to a piece of software called the "kernel" which handles the call. All modern operating systems have some sort of kernel. The kernel handles most everything at a hardware level. For example, if I want to save a file to the hard drive, that is a physical process. Something has to tell the hard drive to spin up, something has to tell the actual write head what to do. Given there's thousands of different pieces of hardware out there, nobody wants to write code for a specific device. No one wants to write a program that can only save files for a very specific hard drive, for example. So instead, the kernel handles all file operations, and the programmer just has to send messages to the kernel. This way, the programmer doesn't need to know how the hard drive works, they just need to know how to control the kernel, and this kernel contains the information of thousands of different pieces of hardware and how to use them. The programmer only needs to know how to code for 1 kernel which then can control thousands of unique pieces of hardware. For my assembler I built something very similar I called the "virtual kernel". It is not a true kernel as it does not directly interact with the hardware, but from the programmer's perspective inside the virtual machine it's the same exact thing. Imagine if I want to save a file in my virtual machine, but the virtual machine is running on a Windows computer. The Windows kernel expects completely different code for file operations than, let's say, the Mac or Linux kernel. Or, let's say I'm in SmileBASIC. SmileBASIC also expects very different code for file operations. So what the "virtual kernel" does is abstract all that out. I build a "virtual kernel" for each platform, and what this does is allows the assembly language to make a system call to the virtual kernel, and the virtual kernel handles the actual operation depending on the platform. This way, the code for saving a file will be exactly the same on the programmer's end whether or not I'm running on Windows, Mac, Linux, SmileBASIC, etc. The goal of the virtual kernel should be to abstract out all of your hardware specific stuff. It should have things built-in like saving and loading data to a virtual disk, getting input from the mouse/keyboard/touch screen, playing audio, and outputting graphics to the screen. You should never directly do any hardware-specific operations within the virtual machine's assembly code itself. That will not only make coding more difficult and convoluted but it will make porting your virtual machine to other platforms insanely difficult. Stack It's not required but highly recommended to implement a stack inside of your virtual machine. All modern computers have one. What's a stack? It's a type of data structure called LIFO (last-in, first-out). Both names describe how it works in different ways. Imagine you have a box that can only fit the width and length of a single book, but you can fit 20 books if you stack them on top of each other. I place the first book in, then the second, then the third, then the fourth, then the fifth. Now, let's say I want to read the first book. Well, I can't, it's at the bottom of the stack. So I have to remove the fifth book first, then the fourth, then the third, etc, until I get to the bottom. This is why a stack is also called LIFO, because the last book I put on the stack (the fifth one) is the first one to come off the stack. With a stack, you can only look at the books on the top, and you can only remove books from the top. You can't remove anything that is not on the top of the stack. Looking at the book at the top is called "peeking". Removing that book from the stack is called "popping". Adding another book onto the stack is called "pushing". So, you might be wondering, what do stacks have to do with computers? Imagine if I'm writing an RPG, and in my RPG I have some code to save your game. Most RPGs have multiple save points, so there are dozens of different places I could save my game. Should I copy and paste the save code to all those dozens of places? No, that's a waste of memory. Instead, I could only write the code one time, and call it when I need it. Calling code stored somewhere else is known as a "subroutine". How does a subroutine work? Basically, when I call the subroutine with some sort of "call" instruction in assembly code, the CPU changes the program counter to the address of the subroutine, so then it will started executing instructions at the subroutine. Then, when it hits the "return" instruction at the end of the subroutine, the CPU will change the program counter back to the value it was before I called the subroutinee (plus one in order to not get stuck in an infinite loop of calling and returning). But how does the CPU remember the address from which it was called? It remembers it using a stack. When you "call" a subroutine, it pushes your current program counter onto the stack. It then changes the program counter to the address of the subroutine, and when it's done executing, it pops the program counter back off the stack which returns it to where it was originally called. Stacks are also useful since CPUs always have a limited number of registers, so all code shares the same registers. So if I am using register2 for example, and I call a subroutine that also uses register2, my data in register2 will be destroyed. This can be avoided by pushing register2 onto the stack before I call the subroutine and then popping it off the stack when I'm done. So yeah I probably wrote way too much but maybe some of these tips will be helpful.

Whoa thanks for all those bits of info, amihart! My virtual machine thingy isn't meant to be a higher-level assembler and a virtual machine to run that code though. It's supposed to be the hardware itself at the level where an OS's kernal functions. This way the user can code their own language, kernal, os, game , or whatever. I'm not sure how exactly the primitive code will look like (I'll probably use hexadecimal numbers). 0x00 could be NOP, 0x01 could be LOAD, etc. I'll have to take a look at the instruction sets of existing systems.

I tried this for a bit. I wrote some code that literally emulated the circuitry of a computer I built in Minecraft with redstone. Individual bus lines, memory caches, down to the individual operations inside the adders of the CPU, were all emulated in DEF blocks. It was mainly for programming purposes, on the Minecraft end. I could create programs on the fly on my 3ds, output the opcodes, and physically program it in on Minecraft. Testing and debugging was faster this way too, although the continuity didn't always remain . . . The idea isn't far fetched. Just design a chip, write the right code to properly emulate the chip, and expand on its functions through programming. It would also create a medium for 'guarenteed' cross compatibility. You could build an emulator on SmileBasic, an emulator in C++, and maybe even a physical chip in Minecraft, and the code you write for it 'should' work across all platforms.

-snip-
Right. A spec helps as well. If you have a good spec you can design your emulator off of the spec and all should go well.

At first I'll base my sim off as close as I can to the one in But How Do It Know by J. Clark Scott but without some of the commands so I can have a basic framework to work with. Then I'll add more functions/functionality to it and give it a bit of a twist after looking at other systems.
I tried this for a bit. I wrote some code that literally emulated the circuitry of a computer I built in Minecraft with redstone. Individual bus lines, memory caches, down to the individual operations inside the adders of the CPU, were all emulated in DEF blocks. It was mainly for programming purposes, on the Minecraft end. I could create programs on the fly on my 3ds, output the opcodes, and physically program it in on Minecraft. Testing and debugging was faster this way too, although the continuity didn't always remain . . . The idea isn't far fetched. Just design a chip, write the right code to properly emulate the chip, and expand on its functions through programming. It would also create a medium for 'guarenteed' cross compatibility. You could build an emulator on SmileBasic, an emulator in C++, and maybe even a physical chip in Minecraft, and the code you write for it 'should' work across all platforms.
Unfortunately I'll have to code/design a "companion" version of my vm for it to count as cross-platform. I don't want the instruction set to be too similar to existing ones. If I were to do this maybe I can design a redstone computer like you did.