Ray162K, the 16-bit computer

  • LBPHacker
    11th Mar 2015 Member 11 Permalink

    Hey, LBPHacker here!


    Ladies and gentlemen, I present you Ray16, a 16-bit computer with 2 kibibytes of memory, 8 IO ports and 10 instructions.


    The save

    Its ID is 1748157. It currently contains the computer itself, 16-bit input panels connected to ports 0 and 1, and 16-bit output panels connected to ports 2 and 3. Copies of these panels can be found in the top right corner.


    The memory is the rectangle of FILT. The coordinates of the top-left corner of the memory are (224; 235). Don't confuse that rectangle of FILT with the other three similar blocks; they look similar but have different purposes.


    Both code and data reside in the memory (which is kind of a hard drive too as its content does not change until it's changed explicitly by the computer), this allows for easier programming as the programmer does not have to maintain two separate blocks of memory. It has some caveats, that's for sure, but let's face it, that's how I built it. Fear not though for this save is just a proof-of-concept, so this might change in the future.


    Execution of the program is started by SPRKing the button located at (166; 256), the one that looks like a play button; this is the Start button (duh). SPRKing this button will reset the computer so that the Instruction Pointer will point to 0x0000 (this reset does not affect the registers, the flags or the memory).


    Execution may be paused by SPRKing the button above the Start button, this is called the Stop button. This disables the clock source of the computer, but does not affect it in any other way.


    Execution may be resumed by SPRKing the button below the Start button, this is the Resume button. This re-enables the clock source.


    The input panels may be a bit tricky to use. Usage of these requires basic knowledge of binary numbers. Sixteen squares are seen, these are used to indicate the value stored in the panel. A hollow square represents a 0, a filled one represents a 1, the least significant bit being shown in the rightmost square. SPRKing a green switch activates the corresponding bit (the square below the switch), the red switches do the exact opposite.


    The output panels look similar and work almost like the input panels, except that they don't accept user input. They indicate the value of the port they are connected to the same way input panels do.


    Let's see if the tilde works here



    • Three 16-bit registers: Use the registers for short term storage of data. The registers are named AX, BX and CX.
    • Eight IO ports: Data may be written to or read from them. These ports provide a way to make contact with the user through peripherals.
    • Two kibibytes of memory: the memory consists of 1024 16-bit cells. It's not possible to address odd bytes of the memory, that is, reading the high byte of a cell and the low byte of the one next to it in one read operation. See The Memory section below.
    • Conditional jumps: Some instructions alter the 5-bit state information (the flags; carry, overflow, sign, zero and parity) stored in the processor. Opcodes that execute conditional jumps based on that state information are available. See the Instruction Reference section below.
    • 16-bit ALU: The Arithmetical and Logical Unit, or the ALU of this processor supports various arithmetical operations such as addition (with or without carry), subtraction (with or without borrow), shifts, rotates and logical operations such as OR, AND, XOR and NOT. I might implement multiplication, division and shift/rotate-through-carry instructions in the future.
    • 16 frame per instruction: It takes 16 frames to process an instruction. I might implement some kind of pipelining in the future; that'd shorten the execution time of several instructions (like MOV or ADD). Memory operations would still take 16 frames as the program code and data reside in the same block of memory.
    • The computer uses absolutely no WIFI or layering.


    The Memory

    One of the most complicated parts of the computer. Heavy use of DRAY and various other RAYs allows the memory to be quite fast and be done with a read or a write operation in 8 frames. Thats's a good thing, as memory operation instructions have to be read from the memory and processed (that's two memory operations) in a 16 frame window, which is not that much.


    It consists of 1024 (64x16) cells, each storing 16 bits (actually 30, but the two most significant bits are not used and bits 16-27 are reserved for the 28-bit wide instruction set), yielding 2 kibibytes of memory.


    Writing to location 0x0000 and 0x0001 will not alter the same cell of the memory, as the locations are represented in cells, not in bytes. Yes, that means it's not possible to actually address 0x0001. Addressing 0x0001 is the same as addressing 0x0002 on a computer in real life. The highest possible address is 0x03FF, the bits above bit 9 are not considered.


    The pixel of FILT representing address 0x0000 is in the top-left corner of the block. The one representing address 0x0001 is on its right and the one representing address 0x0040 is right below it, and so on.


    Instruction Reference

    Read this very carefully, then re-read it as many times as needed. It might not be as obvious as I think it is, so just ask me if something is not clear.


    The instruction set is 28 bits wide, which means every instruction is a 28-bit value, not a 16-bit one. Storing instructions in one pixel of FILT is possible due to FILT being able to store 30 bits in its CTYPE field, not just 16. Some may consider this cheating, I consider it hackish use of resources.


    The sixteen least significant bits of the instructions usually represent immediate values. The structure of an opcode of an instruction is shown below (bit-by-bit):

    • 29: "Keepalive" bit, always set; this bit is what gives the inside of the computer its reddish hue.
    • 27-24: General instruction bits; determine the general type of the instruction (move, ALU, memory, IO, etc.)
    • 23-20: Operand bits; Determine the operands of the instruction. Both the primary and the secondary operands are encoded as 2-bit values, 00 meaning AX, 01 meaning BX, 10 meaning CX and 11 meaning the immediate stored in the least significant bits of the opcode.
      • 23-22: Primary operand
      • 21-20: Secondary operand
    • 19-16: Sub-instruction bits: These determine the subtype of the instruction (eg. the condition to be checked on a conditional jump or the operation to be executed in an ALU instruction).
    • 15-0: A 16-bit immediate value. Since there is only one immediate value encoded in the opcode, it's not possible to for example write an immediate value to an immediate location of the memory, except if the location and the value to be written are the same (which is unlikely).

    The following instructions are supported (the four digits being bits 24-27 of the opcode):

    • 0000; NOP: Do nothing, waste a clock cycle.
    • 0001; HLT: Halt the execution of the program. Equivalent to pressing the Stop button.
    • 0010; PUT: Write to memory. Address is secondary operand, register is the primary operand.
    • 0011; GET: Read from memory. Address is secondary operand, register is the primary operand.
    • 01rf; ALU: Do an ALU operation. If bit 25 of the opcode is set ("r"), results are not stored in the destination operand, but the flags are updated according to the result. There are 16 sub-instructions (bits 16-19 of the opcode are used to select from these):
      • 0000; ADD*: Increment the value stored in the primary operand by the value stored in the secondary operand.
      • 0001; ADC*: Like ADD, but takes the carry from the Carry Flag.
      • 0010; SUB*: Decrement the value stored in the primary operand by the value stored in the secondary operand.
      • 0011; SBB*: Like SUB, but takes the borrow from the Carry Flag.
      • 0100; MUL: Not yet implemented.
      • 0101; DIV: Not yet implemented.
      • 0110; IMUL: Not yet implemented.
      • 0111; IDIV: Not yet implemented.
      • 1000; SHL**: Shift the value stored in the primary operand left by the value stored in the secondary operand.
      • 1001; SHR**: Like SHL, but in the other direction.
      • 1010; SCL**: Like SHL, but the first bit shifted in mirrors the Carry Flag.
      • 1011; SCR**: Like SHR, but the first bit shifted in mirrors the Carry Flag.
      • 1100; OR: Does a logical OR on the values stored in the primary and secondary operands, stores the result in the primary operand.
      • 1101; AND: Like OR, but does a logical AND.
      • 1110; XOR: Like OR, but does a logical XOR.
      • 1111; NOT: Does a logical NOT on the values stored in the primary operand.
    • 1000; JMP: Jump to the location pointed to by the primary operand.
    • 1001; JMC: Like JMP, but takes the jump only if a certain condition is met. This condition is encoded in the sub-instruction bits (bits 16-19 of the opcode):
      • 0000; JNO: Jump if not overflow
      • 0001; JO: Jump if overflow
      • 0010; JNS: Jump if not sign
      • 0011; JS: Jump if sign
      • 0100; JNE/JNZ: Jump if not zero / not equal
      • 0101; JE/JZ: Jump if zero / equal
      • 0110; JNB/JAE/JNC: Jump if not below / above or equal / not carry
      • 0111; JB/JNAE/JC: Jump if below / not above or equal / carry
      • 1000; JNBE/JA: Jump if not below or equal / above
      • 1001; JBE/JNA: Jump if below or equal / not above
      • 1010; JNL/JGE: Jump if not lower / greater or equal
      • 1011; JL/JNGE: Jump if lower / not greater or equal
      • 1100; JNLE/JG: Jump if not lower or equal / greater
      • 1101; JLE/JNG: Jump if lower or equal / not greater
      • 1110; JNP/JPO: Jump if not parity / parity odd
      • 1111; JP/JPE: Jump if parity / parity even
    • 1100; IN: Read the value from the port pointed to by the secondary operand and store it in the primary operand.
    • 1101; OUT: Write the value stored in the primary operand to the port pointed to by the secondary operand.
    • 1110; MOV: Copy the value from the secondary operand to the primary operand.


    *: Flags are not updated if bit 24 of the opcode ("f") is set.

    **: The bits above bit 4 of the secondary operand are not considered. The shifts become rotates if bit 24 of the opcode ("f") is set.


    Sample opcodes:


    • 0x2C300000: IN AX, 0
    • 0x2C700001: IN BX, 1
    • 0x24100000: ADD AX, BX
    • 0x2D300003: OUT AX, 3
    • 0x2E300000: MOV AX, 0
    • 0x29360007: JNC 0x0007
    • 0x2E300001: MOV AX, 1
    • 0x2D300002: OUT AX, 2
    • 0x21000000: HLT


    This is actually the disassembly of the program currently residing in the memory provided with the save. Can you tell what it does? It adds up the values of the two input panels, prints the result on the bottommost output panel and the carry on the one above it.



    I've been builing this computer for two weeks now, and every time I add something to it I check if adding that something broke some other thing or not, so there shouldn't be any bugs in there, but you can never be sure. I'd appreciate if you told me if you found a bug.



    Should any questions arise, feel free to ask anything below.

    This is the first time I'm posting something here, so please let me know if you think I could somehow improve the wording, layout or whatever of this post. I'll be updating this post quite frequently, as I'm bound to make typos here and there. You know how it is with posting. Thanks for reading!

    Edited 8 times by LBPHacker. Last: 15th Mar 2015
  • johnpears
    11th Mar 2015 Member 0 Permalink

    Mazing save +1 totally fp worthy

  • LBPHacker
    11th Mar 2015 Member 0 Permalink

    Thanks, it means a lot!


    Here's an extension to the OP.


    The Status Flags

    The status of the computer is stored in five flags:

    • Carry Flag: Determines if an unsigned overflow occured during the last (flag-altering*) addition or subtraction; reset by logical operations.
    • Overflow Flag: Determines if a signed overflow occured during the last (flag-altering*) addition or subtraction; reset by logical operations.
    • Sign Flag: Determines if the most significant bit of the result of the last (flag-altering*) shift, rotate, addition, subtraction or logical operation is set.
    • Zero Flag: Determines if the result of the last (flag-altering*) shift, rotate, addition, subtraction or logical operation is zero.
    • Parity Flag: Determines if the least significant bit of the result of the last (flag-altering*) shift, rotate, addition, subtraction or logical operation is set.


    While flags may be altered indirectly using flag-altering* instructions, directly altering flags is not possible.


    *There are ALU instructions which don't alter the flags at all. See the Programming section below.



    You can either choose to program Ray using the PROP tool and the Instruction Reference above, or you can fetch the Ray Assembler (http://lbphacker.hu/powdertoy/rasm.lua) and run it from the TPT console after opening the save. Lua gurus have an advantage here.


    Of course this assumes that rasm.lua and some_program.asm reside in the directory where TPT is installed.


    The syntax itself is pretty simple; here's the default program from above in Ray Assembly:

    in ax, 0 ; comments are at the end of the line preceded by a semicolon
    in bx, 1 ; the commas are not important, I could write "in bx 1" if I wanted to
    add ax, bx
    out ax, 3
    mov ax, 0
    jnc nocarry ; the script is smart enough to find labels defined later in the code
    mov ax, 1
    nocarry: ; the name of the label is followed by a colon
    out ax, 2 ; a dumb way to display the carry of the addition

    I'm sorry, the editor ate the tabbing here. I swear the comments were organized into a nice column when I pasted the code. 


    The instructions have the exact same names as the ones in the Instruction Reference section. There are new instructions as well:

    CMP: compare the two operands; works like SUB but doesn't store the result
    TEST: mask the two operands; works like AND but doesn't store the result
    ADDS, ADCS, SUBS, SBBS: like their S-less counterparts, but these don't alter the flags


    There's a prefix called nores; it tells ALU instructions not to store the result. Yup, that means cmp actually translates to nores sub.

    Edited once by LBPHacker. Last: 14th Mar 2015
  • basiliotornado
    11th Mar 2015 Member 0 Permalink
    This post has been removed by jacob1: useless
  • Kikkin
    11th Mar 2015 Banned 0 Permalink
    This post is hidden because the user is banned
  • cylers
    11th Mar 2015 Member 0 Permalink

    you lost me at 16 bit computer, but I read it regardless and what I learned is that it can perform simple addition and subtraction as well as some advanced functions that I will probably never learn. your instructions (as you said) will probably only be understood by those with the knowledge of binary computing, But regardless sweet save!

    EDIT: For those of us too nooby to understand anything, can you label the specific pieces such as the ALU and the different memory sections in the save, there appears to be enough room in some spots for it.

    Edited once by cylers. Last: 11th Mar 2015
  • Synergy
    12th Mar 2015 Member 0 Permalink

    Very nice. It's good to see that you made use of that constant time adder. Was sad to see that it got absolutely no attention when you published that.


    I would suggest creating a more interactive default program. People are far more likely to upvote if they see the computer do something tangeable and easy to understand. 


    I've also been in the process of creating parts for my computer over the last couple of weeks.

  • LBPHacker
    12th Mar 2015 Member 1 Permalink

    @cylers (View Post)

     Here you go. I've also linked this in the original save.



    @Synergy (View Post) (point-by-point)

    Thanks! I knew somebody would point that out. Yeah, the adder didn't get too much attention, but as a matter of fact, it got way more than I expected. It was just an adder not connected to anything interesting, after all.


    I'd happily do that if I had any idea what kind of program it should be. The most user-friendly parts of the save are the IO panels, and even those are not *that* user-friendly. Maybe I should wire a firework launcher up to port 4 and do some tricks with that ...


    I'd very much like to see how that turns out. I know I'll see it when it's done as computers are not published every other day, but in case you feel like showing off, I'm all ears.



    Hear that? That goes for every one of you, computer developers of TPT.

    Edited 2 times by LBPHacker. Last: 12th Mar 2015
  • boxmein
    12th Mar 2015 Moderator 0 Permalink
    Can't wait for someone to implement x86 or ARM in Powder Toy.
  • HitlerSucks
    12th Mar 2015 Member 0 Permalink

    ...It's beautiful. *sheds a small tear*


    I have absolutely no clue how to use it, but I know, deep down inside... That it's a thing of beauty.