|
|
|||||||
| Home | Register | Downloads | FAQ | Members List | Calendar | Arcade | Mark Forums Read |
» Less advertising throughout
» Post and participate in discussions
» Network with other forum members
» Free private messaging
![]() |
|
|
Thread Tools | Display Modes |
|
|
#1 |
|
Moderator
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Join Date: Feb 2006
Location: Croatia
Posts: 4,372
|
Codename: CHIP16 (prev. CHIP9)
What is Chip16? Chip16 is intended as a fairly easy to implement, well documented and community supported system which would aid beginners when writing their emulator. This would solve many CHIP8 inconsistencies as well as undocumented features sometimes (possibly) added by interpreter authors themselves. CHIP16 has a regular D-PAD controller with 4 buttons in contrast to CHIP8's somewhat messy 16 keys keyboard. If you are interested in emulating the machine or making games/demos, read further. Latest version of the Chip16 interpreter Pack of various Chip16 software: Chip16 program pack 08.04.2011.zip tchip16 assembler and BMP to sprite converter can be found here. Screenshots of some Chip16 software: AsciiChip16.png HerdleChip16.png IntroChip16.png MazeChip16.png PaletteChip16.png MusicMakerChip16.png PongChip16.png SoundChip16.png StarfieldChip16.png SnafuChip16.png StaticChip16.png MandelChip16.png NinjaChip16.png TestromChip16.png TriangleChip16.png AnimChip16.png Code:
Chip16 virtual machine Hardware spec: 0.9.1 Contributors: -------------- * Chris2Balls * Cottonvibes * Runawayprisoner * Serge2k * Shendo * Tronix286 * Tykel Thanks to: ----------- * @ruantec * Arne * BestCoder * Bill_gates * Chrono Archangel * Exophase * Fieryrage * LoRd_SnOw * Tricky Upgrade * Xtreme2Damax 1x 16 bit program counter (PC) 1x 16 bit stack pointer (SP) 16x 16 bit general purpose registers (R0 - RF) 1x 8 bit flag register Machine uses little-endian byte ordering. CPU: ----- All opcodes take exactly 1 cycle to execute. CPU speed is 1 Mhz. GPU: ----- 60 FPS, raises an internal VBlank flag each frame (~16 ms) which the CPU can wait-on with the VBLNK instruction. Resolution is 320x240 pixels with 4 bit indexed color. Along with a primary layer is a background layer which can be set to only 1 color index. Screen does not wrap. Everything that is offscreen stays offscreen. Sprites can be drawn to negative coordinates, so that a 4x4 pixel sprite drawn at (-2,-2), will show 2x2 pixels on the top-left part of the screen. Sprites are byte coded color indexes. 1 Byte represents a block of 2 pixels. For example, if the sprite height and width are set to 2 the sprite is 4 pixels wide and 2 pixels tall. Higher nibble is the left pixel and lower nibble is the right pixel. If a new sprite overlaps any existing pixel (other then 0 - transparent) carry flag is raised. Color indexes (0xRRGGBB): ------------------------ 0x0 - 0x000000 (Black, Transparent in foreground layer) 0x1 - 0x000000 (Black) 0x2 - 0x888888 (Gray) 0x3 - 0xBF3932 (Red) 0x4 - 0xDE7AAE (Pink) 0x5 - 0x4C3D21 (Dark brown) 0x6 - 0x905F25 (Brown) 0x7 - 0xE49452 (Orange) 0x8 - 0xEAD979 (Yellow) 0x9 - 0x537A3B (Green) 0xA - 0xABD54A (Light green) 0xB - 0x252E38 (Dark blue) 0xC - 0x00467F (Blue) 0xD - 0x68ABCC (Light blue) 0xE - 0xBCDEE4 (Sky blue) 0xF - 0xFFFFFF (White) SPU: ----- 500Hz, 1000Hz and 1500Hz sine wave tone generator. Controllers: ------------- Controllers are accessed through memory mapped I/O ports. Controller 1: FFF0. Controller 2: FFF2. Bit[0] - Up Bit[1] - Down Bit[2] - Left Bit[3] - Right Bit[4] - Select Bit[5] - Start Bit[6] - A Bit[7] - B Bit[8 - 15] - Unused (Always zero). Controller status gets updated each VBlank. Memory: -------- 64 KB (65536 bytes). 0x0000 - Start of ROM. 0xFDF0 - Start of stack (512 bytes). 0xFFF0 - IO ports. Flag register: --------------- Bit[0] - Reserved Bit[1] - c (Unsigned Carry and Unsigned Borrow flag) Bit[2] - z (Zero flag) Bit[3] - Reserved Bit[4] - Reserved Bit[5] - Reserved Bit[6] - o (Signed Overflow Flag) Bit[7] - n (Negative Flag; aka Sign Flag) c flag: - for add - is set when the result would have imaginary bit[16] set, else its cleared. - for sub - is set when the operation needed to borrow from bit[16], else its cleared. - for mul - is set when the result is too big to fit into 16 bits, else its cleared. - for div - is set when the remainder of the division is non-zero, else it is cleared. z flag: - is set when the result is 0, else its cleared. o flag: - in general this flag is used when the sign of the result differs from what would be expected. - for addition it is set when the result is positive and both operands were negative, or if the result is negative and both operands were positive; else it is cleared. - for subtraction (x-y=z) it is set when z is positive and x is negative and y is positive, or if z is negative and x is positive and y is negative; else it is cleared. n flag: - it is set when the result is less than 0 (bit[15] == 1); else it is cleared. Conditional Types: --------------- The conditions below are used for conditional jumps and conditional calls. For example: "jle some_label" or "cno some_label" The condition in brackets refer to the Flag register's flags. Z = 0x0 // [z==1] Equal (Zero) NZ = 0x1 // [z==0] Not Equal (Non-Zero) N = 0x2 // [n==1] Negative NN = 0x3 // [n==0] Not-Negative (Positive or Zero) P = 0x4 // [n==0 && z==0] Positive O = 0x5 // [o==1] Overflow NO = 0x6 // [o==0] No Overflow A = 0x7 // [c==0 && z==0] Above (Unsigned Greater Than) AE = 0x8 // [c==0] Above Equal (Unsigned Greater Than or Equal) B = 0x9 // [c==1] Below (Unsigned Less Than) BE = 0xA // [c==1 || z==1] Below Equal (Unsigned Less Than or Equal) G = 0xB // [o==n && z==0] Signed Greater Than GE = 0xC // [o==n] Signed Greater Than or Equal L = 0xD // [o!=n] Signed Less Than LE = 0xE // [o!=n || z==1] Signed Less Than or Equal RES = 0xF // Reserved for future use Alternative Valid Mnemonics: C = 0x9 // [c==1] Carry (Same as B) NC = 0x8 // [c==0] Not Carry (Same as GE) Opcodes: --------- HH - high byte. LL - low byte. N - nibble (4 bit value). X, Y, Z - 4 bit register identifier. Opcode (Hex) Mnemonic Usage 00 00 00 00 NOP Not operation. Wastes a cycle. 01 00 00 00 CLS Clear screen (Foreground layer is erased, background is set to index 0). 02 00 00 00 VBLNK Wait for VBlank. If (!vblank) PC-=4; 03 00 0N 00 BGC N Set background color to index N. If the index was set to 0 the color is black. 04 00 LL HH SPR HHLL Set sprite width (LL) and height (HH). 05 YX LL HH DRW RX, RY, HHLL Draw sprite from address HHLL at coordinates stored in register X and Y (treated as signed numbers). Affects carry flag (explained in GPU info). 06 YX 0Z 00 DRW RX, RY, RZ Draw sprite from address pointed by register Z at coordinates stored in register X and Y (treated as signed numbers). Affects carry flag. 07 0X LL HH RND RX, HHLL Generate a random number and store it in register X. Maximum value is HHLL. 08 00 00 00 FLIP 0, 0 Set flip orientation for sprites. horizontal flip = false; vertical flip = false 08 00 00 01 FLIP 0, 1 Set flip orientation for sprites. horizontal flip = false; vertical flip = true 08 00 00 02 FLIP 1, 0 Set flip orientation for sprites. horizontal flip = true; vertical flip = false 08 00 00 03 FLIP 1, 1 Set flip orientation for sprites. horizontal flip = true; vertical flip = true 09 00 00 00 SND0 Stop playing sounds. 0A 00 LL HH SND1 HHLL Play 500Hz tone for HHLL miliseconds. 0B 00 LL HH SND2 HHLL Play 1000Hz tone for HHLL miliseconds. 0C 00 LL HH SND3 HHLL Play 1500Hz tone for HHLL miliseconds. // Jumps 10 00 LL HH JMP HHLL Jump to the specified address. 12 0x LL HH Jx HHLL Jump to the specified address if condition 'x' (x refers to a Conditional Type) 13 YX LL HH JME RX, RY, HHLL Jump to the specified address if value in register X is equal to value in register Y. 16 0X 00 00 JMP RX Jump to the address specified in register X (Indirect Jump) // Calls 14 00 LL HH CALL HHLL Call subroutine at the specified address. Store PC to [SP]. Increase SP by 2. 15 00 00 00 RET Return from a subroutine. Decrease SP by 2. Get PC from [SP]. 17 0x LL HH Cx HHLL If condition 'x', then performs a CALL; Else, does nothing. (x refers to a Conditional Type). 18 0X 00 00 CALL RX Call subroutine at the address specified in RX. Store PC to [SP]. Increase SP by 2. // Loads 20 0X LL HH LDI RX, HHLL Load immediate value to register X. 21 00 LL HH LDI SP, HHLL Point SP to the specified address. Does not move existing values in memory to new location. 22 0X LL HH LDM RX, HHLL Load register X with the 16bit value at the specified address. 23 YX 00 00 LDM RX, RY Load register X with the 16bit value at the specified address pointed by register Y. 24 YX 00 00 MOV RX, RY Copy data from register Y to register X. // Stores 30 0X LL HH STM RX, HHLL Store value of register X at the specified address. 31 YX 00 00 STM RX, RY Store value of register X at the specified address pointed by register Y. // Arithmetic 40 0X LL HH ADDI RX, HHLL Add immediate value to register X. Affects [c,z,o,n] 41 YX 00 00 ADD RX, RY Add value of register Y to register X. Result is stored in register X. Affects [c,z,o,n] 42 YX 0Z 00 ADD RX, RY, RZ Add value of register Y to register X. Result is stored in register Z. Affects [c,z,o,n] 50 0X LL HH SUBI RX, HHLL Subtract immediate value from register X. Result is stored in register X. Affects [c,z,o,n]. 51 YX 00 00 SUB RX, RY Subtract value of register Y from register X. Result is stored in register X. Affects [c,z,o,n] 52 YX 0Z 00 SUB RX, RY, RZ Subtract value of register Y from register X. Result is stored in register Z. Affects [c,z,o,n] 53 0X LL HH CMPI RX, HHLL Subtract immediate value from register X. Result is discarded. Affects [c,z,o,n]. 54 YX 00 00 CMP RX, RY Subtract value of register Y from register X. Result is discarded. Affects [c,z,o,n] 60 0X LL HH ANDI RX, HHLL AND immediate value with register X. Result is stored in register X. Affects [z,n] 61 YX 00 00 AND RX, RY AND value of register Y with value of register X. Result is stored in register X. Affects [z,n] 62 YX 0Z 00 AND RX, RY, RZ AND value of register Y with value of register X. Result is stored in register Z. Affects [z,n] 63 0X LL HH TSTI RX, HHLL AND immediate value with register X. Result is discarded. Affects [z,n] 64 YX 00 00 TST RX, RY AND value of register Y with value of register X. Result is discarded. Affects [z,n] 70 0X LL HH ORI RX, HHLL OR immediate value with register X. Result is stored in register X. Affects [z,n] 71 YX 00 00 OR RX, RY OR value of register Y with value of register X. Result is stored in register X. Affects [z,n] 72 YX 0Z 00 OR RX, RY, RZ OR value of register Y with value of register X. Result is stored in register Z. Affects [z,n] 80 0X LL HH XORI RX, HHLL XOR immediate value with register X. Result is stored in register X. Affects [z,n] 81 YX 00 00 XOR RX, RY XOR value of register Y with value of register X. Result is stored in register X. Affects [z,n] 82 YX 0Z 00 XOR RX, RY, RZ XOR value of register Y with value of register X. Result is stored in register Z. Affects [z,n] 90 0X LL HH MULI RX, HHLL Multiply immediate value with register X. Result is stored in register X. Affects [c,z,n] 91 YX 00 00 MUL RX, RY Multiply value of register Y with value of register X. Result is stored in register X. Affects [c,z,n] 92 YX 0Z 00 MUL RX, RY, RZ Multiply value of register Y with value of register X. Result is stored in register Z. Affects [c,z,n] A0 0X LL HH DIVI RX, HHLL Divide immediate value with register X. Result is stored in register X. Affects [c,z,n] A1 YX 00 00 DIV RX, RY Divide value of register Y with value of register X. Result is stored in register X. Affects [c,z,n] A2 YX 0Z 00 DIV RX, RY, RZ Divide value of register Y with value of register X. Result is stored in register Z. Affects [c,z,n] B0 0X 0N 00 SHL RX, N Logical Shift value in register X left N times. Affects [z,n] B1 0X 0N 00 SHR RX, N Logical Shift value in register X right N times. Affects [z,n] B0 0X 0N 00 SAL RX, N Arithmetic Shift value in register X left N times. Affects [z,n] (same as SHL) B2 0X 0N 00 SAR RX, N Arithmetic Shift value in register X right N times. Affects [z,n] B3 YX 00 00 SHL RX, RY Logical Shift value in register X left by the value in (RY & 0xf). Affects [z,n] B4 YX 00 00 SHR RX, RY Logical Shift value in register X right by the value in (RY & 0xf). Affects [z,n] B3 YX 00 00 SAL RX, RY Arithmetic Shift value in register X left by the value in (RY & 0xf). Affects [z,n] (same as SHL) B5 YX 00 00 SAR RX, RY Arithmetic Shift value in register X right by the value in (RY & 0xf). Affects [z,n] // Push / Pop C0 0X 00 00 PUSH RX Store register X on stack. Increase SP by 2. C1 0X 00 00 POP RX Decrease SP by 2. Load register X from stack. C2 00 00 00 PUSHALL Store all general purpose registers (r0~rF) at [SP]. Increase SP by 32. C3 00 00 00 POPALL Decrease SP by 32. Load all general purpose registers (r0~rF) from [SP]. C4 00 00 00 PUSHF Store flags register on stack. Increase SP by 2. C5 00 00 00 POPF Decrease SP by 2. Load flags register from stack.
__________________
Shendo's software blog Core i5 2400 3.1 Ghz | ASRock H67M | HD4850 512MB | 8GB DDR3 1333 | 500 GB + 1500 GB HDD Grundig VLC 7121 C (1080p) 32" | Razer DeathAdder | Logitech G110 | Windows 7 x64 Last edited by ShendoXT; October 28th, 2011 at 21:32.. |
|
|
| Advertisement | [Remove Advertisement] | ||
|
|
|
|
#2 |
|
Level 9998
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Join Date: Nov 2006
Location: Java
Posts: 8,267
|
Hey, count me in. Though... I'm kinda busy working on JPSX right now. ![]() And you know that I'd specifically request... color support.
|
|
|
|
|
#3 |
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Join Date: Dec 2001
Location: Montreal, Canada
Posts: 8,062
|
Interesting ![]() Is it just me but only one player support? The 16 keys the Chip8 had allowed 2 players on the same "pad" for games like pong.
__________________
|
|
|
|
|
#4 |
|
Moderator
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Join Date: Feb 2006
Location: Croatia
Posts: 4,372
|
Well I cramped controller status in the flag register in order to simplify things. However I/O ports could easily be added which would allow 2 input devices.
__________________
Shendo's software blog Core i5 2400 3.1 Ghz | ASRock H67M | HD4850 512MB | 8GB DDR3 1333 | 500 GB + 1500 GB HDD Grundig VLC 7121 C (1080p) 32" | Razer DeathAdder | Logitech G110 | Windows 7 x64 |
|
|
|
|
#5 |
|
You're already dead...
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Join Date: Sep 2007
Location: Planet Vegeta
Posts: 5,235
|
i don't think i have time to help out implementing this machine, but i'll give my suggestions on the architecture: 1) screen refresh rate should be 60hz, not 30hz. 2) the flag register shouldn't contain gamepad info 3) gamepad status should be polled from I/O ports (maybe address 0x440 ~ 0x450 should be reserved for I/O ports) 4) gamepad should have 'select' and 'start' keys so gamepad #1 can look like: I/O Port: 0x440 bit[0] - Controller UP bit[1] - Controller DOWN bit[2] - Controller LEFT bit[3] - Controller RIGHT bit[4] - Controller A bit[5] - Controller B bit[6] - Controller Select bit[7] - Controller Start Gamepad #2 can be the same thing but at port 0x441 5) the system would be much faster if the BIOS calls were opcodes like in chip8. if they're left as BIOS calls, then that means they have to be implemented using the chip9's instruction set, which would in-turn need to be interpreted, and thus make a simple operation like CLS a very expensive one. so i suggest having SYS (system) opcodes, which will consist of all the calls you originally wanted in the bios. and then these calls are expected to be high-level emulated, instead of being subroutines in the bios. also IsKeyPressed() should be removed since all you need to do is check if a bit on an I/O port is set. ( mov r0, [0x440]; test r0, 0xff; ) 6) logic operations AND/OR/XOR/TEST should be added to the instruction set. 7) all instructions should be 32bits, with this format: | opcode x 8bits | operands x 24bits | 8) maybe r0~r7 should all be 16bit registers, and you just do away with the address register.
__________________
"It was, of course, a lie what you read about my religious convictions, a lie which is being systematically repeated. I do not believe in a personal God and I have never denied this but have expressed it clearly. If something is in me which can be called religious then it is the unbounded admiration for the structure of the world so far as our science can reveal it." - Albert Einstein check out my blog ![]() |
|
|
|
|
#6 |
|
Moderator
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Join Date: Feb 2006
Location: Croatia
Posts: 4,372
|
Very good and reasonable suggestions cottonvibes ![]() The design posted above was pre-skeleton so to speak. It's just there to give a vague idea where this project would be heading. Anyway, I'm off to the drawing board.
__________________
Shendo's software blog Core i5 2400 3.1 Ghz | ASRock H67M | HD4850 512MB | 8GB DDR3 1333 | 500 GB + 1500 GB HDD Grundig VLC 7121 C (1080p) 32" | Razer DeathAdder | Logitech G110 | Windows 7 x64 |
|
|
|
|
#7 |
|
Registered User
![]() ![]() ![]() Join Date: Dec 2007
Location: That place in that somewhere.
Posts: 270
|
I recommend ignoring microarchitectural realities.
|
|
|
|
|
#8 |
|
You're already dead...
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Join Date: Sep 2007
Location: Planet Vegeta
Posts: 5,235
|
i think some opcodes to add would be "ADD with Carry" and "Sub with Borrow" i don't know if the 'borrow' flag should be the same flag as the 'carry' flag, or if it should be separate. usually its the same flag as the carry flag. i don't know if there's a reason for this (algorithmically) or if they just do this to have 1 less flag. you may want to have an Overflow flag and a separate Carry flag. One of these is set when there's unsigned overflow on additions, and the other is set when there's signed overflow. i always forget which is which D: other suggestions would depend on if you want this easier to implement (for the emu coders), or if you want the language more easy to work with (for the game programmers). if you care more about the game programmers, then it would be nice if every arithmetic instruction has 4 forms: Register-with-Register, Register-with-Memory, Memory-with-Register, Register-with-Immediate. For example with the ADD opcode: Code:
| ADDrr*8 | Dest Reg*3 | Src Reg*3 | 0*2 | 0*16 | | ADDrm*8 | Dest Reg*3 | 0*3 | 0*2 | Mem Addr*16 | | ADDmr*8 | 0*3 | Src Reg*3 | 0*2 | Mem Addr*16 | | ADDri*8 | Dest Reg*3 | 0*3 | 0*2 | Immediate*16 | The bad part is that this means implementing 4 opcodes for every arithmetic instructions (more work for the emu coder). However, since every arithmetic instruction will use the exact same addressing modes, you can design your code in a way so that you can reuse each addressing mode function and just change the operation (+,-,*,/,^,|,&,...). Another thing to think about are 3-operand instructions vs 2-operand instructions. r0 = r1 + r2; vs r0 = r0 + r1; If you use 3-operand instructions, then you reduce the amount of register trashing compared to 2-operand instructions. however, if you use the 4-addressing modes i outlined above, they become more complex when adding 3-operand modes. I think the way to do it for this simple (but powerful) interpreter, is to use 2-operand instructions, BUT double the amount of registers (r0 to r15). 8 registers makes it harder to code with when you're doing the assembly by hand. 16 is a good number, or maybe you want to use 32 registers which will make it even easier to code with. With 16 registers, the above addressing modes look like this: Code:
| ADDrr*8 | Dest Reg*4 | Src Reg*4 | 0*16 | | ADDrm*8 | Dest Reg*4 | 0*4 | Mem Addr*16 | | ADDmr*8 | 0*4 | Src Reg*4 | Mem Addr*16 | | ADDri*8 | Dest Reg*4 | 0*4 | Immediate*16 |
__________________
"It was, of course, a lie what you read about my religious convictions, a lie which is being systematically repeated. I do not believe in a personal God and I have never denied this but have expressed it clearly. If something is in me which can be called religious then it is the unbounded admiration for the structure of the world so far as our science can reveal it." - Albert Einstein check out my blog ![]() |
|
|
|
|
#9 |
|
Er. Redlof
![]() ![]() ![]() ![]() ![]() ![]() ![]() Join Date: Aug 2008
Location: C:>
Posts: 6,599
|
I agree with cotton on the registers part. Just 7 registers including acc is not enough. I'm also waiting for the opcodes. I need a reference program. Can anyone provide a link?
__________________
![]() SONY Vaio Laptop////Intel Core i3-2330M Processor 2.20GHz////Windows7 Home Basic (64-bit)////Memory 2+4GB / Hard Disk Drive 320GB////Graphics card Nvidia GeForce 410M Good people are good because they've come to wisdom through failure. We get very little wisdom from success, you know
|
|
|
|
|
#10 | |
|
Registered User
![]() ![]() Join Date: Sep 2006
Location: surrey
Posts: 111
|
Quote:
for example: Add r0, r1, r2 => r0 = r1 + r2 in a 4 byte opcode: byte 1 is the opcode, byte 2 is r0, byte 3 is r1, and byte 4 is r2. Now since you have up to 255 possibilities for each of r0, r1, r2 you can easily specify 32 registers or you can have a value that indicates a memory addres or immediate value follows. 0-32 = register 33 = memory 34 = immediate. something like that, anyway this is still a simple design and can be easily implemented. The only problem is if more than 256 opcodes are needed. The other idea I have would be to allow a movable stack pointer. It's really no more difficult to implement and it's more flexible than fixing it at one poisition (and restricting the size based on that). It does make things more difficult for the person programming, but it gives more power. It can also be specified that the initial position of the stack is in a set place, but it's movable. |
|
|
|
|
|
#11 |
|
Level 9998
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Join Date: Nov 2006
Location: Java
Posts: 8,267
|
Don't go too crazy on opcodes. Chip-8 was simple and I'd think that you should keep Chip-9 as simple... In any case, I think the opcode encoding can be like this: Code:
[ 15][ 14][ 13][ 12][ 11][ 10][ 9][ 8][ 7][ 6][ 5][ 4][ 3][ 2][ 1][ 0] [cat][cat][ind][ind][ind][ind][op0][op0][op0][op1][op1][op1][op2][op2][op2][flg] Where... [cat] (15-14) is a 2-bit category index that represents the category of the following 4 bits for the opcode: 0 - io operations 1 - arithmetic 2 - logical/branching 3 - draw opcodes [ind] (13-10) is a 4-bit index that has 16 slots (0 - 15) for up to 16 instructions. So coupled with the category bits, that means you can have up to 64 instructions in total. I don't think you'll need more than that. Chip-8 itself has less than 40. For instance, the arithmetic category will only need these opcodes: 0 add 1 addu 2 sub 3 subu 4 mult 5 multu 6 div 7 divu 8 mod 9 modu a sin b cos c tan d asin e acos f atan See what I mean? Don't waste valuable bits... The less bits you have for the opcodes, the easier it is to implement the interpreter. [op0] (9-7), [op1] (6-4), [op2] (3-1) each represents a 3-bit operand. If used normally, each can be an index for registers, so up to 8 registers can be indexed at a time. I don't think you'll need more than 8... If you do, we can do register paging, so 1 set of 8 registers specifically for each category would make 32 registers in total. op2 can be specifically used for the paging index for inter-category register access, or op2 can be used as a pointer to a third register. How to do that depends right on the next bit... flg (0) is a 1-bit flag to determine what kind of data op2 should hold, be it a register index (flag == 0), or page index (flag == 1). [stuffs...] I think this should only be used when op2 holds a value larger than 4 (100) and the flag bit right after indicates that op2 should be a page index (flag == 1), in which case, [stuffs...] would be either: op2 == (100) means the next 8 bits after the flag represents an 8-bit number (signed/unsigned depending on the opcode) op2 == (101) means the next 16 bits after the flag represents a 16-bit number (signed/unsigned depending on the opcode) op2 == (110) means the next 32 bits after the flag represents a 32-bit number (signed/unsigned depending on the opcode) op2 == (111) means the next 32 bits after the flag represents a 32-bit address (mem access) Otherwise: op2 == (000) means op1 points to a register in the io category op2 == (001) means op1 points to a register in the arithmetic category op2 == (010) means op1 points to a register in the logical/branching category op2 == (011) means op1 points to a register in the draw category If op2 is pointing to a page index instead of a register, the result of the operation will be stored in op1. This way, you don't have to do an extra read/write operations to get the result out of the arithmetic category, and you only need to do a read operation before hand if you'd like to get the value of a register elsewhere. In case you didn't get it, if op2 is used without the flag (flag == 0), it'll just point to a register in the same category that the opcode is in. In which case, the result will be stored in op2. You can do it like this and then do a load operation to get the result out of the arithmetic category. That way, you can have 16-bit opcodes just like Chip-8 without worrying about memory address or explicit number declarations like with Chip-8. Plus... 16 opcodes for drawing routines is really plenty, and coupled with trig functions (sin, cos, tan) means you can do affine transformations or even 3D!And with this method of encoding, you can easily get the flag first, then push each operand and the opcode index into the execution stack before getting the category code to call the opcode. Something like... Code:
Boolean flag = opcode & 1;
push(flag);
int op2 = (opcode >> 1) & 7;
push(op2);
int op1 = (opcode >> 4) & 7;
push(op1);
int op0 = (opcode >> 7) & 7;
push(op0);
int ind = (opcode >> 10) & 15;
push(ind);
int cat = (opcode >> 14) & 3;
switch(cat){
case 0: call(opc); ... // blah
}
PC += 2;
Last edited by runawayprisoner; September 4th, 2010 at 20:43.. |
|
|
|
|
#12 |
|
Registered User
![]() ![]() Join Date: Sep 2006
Location: surrey
Posts: 111
|
I would actually say that 4 byte opcodes are easier to implement. You get simplicity in that extracting the opcodes and arguments is easier (no shifting). You get expandibility, if the machine has 20 instructions you have room for 236 more. You have room for a ton of registers. You don't have to implement multiple sets of registers, or any other more complicated logic. The big issue with 4 byte vs 2 byte is the size. The address space is 0x0000 to 0xFFFF, so with 4 bytes you only get around 16000 operations. Not including the additional space for immediate values and addresses. If using two byte opcodes it might be worth it to go to a 2 registers/instruction system. so you get 6 bits for the opcode, 5 for the first arg, 5 for the second. You can get 64 possible operations and 32 possible locations (say 30 registers + immediate + memory). THe space savings of 2 bytes are there. Plenty of registers to work with. |
|
|
|
|
#13 |
|
Level 9998
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Join Date: Nov 2006
Location: Java
Posts: 8,267
|
That's why I opted to do 16-bit opcodes with a 32-bit addressing mode. That way, you can address up to 0xFFFFFFFF, giving a lot of room for expansion. Because aside from the main memory, you have the expansion ports, and all of the other funky thingss. Having a 32-bit addressing mode can help tremendously and give you a lot of room to move around. ![]() As for the arg thing, like I mentioned, there's a problem because without a third operand, or arg, you'd waste more instructions processing something such as r2 = r0 + r1 if you want to keep the values of r0 and r1 intact, see what I mean? The third arg or operand can be used as a paging executive so the second arg can be used to access up to 32 registers, and the later bits can be used as constants. The first arg only needs to access 8 registers, because sincerely, you'd have to do some sort of retarded loop and branch to actually need 32 registers. For the most part, you could just use the stack if you need more, and so around 8 registers would give you up to 8 different kinds of operations all in one loop. And you only have around 8 main arithmetic functions anyways. That was what I had in mind. So technically, you have access to 32 registers still, but they are segmented into different categories so they can be used both as general-purpose registers, and dedicated registers, of which you have 4 different sets of 8 32-bit registers per opcode category, and you won't waste your opcode space trying to make opcodes for addmem and submem and so on... since the third operand can be used to indicate those. That was what cotton was trying to say... I think. Because if you consider arithmetic, you'd have at least this many add instructions: add_reg add_refu add_mem add_memu add_const add_constu So it gets annoying as developers want to use constants and memory access and so on... thus you need a third operand. Last edited by runawayprisoner; September 4th, 2010 at 21:53.. |
|
|
|
|
#14 |
|
Registered User
![]() ![]() Join Date: Sep 2006
Location: surrey
Posts: 111
|
If you are using a 32 bit address space I don't see any reason not to go with 32 bit opcodes. The problem is a 32 bit address space is more complex. with 16 bit you can just allocate the block on the heap and go. With 32 bit you have to start worrying about how you are actually going to manage the 4GB of space. Actually, thinking about the registers. It actually makes more sense on this type of project to have 6 general purpose. Reason being is that more registers don't really matter when it's not a physical machine (you are doing everything in memory anyway for a simple interpreter, no speed gain by adding more registers). 6 would make sense because you can map them directly to AX, BX, CX, DX, SI, DI. Which would be nice if you wanted to try a dynarec at some point. Then you could have the opcode scheme you suggested, but with 7 and 8 indicating a reference or an immediate value. Would need a special case for altering the stack pointer though, or use the flag value to indicate or something like that. |
|
|
|
|
#15 | |
|
Moderator
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Join Date: Feb 2006
Location: Croatia
Posts: 4,372
|
Heh, isn't 32 bit addressing a tad too much for a simple system? I understand that it would be a cool idea to have 3 operand instructions, bunch of registers and lots of options of storing results of arithmetic operations but Chip9 is intended to be successor to outdated chip8 but still kept as simple as possible. Anyway, I made some 32 bit instructions sorted in nibbles (like in chip8). While it gives a bunch of room to add stuff it just wastes space... So yeah I think 16 bit instructions are the way to go. Quote:
__________________
Shendo's software blog Core i5 2400 3.1 Ghz | ASRock H67M | HD4850 512MB | 8GB DDR3 1333 | 500 GB + 1500 GB HDD Grundig VLC 7121 C (1080p) 32" | Razer DeathAdder | Logitech G110 | Windows 7 x64 |
|
|
|
|
|
#16 | |
|
Registered User
![]() ![]() Join Date: Sep 2006
Location: surrey
Posts: 111
|
Quote:
Is there any reason to keep using bad designs? Personally I think keeping a fixed, inaccessible, stack is a bad design. Especially with a limited number of registers. Making the stack movable is more complex. But how much more? (from an emulation standpoint). The stack proposed so far is contained in memory anyway. If it's fixed I would say just remove it altogether and free up the space. If it's in memory then let it move and leave it up to the programmer (the chip9 programmer, not the emulator programmer) to move it where needed. Only thing the emulator writer has to do is emulator the push and pop opcode. Which has to be done anyway when doing a call. |
|
|
|
|
|
#17 |
|
Moderator
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Join Date: Feb 2006
Location: Croatia
Posts: 4,372
|
Movable stack is by no means hard to implement. There could be an opcode which would assign SP to some user specified address. So if for some reason programmer needs more than 32 levels he could call that opcode at the beginning of the program and set SP wherever he wants (after program ideally).
__________________
Shendo's software blog Core i5 2400 3.1 Ghz | ASRock H67M | HD4850 512MB | 8GB DDR3 1333 | 500 GB + 1500 GB HDD Grundig VLC 7121 C (1080p) 32" | Razer DeathAdder | Logitech G110 | Windows 7 x64 |
|
|
|
|
#18 |
|
Level 9998
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Join Date: Nov 2006
Location: Java
Posts: 8,267
|
Hmm... I think you can discard the stack altogether and just use memory operations instead. So when copying register values, instead of doing a... push r0 pop r1 You can just do load r1, r0 And if you have 3 operands and 1 free register, you can do a serial load instruction to preserve the values of 2 registers (in a series) at once: load r2, r1, r0 Would mean: r2 = r1 then r1 = r0 And you can do that instruction once more to swap values all over again, so the presence of a third operand is very useful. ![]() I'm sure with 32 registers in total, you'd have more free registers than you can ever think of. Having a stack in which case is totally unnecessary. The only valid reason why the Chip-8 and any system should have a stack is so that jumps and branches can store their last jump/branch position, then they can use a RET instruction after they're done processing their stuffs. Because for the most part... push r0 pop r1 Is not really that much different from: load mem, r0 load r1, mem Push and pop are faster on systems with CPU caches, but... an interpreter uses a virtual CPU anyway, so cache or not doesn't even matter. See what I mean? It might well not have a stack at all. Of course, it might need a stack for the debugger because developers would love to see what is being called and where it's being called, right? ![]() And by the way, I was even thinking of throwing code-modifying instructions into the mix just for the heck of it... Would help with situations where someone would like to write an emulator or simulator on top of the interpreter. So if you thought that was complicated, you ain't seen it all yet!
Last edited by runawayprisoner; September 5th, 2010 at 00:33.. |
|
|
|
|
#19 | ||
|
You're already dead...
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Join Date: Sep 2007
Location: Planet Vegeta
Posts: 5,235
|
Quote:
Quote:
but if you want to make something clean and more powerful, you should use 32bit instructions. like i said you can even have immediate values if you use 32bit instructions, which will speedup the emulator. the less instructions you need to do something for this interpreter, the less the emulator needs to decode, and therefore the faster it runs. i got another idea, since you're worried about the extra space it takes up, then how about the chip9 has its own RAM, and then has its own Program-Memory separate. 64kb for program ROM, and 64kb for RAM. program is loaded into the ROM address space at address 0, and then any memory accesses it makes are done to the RAM (it can't do self-modifying code).
__________________
"It was, of course, a lie what you read about my religious convictions, a lie which is being systematically repeated. I do not believe in a personal God and I have never denied this but have expressed it clearly. If something is in me which can be called religious then it is the unbounded admiration for the structure of the world so far as our science can reveal it." - Albert Einstein check out my blog ![]() |
||
|
|
|
|
#20 | |
|
Registered User
![]() ![]() Join Date: Sep 2006
Location: surrey
Posts: 111
|
Quote:
Opens up the possibility for easy expansion using extra ram banks. primitive form of protection as well I suppose. I wouldn't get rid of the stack, it's a very simple way to store variables. Putting everything onto the heap would just be messy and complicated. |
|
|
|
![]() |
| Thread Tools | |
| Display Modes | |
|
|