-=OVERALL INFO=-
* 32 bytes of RAM. One byte = one address.
* All data in bytes. ALL DATA. Even the program counter, meaning a hard max of 256 bytes of RAM and drive space.
* Two 1-byte registers. Used by MOV, DAT, OPR, BRN, and more - see op-code documentation for specifics.
* Runs at 30 Hz.
* First 3 bits are op-code, meaning there are 8 code slots, with 5 bits of argument space.
* 256-byte "drive" on port 0. (literal max because byte-based)
* Run DAT 1 twice to write to it - first to specify the output address, then the data itself.
* DAT 0 expects its argument to be the requested address, and returns its contents.
* 2-color 16x8 monitor on port 1. (1 bit color, 4 bit X, 3 bit Y)
* DAT 1 expects first a color bit, then the 4 X bits, then the 3 Y bits, and will draw immediately.
* DAT 0 expects first a 1-bit flag for COMMAND or COLOR. If flag is 0, it returns the status of the selected pixel, otherwise the full command, then 4 X bits, then 3 Y bits.
* This is a "reduced instruction"/simplified version of the TC-06 architecture, potentially to be implemented with homebrew IRL hardware.
* In theory, it is, like the original TC-06, a TURING COMPLETE system.
* SAMPLE CODE - WRITE BLINKENLIGHTS POSITIONS TO DRIVE, FLASH:
DAT 1 1 0 0 //ADR00
DAT 1 1 0 1 //ADR01
JMP 0 2 //ADR03
DTC 10001001 //ADR04
OPR 6 1 //ADR05
DAT 1 1 0 0 //ADR06
DAT 1 1 0 1 //ADR07
JMP 0 2 //ADR08
DTC 00001001 //ADR09
OPR 6 0 //ADR10-LOOPS
DAT 0 1 1 0 //ADR11
DAT 1 0 1 0 //ADR12
OPR 6 1 //ADR13
DAT 0 1 1 0 //ADR14
DAT 1 0 1 0 //ADR15
JMP 1 6 //ADR16-LOOPE
//RAM[4] & [9] are pixel data.
//4 is 1,1=ON, and 9 is OFF.
//Write them to drive[0,1].
//Loop loads them to Reg1...
//...and draws 'em. -=OP-CODE: NLS (N/A)=-
* The equivalent to Senbir's NILLIST.
* NILLIST.
-=OP-CODE: NIL (000)=-
* Null space. Skipped past, though it takes a cycle. Assumed to be empty.
* NIL.
-=OP-CODE: HLT <5-bit timer> (001)=-
* If timer==0, halts until reboot.
* Otherwise, halts for specified number of clock cycles, meaning halts of up to ~1.06 seconds.
* HLT.
-=OP-CODE: JMP <1-bit flag> <4-bit dest. addr.> (010)=-
* Jumps the program counter forwards or backwards the specified number of addresses. Will wrap around RAM if need-be.
* Flag 0 = forwards
* Flag 1 = backwards
* JMP, but localized.
-=OP-CODE: MOV <1-bit flag> <1-bit register> <3-bit addr.> (011)=-
* If flag is 0, loads something from the first 8 bytes of RAM into register 0/1.
* If flag is 1, does the opposite, loading from register 0/1 into RAM.
* If an offset is done with OPR, it adds that to the specified address.
* MOVI and MOVO combined into one, more limited function.
-=OP-CODE: DAT <1-bit flag> <2-bit peripheral id> <bit argument 1> <bit argument 2> (100)=-
* If flag is 0, is GETDATA.
* Argument 1 specifies the return register, either 0 or 1.
* Argument 2 specifies where to get the command data from, either the opposite register (false), or RAM, 2 addresses ahead (true).
* If flag is 1, is SETDATA.
* Argument 1 specifies which register to use, if registers are to be used.
* Argument 2 specifies where to get the command data from, either the specified register (false), or RAM, 2 addresses ahead (true).
* GETDATA and SETDATA combined into one, more limited function.
-=OP-CODE: OPR <4-bit op> <1-bit optional flag> (101)=-
* Operation 0: Addition & Subtraction.
* Flag 0: Subtraction. Registers[0] = Registers[0]-Registers[1];
* Flag 1: Addition. Registers[0] = Registers[0]+Registers[1];
* Operation 1: Multiplication & Division.
* Flag 0: Division. Registers[0] = Registers[0]/Registers[1];
* Flag 1: Multiplication. Registers[0] = Registers[0]*Registers[1];
* Operation 2: Copy
* Flag 0: Registers[1] = Registers[0];
* Flag 1: Registers[0] = Registers[1];
* Operation 3: Modulo & Exponent
* Flag 0: Modulo. Registers[0] = Registers[0]%Registers[1];
* Flag 1: Exponent. Registers[0] = Registers[0]^Registers[1];
* Operation 4: Jump Proper
* Flag 0: Jump directly to the memory address with ID equal to the contents of register 0.
* Flag 1: The same, but for register 1.
* Wraps around if there's an overflow (e.g, if addr 48 is requested when there's only 32 addresses, it goes to addr 16)
* Operation 5: Offset
* Flag 0: Sets the current offset to the contents of register 0.
* Flag 1: Sets the current offset to the current program counter.
* Like the proper jump, will wrap on overflow in the case of flag 0.
* Operation 6: Set01[0]
* Flag 0: Registers[0] = 0;
* Flag 1: Registers[0] = 1;
* Operation 7: Set01[1]
* Flag 0: Registers[1] = 0;
* Flag 1: Registers[1] = 1;
* Operation 8: Set23[0]
* Flag 0: Registers[0] = 2;
* Flag 1: Registers[0] = 3;
* Operation 9: Set23[1]
* Flag 0: Registers[1] = 2;
* Flag 1: Registers[1] = 3;
* Operation 10: Shift
* Flag 0: Shift Registers[0] forwards by the number of bits specified in Registers[1].
* Flag 1: Shift Registers[1] forwards by the number of bits specified in Registers[0].
* A mix of MATH and UTL.
-=OP-CODE: BRN <2-bit op> <3-bit dest. addr> (110)=-
* Operation is either == (0), != (1), 1>2 (2), or 1<2 (3). Compares registers 1 and 2.
* Jumps forwards up to 9 addresses (pointer = pointer + 2 + destAddr (0-7)) if the comparison is true.
* Otherwise, ticks forwards once like normal.
-=OP-CODE: SPC <3-bit start point> <1-bit length (length=(arg+1)*2)> <1-bit offset>=-
* Splices 2 or 4 bits from register 0 and pastes them into the same position (or the same position+1, wrapping cleanly if need-be) in register 1.
* A simplified, even more finnicky PMOV.
Despite its limitations, I feel like it's actually more usable than the standard TC-06 assembly. It seems a bit less reliant on self-modifying code (SPC/"Splice", the PMOV equivalent, and OPR 10, the PMOV offset equivalent, haven't been implemented under the hood yet, and I still wrote that image renderer!), and the register setup feels somehow less overwhelming than Senbir, even though it's far more limited. I'm loving working with it so far.
Of exciting note, the actual VM for it is written using C++, in such a way that I can hook the simulation function up to Unity down the line and make it work in Senbir. Getting an ingame RISC-06 computer up and running will be good practice for porting the TC-06 architecture itself to C++ - I've already learned a good bit about compiling it all, linking, dealing with data types, etc. The visual/UX frontend here is QT-powered, and themes itself depending on your OS theme settings. I plan to change some of it (at least the Assembler portion) to use some color choosers in the Options menu so it'll look good & be easy to tweak no matter what OS you're running on.
Your RISC-y setup looks good! I'll admit I'm having a little bit of trouble wrapping my head around parts of it - like immediate-mode numbers in ADD/SUB/etc, how do they work, both on the Assembler level (e.g, would ADD 1 5 jump the pointer forwards five?), and the processor level (how does it decide between using registers & immediate-mode numbers?); the latter is why there's not much of that in Senbir, I couldn't figure out a good way of using immediate-mode numbers without making them an argument (say, a bit), which removes from the number of bits you have available for other arguments.
(I was...a bit one-track-minded about trying to maximize the number of available memory addresses, originally; the max was originally bound by the limitation of MOVI/MOVO, though you can now skirt past that to 2^32 addresses via the power of OFST. This was especially noticable in the ye-olden TC-06 prototype I shared the gif of a while back - both my rather slipshod attempts to counter the storage issues, and the issues themselves having pretty big numerical impacts.)
Should definitely add an MMU as an optional addition at some point, though I'm not sure if it should be a custom addition to the assembly language itself (e.g, a new op-code for memory management), or a custom device of some sort you ping with GET/SETDATA - they sound way too handy for just about any situation one could imagine not to have around in some form. ...Hm, maybe an OFST expansion? Like something you could enable/disable in Custom mode, "MMU OFST" - it would rework the op-code in some shape or form to emulate an MMU.
I understand, now. Thought there was an undocumented/unintended feature that would enable a monitor that technically uses more than 32 bits, but can function normally via the Extended SETDATA offset feature.
Not too sure what to say about that storage quandry, though. If I were designing it, I'd probably choose to store emulated monitor commands in some chunk of memory set aside for that purpose, even if it means suffering a performance/storage hit. Maximum compatibility is important, and as something of a security freak, I'd say that having little VM-specific quirks like that is...dangerous. Someone looking to write a TC-06 virus (why anyone would is beyond me, but...thinking through these things anyway!) could intentionally check the behavior of that operation to deduce if their program is in a VM or not. Generally tend to think that providing that sort of info can be a dangerous security hole.