Releases: strinsberg/lt64
Debug mode improvements
The changes improve the debug mode.
- Adding a step to the debug process allowing executing only a single instruction at a time or choosing to execute multiple instructions before stopping again.
- Adjusting output to give decimals in brackets next to hex stack values. Helps when the values on the stack are numbers.
- Added function to print the name for each op code. This makes it possible to easily see what the next operation will be rather than having to know/lookup the hex codes.
- Debug steps automatically skip the operations that are just there to jump past the static data section.
It should be noted that debug mode is not a "debugger" exactly. It just allows stepping through the code to see what operations are being executed and what the contents of the stack are. Even this basic functionality can be extremely helpful when debugging programs, but it is still very basic.
Bug fixes and op encoding adjustments
Removed portions of the operations that used offsets encoded into the top byte of ops. This was a mistake and now all operations take their values of the stack only. There are still a couple of operations that take a 1 in the top byte to treat the top of the stack as an address or an offset. The newest of these is prnmem that previously would only use the the value as an offset. This made it impossible to print static memory by label in the assembly code. In the future there will be a significant adjustment to pack all ops 2 to a word where possible and using top bytes for extra information makes this far too complex.
With the above changed, all of the user scaled fixed point numbers to take values off of the stack instead of offsets.
Added a print op prnpk that prints the top of the stack as 2 characters.
Updated documentation to deal with these changes.
Documentation and tweaks make it ready to be a public repository
The major changes in this version are the addition of documentation for most things along with a few comments to make some code clearer. This includes the README that goes over the usage, architecture, and available operations. These changes make it possible to make it a public repo that other people will see. I think it the project is in a pretty stable state now.
Other small tweaks include some adjustments to the way that test and debug output was made. It is easier to see what is going on during a program run with the debug output for each operation. Testing output no longer prints the range of the memory being printed, which was mostly just clutter since it is only ever used to print the stack. Finally, some small changes were made to the Makefile to be more consistent with object file creation.
Rewrite to more standard stack based architecture
This release represents a complete rewrite of the first try. The goal was to remove some of the things that made the first version hard to work with.
- Most notably adding a return stack and making CALL and RET ops very simple.
- The addition of a return stack makes the operation a little bit more like a forth stack machine. Though not nearly the same kind of functionality is available. By making it a more traditional stack machine some forth operations like DUP and ROT were added to make the stack a little easier to work with. However, this is not an attempt to make a forth machine.
- It also lead to changing the architecture to use 16 bit words and op codes. This is a bit wasteful space wise for the ops but it makes the machine itself simpler and more efficient when using 32 bit numbers. There is no longer a word, double word, and quad word just to have a generally useful.
- Because of the loss of char size memory some extra ops and memory were setup for characters and strings. There is a buffer after program memory of 1024 words where strings are read into. This memory packs two chars into the 16 bit word. Some ops to pull the words out and pack and unpack them and store them again were added to make string manipulation a little easier.
- Printing of C style strings can be done directly from the buffer or starting with a memory position.
- It is also possible to copy memory from the buffer directly to memory or the reverse. This uses the C memcpy function and reduces the need for the programmer to take care of the copy with loops and extra instructions. The string version of this operation automatically finds the length of the string if it is not known already and then performs the copy. This is less efficient than the normal memory copy, but it again saves the programmer having to write loops to find the string length and then copy it. So it should still be much more efficient than doing it manually.
- Some support for user supplied fixed point scaling was added. This makes the fixed point use a little more flexible, though the default is still there at 3 decimal places.
This is not really a release yet as it has no documentation. The priorities now will be to add documentation, mostly outside of the code, describing the operations and architecture and some of the possibly unexpected quirks. Along with this the tests need some additions in a few places, though this could also be tested through the assembler. The basic simple cases and some edge cases should already be tested to confirm that things work as expected, but once the assembler is made it can be used to create tests that can find the edge cases easier and exercise more complex functionality. I.e. function calls and recursion. This makes the assembler the next priority before this can be considered a "release" (I use quotes because this is really just for fun and certainly as it is there is no reason for me or anyone else to use it over the many high quality alternatives). The assembler is necessary as it is not practical to write programs in a hex editor or hex string. If I want to play with targeting the vm with a compiler or writing an interpreter that runs on it then there needs to be an assembly language that makes writing programs easier and abstracts some of the compound op codes.
First Try
This represents a point where I had a working machine. However, it had some flaws because I tried to implement the architecture as a stack machine, but also sort of following register architecture. Things like procedure calls were possible saving things on the stack, but different kinds of scoping were harder to work with. The need to save previous arguments on the stack, but also maintain pointers to them and do computations on the stack was not ideal. Access and manipulation of the stack was not really under the programmers control as it would be with a register machine, but also could only be done with stack based operations. Mixing these two approaches made things messy and left me with difficulty imagining how to do things like static scope. The static scope of a block or a procedure is something that a programmer or compiler would have to figure out before runtime, but my attempt at it with OP codes really just made something like procedure calls but without arguments. This was not useful and I would revert it and think again, but I think some things need to be adjusted.
This is what I get for not knowing very much about stack based architecture, and basing my knowledge of it on a stack based interpreter I used/extended for a school compilers project. The main reason this messed me up is that that stack machine was purpose built with a pascal like language in mind. So the operations and layout of memory ect. was setup very specifically to support a block based language. To support something more general I would like to try and adjust things to work like actual stack based hardware and software might, perhaps a little like forth (which I also know very little about). The goal is to find a way to build a vm that is general enough that it can be used as a target for various of my own projects without having to be tailored for a given language.