Jade

Zeda Thomas (xedaelnara@gmail.com)
Updated, 19 May 2018
Intro
Jade is a simulated processor for the TI-83+/84+ series of calculators. Jade doesn't exist in real life, so there is no worry about accuracy of 'emulation.' This means I had a lot of freedom in designing the processor and port system. As well, since it is an emulator/simulator, you don't need to worry about bad code crashing your calculator. Jade modifies only two areas of memory on your calculator:
  • saveSScreen - contains the RAM and ROM for the Jade system. This is entirely safe for use and is 768 bytes of non-user RAM, so it won't affect any programs or variables.
  • plotSScreen - Also known as 'gbuf' or the graph screen. This is where Jade draws sprites and whatnot.
  • So what does this all mean? If you want a safe introduction to assembly, you have it here. You can press [ON] at any point during Jade's processing to turn the system off, so you have an added level of security against infintite loops. Now, on to coding!
    Getting Started
    You will need to send Jade to your calcualtor, using some linking software such as TiLP or TI-Connect. If you want to program for Jade directly on the calculator, I suggest using AsmDream and the included files. Send the following to your calculator as well:
        [JADEMCR.8xp     Jade macro file for AsmDream
        [JADEEQU.8xp     Jade equate file for AsmDream
    If you plan to program for Jade on the computer, you can use the included Jade.inc file for equates and macros.
    AsmDream Setup
    For AsmDream, your program source should start with a theta in its name. The first lines are:
      :SRC
      :θ=0
      :prgmPROGRAM
      :START
      :<code>
      :End
    Assembler Setup
    The following header works with my preferred assembler, SPASM, but may need to be modified slightly to fit your preferred one.
      #include 'Jade.inc'
      .org 0
      start:
           <code>
    Programming
    Jade programs have 256 bytes of RAM (editable memory space) and 512 bytes for the ROM (uneditable code space). Jade uses the first portion of its RAM to interact with its peripherals. We will go into detail on those shortly.

    Instructions tell the processor what to do. In Jade, there are two main types of instructions-- those that uses constants or 'immediate' values and those that use addresses. RAM holds data in bytes, and each byte has a unique address. Since there are 256 bytes, we use numbers 0 to 255 to identify each byte. The naming scheme of the instructions usually reflects which type of instruction it is, except in the cases where it makes no sense for it to be any other type. So without further ado, the first instruction:

        ldc(addr1,const)     ;copies or 'loads' the constant value to the byte at addr1

        lda(addr1,addr2)     ;copies or 'loads' the byte at addr2 to the byte at addr1

    Notice that the instruction suffix is a c when a constant is being used, and an a when an address reference is being used. These is the most basic ways of moving data around. An example of its use might be:

        ldc(status,80h)

    That loads the value 80h to the status port byte. 'status' is just a special name given to the byte and is equivalent to its address. 80h is a hexadecimal number equivalent to 128 or 1000000b in binary. When this value is written to the status port byte, it turn off Jade.

    Math and Data Movement Instructions
    These are the main instructions that will be used to make Jade do 'stuff'
      Instruction       Flags   Description
      LDA(addr1,addr2)    --    load the byte at addr2 to addr1
      LDC(addr1,const)    --    load the the constant value to add1
      ADDA(addr1,addr2)   cz    Add the byte at addr2 to addr1, store the result at addr1
      ADDC(addr1,const)   cz    Add the constant to addr1, store the result at addr1
    

    Here is a quick break. Flags are used to provide additional information about the result of an instruction. The 'c' flag indicates overflow in most cases, and the 'z' flag indicates that the result was 0 in most cases. The CP instructions are a little different, and they will be explained later.

      ADCA(addr1,addr2)   cz    Add the byte+'c' to addr1, store the result to addr1
      ADCC(addr1,const)   cz    Add the constant+'c'to addr1, store the result at addr1
      SUBA(addr1,addr2)   cz    Subtract the byte at addr2 from addr1, the result to addr1
      SUBC(addr1,const)   cz    Subtract the constant from addr1, the result to addr1
      SBCA(addr1,addr2)   cz    Subtract the byte+'c' from addr1, the result to addr1
      SBCC(addr1,const)   cz    Subtract the constant+'c' from addr1, the result to addr1
      XORA(addr1,addr2)   0z    Performs bitwise XOR on the bytes, the result to addr1
      XORC(addr1,sonst)   0z    Performs bitwise XOR on the bytes, the result to addr1
      ORA(addr1,addr2)    0z    Performs bitwise OR on the bytes, the result to addr1
      ORC(addr1,const)    0z    Performs bitwise OR on the bytes, the result to addr1
      ANDA(addr1,addr2)   0z    Performs bitwise AND on the bytes, the result to addr1
      ANDC(addr1,const)   0z    Performs bitwise AND on the bytes, the result to addr1
      CPA(addr1,addr2)    cz    Compares the two bytes, returns flags
      CPC(addr1,const)    cz    Compares the two bytes, returns flags
        The flags returned by CP work like this:
          -If the bytes are equal, z flag is set, else it is reset
          -If the second byte is less than the first, the c fag is set, else it is reset.
      ROTL(addr1)         cz    rotate the bits left at the byte
      ROTR(addr1)         cz    rotate the bits right at the byte
      SHFTL(addr1)        cz    shift the bits left at the byte, carrying in the c flag
      SHFTR(addr1)        cz    shift the bits left at the byte, carrying in the c flag
      PUSHA(addr1)        --    push the value at addr1 onto the stack, increment SP
      PUSHC(const)        --    push the constant onto the stack, increment SP
      POPA(addr1)         --    decrements SP and pops the value off the stack to addr1
      EX(addr1,addr2)[note]    --    swaps the bytes at add1 and addr2
      INV(addr1)          --    Inverts the bits of the byte at addr1
      BITS(addr1,const)   0z      Performs bitwise AND on the two values, no value returned
      LDIRA(addr1,size)   --    Copies the subsequent bytes to where addr1 points
      LDIRC(const,size)   --    Copies the subsequent bytes to where const points
        These two instructions should be followed by a '.db ' statement with the data
    Control Instructions
    These instructions modify program flow or exert some low-level control:
       Instruction:    Description:
       RET()          Pops the last two bytes off the stack and stores them to PC
    Note that RET() is generally used to end subroutines. Its full name is RETurn.
       SETZ()         Sets the 'z' flag
       SETC()         Sets the 'c' flag
       TOGZ()         Toggles the 'z' flag
       TOGC()         Toggles the 'c' flag
       JP1(addr)      Jumps to the address in the first 256 bytes of code space
       JP2(addr)      Jumps to the address in the second 256 bytes of code space
       JRF(offset)    Jumps forward a given number of bytes
       JRB(offset)    Jumps backwards a given number of bytes
       CALL1(addr)    Executes the subroutine at the address in the first 256 bytes of code space
       CALL2(addr)    Executes the subroutine at the address in the second 256 bytes of code space
       CALLF(offset)  Executes the subroutine that is a relative distance forward.
       CALLB(offset)  Executes the subroutine that is a relative distance backwards.
    

    The call instructions push the value of PC onto the stack (two bytes), then jumps to the subroutine. Because the subroutine ends with RET(), it returns to the byte after the CALL() instruction.

       IND1()         The next argument will be interpreted as indirection
       IND2()         The second argument will be interpreted as indirection
    Conditional Instruction Execution
    Every instruction can be optionally run based on the value of the 'z' flag or 'c' flag. For example, to turn off Jade if the z flag is set:
       ldcz(status,80h)
    To turn it off if the c flag is set:
       ldcc(status,80h)
    To just turn it off:
       ldc(status,80h)
    This is why there is no instruction to reset the c or z flag. You just use these:
       togcc()
       togzz()
    Stack
    If you have never programmed with a stack before, they are often pretty useful. Basically, there is a 'Stack Pointer' (SP) which points to a RAM location. When you PUSH a value to the stack, the value is written to where SP points, then SP is incremented. When you POP a value from the stack, SP is decremented and the byte at SP is read. When a CALL instruction is read, the current value of the 'Program Counter' is pushed to the stack in little-endian style. PC is the thing that keeps track of where the code is being read. In Jade, the stack memory is the last 128 bytes of RAM, but it is unlikely that you will need all of that. You should be able to safely use RAM there. As well, the stack wraps around. It will never read or write to RAM outside of the stackspace.
    Ports
    Ports allow you to access peripheral (external) units. For example, ports are used to read keyboards. In Jade, all of the ports are linked to a byte in RAM. This means that to read from a port, you simply read its corresponding byte and to write to a port, you simply write to the corresponding byte. The first 40 bytes are linked to 8 sets of ports that are in groups of 5. These all correspond to 8x8 sprites. Their names are of the format...
    Sprite Ports
       sLSB#    This is the Least Significant Byte (lower byte) of the sprite data address
       sMSB#    This is the high byte of the sprite data address. 0 if it is in RAM, 1 or
                2 depending on which 256-byte section it is on in the code space
       sX#      This is the x-coordinate of the sprite (0 to 95 is on screen)
       sY#      This is the y-coordinate of the sprite (0 to 63 is on screen)
       sMethod# This is how the sprite will be drawn to the screen (the logic method):
            1=AND logic
            2=XOR logic
            3=OR logic
    
    # corresponds to the sprite number and is 0 to 7. Then it is easy to control sprites with Jade, and this is typically a style called hardware sprites. For example, to increment the X coordinate of sprite0:
       inc(sX0)
    And now to make Jade draw the sprites...
    
    Status Port
    In order to tell Jade to draw the sprites, you must set bit 1 of the status port. A clever way to do this is to use the ORC() command, using a value of 2 (because 2=00000010b in binary):
       orc(status,2)
    Jade will then reset that bit immediately after drawing it so that it doesn't waste CPU time drawing the sprites over and over. But we aren't neceassarily finished. In order to update the LCD with the new image, we must set bit 0 of the status port. using a similar trick:
       orc(status,1)
    The bit will immediately be reset once the LCD is drawn to save CPU time. You can set both bits at the same time, but the LCD will be updated first, then the sprites. A common technique is to draw your sprites with XOR logic. Drawing a sprite twice with XOR logic results in no net change, but you can show the user the effect of one XORed sprite, without updating the LCD to show them the second:
       orc(status,2)
       orc(status,3)
    The status port also allows you to turn off Jade by setting bit 7. So for example, both of these will work:
       orc(status,80h)
       ldc(status,80h)
    The former method preserves the other bits, but if you are turning off Jade, it doesn't really matter.
    Sprite Mask Port
    The last (well, first normally) sprite based port, the sprite mask port, tells Jade which sprites actually need to be updated. Set the corresponding bit in sMask and the sprite will be drawn on the next sprite update.
    Key Ports
    There are 7 key ports. If a bit is SET in the key port, it means the key is not being pressed. If the bit is RESET, it means the key IS being pressed.
       key0
         bit 0 = Down
         bit 1 = Left
         bit 2 = Right
         bit 3 = Up
         bit 4 = --
         bit 5 = --
         bit 6 = --
         bit 7 = --
       key1
         bit 0 = Enter
         bit 1 = +
         bit 2 = -
         bit 3 = *
         bit 4 = /
         bit 5 = ^
         bit 6 = Clear
         bit 7 = --
       key2
         bit 0 = (-)
         bit 1 = 3
         bit 2 = 6
         bit 3 = 9
         bit 4 = )
         bit 5 = TAN
         bit 6 = VARS
         bit 7 = --
       key3
         bit 0 = .
         bit 1 = 2
         bit 2 = 5
         bit 3 = 8
         bit 4 = (
         bit 5 = COS
         bit 6 = PRGM
         bit 7 = STAT
       key4
         bit 0 = 0
         bit 1 = 1
         bit 2 = 4
         bit 3 = 7
         bit 4 = ,
         bit 5 = SIN
         bit 6 = APPS
         bit 7 = X,T,O,N
       key5
         bit 0 = --
         bit 1 = Sto>
         bit 2 = LN
         bit 3 = LOG
         bit 4 = X^2
         bit 5 = X^-1
         bit 6 = MATH
         bit 7 = ALPHA
       key6
         bit 0 = GRAPH
         bit 1 = TRACE
         bit 2 = ZOOM
         bit 3 = WINDOW
         bit 4 = Y=
         bit 5 = 2ND
         bit 6 = MODE
         bit 7 = DEL
    Fixed RAM
       stackptr  holds the value of the stack pointer (0 to 127)
       PClow     holds the LSB of the program counter
       PChigh    holds the MSB of the program counter (only 0 or 1)