📄 cpu.asm
字号:
;-------------------------------------------------------------------------------
; Name: Cpu.asm
; Desc: The cpu core written in assembly.
;-------------------------------------------------------------------------------
.486
.MODEL FLAT
; Public symbols so other files may use these functions.
PUBLIC RunCPU
; Constants
RUNCPU_STEP EQU 01h
RUNCPU_RUN EQU 02h
; The CPU structure from Cpu.h
_NES6502 STRUCT 4
A DB ? ; The Accumulator register on the 6502.
X DB ? ; The X Index register on the 6502.
Y DB ? ; The Y Index register on the 6502.
S DB ? ; The stack pointer on the 6502.
F DB ? ; The flags register on the 6502.
P DW ? ; The program counter on the 6502.
Memory DB 08000h dup(?) ; All the memory on the NES except the PRG-ROM.
pbyPRGROMBank1 DD ? ; Points to the first PRG-ROM bank on the NES cartridge.
pbyPRGROMBank2 DD ? ; Points to the second PRG-ROM bank on the NES cartridge.
byCycles DB ? ; Number of cycles left until the end of the scanline.
_NES6502 ENDS
; External variables/functions needed.
.FARDATA?
EXTRN ?CPU@@3UtagNES6502@@A :_NES6502
EXTRN ?GetMemoryByte@@YGEG@Z :PROC
EXTRN ?GetMemoryPointer@@YGPAEG@Z :PROC
EXTRN ?ReadMemory@@YGEG@Z :PROC
EXTRN ?WriteMemory@@YGXGE@Z :PROC
CPU EQU ?CPU@@3UtagNES6502@@A
.DATA
; Holds the address of all the instructions. This way,
; whatever the opcode is, we immediately know where to
; jump to execute it.
adwOpcodeJumpTable dd _00, _01, _??, _??, _??, _05, _06, _??
dd _08, _09, _0A, _??, _??, _0D, _0E, _??
dd _10, _11, _??, _??, _??, _15, _16, _??
dd _18, _19, _??, _??, _??, _1D, _1E, _??
dd _20, _21, _??, _??, _24, _25, _26, _??
dd _28, _29, _2A, _??, _2C, _2D, _2E, _??
dd _30, _31, _??, _??, _??, _35, _36, _??
dd _38, _39, _??, _??, _??, _3D, _3E, _??
dd _40, _41, _??, _??, _??, _45, _46, _??
dd _48, _49, _4A, _??, _4C, _4D, _4E, _??
dd _50, _51, _??, _??, _??, _55, _56, _??
dd _58, _59, _??, _??, _??, _5D, _5E, _??
dd _60, _61, _??, _??, _??, _65, _66, _??
dd _68, _69, _6A, _??, _6C, _6D, _6E, _??
dd _70, _71, _??, _??, _??, _75, _76, _??
dd _78, _79, _??, _??, _??, _7D, _7E, _??
dd _??, _81, _??, _??, _84, _85, _86, _??
dd _88, _??, _8A, _??, _8C, _8D, _8E, _??
dd _90, _91, _??, _??, _94, _95, _96, _??
dd _98, _99, _9A, _??, _??, _9D, _??, _??
dd _A0, _A1, _A2, _??, _A4, _A5, _A6, _??
dd _A8, _A9, _AA, _??, _AC, _AD, _AE, _??
dd _B0, _B1, _??, _??, _B4, _B5, _B6, _??
dd _B8, _B9, _BA, _??, _BC, _BD, _BE, _??
dd _C0, _C1, _??, _??, _C4, _C5, _C6, _??
dd _C8, _C9, _CA, _??, _CC, _CD, _CE, _??
dd _D0, _D1, _??, _??, _??, _D5, _D6, _??
dd _D8, _D9, _??, _??, _??, _DD, _DE, _??
dd _E0, _E1, _??, _??, _E4, _E5, _E6, _??
dd _E8, _E9, _??, _??, _EC, _ED, _EE, _??
dd _F0, _F1, _??, _??, _??, _F5, _F6, _??
dd _F8, _F9, _??, _??, _??, _FD, _FE, _??
; Number of cycles for each opcode.
abyOpcodeCycles db 7, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 0, 4, 6, 0
db 2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0
db 6, 6, 0, 0, 3, 3, 5, 0, 4, 2, 2, 0, 4, 4, 6, 0
db 2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0
db 13, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 3, 4, 6, 0
db 2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0
db 6, 6, 0, 0, 0, 3, 5, 0, 4, 2, 2, 0, 5, 4, 6, 0
db 2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0
db 0, 6, 0, 0, 3, 3, 3, 0, 2, 0, 2, 0, 4, 4, 4, 0
db 2, 6, 0, 0, 4, 4, 4, 0, 2, 5, 2, 0, 0, 5, 0, 0
db 2, 6, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2, 4, 4, 4, 2
db 2, 5, 2, 2, 4, 4, 4, 2, 2, 4, 2, 2, 4, 4, 4, 2
db 2, 6, 2, 2, 3, 3, 5, 2, 2, 2, 2, 2, 4, 4, 6, 2
db 2, 5, 2, 2, 2, 4, 6, 2, 2, 4, 2, 2, 2, 4, 7, 2
db 2, 6, 2, 2, 3, 3, 5, 2, 2, 2, 2, 2, 4, 4, 6, 2
db 2, 5, 2, 2, 2, 4, 6, 2, 2, 4, 2, 2, 2, 4, 7, 2
; The number of bytes for each opcode. The default length is one
; so the PC will increment if a bad opcode or a NOP is executed
abyOpcodeBytes db 0, 2, 1, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1, 3, 3, 1
db 2, 2, 1, 1, 1, 2, 2, 1, 1, 3, 1, 1, 1, 3, 3, 1
db 0, 2, 1, 1, 2, 2, 2, 1, 1, 2, 1, 1, 3, 3, 3, 1
db 2, 2, 1, 1, 1, 2, 2, 1, 1, 3, 1, 1, 1, 3, 3, 1
db 0, 2, 1, 1, 1, 2, 2, 1, 1, 2, 1, 1, 0, 3, 3, 1
db 2, 2, 1, 1, 1, 2, 2, 1, 1, 3, 1, 1, 1, 3, 3, 1
db 1, 2, 1, 1, 1, 2, 2, 1, 1, 2, 1, 1, 0, 3, 3, 1
db 2, 2, 1, 1, 1, 2, 2, 1, 1, 3, 1, 1, 1, 3, 3, 1
db 1, 2, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 3, 3, 3, 1
db 2, 2, 1, 1, 2, 2, 2, 1, 1, 3, 1, 1, 1, 3, 1, 1
db 2, 2, 2, 1, 2, 2, 2, 1, 1, 2, 1, 1, 3, 3, 3, 1
db 2, 2, 1, 1, 2, 2, 2, 1, 1, 3, 1, 1, 3, 3, 3, 1
db 2, 2, 1, 1, 2, 2, 2, 1, 1, 2, 1, 1, 3, 3, 3, 1
db 2, 2, 1, 1, 1, 2, 2, 1, 1, 3, 1, 1, 1, 3, 3, 1
db 2, 2, 1, 1, 2, 2, 2, 1, 1, 2, 1, 1, 3, 3, 3, 1
db 2, 2, 1, 1, 1, 2, 2, 1, 1, 3, 1, 1, 1, 3, 3, 1
; Used to set the zero and the sign flags.
; Just a lookup table for all the possible values.
abyZNTable db 02h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h
db 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h
db 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h
db 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h
db 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h
db 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h
db 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h
db 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h
db 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h
db 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h
db 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h
db 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h
db 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h
db 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h
db 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h
db 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h
.CODE
;******************************************************************************
;* *
;* Macros for reading/writing memory *
;* *
;******************************************************************************
GET_MEMORY_BYTE MACRO wParam:REQ
xor edx, edx
mov dx, wParam
push edx
call ?GetMemoryByte@@YGEG@Z
and eax, 000000FFh
ENDM
GET_MEMORY_POINTER MACRO wParam:REQ
xor edx, edx
mov dx, wParam
push edx
call ?GetMemoryPointer@@YGPAEG@Z
ENDM
READ_MEMORY MACRO wParam:REQ
movzx edx, wParam
push edx
call ?ReadMemory@@YGEG@Z
and eax, 000000FFh
ENDM
; NOTE: Since I don't know enough about writting macros to make
; sure that wParam isn't edx, you'll just have to make sure
; you never call this macro with edx as the wParam or it
; will not work right.
WRITE_MEMORY MACRO wParam:REQ, byData:REQ
movzx edx, byData
push edx
movzx edx, wParam
push edx
call ?WriteMemory@@YGXGE@Z
ENDM
; Push a byte onto the NES's stack.
PUSH_BYTE MACRO byData:REQ
; The stacks address space is 100h-1FFh
mov cl, CPU.S
mov ch, 1
WRITE_MEMORY cx, byData
dec CPU.S
ENDM
; Pop a byte from the NES's stack.
POP_BYTE MACRO byDest:REQ
; The stacks address space is 100h-1FFh
inc CPU.S
mov cl, CPU.S
mov ch, 1
GET_MEMORY_BYTE cx
mov byDest, al
ENDM
;******************************************************************************
;* *
;* Macro for setting/clearing the flags register on the 6502 *
;******************************************************************************
MODIFYFLAGS MACRO Sign, Overflow, Zero, Carry
; Clear all the flags if they are passed.
pushfd
IFNB <Sign>
and CPU.F, 01111111b
ENDIF
IFNB <Overflow>
and CPU.F, 10111111b
ENDIF
IFNB <Zero>
and CPU.F, 11111101b
ENDIF
IFNB <Carry>
and CPU.F, 11111110b
ENDIF
popfd
; Set the flags that are passed to the macro.
IFNB <Sign>
pushfd
sets dl
shl dl, 7
or CPU.F, dl
popfd
ENDIF
IFNB <Overflow>
pushfd
seto dl
shl dl, 6
or CPU.F, dl
popfd
ENDIF
IFNB <Zero>
pushfd
setz dl
shl dl, 1
or CPU.F, dl
popfd
ENDIF
IFNB <Carry>
pushfd
setc dl
or CPU.F, dl
popfd
ENDIF
ENDM
;******************************************************************************
;* *
;* Addressing mode macros *
;* *
;******************************************************************************
;1) Immediate
;
; In this mode the operand's value is given in the instruction itself. In
; assembly language this is indicated by "#" before the operand.
; eg. LDA #$0A - means "load the accumulator with the hex value 0A"
; In machine code different modes are indicated by different codes. So LDA
; would be translated into different codes depending on the addressing mode.
; In this mode, it is: $A9 $0A
GET_ADDR_IMMEDIATE MACRO
; Move the PC address up by one.
mov di, CPU.P
inc di
; Now get the memory byte to be used as immediate data.
GET_MEMORY_BYTE di
ENDM
;2 & 3) Absolute and Zero-page Absolute
; In these modes the operands address is given.
; eg. LDA $31F6 - (assembler)
; $AD $31F6 - (machine code)
; If the address is on zero page - i.e. any address where the high byte is
; 00 - only 1 byte is needed for the address. The processor automatically
; fills the 00 high byte.
; eg. LDA $F4
; $A5 $F4
; Note the different instruction codes for the different modes.
; Note also that for 2 byte addresses, the low byte is store first, eg.
; LDA $31F6 is stored as three bytes in memory, $AD $F6 $31.
; Zero-page absolute is usually just called zero-page.
GET_ADDR_ZEROPAGE MACRO
; Increment the PC address.
mov di, CPU.P
inc di
; Get the byte that is the address of the byte to be read.
GET_MEMORY_BYTE di
ENDM
GET_ADDR_ABSOLUTE MACRO
mov di, CPU.P
; Get the low address byte.
inc di
GET_MEMORY_BYTE di
mov bl, al
; Get the high address byte.
inc di
GET_MEMORY_BYTE di
mov bh, al
ENDM
; 4) Implied
; No operand addresses are required for this mode. They are implied by the
; instruction.
; eg. TAX - (transfer accumulator contents to X-register)
; $AA
;
; 5) Accumulator
; In this mode the instruction operates on data in the accumulator, so no
; operands are needed.
; eg. LSR - logical bit shift right
; $4A
; 6 & 7) Indexed and Zero-page Indexed
; In these modes the address given is added to the value in either the X or
; Y index register to give the actual address of the operand.
; eg. LDA $31F6, Y
; $D9 $31F6
; LDA $31F6, X
; $DD $31F6
; Note that the different operation codes determine the index register used.
; In the zero-page version, you should note that the X and Y registers are
; not interchangeable. Most instructions which can be used with zero-page
; indexing do so with X only.
; eg. LDA $20, X
; $B5 $20
GET_ADDR_ZEROPAGE_X MACRO
; Get the normal zero page address
GET_ADDR_ZEROPAGE
; Now add the X register to the address.
add al, CPU.X
ENDM
GET_ADDR_ZEROPAGE_Y MACRO
; Get the normal zero page address
GET_ADDR_ZEROPAGE
; Now add the Y register to the address.
add al, CPU.Y
ENDM
GET_ADDR_ABSOLUTE_X MACRO
; Get the normal absolute page address.
GET_ADDR_ABSOLUTE
; Now add the X register to the address.
xor ax, ax
mov al, CPU.X
add bx, ax
ENDM
GET_ADDR_ABSOLUTE_Y MACRO
; Get the normal absolute page address.
GET_ADDR_ABSOLUTE
; Now add the Y register to the address.
xor ax, ax
mov al, CPU.Y
add bx, ax
ENDM
; 8) Indirect
; This mode applies only to the JMP instruction - JuMP to new location. It is
; indicated by parenthesis around the operand. The operand is the address of
; the bytes whose value is the new location.
; eg. JMP ($215F)
; Assume the following - byte value
; $215F $76
; $2160 $30
; This instruction takes the value of bytes $215F, $2160 and uses that as the
; address to jump to - i.e. $3076 (remember that addresses are stored with
; low byte first).
GET_ADDR_INDIRECT MACRO
; Get the address of the value to set the PC to.
GET_ADDR_ABSOLUTE
; Get the value to store in the PC register.
mov di, bx
GET_MEMORY_BYTE di
mov bl, al
inc di
GET_MEMORY_BYTE di
mov bh, al
ENDM
; 9) Pre-indexed indirect
; In this mode a zer0-page address is added to the contents of the X-register
; to give the address of the bytes holding the address of the operand. The
; indirection is indicated by parenthesis in assembly language.
; eg. LDA ($3E, X)
; $A1 $3E
; Assume the following - byte value
; X-reg. $05
; $0043 $15
; $0044 $24
; $2415 $6E
;
; Then the instruction is executed by:
; (i) adding $3E and $05 = $0043
; (ii) getting address contained in bytes $0043, $0044 = $2415
; (iii) loading contents of $2415 - i.e. $6E - into accumulator
;
; Note a) When adding the 1-byte address and the X-register, wrap around
; addition is used - i.e. the sum is always a zero-page address.
; eg. FF + 2 = 0001 not 0101 as you might expect.
; DON'T FORGET THIS WHEN EMULATING THIS MODE.
; b) Only the X register is used in this mode.
GET_ADDR_PREINDEXED MACRO
; The first part is just like zero-paged x addressing,
; but lets save the address for later use.
GET_ADDR_ZEROPAGE_X
mov di, ax
; Now that we have the address of our address, we can
; get our real address. (thats confusing...hehe)
GET_MEMORY_BYTE di
mov bl, al
inc di
GET_MEMORY_BYTE di
mov bh, al
ENDM
; 10) Post-indexed indirect
; In this mode the contents of a zero-page address (and the following byte)
; give the indirect addressm which is added to the contents of the Y-register
; to yield the actual address of the operand. Again, inassembly language,
; the instruction is indicated by parenthesis.
; eg. LDA ($4C), Y
; Note that the parenthesis are only around the 2nd byte of the instruction
; since it is the part that does the indirection.
; Assume the following - byte value
; $004C $00
; $004D $21
; Y-reg. $05
; $2105 $6D
; Then the instruction above executes by:
; (i) getting the address in bytes $4C, $4D = $2100
; (ii) adding the contents of the Y-register = $2105
; (111) loading the contents of the byte $2105 - i.e. $6D into the
; accumulator.
; Note: only the Y-register is used in this mode.
GET_ADDR_POSTINDEXED MACRO
; Move to the next byte.
mov di, CPU.P
inc di
; Get the address of our first address.
GET_MEMORY_BYTE di
mov di, ax
; Now get the address to add the y register to.
GET_MEMORY_BYTE di
mov bl, al
inc di
GET_MEMORY_BYTE di
mov bh, al
; Finally add the contents of the y register to the address.
xor ax, ax
mov al, CPU.Y
add bx, ax
ENDM
; 11) Relative
; This mode is used with Branch-on-Condition instructions. It is probably
; the mode you will use most often. A 1 byte value is added to the program
; counter, and the program continues execution from that address. The 1
; byte number is treated as a signed number - i.e. if bit 7 is 1, the number
; given byt bits 0-6 is negative; if bit 7 is 0, the number is positive. This
; enables a branch displacement of up to 127 bytes in either direction.
; eg bit no. 7 6 5 4 3 2 1 0 signed value unsigned value
; value 1 0 1 0 0 1 1 1 -39 $A7
; value 0 0 1 0 0 1 1 1 +39 $27
; Instruction example:
; BEQ $A7
; $F0 $A7
; This instruction will check the zero status bit. If it is set, 39 decimal
; will be subtracted from the program counter and execution continues from
; that address. If the zero status bit is not set, execution continues from
; the following instruction.
; Notes: a) The program counter points to the start of the instruction
; after the branch instruction before the branch displacement is added.
; Remember to take this into account when calculating displacements.
; b) Branch-on-condition instructions work by checking the relevant
; status bits in the status register. Make sure that they have been set or
; unset as you want them. This is often done using a CMP instruction.
; c) If you find you need to branch further than 127 bytes, use the
; opposite branch-on-condition and a JMP.
GET_ADDR_RELATIVE MACRO
; Move the PC address up by one.
mov di, CPU.P
inc di
; Now get the memory byte to be used as a relative address.
GET_MEMORY_BYTE di
ENDM
;******************************************************************************
;* *
;* Instruction macros *
;* *
;******************************************************************************
; ********************************************************************
; * ADC. Opcodes 69, 65, 75, 6D, 7D, 79, 61, 71 *
; ********************************************************************
CPU_ADC MACRO Operand:REQ
; Move the NES's carry flag into the real cpu's carry flag.
mov dl, CPU.F
shr dl, 1
; Now add the operand to the A register with the carry flag.
adc CPU.A, Operand
MODIFYFLAGS S, V, Z, C
ENDM
; ********************************************************************
; * AND. Opcodes 29, 25, 35, 2D, 3D, 39, 21, 31 *
; ********************************************************************
CPU_AND MACRO Operand:REQ
; Perform the operation.
and CPU.A, Operand
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -