So, this is less of the usual "share a cool thing" post, and more a general question about how to make the TC-06 more...well, usable.
I'm toying with ideas on how to implement a C compiler for it (likely either an LLVM extension, or GCC extension), because being able to write in a higher-level language and then compile to the TC-06 would make programming advanced things way easier, not to mention just being a huge achievement. As I understand it, most compiler implementations are going to want machine-code/assembly implementations of various functions (e.g, the function for the + operand), so in-between google searches and delves into manuals, I decided to start prototyping some of those basic functions.
I don't think the extent of how utterly useless the RAM is had hit me until now. I decided to start with = and +, as they're arguably the most basic (and most commonly used) operands - for instance, you can represent subtraction & multiplication with +. Here're my notes on the two.
-=SET OPERATION (=)=-
* x = y;
* I don't think you can build a programming language without this.
* Assumes register 0 is a pointer to the start of the arguments, and register 15 is the integer 1.
* Assumes OFST <N> is run, where N is the first address of the SET op.
* ASM, V1:
MOVI 2 4 //Addr 00. Move the command to be modified into register 2.
PMOV 0 2 7 31 0 0 //Addr 01. Splice the pointer/address from register 0 into register 2.
MOVO 2 4 //Addr 02. Move the modified MOVI back into RAM.
PCSR 2 0 //Addr 03. Temporarily disable the current offset.
MOVI 4 0 //Addr 04. Acquire the source address for MOV and put it in register 4.
PCSR 2 1 //Addr 05. Re-enable the offset.
MATH 15 0 0 //Addr 06. Increment the pointer by one.
MOVI 2 10 //Addr 07. Move the command to be modified into register 2.
PMOV 0 2 7 31 0 0 //Addr 08. Splice the pointer/address from register 0 into register 2.
MOVO 2 4 //Addr 09. Move the modified MOVI back into RAM.
PCSR 2 0 //Addr 10. Temporarily disable the current offset.
MOVI 5 0 //Addr 11. Move the destination address for MOV into register 5.
PCSR 2 1 //Addr 12. Re-enable the offset.
MOVR 0 5 4 //Addr 13. Actually set X (register 4 pointer) to Y (register 5 pointer). Function finished.
* Version 2 assumptions?
* It assumes that register 15==int 1, and that OFST<N> is run, where N is the pointer for variable X (target), N+1 is the pointer for variable Y (destination), and N+2 is the code.
* ASM, V2:
DATAC <X> //Addr 0. Variable X.
DATAC <Y> //Addr 1. Variable Y.
MOVI 0 0 //Addr 2. Set registers == the pointer for X.
MOVI 1 1 //Addr 3. Set registers == the pointer for Y.
MOVR 0 0 1 //Addr 4. Set ram[x]=ram[y]. Function finished. -=ADD OPERATION (+)=-
* x = y+z;
* Assumes OFST<N> is run, where N is the pointer for variable X (target), N+1 is the pointer for variable Y (first number), N+2 is the pointer for variable Z (num 2), and N+3 is the code.
DATAC <X> //Addr 00. Variable X.
DATAC <Y> //Addr 01. Variable Y.
DATAC <Z> //Addr 02. Variable Z.
MOVI 0 0 //Addr 03. Set registers == the pointer for X.
MOVI 1 1 //Addr 04. Set registers == the pointer for Y.
MOVI 2 2 //Addr 05. Set registers == the pointer for Z.
MOVI 3 10 //Addr 06. Move a MOVI command into register 3 for modification.
PMOV 1 3 7 31 0 0 //Addr 07. Splice the pointer for Y into the MOVI command.
MOVO 3 10 //Addr 08. Move it back into RAM.
PCSR 2 0 //Addr 09. Disable the offset.
MOVI 1 0 //Addr 10. Go ahead and actually load the contents of variable Y into register 1, overwriting the pointer.
PCSR 2 1 //Addr 11. Re-enable the offset.
MOVI 3 16 //Addr 12. Move a MOVI command into register 3 for modification.
PMOV 2 3 7 31 0 0 //Addr 13. Splice the pointer for Z into the MOVI command.
MOVO 3 16 //Addr 14. Move it back into RAM.
PCSR 2 0 //Addr 15. Disable the offset.
MOVI 2 0 //Addr 16. Load the actual contents of variable Z into register 2, overwriting the pointer.
PCSR 2 1 //Addr 17. Re-enable the offset.
MATH 2 1 0 //Addr 18. Add the contents of register 2 (variable Z) to register 1.
MOVI 3 22 //Addr 19. Move a MOVO command into register 3 for modification.
PMOV 0 3 7 31 0 0 //Addr 20. Splice the pointer for X into the MOVI command.
MOVO 3 22 //Addr 21. Move the command back into RAM.
PCSR 2 0 //Addr 22. Disable the offset.
MOVO 1 0 //Addr 23. Set X to the result, by moving the value into the RAM location specified by its pointer.
PCSR 2 1 //Addr 24. Re-enable the offset. Function finished.
Yikes. Had to draft some new codes just to make them possible with any degree of ease. From the current documentation file:
OP-CODE: UTL (0x1011) <sub-code = 4b> <arguments = 24b>
- Gives 16 new sub-op-codes, primarily utility functions, like enabling an offset to Absolute operations (e.g, MOVI 1 0 becomes MOVI 1 0+offset)
- If a sub-code is given a title, then you can refer to it by name directly in the Assembler - i.e, UTL 0 0 has the same result as OFST 0 in the compiled bytecode.
- Operation 0x0 - OFST <24-bit address>: Sets an OFFSET (from address 0) for all ABSOLUTE ADDRESSING. Affects MOVI, MOVO, SETDATA flag 2, and GETDATA flag 2. Uses the full 24 bits given to it.
- OFST 5 sets a 5-address offset to all absolute address references - save for those in IFJMP & JMP with Flag 1 or Flag 3.
- This means OFST 5 followed by MOVI 1 0 will result in MOVI moving the contents of address 5 (0 + 5) into register 1.
- But JMP 1 0 will still jump to & execute the contents of RAM address 0. Since flags 0 and 2 provide relative addressing, it's not necessary.
- OFST 0 will also reset it so things work as expected.
- Operation 0x1 - PCSR <4-bit sub-sub code> <20-bit argument>: Subcodes upon subcodes! This lets you interact with the processor somewhat.
- PCSR 0 <arguments ignored>: Get CURRENT COUNTER POSITION. Similar to a GETDATA op, but for processor info instead. Returns the current counter position in Register 1.
- PCSR 1 <arguments ignored>: Get CURRENT OFFSET. Same as PCSR 0, but for the offset instead.
- PCSR 2 <1-bit flag>: Temporarily ENABLE or DISABLE the offset.
- Operation 0x2 - TIMR <2-bit OFF, START, PAUSE, GET value> <22-bit TIMER ID>: Times the number of cycles run by the processor.
- TIMR OFF <id> (argument 0x0): The default state. Sets the # of cycles to 0, and won't increment them.
- TIMR START <id> (argument 0x1): Sets the # of cycles to 0, and starts incrementing it with each clock cycle run.
- TIMR PAUSE <id> (arguiment 0x2): Pauses incrementation, but leaves the # of cycles alone. If run while paused, resumes cycling.
- TIMR GET <id> (argument 0x3): Sets the contents of Register 1 to be the number of clock cycles run since the last call of TIMR SET.
- The ID is useful if you're working in, say, a multi-tasked environment, and you want your program to time itself so it won't run over a requested number of cycles.
- Operation 0x3 - MOVR <1-bit flag> <4-bit source-addr register> <4-bit dest-addr register> <remaining bits ignored>: Like MOVI and MOVO, but lets you MOV directly from one RAM location to another.
- MOVR 0 <x> <y>: When the flag is 0, it uses ABSOLUTE ADDRESSING, no matter what. If registers[x]==4, then the source address will always be 4.
- MOVR 1 <x> <y>: When the flag is 1, it will use RELATIVE ADDRESSING only if OFST is active. In that case, the source address would be registers[x]+OFST; say an offset of 5, and [x]=4, that gives 4+5 for a source address of 9.
- Operation 0x4 - TBD
- Further operations: TBD
I'm not sure about MOVR - it basically compresses a bunch of operations down into one, and serves only a very specific edge-case purpose, to my knowledge. It's not even implemented or anything, just wrote it up to use in simplifying the = function.
So, that leads me to the question of the hour - how do I render the TC-06's RAM practical for storing more than just code, and should I? Part of Senbir's charm at this point might be that its instruction set is unusual, and that programming for it is a challenge, even when you have lots of RAM, disk space, and processor speed available, simply due to that unusual set - adding, say, a "RAMC" op-code that does sub-codes for most major functions, but solely in RAM (e.g, an actual MOV comparable to that of Redcode or similar) would probably circumvent some of that challenge, and that charm.
If making it more practical were to potentially be detrimental to the spirit of the game, should I then consider just doing a proper spinoff with that alternate instruction set? Something you could select in, say, Custom mode - same peripherals, world, underlying "hardware", etc, just a new instruction set/processor.