To add another project to my long list, I am currently working on a project called Jade. Jade is basically a simulated CPU that doesn't exist in real life (that I am aware of). It is for the TI-83+/84+ calculators and simulates a CPU and set of ports. It uses 256 bytes of editable RAM and 512 bytes of coding space that is the 'ROM'. Unlike the z80 and I am assuming many other processors, it uses no registers. Instead works directly with its RAM. The first 53 bytes of RAM controls such things as the stack pointer, PC, and all of the ports. The latter 128 bytes of RAM are allotted for a stack (though if you know you aren't going to use all 128 bytes, there is no harm in using that for RAM.

There are 8 sets of 5 ports that control sprites. You provide a pointer to the sprite, some coordinates, and the method for drawing.
There are 7 ports for key presses
There is a port that controls which sprites are drawn
There is a port that controls which sets of keys are scanned
There is a status port for turning Jade off or turnign on the LCD update
There is a port that returns which sprites have been drawn
There is a stack pointer
There is a program counter (using 2 bytes).

The first version has been basically abandoned because I have a much better setup and instruction set (I think). The instruction set allows all instructions to be optionally executed if the c or z flag is set, but the base instruction set is:

Code:

   0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
0   lda()   adda()   adca()   suba()   sbca()   xora()   ora()   anda()   cpa()   inc()   rotl()   shftl()   pusha()   pop()   inv()   ldira()
1   ldc()   addc()   adcc()   subc()   sbcc()   xorc()   orc()   andc()   cpc()   dec()   rotr()   shftr()   pushc()   ex()   bits()   ldirc()
2   ret()   setz()   setc()   togz()   togc()   jp1()   jp2()   jrf()   jrb()   call1()   call2()   callf()   callb()
3
;Duplicate for execution on the c flag condition
4x,5x,6x,7x
;Duplicate for execution on the z flag condition
8x,9x,Ax,Bx
;<not implemented>
Cx,Dx,Ex,Fx

To elaborate a little on the syntax (thanks to chickendude for the naming scheme, by the way), lda() will load a byte from one address to another. ldc() will load a constant value to an address. The ideas for jp1(), jp2(), call1(), and call2() were from Runer. I wanted to keep all addreses 1 byte, so before I only had relative jumping and calling. Those instructions now allow for absolute jumps/calls. If you are familiar with z80 assembly, I am sure you can figure out most of the command set.


So my current progress is that I have the instruction set working and I am now working on adding back in the ports (as well as starting to document things). I have had code ready for the past 6 days to test:

Code:

#include "jade.inc"
.org $00
 jrf(start)
sprite:
.db %00111100
.db %01100110
.db %11000011
.db %11100111
.db $FF
.db $FF
.db $FF
.db $FF
start:
 ldirc(sprite0,5)
  .dw $100+sprite \ .db 0,0,2
 ldirc(keyMask,2)
  .db 3,1
Loop:
 orc(status,1)        ;set bit 0 of the status port
KeyLoop:
 cpc(key1,kClear)     ;Check if clear is pressed
 ldcz(status,$80)     ;Turn off Jade if the z flag is set by writing 80h to the status port
 cpc(key0,-1)         ;test if no keys are being pressed
 jrbz(KeyLoop)

 callf(SpriteWait)    ;In case not enough time has passed for the sprite to be drawn
 ldc(sUpdate,0)       ;Acknowledge the sprite drawing, allowing it to redraw the sprite (XORing twice results in no change)
 callf(SpriteWait)    ;We need to wait until the sprite is drawn before updating coordinates

 cpc(key0,kDown)
 incz(sY0)
 cpc(key0,kUp)
 decz(sY0)
 cpc(key0,kLeft)
 incz(sX0)
 cpc(key0,kRight)
 decz(sX0)
 ldc(sUpdate,0)       ;Acknowledge any sprites drawn, allowing the updated coordinates to be displayed
 jrb(Loop)

SpriteWait:
 cpa(sUpdate,sMask)   ;Check if all of the sprites are drawn
 retz()
 jrb(SpriteWait)

That should allow you to move a sprite around on the screen, once I have the ports added in. The old version was rather unwieldy, and the only screenshot I have to offer is this:

This new version should be faster and smoother. As well, the code is more efficient and so it tends to be smaller (quite a bit smaller, actually).
It's rather impressive what you accomplish, I hope you know that Smile

Keep up the fantastic work, and I hope to see a new screenshot pop up soon. ^_^
Thanks! It took me longer than I expected to add in the ports for the keys because I found a few bugs in some of the instructions. I fixed those and I now have a program that stays in a loop until [Clear] is pressed. Next I need to add in sprites and LCD updating (this ought to take a while).

Jade draws one sprite at a time. The changes in this version are basically all in protocol. It will check if the corresponding bit in sMask is set AND the corresponding bit in sUpdate is reset. When the sprite is drawn, the bit in sUpdate gets set to inform that the sprite is drawn. Once the eighth sprite (Sprite7) is drawn, if bit 0 of the status ports is set (the LCD Update bit), the LCD will be updated with the new data, and the bit will be reset.
I now have it working after some coding pains. The instruction set seems to be working, but I am not convinced that it is bug free. After some frustration in not getting the sprite and LCD updating to work as I was expecting it, I chose a completely different route. Now, when bit 1 of the status port is set, all of the sprites will be drawn and the bit will be reset. If bit 0 is set, the LCD will be updated, then the bit will be reset. I fixed a silly mistake with the sprite clipping (if the Y-coordinate was negative, it caused a crash). I haven't really documented everything, but if you want to download a testing version, you can try this:
Jade (v2)
And included is a sample program. The code is:

Code:

#include 'Jade.inc'
.org $00
 jrf(start)
sprite:
.db %00111100
.db %01100110
.db %11000011
.db %11100111
.db $FF
.db $FF
.db $FF
.db $FF
start:
 ldirc(sLSB0,5)
  .dw $100+sprite \ .db 0,0,2
 ldirc(keyMask,2)
  .db 3,1             ;
Loop:
 orc(status,2)        ;set bit 1 of the status port to draw the sprites
 orc(status,3)        ;set bit 0 and 1 of the status port to update the LCD, then redraw the sprites
KeyLoop:
 cpc(key1,jkClear)    ;Check if clear is pressed
 ldcz(status,$80)     ;Turn off Jade if the z flag is set by writing 80h to the status port
 cpc(key0,-1)         ;test if no keys are being pressed
 jrbz(KeyLoop)


 cpc(key0,jkDown)
 incz(sY0)
 cpc(key0,jkUp)
 decz(sY0)
 cpc(key0,jkLeft)
 incz(sX0)
 cpc(key0,jkRight)
 decz(sX0)
 jrb(Loop)

I am not sure why I still have the sprite data at the beginning, but this is what it looks like when run:

I am thinking it might be a good idea to have the 'ROMs' use some kind of header and have Jade work as a shell. That would be better than the current setup for running Jade files, I think.
That does look rather smooth moving across the screen! Nicely done Very Happy
Thanks! Also, it runs at 6MHz and Jade is currently 1136 bytes of code. I also completely forgot that it can load 'ROMs' from archive Smile I just tested it for the first time and it works well.

EDIT: I found two bugs in the last version that prevent CP, SUB, and SBC commands from working properly and another error that prevented the c flag from being set. I fixed those and I started work on Pong Smile You can click the screenshot to download the updated program (with the examples).

The code for that is precisely 200 bytes and the code is:

Code:
#include     "ti83plus.inc"
#include     "Jade.inc"
.org 0
scratchmath  equ  252
scratchmath1 equ  253
ballxinc     equ  254
ballyinc     equ  255

Start:
     ldirc(sLSB0,25)
       .dw $100+ball \ .db 3,3,2  ;load the sprite data for Sprite0. The 2 is for XOR logic.
       .dw $100+paddle \ .db 0,24,2  ;load the sprite data for Sprite1. The 2 is for XOR logic.
       .dw $100+paddle \ .db 0,32,2  ;load the sprite data for Sprite2. The 2 is for XOR logic.
       .dw $100+paddle \ .db 94,24,2  ;load the sprite data for Sprite3. The 2 is for XOR logic.
       .dw $100+paddle \ .db 94,32,2  ;load the sprite data for Sprite4. The 2 is for XOR logic.
     ldirc(keyMask,2)
       .db %00010110,31              ;the second byte is for sMask (the sprite mask)
     ldc(ballxinc,1)
     ldc(ballyinc,1)
Loop:
     orc(status,2)      ;tell the status port to draw the sprites
     orc(status,3)      ;tell the status port to update the LCD, then draw the sprites
KeyLoop:
     bits(key1,40h)     ;check for the Clear key being pressed
     orcz(status,80h)   ;sets bit 7 of the status port if the z flag is set. This turns off Jade.
     bits(key2,2)       ;check if player 2 down key is being pressed
     togz()
     jrfz(TestP2Up)
     cpc(sY3,48)
     jrfz(TestP2Up)
     addc(sY3,2)
     addc(sY4,2)
TestP2Up:
     bits(key2,8)       ;check if player 2 down key is being pressed
     togz()
     jrfz(TestP1Down)
     cpc(sY3,0)
     jrfz(TestP1Down)
     subc(sY3,2)
     subc(sY4,2)
TestP1Down:
     bits(key4,2)       ;check if player 2 down key is being pressed
     togz()
     jrfz(TestP1Up)
     cpc(sY1,48)
     jrfz(TestP1Up)
     addc(sY1,2)
     addc(sY2,2)
TestP1Up:
     bits(key4,8)       ;check if player 2 down key is being pressed
     togz()
     jrfz(MoveBall)
     cpc(sY1,0)
     jrfz(MoveBall)
     subc(sY1,2)
     subc(sY2,2)
MoveBall:
     adda(sX0,ballxinc)
     callfz(TestP1Collision)
     cpc(sX0,89)
     callfz(TestP2Collision)
     adda(sY0,ballyinc)
     jrfz(NegYinc)
     cpc(sY0,60)
     togz()
     jrbz(Loop)
NegYinc:
     inv(ballyinc)
     inc(ballyinc)
     jrb(loop)

TestP1Collision:
     lda(scratchmath,sY1)
     jrf(CheckBounds)
TestP2Collision:
     lda(scratchmath,sY3)
CheckBounds:
     lda(scratchmath1,scratchmath)
     subc(scratchmath,3)
     ldcc(scratchmath,0)
     suba(scratchmath,sY0)
     jrfc(CheckOtherBound)
LoseCode:
     ldc(status,80h)
CheckOtherBound:
     addc(scratchmath1,15)
     suba(scratchmath1,sY0)
     jrbc(LoseCode)
NegXinc:
     inv(ballxinc)
     inc(ballxinc)    
     ret()
    
ball:
     .db 18h
     .db 24h
     .db 24h
     .db 18h
     .db 0,0,0,0
paddle:
     .db $C0
     .db $C0
     .db $C0
     .db $C0
     .db $C0
     .db $C0
     .db $C0
     .db $C0

Use [3] and [9] to move the paddle of player 2 and [1] and [7] for Player 1. [Clear] exits.

I still need to finish the documentation and the include file needs a lot more #define statements.

EDIT2: This is basically a crosspost:
I was trying to write an oncalc compiler thing for Jade earlier when I remembered about this project: ASMDREAM
So after an hour of working on a compiler I decided to instead incorporate ASMDream's system using macros and it works! All of the instructions and equates have been made except for one area-- the header. What sort of header should I use for Jade files? I was thinking there could be several types of file:
ROMs
ROM packs (multiple ROMs in one file)
Save states

So I thought a good header might look like:

Code:

1 byte for the file info and type
   bit 0:1 for the type
   bit 2 for the save state size (128 bytes or 256)
   bit 3 set if there is a description
   bit 4 set if there is an icon
1 bytes for the name length
n bytes for the name
2 bytes for the miscellaneous data size
n bytes for miscellaneous data such as description and icon
2 bytes for the ROM size
n bytes for ROM data

It is pretty close in syntax to what is used by FileSyst, so I should be able to use code form that if I go with this idea.

Hopefully this is will make it easier for people to code for Jade Smile

ASMDream Equates and Macros
I have made a working tilemap routine. It isn't super fast, but it works!


Code:

spriteX = stackbase-1
spriteY = stackbase-2
tilenum = stackbase-3
.org 0
     ldc(keyMask,255)
     ldirc(160,96)
;tilemap data
;drawn by column
     .db 1,1,1,1,1,1,1,1
     .db 1,0,2,2,2,2,0,1
     .db 1,0,0,2,2,0,0,1
     .db 1,0,0,0,0,0,0,1
     .db 1,0,0,0,0,0,0,1
     .db 1,0,0,0,0,0,0,1
     .db 1,0,0,0,0,0,0,1
     .db 1,0,0,0,0,0,0,1
     .db 1,0,0,0,0,0,0,1
     .db 1,0,0,2,2,0,0,1
     .db 1,0,2,2,2,2,0,1
     .db 1,1,1,1,1,1,1,1
     ldirc(sMSB0,4)
       .db 1,96,0,2
     ldc(sMask,1)
TileMap:
     ldc(sY0,64)
     subc(sX0,8)
     jrfc(Start)
TileMapLoop:
     subc(sY0,8)
     jrbc(TileMap)
     pop(tilenum)
     rotl(tilenum)
     rotl(tilenum)
     rotl(tilenum)
     addc(tilenum,tiles)
     lda(sLSB0,tilenum)
     orc(status,2)
     jrb(TileMapLoop)
tiles:
     .db 0,0,0,0,0,0,0,0
     .db 3Ch,42h,81h,81h,81h,81h,42h,3Ch
     .db 3Ch,7Eh,$FF,$FF,$FF,$FF,7Eh,3Ch     
Start:
     ldc(stackptr,0)
     orc(status,1)
     ldc(status,80h)
Great work on this, Xeda, it looks very neat. Have you thought at all about a higher-level language that will compile down to this?
I haven't thought of that, but that would be a good idea. For now, macros could be used to make things easier.

Also, as a little update, I realised that I need to add in some form of indirection, otherwise it is very difficult to copy data from a non-fixed location (well, you need to set the stack pointer to the spot in RAM, then pop a byte off the stack and reset the stack pointer to where it was-- not simple).

So I have two options that I am looking at:

  • Currently, if bit 7 of the instruction is set, it means execute the instruction on the condition that the z flag is set. If bit 6 is set, it uses the c flag as a conditional. I could make it so that if both bits are set, it uses indirection. The positive is that instructions stay the same size, the negative is that you cannot use conditionals when using indirection, this way
  • Have a prefix byte that causes the next instruction to be executed using indirection on an argument. The negative to this is that it will cost an extra byte, the positive is that it can be used with conditionals. Also, it could conditionally use indirection, if that makes any sense. (Basically, something like using indirection if the c flag is set, otherwise, execute the code as normal).
I made two main changes. The first: I removed keyMask since it was not needed, it actually slowed things down, and it used more code to mask out keygroups. Now Jade will quickly scan each of the keygroups regardless. If you don't want a key group scanned, you probably aren't reading from it, so it won't matter.

I added in the ind1() and ind2() instructions. The first causes the next argument to be read as indirection. For example, if you used lda(indirect,addr), then the byte located at (addr) will be copied to the byte that is pointed to by (indirect). Essentially, in z80 speak, this would be 'ld ((addr1)),(addr2)' since lda() would translate to 'ld (addr1),(addr2)'. ind2() simply causes the second argument from that instruction to be read as indirection. Indirection isn't wasted on instructions with no arguments. In fact, this code:

Code:

     ind2()
     inc(addr1)
     inc(addr1)

Will cause the second inc() instruction to use indirection. The reason is because instead of simply setting a bit in some counter, Jade rotates a bit in to an 8-cycle counter (internally, it is just a byte being shifted). Then when an argument is read, that counter gets shifted and if the bit cycled out is set, it works as indirection, otherwise it proceeds as normal. SO you can do something like this:

Code:

     ind1()
     ind1()
     inc(addr1)
     inc(addr2)

And both increment instructions will use indirection. The reason for why this second level of indirection is needed is because Jade doesn't use registers, so all 'registers' have to be stored in RAM. If one of those registers contains an address to a byte to be read, you need to use the first level of indirection that all instructions use, then the second level to make it read elsewhere.


Also, the byte holding indirection info is stored where sMask used to be, so if you are going to use more than 2 ind() instructions in a row, you can simply use an orc() or ldc() instruction to manually set the appropriate bits. Other hackadasical uses of this indirection counter would be to pass indirect arguments to a subroutine, instead of directly passing values.

Unfortunately, I have no screenshot, but I think it is almost ready for me to upload an official version to Cemetech! There is just one more thing that I am forgetting, but I cannot remember what it is :[

Jade Download This includes mostly complete documentation, Jade.inc, the source, an example file, and ASMDream macro and label files for on-calc programming.

The next step from here is JadeCA which won't be designed for game making, but rather a personal experiment that I have been working toward. The ports will be different and the first 44 bytes of RAM will be used, but it will support up to 32 processors in parallel that will interact with each other in an environment. The first 32 ports alone or for the 'eyes' of each organism, there will be the ability to reproduce asexually and some other form that I wouldn't call 'sexual reproduction.' Mutations have two forms, one that modifies only the arguments in instruction, and the other that is more likely to damage the organism (it modifies instructions). They will be able to sense food, store energy and small amounts of food that can be dropped or consumed, the ability to be offensive or defensive (each organism that dies counts as double the amount of food as regular nutrients). There is a way to display distress and move, and there is a way to move faster in panic mode or be more offensive/defensive. The cost is more energy being used, and if the energy reaches 0, it dies. I plan to intialise the field with random code generated for each CPU, so it will likely take a long time for anything fruitful to occur.
So JadeCA is a combo virtual machine/cellular automata? That sounds pretty neat, though it sounds like a very big project with the 32 processors and such. Have you started working on that at all? Can we have more technical details about how it's going to work?
Oh, it will actually be pretty easy to run the 32 processors since everything is self-contained. It really is meant purely for experimentation. I have a calc with 8 RAM pages, so I will just use that to swap in fresh pages to have a full 32KB of RAM to work with. There are four 128x128 pixel buffers planned to hold information.

Buffer 0 is where everything is physically placed. This will be used for sensing other organisms near by.

Buffer 1 is where some other signals will be displayed, such as for blips of communication (think of this as if they are flashing from distress or if they can communicate, to do that).

Buffer 2 holds food location. If an organism dies, it will be marked in buffer 2 as well as buffer 0 and this will signal more nutrient rich food.

Buffer 3 holds an regions which are being attacked. So if an organism tries to attack another organism, the outline of the shape of the organism will be printed here, and any organisms that are in contact with that will be damaged on the next cycle.

That all consumes 8192 bytes, if my math is correct. Then each processor/organism has 512 bytes of code space and 256 bytes of RAM. I will need some RAM for JadeCA to use for things such as the viewing area (128x128 will not fit on one screen). So with the rest of RAM, I can include 31 organisms.

There are 8 ports for sensing on each buffer. This basically returns how many pixels away there is a pixel on in 8 fixed directions (vertical, horizontal, and diagonals). These are read-only ports that use the first 32 bytes of RAM. There is a status port which controls the state of being and some actions. It controls:
-Whether the cell is in panic or not. Panic makes it stronger, faster, and more resilient to attacks, but it consumes more energy.
-Whether the cell should reproduce. If there are other cells close by, they will go through a very difficult to code reproduction process that involves swapping code in a complicated way. Otherwise, they will simply create a copy of themselves. This costs more energy to reproduce asexually, so it will be advantageous for cells that use other means. As well, there will be two types of mutations that can occur, for variety. The first will simply change instruction arguments. This may provide a better fit (for example, if a code sends the cell into panic mode for anything within 80 units, this might be changed to 15 units, preserving energy). This might also be bad, especially if the argument is a call to a useful subroutine. The second type of mutation will most likely be bad as it will change instructions and not arguments. So a CALL() instruction might get changed to a ADDA() instruction. The former uses a 1 byte argument, the latter uses 2, 1-byte arguments and modifies RAM.
-Whether the cell is going to pick up nutrients that it doesn't need or drop an excess unit of nutrients (this might be result in 'trade')
-Whether the cell is in an offensive or defensive mode

That port is editable.
The next two ports contain how much energy the cell has for reproduction, movement, et cetera. This port is not editable.

The next port is for movement. Each 2 bits corresponds to a direction. When the cell is not in panic mode, the upper bit of each 2-bit section is used to determine if the cell moves a unit or not. In panic mode, the 2 bits give a value 0~3 telling how many units to move. This port is editable.

The next port is for excess nutrients and is read only.
The next 2 bytes are for the program counter of that cell.
The next byte is for the stack pointer.
The c and z flags are stored in the upper two bits of the status port.

The cells are only going to be 3x3 or 5x5 (I have not yet decided). The way Jade will work, is once it finishes the instruction for one processor, it moves to the next processor and executes an instruction. Once all of the processors have been cycled through, if any changes were made to the buffers, they will be updated. I actually think this last bit needs to be changed. Since I am thinking of making it possible to view the other buffers in grayscale, it might be better to have the Jade engine running in an interrupt that is allowed to fire at particularly frequent interval and just loop through updating the LCD and testing for the keys to turn the calculator ON or OFF, Exit, or turn the LCD on or off.

TL;DR: I haven't started the coding of simulating the processors. I haven't even coded in the routines for the ports. But I will be using the same Jade processor, so all of that is coded, and the rest shouldn't be too difficult. I don't even have to worry about the issue of distance for seeing objects in the diagonal directions, or movement technically being sqrt(2) units instead of 1. This is a world that is meant to be based on other rules XD
I am not at home at the moment, but I did have a little break in my schedule today, so I started coding the JadeCA stuff (and found a few optimisations for Jade). It is currently in app form and it attempts to swap out the RAM pages for extra ones, then swaps them back in, so it could theoretically be safe on some calculators. It also sets it to the fastest speed setting possible and removes flash write delays to get the most speed out of things. The code completely wipes the 32KB of RAM and I have yet to make it fill in the CPUs with random data. I am currently optimising and altering code to get rid of SMC and whatnot (it is in an app). My current estimates are that it will cycle through one CPU in at most 1200 t-states, so at 15MHz each CPU will be emulating at 400Hz. That isn't terribly fast and I hope it isn't going to be made noticeably slower if I am careful to use clever LCD updating. I am thinking that when it is ready to present, it will only manage about 250Hz.
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 1 of 1
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement