MOS 6502 CPU 에뮬레이션


29

이것은 또한 여기에있는 Intel 8086 도전에서 영감을 얻었지만 6502 도전도 흥미로울 것이라고 생각했습니다.

도전

나는 이것이 결과를 보는 것이 재미있을 것이라고 생각했다. 이것은 분명히 스펙트럼의 고급 측면을 향하고 있습니다. 문제는 자신의 6502 CPU 에뮬레이터를 작성하는 것입니다. 물론 이것은 명령 세트와 인코딩 형식을 이해하는 것과 관련이 있습니다. 리소스는이 하단에 연결되어 있습니다. 6502는 가장 쉬운 실제 프로세서 중 하나입니다. 이 도전의 목적을 위해, 당신이 원하지 않는다면 사이클 타이밍에 대해 걱정할 필요는 없지만 항상 포함시키는 것이 좋습니다!

다른 코드는 복사하지 마십시오 !! 물론 다른 에뮬레이터를 들여다 보면 이해할 수 있지만 복사 및 붙여 넣기는 할 수 없습니다! :)

코드가 작동하면 원하는 경우 언제든지 추가 마일리지를 사용하여 Apple II 에뮬레이터 또는 NES, C64, VIC-20 또는 다른 수십억 개의 오래된 6502 기반 시스템으로 바꿀 수 있습니다.

에뮬레이터 테스트

소스 코드를 찾은 6502 테스트 스위트를 컴파일했습니다. http://code.google.com/p/hmc-6502/source/browse/trunk/emu/testvectors/AllSuiteA.asm

내 컴파일 된 버전은 여기에서 다운로드 할 수 있습니다 : http://rubbermallet.org/AllSuiteA.zip

48KB 바이너리를 $ 4000의 에뮬레이터 메모리 공간에로드하면 16KB의 읽기 / 쓰기 RAM이 그 아래에 남습니다. 테스트가 완료되면 CPU가 통과 한 경우 주소 $ 0210의 값은 $ FF 여야합니다. 프로그램 카운터 (PC)가 $ 45C0에 도달하면 테스트가 완료되었음을 알 수 있습니다.

다른 테스트도 여기에서 볼 수 있습니다 : http://visual6502.org/wiki/index.php?title=6502TestPrograms

대화식으로하는 것

CPU가 작동하면 테스트 출력을 응시하는 것보다 더 재미있는 것을 원할 것입니다! 6502 용 Enhanced BASIC의 ROM 이미지를 컴파일했습니다. 16KB이므로 에뮬레이트 된 메모리 공간 $ C000에로드하고 가상 6502를 재설정하고 실행을 시작해야합니다.

ehbasic.bin이 포함 된이 ZIP을 다운로드하십시오. http://rubbermallet.org/ehbasic.zip

EhBASIC이 입 / 출력을 처리하는 방식은 매우 간단합니다. 콘솔에 문자를 쓰려면 바이트를 메모리 위치 $ F001에 씁니다. 따라서 에뮬레이터에서 6502가 해당 위치에 쓰려고하면 printf ( "% c", value); 또는 당신이 원하는 다른. (이 도전은 물론 C로 제한되지 않습니다)

콘솔에서 입력되는 문자를 폴링 할 때도 비슷합니다. 메모리 위치 $ F004에서 계속 읽습니다. 여기서 키보드에서 다음 ASCII 문자 값을 읽어야합니다. 더 이상 읽을 입력이 없으면 값 0을 리턴해야합니다.

EhBASIC은 0이 될 때까지 해당 위치의 값을 폴링하여 바이트가 유효한 키보드 입력임을 알립니다. 따라서 더 이상 읽을 입력이 없으면 에뮬레이터에서 0을 반환해야합니다. EhBASIC은 입력을 찾을 때 다음 유효한 키까지 회전합니다.

마지막 키 값을 읽은 후 해당 값을 0으로 지우지 않으면 키를 누른 것처럼 반복하여 값을 반복하므로 올바르게 수행하십시오!

에뮬레이터가 올바르게 작동하면 ROM 이미지를 실행할 때 다음과 같이 콘솔에 인쇄됩니다.

6502 EhBASIC [C]old/[W]arm ?

C를 누른 다음 enter를 누르면 다음이 표시됩니다.

Memory size ?

31999 Bytes free

Enhanced BASIC 2.22

Ready

사용 가능한 바이트 수는 다를 수 있지만 에뮬레이터에서는 쓰기 가능한 메모리 영역을 32KB의 캡으로 제한했습니다. ROM이 시작되는 곳까지 48 KB 마크까지 올라갈 수 있습니다.

6502 CPU 리소스 링크

다음은 작업하기에 충분한 정보를 제공하는 몇 가지 리소스입니다.

http://www.obelisk.demon.co.uk/6502/instructions.html

http://www.e-tradition.net/bytes/6502/6502_instruction_set.html

http://www.llx.com/~nparker/a2/opcodes.html <-여기에는 매우 흥미로운 정보가 있습니다

http://en.wikipedia.org/wiki/MOS_Technology_6502

궁금한 점이 있거나 추가 기술 정보가 필요하면 언제든지 문의하십시오. 웹에는 수많은 다른 6502 정보도 있습니다. 구글은 당신의 친구입니다!


이 문장의 차이로 보인다 "존재하지 않는 경우 에는 읽기 더 입력, 그것은 0의 값을 반환해야이이 때까지 폴링을 유지하기 위해 EhBASIC됩니다. -zero."
Igby Largeman 2013 년

어, 내 실수 나는 그것을 잘 설명하지 않았다. EhBASIC이 0이 될 때까지 해당 위치의 값을 폴링하여 바이트가 유효한 키보드 입력임을 알 수 있다고 설명했습니다. 따라서 더 이상 읽을 입력이 없으면 에뮬레이터에서 0을 반환해야합니다. 편집하겠습니다.
Mike C

6502 코어를 결국 게시 할 수도 있지만 먼저 다른 항목의 항목을 기다릴 것입니다. 잘만되면 누군가가이 도전에 도전합니다. 8086 챌린지에 대한 해결책은 꽤 많았으므로 여기에 충분한 사람들이 있습니다. 8086은 훨씬 더 어렵다!
Mike C

1
나는 경쟁적인 의미는 아니지만 이것을 시도하고 싶습니다. 나를위한 문제는 시간을 찾는 것입니다. 에뮬레이터를 철저히 연습하고 8086 챌린지에서 수행 한 것과 비슷한 쉽게 검증 가능한 출력을 생성하는 다른 테스트 프로그램을 제공 할 수 있다면 좋을 것 같습니다.
Igby Largeman

2
누가이기는지 어떻게 알 수 있습니까? (승자가되어야 함)

답변:


22

계속해서 내 구현을 게시 할 것이라고 생각했습니다. 완벽하게 ungolfed이지만 전체 구현입니다.

  • C의 668 줄 (빈 줄이나 주석 만있는 줄은 제외)
  • 문서화되지 않은 모든 지침을 지원합니다.
  • BCD를 지원합니다.
  • CPU 클록 사이클 타이밍. (특정 페이지 경계 랩에 ​​대한 조정 포함)
  • 단일 단계 또는 틱 수를 지정하여 명령을 실행할 수 있습니다.
  • 모든 명령이 실행 된 후 호출 할 외부 함수 후크를 지원합니다. 원래 NES 에뮬레이터를위한 것이기 때문에 오디오 타이밍에 사용했습니다.
/ * Fake6502 CPU 에뮬레이터 코어 v1.1 *******************
 * (c) 2011-2013 Mike Chambers *
 ******************************************************** *** /

#include <stdio.h>
#include <stdint.h>

// 외부 제공 함수
extern uint8_t read6502 (uint16_t 주소);
extern void write6502 (uint16_t 주소, uint8_t 값);

// 6502는 정의
#define UNDOCUMENTED // 이가 정의되면 문서화되지 않은 opcode가 처리됩니다.
                     // 그렇지 않으면 단순히 NOP로 취급됩니다.

// # define NES_CPU // 이것이 정의 될 때, 이진 코드 10 진수 (BCD)
                     // 상태 플래그는 ADC 및 SBC에서 인정하지 않습니다. 2A03
                     // 닌텐도 엔터테인먼트 시스템의 CPU는
                     // BCD 작업을 지원합니다.

#define FLAG_CARRY 0x01
#define FLAG_ZERO 0x02
#define FLAG_INTERRUPT 0x04
#define FLAG_DECIMAL 0x08
#define FLAG_BREAK 0x10
#define FLAG_CONSTANT 0x20
#define FLAG_OVERFLOW 0x40
#define FLAG_SIGN 0x80

#BASE_STACK 0x100 정의

# saveaccum (n) a = (uint8_t) ((n) & 0x00FF) 정의


// 플래그 수정 자 매크로
#define setcarry () status | = FLAG_CARRY
#clearcarry () 상태 정의 & = (~ FLAG_CARRY)
#define setzero () status | = FLAG_ZERO
#clearzero () 상태 정의 & = (~ FLAG_ZERO)
#define setinterrupt () status | = FLAG_INTERRUPT
#define clearinterrupt () 상태 & = (~ FLAG_INTERRUPT)
#define setdecimal () 상태 | = FLAG_DECIMAL
#define cleardecimal () 상태 & = (~ FLAG_DECIMAL)
#define setoverflow () 상태 | = FLAG_OVERFLOW
#define clearoverflow () 상태 & = (~ FLAG_OVERFLOW)
#define setsign () status | = FLAG_SIGN
#define clearsign () 상태 & = (~ FLAG_SIGN)


// 플래그 계산 매크로
#zerocalc (n) 정의 {\
    if ((n) & 0x00FF) clearzero (); \
        그렇지 않으면 setzero (); \
}

#define signcalc (n) {\
    if ((n) & 0x0080) setsign (); \
        그렇지 않으면 clearsign (); \
}

# carrycalc (n) 정의 {\
    if ((n) & 0xFF00) setcarry (); \
        그렇지 않으면 clearcarry (); \
}

# 정의 overflowcalc (n, m, o) {/ * n = 결과, m = 누산기, o = 메모리 * / \
    if (((n) ^ (uint16_t) (m)) & ((n) ^ (o)) & 0x0080) setoverflow (); \
        그렇지 않으면 clearoverflow (); \
}


// 6502 CPU 레지스터
uint16_t pc;
uint8_t sp, a, x, y, 상태 = FLAG_CONSTANT;


// 도우미 변수
uint64_t 명령 = 0; // 실행 된 총 명령 추적
uint32_t clockticks6502 = 0, clockgoal6502 = 0;
uint16_t oldpc, ea, reladdr, 가치, 결과;
uint8_t opcode, oldstatus;

// 다양한 다른 함수에서 사용되는 몇 가지 일반적인 함수
void push16 (uint16_t pushval) {
    쓰기 6502 (BASE_STACK + sp, (pushval >> 8) & 0xFF);
    write6502 (BASE_STACK + ((sp -1) & 0xFF), 푸시 발 & 0xFF);
    sp-= 2;
}

void push8 (uint8_t pushval) {
    write6502 (BASE_STACK + sp--, 푸시 발);
}

uint16_t pull16 () {
    uint16_t temp16;
    temp16 = 읽기 6502 (BASE_STACK + ((sp + 1) & 0xFF)) | ((uint16_t) read6502 (BASE_STACK + ((sp + 2) & 0xFF)) << 8);
    sp + = 2;
    귀국 (temp16);
}

uint8_t pull8 () {
    리턴 (read6502 (BASE_STACK + ++ sp));
}

void reset6502 () {
    pc = (uint16_t) read6502 (0xFFFC) | ((uint16_t) read6502 (0xFFFD) << 8);
    a = 0;
    x = 0;
    y = 0;
    sp = 0xFD;
    상태 | = FLAG_CONSTANT;
}


정적 보이드 (* addrtable [256]) ();
정적 보이드 (* optable [256]) ();
uint8_t 패널티 팝, 패널티 애드 러;

// 주소 모드 기능, 유효 주소 계산
static void imp () {// 암시 적
}

static void acc () {// 누적 기
}

static void imm () {// 즉시
    ea = pc ++;
}

static void zp () {// 제로 페이지
    ea = (uint16_t) read6502 ((uint16_t) pc ++);
}

static void zpx () {// 제로 페이지, X
    ea = ((uint16_t) read6502 ((uint16_t) pc ++) + (uint16_t) x) & 0xFF; // 제로 페이지 랩 어라운드
}

static void zpy () {// 제로 페이지, Y
    ea = ((uint16_t) read6502 ((uint16_t) pc ++) + (uint16_t) y) & 0xFF; // 제로 페이지 랩 어라운드
}

static void rel () {// 분기 연산에 상대적 (8 비트 즉시 값, 부호 확장)
    reladdr = (uint16_t) 읽기 6502 (pc ++);
    if (reladdr & 0x80) reladdr | = 0xFF00;
}

static void abso () {// 절대
    ea = (uint16_t) read6502 (pc) | ((uint16_t) read6502 (pc + 1) << 8);
    pc + = 2;
}

static void absx () {// 절대, X
    uint16_t 시작 페이지;
    ea = ((uint16_t) read6502 (pc) | ((uint16_t) read6502 (pc + 1) << 8));
    시작 페이지 = ea & 0xFF00;
    ea + = (uint16_t) x;

    if (startpage! = (ea & 0xFF00)) {// 일부 opcode에서 페이지 교차를위한 한 번의주기 penlty
        페널티 애드 터 = 1;
    }

    pc + = 2;
}

static void absy () {// 절대, Y
    uint16_t 시작 페이지;
    ea = ((uint16_t) read6502 (pc) | ((uint16_t) read6502 (pc + 1) << 8));
    시작 페이지 = ea & 0xFF00;
    ea + = (uint16_t) y;

    if (startpage! = (ea & 0xFF00)) {// 일부 opcode에서 페이지 교차를위한 한 번의주기 penlty
        페널티 애드 터 = 1;
    }

    pc + = 2;
}

static void ind () {// 간접
    uint16_t eahelp, eahelp2;
    eahelp = (uint16_t) read6502 (pc) | (uint16_t) ((uint16_t) read6502 (pc + 1) << 8);
    eahelp2 = (eahelp & 0xFF00) | ((도움 + 1) & 0x00FF); // 6502 페이지 경계 랩 어라운드 버그를 복제
    ea = (uint16_t) read6502 (eahelp) | ((uint16_t) read6502 (eahelp2) << 8);
    pc + = 2;
}

static void indx () {// (간접, X)
    uint16_t 도움;
    eahelp = (uint16_t) (((uint16_t) read6502 (pc ++) + (uint16_t) x) & 0xFF); // 테이블 포인터를위한 제로 페이지 랩 어라운드
    ea = (uint16_t) read6502 (eahelp & 0x00FF) | ((uint16_t) read6502 ((eahelp + 1) & 0x00FF) << 8);
}

static void indy () {// (간접), Y
    uint16_t eahelp, eahelp2, 시작 페이지;
    eahelp = (uint16_t) 읽기 6502 (pc ++);
    eahelp2 = (eahelp & 0xFF00) | ((도움 + 1) & 0x00FF); // 제로 페이지 랩 어라운드
    ea = (uint16_t) read6502 (eahelp) | ((uint16_t) read6502 (eahelp2) << 8);
    시작 페이지 = ea & 0xFF00;
    ea + = (uint16_t) y;

    if (startpage! = (ea & 0xFF00)) {// 일부 opcode에서 페이지 교차를위한 한 번의주기 penlty
        페널티 애드 터 = 1;
    }
}

정적 uint16_t getvalue () {
    if (addrtable [opcode] == acc) return ((uint16_t) a);
        그렇지 않으면 return ((uint16_t) read6502 (ea));
}

정적 void putvalue (uint16_t saveval) {
    만약 (addrtable [opcode] == acc) a = (uint8_t) (saveval & 0x00FF);
        그렇지 않으면 write6502 (ea, (saveval & 0x00FF));
}


// 명령 핸들러 함수
정적 무효 adc () {
    패널티 = 1;
    값 = getvalue ();
    결과 = (uint16_t) a + 값 + (uint16_t) (상태 및 FLAG_CARRY);

    캐리 칼크 (결과);
    제로 칼크 (결과);
    overflowcalc (결과, a, 값);
    signcalc (결과);

    #ifndef NES_CPU
    if (상태 및 FLAG_DECIMAL) {
        clearcarry ();

        if ((a & 0x0F)> 0x09) {
            + = 0x06;
        }
        if ((a & 0xF0)> 0x90) {
            + = 0x60;
            setcarry ();
        }

        클락 틱스 6502 ++;
    }
    #endif

    saveaccum (결과);
}

정적 무효 and () {
    패널티 = 1;
    값 = getvalue ();
    결과 = (uint16_t) a & 값;

    제로 칼크 (결과);
    signcalc (결과);

    saveaccum (결과);
}

정적 무효 asl () {
    값 = getvalue ();
    결과 = 값 << 1;

    캐리 칼크 (결과);
    제로 칼크 (결과);
    signcalc (결과);

    풋 가치 (결과);
}

정적 무효 bcc () {
    if ((상태 및 FLAG_CARRY) == 0) {
        oldpc = pc;
        pc + = 렐라 드르;
        if ((oldpc & 0xFF00)! = (pc & 0xFF00)) clockticks6502 + = 2; // 점프가 페이지 경계를 넘 었는지 확인
            그렇지 않으면 clockticks6502 ++;
    }
}

정적 무효 bcs () {
    if ((상태 및 FLAG_CARRY) == FLAG_CARRY) {
        oldpc = pc;
        pc + = 렐라 드르;
        if ((oldpc & 0xFF00)! = (pc & 0xFF00)) clockticks6502 + = 2; // 점프가 페이지 경계를 넘 었는지 확인
            그렇지 않으면 clockticks6502 ++;
    }
}

정적 무효 beq () {
    if ((status & FLAG_ZERO) == FLAG_ZERO) {
        oldpc = pc;
        pc + = 렐라 드르;
        if ((oldpc & 0xFF00)! = (pc & 0xFF00)) clockticks6502 + = 2; // 점프가 페이지 경계를 넘 었는지 확인
            그렇지 않으면 clockticks6502 ++;
    }
}

정적 무효 비트 () {
    값 = getvalue ();
    결과 = (uint16_t) a & 값;

    제로 칼크 (결과);
    상태 = (상태 및 0x3F) | (uint8_t) (값 및 0xC0);
}

정적 무효 bmi () {
    if ((status & FLAG_SIGN) == FLAG_SIGN) {
        oldpc = pc;
        pc + = 렐라 드르;
        if ((oldpc & 0xFF00)! = (pc & 0xFF00)) clockticks6502 + = 2; // 점프가 페이지 경계를 넘 었는지 확인
            그렇지 않으면 clockticks6502 ++;
    }
}

정적 무효 bne () {
    if ((상태 및 FLAG_ZERO) == 0) {
        oldpc = pc;
        pc + = 렐라 드르;
        if ((oldpc & 0xFF00)! = (pc & 0xFF00)) clockticks6502 + = 2; // 점프가 페이지 경계를 넘 었는지 확인
            그렇지 않으면 clockticks6502 ++;
    }
}

정적 무효 bpl () {
    if ((상태 및 FLAG_SIGN) == 0) {
        oldpc = pc;
        pc + = 렐라 드르;
        if ((oldpc & 0xFF00)! = (pc & 0xFF00)) clockticks6502 + = 2; // 점프가 페이지 경계를 넘 었는지 확인
            그렇지 않으면 clockticks6502 ++;
    }
}

정적 무효 brk () {
    pc ++;
    push16 (pc); // 다음 명령어 주소를 스택에 푸시
    push8 (상태 | FLAG_BREAK); // 스택 상태로 CPU 상태를 푸시
    setinterrupt (); // 인터럽트 플래그 설정
    pc = (uint16_t) read6502 (0xFFFE) | ((uint16_t) read6502 (0xFFFF) << 8);
}

정적 무효 bvc () {
    if ((상태 및 FLAG_OVERFLOW) == 0) {
        oldpc = pc;
        pc + = 렐라 드르;
        if ((oldpc & 0xFF00)! = (pc & 0xFF00)) clockticks6502 + = 2; // 점프가 페이지 경계를 넘 었는지 확인
            그렇지 않으면 clockticks6502 ++;
    }
}

정적 무효 bvs () {
    if ((status & FLAG_OVERFLOW) == FLAG_OVERFLOW) {
        oldpc = pc;
        pc + = 렐라 드르;
        if ((oldpc & 0xFF00)! = (pc & 0xFF00)) clockticks6502 + = 2; // 점프가 페이지 경계를 넘 었는지 확인
            그렇지 않으면 clockticks6502 ++;
    }
}

정적 무효 clc () {
    clearcarry ();
}

정적 무효 cld () {
    cleardecimal ();
}

정적 무효 cli () {
    clearinterrupt ();
}

정적 무효 clv () {
    clearoverflow ();
}

정적 무효 cmp () {
    패널티 = 1;
    값 = getvalue ();
    결과 = (uint16_t) a-값;

    (a> = (uint8_t) (값 & 0x00FF)) ifcarry () 인 경우;
        그렇지 않으면 clearcarry ();
    if (a == (uint8_t) (value & 0x00FF)) setzero ();
        그렇지 않으면 clearzero ();
    signcalc (결과);
}

정적 무효 cpx () {
    값 = getvalue ();
    결과 = (uint16_t) x-값;

    만약 (x> = (uint8_t) (value & 0x00FF))이면 setcarry ();
        그렇지 않으면 clearcarry ();
    if (x == (uint8_t) (value & 0x00FF)) setzero ();
        그렇지 않으면 clearzero ();
    signcalc (결과);
}

정적 무효 cpy () {
    값 = getvalue ();
    결과 = (uint16_t) y-값;

    (y> = (uint8_t) (값 & 0x00FF))이면 setcarry ();
        그렇지 않으면 clearcarry ();
    if (y == (uint8_t) (값 & 0x00FF)) setzero ();
        그렇지 않으면 clearzero ();
    signcalc (결과);
}

정적 무효 dec () {
    값 = getvalue ();
    결과 = 값-1;

    제로 칼크 (결과);
    signcalc (결과);

    풋 가치 (결과);
}

정적 무효 dex () {
    엑스--;

    제로 칼크 (x);
    signcalc (x);
}

정적 무효 dey () {
    와이--;

    제로 칼크 (y);
    signcalc (y);
}

정적 무효 eor () {
    패널티 = 1;
    값 = getvalue ();
    결과 = (uint16_t) a ^ 값;

    제로 칼크 (결과);
    signcalc (결과);

    saveaccum (결과);
}

정적 무효 inc () {
    값 = getvalue ();
    결과 = 값 + 1;

    제로 칼크 (결과);
    signcalc (결과);

    풋 가치 (결과);
}

정적 무효 inx () {
    x ++;

    제로 칼크 (x);
    signcalc (x);
}

정적 무효 iny () {
    y ++;

    제로 칼크 (y);
    signcalc (y);
}

정적 무효 jmp () {
    pc = ea;
}

정적 무효 jsr () {
    push16 (pc-1);
    pc = ea;
}

정적 무효 lda () {
    패널티 = 1;
    값 = getvalue ();
    a = (uint8_t) (값 및 0x00FF);

    제로 칼크 (a);
    signcalc (a);
}

정적 무효 ldx () {
    패널티 = 1;
    값 = getvalue ();
    x = (uint8_t) (값 및 0x00FF);

    제로 칼크 (x);
    signcalc (x);
}

정적 무효 ldy () {
    패널티 = 1;
    값 = getvalue ();
    y = (uint8_t) (값 및 0x00FF);

    제로 칼크 (y);
    signcalc (y);
}

정적 무효 lsr () {
    값 = getvalue ();
    결과 = 값 >> 1;

    if (value & 1) setcarry ();
        그렇지 않으면 clearcarry ();
    제로 칼크 (결과);
    signcalc (결과);

    풋 가치 (결과);
}

정적 무효 nop () {
    스위치 (opcode) {
        사례 0x1C :
        사례 0x3C :
        사례 0x5C :
        사례 0x7C :
        사례 0xDC :
        사례 0xFC :
            패널티 = 1;
            단절;
    }
}

정적 무효 ora () {
    패널티 = 1;
    값 = getvalue ();
    결과 = (uint16_t) a | 값;

    제로 칼크 (결과);
    signcalc (결과);

    saveaccum (결과);
}

정적 무효 pha () {
    푸시 8 (a);
}

정적 무효 php () {
    push8 (상태 | FLAG_BREAK);
}

정적 무효 pla () {
    a = pull8 ();

    제로 칼크 (a);
    signcalc (a);
}

정적 무효 plp () {
    상태 = pull8 () | FLAG_CONSTANT;
}

정적 무효 rol () {
    값 = getvalue ();
    결과 = (값 << 1) | (상태 및 FLAG_CARRY);

    캐리 칼크 (결과);
    제로 칼크 (결과);
    signcalc (결과);

    풋 가치 (결과);
}

정적 무효 ror () {
    값 = getvalue ();
    결과 = (값 >> 1) | ((상태 및 FLAG_CARRY) << 7);

    if (value & 1) setcarry ();
        그렇지 않으면 clearcarry ();
    제로 칼크 (결과);
    signcalc (결과);

    풋 가치 (결과);
}

정적 무효 rti () {
    상태 = pull8 ();
    값 = pull16 ();
    pc = 값;
}

정적 무효 rts () {
    값 = pull16 ();
    pc = 값 + 1;
}

정적 무효 sbc () {
    패널티 = 1;
    값 = getvalue () ^ 0x00FF;
    결과 = (uint16_t) a + 값 + (uint16_t) (상태 및 FLAG_CARRY);

    캐리 칼크 (결과);
    제로 칼크 (결과);
    overflowcalc (결과, a, 값);
    signcalc (결과);

    #ifndef NES_CPU
    if (상태 및 FLAG_DECIMAL) {
        clearcarry ();

        -= 0x66;
        if ((a & 0x0F)> 0x09) {
            + = 0x06;
        }
        if ((a & 0xF0)> 0x90) {
            + = 0x60;
            setcarry ();
        }

        클락 틱스 6502 ++;
    }
    #endif

    saveaccum (결과);
}

정적 무효 sec () {
    setcarry ();
}

정적 무효 sed () {
    setdecimal ();
}

정적 무효 sei () {
    setinterrupt ();
}

정적 무효 sta () {
    풋 가치 (a);
}

정적 무효 stx () {
    putvalue (x);
}

정적 void sty () {
    풋 가치 (y);
}

정적 무효 세금 () {
    x = a;

    제로 칼크 (x);
    signcalc (x);
}

정적 무효 tay () {
    y = a;

    제로 칼크 (y);
    signcalc (y);
}

정적 무효 tsx () {
    x = sp;

    제로 칼크 (x);
    signcalc (x);
}

정적 void txa () {
    a = x;

    제로 칼크 (a);
    signcalc (a);
}

정적 무효 txs () {
    sp = x;
}

정적 무효 tya () {
    a = y;

    제로 칼크 (a);
    signcalc (a);
}

// 언급되지 않은 지침
#ifdef 문서화되지 않은
    정적 무효 lax () {
        lda ();
        ldx ();
    }

    정적 무효 색소폰 () {
        sta ();
        stx ();
        putvalue (a & x);
        if (penaltyop && 패널티 주소) clockticks6502--;
    }

    정적 무효 dcp () {
        dec ();
        cmp ();
        if (penaltyop && 패널티 주소) clockticks6502--;
    }

    정적 무효 isb () {
        inc ();
        sbc ();
        if (penaltyop && 패널티 주소) clockticks6502--;
    }

    정적 무효 slo () {
        asl ();
        오라 ();
        if (penaltyop && 패널티 주소) clockticks6502--;
    }

    정적 무효 rla () {
        rol ();
        과();
        if (penaltyop && 패널티 주소) clockticks6502--;
    }

    정적 무효 sre () {
        lsr ();
        eor ();
        if (penaltyop && 패널티 주소) clockticks6502--;
    }

    정적 무효 rra () {
        ror ();
        adc ();
        if (penaltyop && 패널티 주소) clockticks6502--;
    }
#그밖에
    # 정의 lax nop
    # 색소폰 nop 정의
    # dcp nop 정의
    # isb nop 정의
    #slo nop 정의
    #rla nop 정의
    #sre nop 정의
    # 라피 노프 정의
#endif


정적 무효 (* addrtable [256]) () = {
/ * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | 전자 | F | * /
/ * 0 * / imp, indx, imp, indx, zp, zp, zp, zp, imp, imm, acc, imm, abso, abso, abso, abso, / * 0 * /
/ * 1 * / rel, indy, imp, indy, zpx, zpx, zpx, zpx, imp, absy, imp, absy, absx, absx, absx, absx, / * 1 * /
/ * 2 * / abso, indx, imp, indx, zp, zp, zp, zp, imp, imm, acc, imm, abso, abso, abso, abso, / * 2 * /
/ * 3 * / rel, indy, imp, indy, zpx, zpx, zpx, zpx, imp, absy, imp, absy, absx, absx, absx, absx, / * 3 * /
/ * 4 * / imp, indx, imp, indx, zp, zp, zp, zp, imp, imm, acc, imm, abso, abso, abso, abso, / * 4 * /
/ * 5 * / rel, indy, imp, indy, zpx, zpx, zpx, zpx, imp, absy, imp, absy, absx, absx, absx, absx, / * 5 * /
/ * 6 * / imp, indx, imp, indx, zp, zp, zp, zp, imp, imm, acc, imm, ind, abso, abso, abso, / * 6 * /
/ * 7 * / rel, indy, imp, indy, zpx, zpx, zpx, zpx, imp, absy, imp, absy, absx, absx, absx, absx, / * 7 * /
/ * 8 * / imm, indx, imm, indx, zp, zp, zp, zp, imp, imm, imp, imm, abso, abso, abso, abso, / * 8 * /
/ * 9 * / rel, indy, imp, indy, zpx, zpx, zpy, zpy, imp, absy, imp, absy, absx, absx, absy, absy, / * 9 * /
/ * A * / imm, indx, imm, indx, zp, zp, zp, zp, imp, imm, imp, imm, abso, abso, abso, abso, / * A * /
/ * B * / rel, indy, imp, indy, zpx, zpx, zpy, zpy, imp, absy, imp, absy, absx, absx, absy, absy, / * B * /
/ * C * / imm, indx, imm, indx, zp, zp, zp, zp, imp, imm, imp, imm, abso, abso, abso, abso, / * C * /
/ * D * / rel, indy, imp, indy, zpx, zpx, zpx, zpx, imp, absy, imp, absy, absx, absx, absx, absx, / * D * /
/ * E * / imm, indx, imm, indx, zp, zp, zp, zp, imp, imm, imp, imm, abso, abso, abso, abso, / * E * /
/ * F * / rel, indy, imp, indy, zpx, zpx, zpx, zpx, imp, absy, imp, absy, absx, absx, absx, absx / * F * /
};

정적 void (* optable [256]) () = {
/ * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | 전자 | F | * /
/ * 0 * / brk, ora, nop, slo, nop, ora, asl, slo, php, ora, asl, nop, nop, ora, asl, slo, / * 0 * /
/ * 1 * / bpl, ora, nop, slo, nop, ora, asl, slo, clc, ora, nop, slo, nop, ora, asl, slo, / * 1 * /
/ * 2 * / jsr 및 nop, rla, 비트 및 rol, rla, plp 및 and, rol, nop, 비트 및 rol, rla, / * 2 * /
/ * 3 * / bmi 및 nop, rla, nop 및 and rol, rla, sec 및 nop, rla, nop 및 and rol, rla, / * 3 * /
/ * 4 * / rti, eor, nop, sre, nop, eor, lsr, sre, pha, eor, lsr, nop, jmp, eor, lsr, sre, / * 4 * /
/ * 5 * / bvc, eor, nop, sre, nop, eor, lsr, sre, cli, eor, nop, sre, nop, eor, lsr, sre, / * 5 * /
/ * 6 * / rts, adc, nop, rra, nop, adc, ror, rra, pla, adc, ror, nop, jmp, adc, ror, rra, / * 6 * /
/ * 7 * / bvs, adc, nop, rra, nop, adc, ror, rra, sei, adc, nop, rra, nop, adc, ror, rra, / * 7 * /
/ * 8 * / nop, sta, nop, sax, sty, sta, stx, sax, dey, nop, txa, nop, sty, sta, stx, sax, / * 8 * /
/ * 9 * / 숨은 참조, sta, nop, nop, sty, sta, stx, sax, tya, sta, txs, nop, nop, sta, nop, nop, / * 9 * /
/ * A * / ldy, lda, ldx, lax, ldy, lda, ldx, lax, tay, lda, 세금, nop, ldy, lda, ldx, lax, / * A * /
/ * B * / bcs, lda, nop, lax, ldy, lda, ldx, lax, clv, lda, tsx, lax, ldy, lda, ldx, lax, / * B * /
/ * C * / cpy, cmp, nop, dcp, cpy, cmp, dec, dcp, iny, cmp, dex, nop, cpy, cmp, dec, dcp, / * C * /
/ * D * / bne, cmp, nop, dcp, nop, cmp, dec, dcp, cld, cmp, nop, dcp, nop, cmp, dec, dcp, / * D * /
/ * E * / cpx, sbc, nop, isb, cpx, sbc, inc, isb, inx, sbc, nop, sbc, cpx, sbc, inc, isb, / * E * /
/ * F * / beq, sbc, nop, isb, nop, sbc, inc, isb, sed, sbc, nop, isb, nop, sbc, inc, isb / * F * /
};

정적 const uint32_t ticktable [256] = {
/ * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | 전자 | F | * /
/ * 0 * / 7, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6, / * 0 * /
/ * 1 * / 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, / * 1 * /
/ * 2 * / 6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 4, 4, 6, 6, / * 2 * /
/ * 3 * / 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, / * 3 * /
/ * 4 * / 6, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 3, 4, 6, 6, / * 4 * /
/ * 5 * / 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, / * 5 * /
/ * 6 * / 6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 5, 4, 6, 6, / * 6 * /
/ * 7 * / 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, / * 7 * /
/ * 8 * / 2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4, / * 8 * /
/ * 9 * / 2, 6, 2, 6, 4, 4, 4, 4, 2, 5, 2, 5, 5, 5, 5, 5, / * 9 * /
/ * A * / 2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4, / * A * /
/ * B * / 2, 5, 2, 5, 4, 4, 4, 4, 2, 4, 2, 4, 4, 4, 4, 4, / * B * /
/ * C * / 2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6, / * C * /
/ * D * / 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, / * D * /
/ * E * / 2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6, / * E * /
/ * F * / 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7 / * F * /
};


무효 nmi6502 () {
    push16 (pc);
    push8 (상태);
    상태 | = FLAG_INTERRUPT;
    pc = (uint16_t) read6502 (0xFFFA) | ((uint16_t) read6502 (0xFFFB) << 8);
}

무효 irq6502 () {
    push16 (pc);
    push8 (상태);
    상태 | = FLAG_INTERRUPT;
    pc = (uint16_t) read6502 (0xFFFE) | ((uint16_t) read6502 (0xFFFF) << 8);
}

uint8_t callexternal = 0;
무효 (* loopexternal) ();

무효 exec6502 (uint32_t 틱 카운트) {
    clockgoal6502 + = 틱 카운트;

    while (clockticks6502 <clockgoal6502) {
        opcode = read6502 (pc ++);

        패널티 = 0;
        페널티 애더 = 0;

        (* addrtable [opcode]) ();
        (* optable [opcode]) ();
        clockticks6502 + = 틱 테이블 [opcode];
        if (penaltyop && 패널티 주소) clockticks6502 ++;

        지침 ++;

        if (callexternal) (* loopexternal) ();
    }

}

void step6502 () {
    opcode = read6502 (pc ++);

    패널티 = 0;
    페널티 애드 = 0;

    (* addrtable [opcode]) ();
    (* optable [opcode]) ();
    clockticks6502 + = 틱 테이블 [opcode];
    if (penaltyop && 패널티 주소) clockticks6502 ++;
    clockgoal6502 = 클락 틱스 6502;

    지침 ++;

    if (callexternal) (* loopexternal) ();
}

void hookexternal (void * funcptr) {
    if (funcptr! = (void *) NULL) {
        loopexternal = funcptr;
        callexternal = 1;
    } 그렇지 않으면 callexternal = 0;
}

참고로, 코드 마킹의 마크 다운 방법 (4 개의 공백으로 들여 쓰기)을 사용하면 화면 크기가 스크롤 가능한 영역에있게됩니다. <태그 괄호>를 html-ize 할 필요가 없습니다. ...하지만이 답변에 대해서는 실제로 그것이 더 낫다고 생각합니다. 참조 구현으로 사용하는 데 필요한 공간을 매우 잘 활용합니다. ... 더 많은 답변이 도착하면 4 칸 들여 쓰기로 전환하여 페이지를 지배하지 않을 수 있습니다. $ 0.02 ... 질문을 좋아합니다 ... +1 +1 +1! 나는 내 일을하고있다, 걱정하지 마라! :)
luser droog

21

Haskell의 MOS 6502 에뮬레이터 특징은 다음과 같습니다.

  • 인덱싱 및 간접적 인 동안 미묘한 P 레지스터 처리 및 페이지 줄 바꿈을 포함한 비트 정확한 구현
  • 스핀 루프 감지 기능이있는 메모리 매핑 IO (입력을 기다리는 동안 호스트 CPU가 페그하지 않음)
  • 정지 감지 (점프 / 분기 자체)
  • 정확히 200 줄 및 6502 자 코드로 구현 된 CPU
  • CPU 구현은 순수한 상태 모나드

이것은 나중에 게시 할이 도전에 대해 수행 한 전체 구현 (더 많은 기능을 갖춘)의 다소 골프 버전입니다. 골프에도 불구하고 코드는 여전히 간단합니다. 알려진 누락 기능 만 BCD 모드입니다 (제공 예정 ...).

ehBASIC 코드를 실행합니다.

& ghc -O2 -o z6502min -Wall -fwarn-tabs -fno-warn-missing-signatures Z6502.hs
[1 of 1] Compiling Main             ( Z6502.hs, Z6502.o )

Z6502.hs:173:1: Warning: Defined but not used: `nmi'

Z6502.hs:174:1: Warning: Defined but not used: `irq'
Linking z6502min ...

& ./z6502min ehbasic.bin 
6502 EhBASIC [C]old/[W]arm ?

Memory size ? 

48383 Bytes free

Enhanced BASIC 2.22

Ready
PRINT "Hello World"
Hello World

Ready
10 FOR I = 1 TO 10
20 FOR J = 1 TO I
30 PRINT J;
40 NEXT J
50 PRINT
60 NEXT I
RUN
 1
 1 2
 1 2 3
 1 2 3 4
 1 2 3 4 5
 1 2 3 4 5 6
 1 2 3 4 5 6 7
 1 2 3 4 5 6 7 8
 1 2 3 4 5 6 7 8 9
 1 2 3 4 5 6 7 8 9 10

Ready

그리고 300 줄 미만의 코드는 다음과 같습니다.

-- Z6502: a 6502 emulator
-- by Mark Lentczner

module Main (main) where

import Control.Applicative
import Control.Monad
import Control.Monad.State.Strict
import Data.Bits
import qualified Data.ByteString as B
import Data.List
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as VU
import Data.Word
import System.Environment
import System.IO

{- === CPU: 200 lines, 6502 characters === -}
type Addr = Word16
toAd = fromIntegral :: Int -> Addr
addr :: Word8 -> Word8 -> Addr
addr lo hi = fromIntegral hi `shiftL` 8 .|. fromIntegral lo
lohi ad = (fromIntegral ad, fromIntegral $ ad `shiftR` 8)
zeroPage v = addr v 0
index ad idx = ad + fromIntegral (idx :: Word8)
relativeAddr ad off = index ad off - if off > 0x7f then 256 else 0

data Page = Missing | ROM !B.ByteString | RAM !(VU.Vector Word8)
type Memory = V.Vector Page
emptyMemory = V.replicate 256 Missing

fetchByte ad mv = case mv V.! hi of
    ROM bs -> B.index bs lo
    RAM vs -> vs VU.! lo
    _ -> 0
  where (hi,lo) = fromIntegral ad `divMod` 256
storeByte ad v mv = case mv V.! hi of
    RAM vs -> mv V.// [(hi, RAM $ vs VU.// [(lo, v)])]
    _ -> mv
  where (hi,lo) = fromIntegral ad `divMod` 256

data S = S { rA, rX, rY, rP, rS :: !Word8, rPC :: !Addr
           , mem :: !Memory, busR,busW :: Maybe Addr }
powerOnState = S 0 0 0 0 0 0 emptyMemory Nothing Nothing

[bitN, bitV, bitX, bitB, bitD, bitI, bitZ, bitC] = [7,6..0]
toBit b t v = (if t then setBit else clearBit) v b
toZ v = toBit bitZ (v == 0)
toZN v = toBit bitZ (v == 0) . toBit bitN (testBit v 7)
to67 v = toBit bitV (testBit v 6) . toBit bitN (testBit v 7)

setZN v = modify $ \s -> s { rP = toZN v $ rP s }
setAZN v = modify $ \s -> s { rA = v, rP=toZN v $ rP s }
setXZN v = modify $ \s -> s { rX = v, rP=toZN v $ rP s }
setYZN v = modify $ \s -> s { rY = v, rP=toZN v $ rP s }
setZVNbit (a,v) = modify $ \s -> s { rP = toZ (a .&. v) $ to67 v $ rP s }
setACZVN (c,v,a) = modify $ \s ->
    s { rA = a, rP = toBit bitC c $ toBit bitV v $ toZN a $ rP s }
setCZN (c,v) = modify $ \s -> s { rP = toBit bitC c $ toZN v $ rP s }

fetch a = state $ \s -> (fetchByte a $ mem s, s { busR = Just a })
fetchIndirectAddr a0 = do
    m <- gets mem
    let (lo,hi) = lohi a0
        a1 = addr (lo+1) hi
        bLo = fetchByte a0 m
        bHi = fetchByte a1 m
    return $ addr bLo bHi
store a v = modify $ \s -> s { mem = storeByte a v $ mem s, busW = Just a }

clearBus = modify $ \s -> s { busR = Nothing, busW = Nothing }
nextPC = state $ \s -> (rPC s, s { rPC = rPC s + 1 })
fetchPC = nextPC >>= \a -> gets mem >>= return . fetchByte a

adjSP n m = state $ \s -> (addr (rS s + m) 1, s { rS = rS s + n })
push v = adjSP (-1) 0 >>= flip store v
pull = adjSP 1 1 >>= fetch
pushAddr a = let (lo, hi) = lohi a in push hi >> push lo
pullAddr = addr <$> pull <*> pull
pushP fromSW = gets rP >>= push . toBit bitX True . toBit bitB fromSW
pullP = pull >>= \v -> modify $ \s -> s { rP = v .&. 0xCF }

indexX a = gets rX >>= return . index a
indexY a = gets rY >>= return . index a
aImm=nextPC
aZero=zeroPage<$>fetchPC
aZeroX=zeroPage<$>((+)<$>fetchPC<*>gets rX)
aZeroY=zeroPage<$>((+)<$>fetchPC<*>gets rY)
aRel=flip relativeAddr<$>fetchPC<*>gets rPC
aAbs=addr<$>fetchPC<*>fetchPC
aAbsX=aAbs>>=indexX
aAbsY=aAbs>>=indexY
aInd=aAbs>>=fetchIndirectAddr
aIndIdx=aZeroX>>=fetchIndirectAddr
aIdxInd=aZero>>=fetchIndirectAddr>>=indexY

decode = V.fromList $ concat $ transpose
 [[iBRK,iBPL,iJSR&aAbs,iBMI,iRTI,iBVC,iRTS,iBVS
  ,iErr,iBCC,iLDY&aImm,iBCS,iCPY&aImm,iBNE,iCPX&aImm,iBEQ]
 ,cAlu aIndIdx aIdxInd
 ,cErr//(10,iLDX&aImm)
 ,cErr
 ,[iErr,iErr,iBIT&aZero,iErr,iErr,iErr,iErr,iErr
  ,iSTY&aZero,iSTY&aZeroX,iLDY&aZero,iLDY&aZeroX,iCPY&aZero,iErr,iCPX&aZero,iErr]
 ,cAlu aZero aZeroX
 ,cBit aZero aZeroX//(9,iSTX&aZeroY)//(11,iLDX&aZeroY)
 ,cErr
 ,[iPHP,iCLC,iPLP,iSEC,iPHA,iCLI,iPLA,iSEI,iDEY,iTYA,iTAY,iCLV,iINY,iCLD,iINX,iSED]
 ,cAlu aImm aAbsY//(8,iErr)
 ,[iASLa,iErr,iROLa,iErr,iLSRa,iErr,iRORa,iErr
  ,iTXA,iTXS,iTAX,iTSX,iDEX,iErr,iNOP,iErr ]
 ,cErr
 ,[iErr,iErr,iBIT&aAbs,iErr,iJMP&aAbs,iErr,iJMP&aInd,iErr
  ,iSTY&aAbs,iErr,iLDY&aAbs,iLDY&aAbsX,iCPY&aAbs,iErr,iCPX&aAbs,iErr]
 ,cAlu aAbs aAbsX
 ,cBit aAbs aAbsX//(9,iErr)//(11,iLDX&aAbsY)
 ,cErr
 ]
cAlt is e o = is >>= (\i->[i&e,i&o])
cAlu = cAlt [iORA,iAND,iEOR,iADC,iSTA,iLDA,iCMP,iSBC]
cBit = cAlt [iASL,iROL,iLSR,iROR,iSTX,iLDX,iDEC,iINC]
cErr = replicate 16 iErr
is//(n,j) = let (f,_:h) = splitAt n is in f++j:h
i&a=a>>=i

loadIns l a = fetch a >>= l
storeIns f a = f >>= store a

aluIns set op ad = do
    v <- fetch ad
    a <- gets rA
    set $ op a v

modIns op a = fetch a >>= op >>= store a
modAccIns op = gets rA >>= op >>= \v -> modify $ \s -> s { rA = v }

stIns b op = modify $ \s -> s { rP = op (rP s) b }

jump a = modify $ \s -> s { rPC = a }
brIns b t = do
    a <- aRel
    p <- gets rP
    when (testBit p b == t) $ jump a

adcOp a b cIn = (cOut, v, s)
  where
    h = b + (if cIn then 1 else 0)
    s = a + h
    cOut = h < b || s < a
    v = testBit (a `xor` s .&. b `xor` s) 7
sbcOp a b cIn = adcOp a (complement b) cIn
carryOp f = gets rP >>= setACZVN . f . flip testBit bitC

cmpOp a b = (a >= b, a - b)

shiftOp shifter isRot inBit outBit v = do
    s <- get
    let newC = testBit v outBit
        bitIn = toBit inBit $ isRot && testBit (rP s) bitC
        v' = bitIn $ shifter v 1
    put s { rP = toBit bitC newC $ toZN v' $ rP s }
    return v'

vector a = fetchIndirectAddr a >>= jump

interrupt isBrk pcOffset a = do
    gets rPC >>= pushAddr . flip index pcOffset
    pushP isBrk
    iSEI
    vector a

reset = vector $ toAd 0xFFFC
nmi = interrupt False 0 $ toAd 0xFFFA
irq = interrupt False 0 $ toAd 0xFFFE

[iORA,iAND,iEOR]=aluIns setAZN<$>[(.|.),(.&.),xor]
[iADC,iSBC]=aluIns carryOp<$>[adcOp,sbcOp]
iSTA=storeIns$gets rA
iLDA=loadIns setAZN
iCMP=aluIns setCZN cmpOp

[iSTX,iSTY]=storeIns.gets<$>[rX,rY]
[iLDX,iLDY]=loadIns<$>[setXZN,setYZN]
[iCPX,iCPY]=(\r a->gets r>>= \v->fetch a>>=setCZN.cmpOp v)<$>[rX,rY]
[iDEC,iINC]=modIns.(\i v->setZN(v+i)>>return(v+i))<$>[-1,1]
[iDEX,iINX]=(gets rX>>=).(setXZN.).(+)<$>[-1,1]
[iDEY,iINY]=(gets rY>>=).(setYZN.).(+)<$>[-1,1]

shOps=[shiftOp d r b(7-b)|(d,b)<-[(shiftL,0),(shiftR,7)],r<-[False,True]]
[iASL,iROL,iLSR,iROR]=modIns<$>shOps
[iASLa,iROLa,iLSRa,iRORa]=modAccIns<$>shOps

iBIT=aluIns setZVNbit(,)
iJMP=jump

[iBPL,iBMI,iBVC,iBVS,iBCC,iBCS,iBNE,iBEQ]=brIns<$>[bitN,bitV,bitC,bitZ]<*>[False,True]
[iCLC,iSEC,iCLI,iSEI,iCLV,_,iCLD,iSED]=stIns<$>[bitC,bitI,bitV,bitD]<*>[clearBit,setBit]

iBRK=interrupt True 1 $ toAd 0xFFFE
iJSR a=gets rPC>>=pushAddr.(-1+)>>jump a
iRTI=iPLP>>pullAddr>>=jump
iRTS=pullAddr>>=jump.(1+)

iPHP=pushP True
iPLP=pullP
iPHA=gets rA>>=push
iPLA=pull>>=setAZN

iNOP=return ()

[iTAX,iTAY]=(gets rA>>=)<$>[setXZN,setYZN]
[iTXA,iTYA]=(>>=setAZN).gets<$>[rX,rY]
iTXS=modify $ \s -> s { rS=rX s }
iTSX=gets rS>>=setXZN

iErr=gets rPC>>=jump.(-1+)

executeOne = clearBus >> fetchPC >>= (decode V.!) . fromIntegral
{- === END OF CPU === -}


{- === MOTHERBOARD === -}
buildMemory rom =
    loadRAM 0xF0 1 $ loadRAM 0x00 ramSize $ loadROM romStart rom $ emptyMemory
  where
    ramSize = 256 - (B.length rom `div` 256)
    romStart = fromIntegral ramSize

    loadRAM p0 n = (V.// zip [p0..] (map RAM $ replicate n ramPage))
    ramPage = VU.replicate 256 0

    loadROM p0 bs = (V.// zip [p0..] (map ROM $ romPages bs))
    romPages b = case B.length b of
        l | l == 0    -> []
          | l < 256   -> [b `B.append` B.replicate (256 - l) 0]
          | l == 256  -> [b]
          | otherwise -> let (b0,bn) = B.splitAt 256 b in b0 : romPages bn

main = getArgs >>= go
  where
    go [romFile] = B.readFile romFile >>= exec . buildState . buildMemory
    go _ = putStrLn "agument should be a single ROM file"

    buildState m = execState reset (powerOnState { mem = m })

    exec s0 = do
        stopIO <- startIO
        loop (0 :: Int) s0
        stopIO

    loop n s = do
        let pcsp = (rPC s, rS s)
        (n',s') <- processIO n (execState executeOne s)
        let pcsp' = (rPC s', rS s')
        if pcsp /= pcsp'
            then (loop $! n') $! s'
            else do
                putStrLn $ "Execution snagged at " ++ show (fst pcsp')

    startIO = do
        ibuf <- hGetBuffering stdin
        obuf <- hGetBuffering stdout
        iecho <- hGetEcho stdin
        hSetBuffering stdin NoBuffering
        hSetBuffering stdout NoBuffering
        hSetEcho stdin False
        return $ do
            hSetEcho stdin iecho
            hSetBuffering stdin ibuf
            hSetBuffering stdout obuf
            putStr "\n\n"

    processIO n s = do
        when (busW s == Just outPortAddr) $ do
            let c = fetchByte outPortAddr $ mem s
            when (c /= 0) $ hPutChar stdout $ toEnum $ fromIntegral c
        if (busR s == Just inPortAddr)
            then do
                r <- if n < 16
                        then hWaitForInput stdin 50
                        else hReady stdin
                c <- if r then (fromIntegral . fromEnum) <$> hGetChar stdin else return 0
                let c' = if c == 0xA then 0xD else c
                let s' = s { mem = storeByte inPortAddr c' $ mem s }
                return (0,s')
            else return (n+1,s)

    inPortAddr = toAd 0xF004
    outPortAddr = toAd 0xF001

5
잘 했어! 매우 작은. 나는 Haskell을 모른다. 아마도 배울 것이다. 나는 그것이 6502 자라는 사실을 좋아합니다. :)
Mike C

6

관심있는 사람은 C #에서 6502 구현을 공유한다고 생각했습니다. 다른 게시물과 마찬가지로 완전히 ungolfed이지만 완전한 기능 구현입니다.

  • NMOS 및 CMOS 지원
  • 위의 AllSuite 테스트를 포함하여 여러 테스트 프로그램이 단위 테스트로 포함됩니다.
  • BCD 지원

CPU에 대해 처음 배울 때 지침 스프레드 시트를 만들어이 프로젝트를 시작했습니다. 이 스프레드 시트를 사용하여 입력 내용을 저장할 수 있다는 것을 깨달았습니다. 이것을 사이클 횟수를 계산하고 쉽게 분해 할 수 있도록 에뮬레이터가로드하는 텍스트 파일 테이블로 바꿨습니다.

전체 프로젝트는 Github https://github.com/amensch/e6502에서 사용할 수 있습니다

/*
 * e6502: A complete 6502 CPU emulator.
 * Copyright 2016 Adam Mensch
 */

using System;

namespace e6502CPU
{
    public enum e6502Type
    {
        CMOS,
        NMOS
    };

    public class e6502
    {
        // Main Register
        public byte A;

        // Index Registers
        public byte X;
        public byte Y;

        // Program Counter
        public ushort PC;

        // Stack Pointer
        // Memory location is hard coded to 0x01xx
        // Stack is descending (decrement on push, increment on pop)
        // 6502 is an empty stack so SP points to where next value is stored
        public byte SP;

        // Status Registers (in order bit 7 to 0)
        public bool NF;    // negative flag (N)
        public bool VF;    // overflow flag (V)
                           // bit 5 is unused
                           // bit 4 is the break flag however it is not a physical flag in the CPU
        public bool DF;    // binary coded decimal flag (D)
        public bool IF;    // interrupt flag (I)
        public bool ZF;    // zero flag (Z)
        public bool CF;    // carry flag (C)

        // RAM - 16 bit address bus means 64KB of addressable memory
        public byte[] memory;

        // List of op codes and their attributes
        private OpCodeTable _opCodeTable;

        // The current opcode
        private OpCodeRecord _currentOP;

        // Clock cycles to adjust due to page boundaries being crossed, branches taken, or NMOS/CMOS differences
        private int _extraCycles;

        // Flag for hardware interrupt (IRQ)
        public bool IRQWaiting { get; set; }

        // Flag for non maskable interrupt (NMI)
        public bool NMIWaiting { get; set; }

        public e6502Type _cpuType { get; set; }

        public e6502(e6502Type type)
        {
            memory = new byte[0x10000];
            _opCodeTable = new OpCodeTable();

            // Set these on instantiation so they are known values when using this object in testing.
            // Real programs should explicitly load these values before using them.
            A = 0;
            X = 0;
            Y = 0;
            SP = 0;
            PC = 0;
            NF = false;
            VF = false;
            DF = false;
            IF = true;
            ZF = false;
            CF = false;
            NMIWaiting = false;
            IRQWaiting = false;
            _cpuType = type;
        }

        public void Boot()
        {
            // On reset the addresses 0xfffc and 0xfffd are read and PC is loaded with this value.
            // It is expected that the initial program loaded will have these values set to something.
            // Most 6502 systems contain ROM in the upper region (around 0xe000-0xffff)
            PC = GetWordFromMemory(0xfffc);

            // interrupt disabled is set on powerup
            IF = true;

            NMIWaiting = false;
            IRQWaiting = false;
        }

        public void LoadProgram(ushort startingAddress, byte[] program)
        {
            program.CopyTo(memory, startingAddress);
            PC = startingAddress;
        }

        public string DasmNextInstruction()
        {
            OpCodeRecord oprec = _opCodeTable.OpCodes[ memory[PC] ];
            if (oprec.Bytes == 3)
                return oprec.Dasm( GetImmWord() );
            else
                return oprec.Dasm( GetImmByte() );
        }

        // returns # of clock cycles needed to execute the instruction
        public int ExecuteNext()
        {
            _extraCycles = 0;

            // Check for non maskable interrupt (has higher priority over IRQ)
            if (NMIWaiting)
            {
                DoIRQ(0xfffa);
                NMIWaiting = false;
                _extraCycles += 6;
            }
            // Check for hardware interrupt, if enabled
            else if (!IF)
            {
                if(IRQWaiting)
                {
                    DoIRQ(0xfffe);
                    IRQWaiting = false;
                    _extraCycles += 6;
                }
            }

            _currentOP = _opCodeTable.OpCodes[memory[PC]];

            ExecuteInstruction();

            return _currentOP.Cycles + _extraCycles;
        }

        private void ExecuteInstruction()
        {
            int result;
            int oper = GetOperand(_currentOP.AddressMode);

            switch (_currentOP.OpCode)
            {
                // ADC - add memory to accumulator with carry
                // A+M+C -> A,C (NZCV)
                case 0x61:
                case 0x65:
                case 0x69:
                case 0x6d:
                case 0x71:
                case 0x72:
                case 0x75:
                case 0x79:
                case 0x7d:

                    if (DF)
                    {
                        result = HexToBCD(A) + HexToBCD((byte)oper);
                        if (CF) result++;

                        CF = (result > 99);

                        if (result > 99 )
                        {
                            result -= 100;
                        }
                        ZF = (result == 0);

                        // convert decimal result to hex BCD result
                        A = BCDToHex(result);

                        // Unlike ZF and CF, the NF flag represents the MSB after conversion
                        // to BCD.
                        NF = (A > 0x7f);

                        // extra clock cycle on CMOS in decimal mode
                        if (_cpuType == e6502Type.CMOS)
                            _extraCycles++;
                    }
                    else
                    {
                        ADC((byte)oper);
                    }
                    PC += _currentOP.Bytes;
                    break;

                // AND - and memory with accumulator
                // A AND M -> A (NZ)
                case 0x21:
                case 0x25:
                case 0x29:
                case 0x2d:
                case 0x31:
                case 0x32:
                case 0x35:
                case 0x39:
                case 0x3d:
                    result = A & oper;

                    NF = ((result & 0x80) == 0x80);
                    ZF = ((result & 0xff) == 0x00);

                    A = (byte)result;
                    PC += _currentOP.Bytes;
                    break;

                // ASL - shift left one bit (NZC)
                // C <- (76543210) <- 0

                case 0x06:
                case 0x16:
                case 0x0a:
                case 0x0e:
                case 0x1e:

                    // On 65C02 (abs,X) takes one less clock cycle (but still add back 1 if page boundary crossed)
                    if (_currentOP.OpCode == 0x1e && _cpuType == e6502Type.CMOS)
                        _extraCycles--;

                    // shift bit 7 into carry
                    CF = (oper >= 0x80);

                    // shift operand
                    result = oper << 1;

                    NF = ((result & 0x80) == 0x80);
                    ZF = ((result & 0xff) == 0x00);

                    SaveOperand(_currentOP.AddressMode, result);
                    PC += _currentOP.Bytes;

                    break;

                // BBRx - test bit in memory (no flags)
                // Test the zero page location and branch of the specified bit is clear
                // These instructions are only available on Rockwell and WDC 65C02 chips.
                // Number of clock cycles is the same regardless if the branch is taken.
                case 0x0f:
                case 0x1f:
                case 0x2f:
                case 0x3f:
                case 0x4f:
                case 0x5f:
                case 0x6f:
                case 0x7f:

                    // upper nibble specifies the bit to check
                    byte check_bit = (byte)(_currentOP.OpCode >> 4);
                    byte check_value = 0x01;
                    for( int ii=0; ii < check_bit; ii++)
                    {
                        check_value = (byte)(check_value << 1);
                    }

                    // if the specified bit is 0 then branch
                    byte offset = memory[PC + 2];
                    PC += _currentOP.Bytes;

                    if ((oper & check_value) == 0x00)
                        PC += offset;

                    break;

                // BBSx - test bit in memory (no flags)
                // Test the zero page location and branch of the specified bit is set
                // These instructions are only available on Rockwell and WDC 65C02 chips.
                // Number of clock cycles is the same regardless if the branch is taken.
                case 0x8f:
                case 0x9f:
                case 0xaf:
                case 0xbf:
                case 0xcf:
                case 0xdf:
                case 0xef:
                case 0xff:

                    // upper nibble specifies the bit to check (but ignore bit 7)
                    check_bit = (byte)((_currentOP.OpCode & 0x70) >> 4);
                    check_value = 0x01;
                    for (int ii = 0; ii < check_bit; ii++)
                    {
                        check_value = (byte)(check_value << 1);
                    }

                    // if the specified bit is 1 then branch
                    offset = memory[PC + 2];
                    PC += _currentOP.Bytes;

                    if ((oper & check_value) == check_value)
                        PC += offset;

                    break;

                // BCC - branch on carry clear
                case 0x90:
                    PC += _currentOP.Bytes;
                    CheckBranch(!CF, oper);
                    break;

                // BCS - branch on carry set
                case 0xb0:
                    PC += _currentOP.Bytes;
                    CheckBranch(CF, oper);
                    break;

                // BEQ - branch on zero
                case 0xf0:
                    PC += _currentOP.Bytes;
                    CheckBranch(ZF, oper);
                    break;

                // BIT - test bits in memory with accumulator (NZV)
                // bits 7 and 6 of oper are transferred to bits 7 and 6 of conditional register (N and V)
                // the zero flag is set to the result of oper AND accumulator
                case 0x24:
                case 0x2c:
                // added by 65C02
                case 0x34:
                case 0x3c:
                case 0x89:
                    result = A & oper;

                    // The WDC programming manual for 65C02 indicates NV are unaffected in immediate mode.
                    // The extended op code test program reflects this.
                    if (_currentOP.AddressMode != AddressModes.Immediate)
                    {
                        NF = ((oper & 0x80) == 0x80);
                        VF = ((oper & 0x40) == 0x40);
                    }

                    ZF = ((result & 0xff) == 0x00);

                    PC += _currentOP.Bytes;
                    break;

                // BMI - branch on negative
                case 0x30:
                    PC += _currentOP.Bytes;
                    CheckBranch(NF, oper);
                    break;

                // BNE - branch on non zero
                case 0xd0:
                    PC += _currentOP.Bytes;
                    CheckBranch(!ZF, oper);
                    break;

                // BPL - branch on non negative
                case 0x10:
                    PC += _currentOP.Bytes;
                    CheckBranch(!NF, oper);
                    break;

                // BRA - unconditional branch to immediate address
                // NOTE: In OpcodeList.txt the number of clock cycles is one less than the documentation.
                // This is because CheckBranch() adds one when a branch is taken, which in this case is always.
                case 0x80:
                    PC += _currentOP.Bytes;
                    CheckBranch(true, oper);
                    break;

                // BRK - force break (I)
                case 0x00:

                    // This is a software interrupt (IRQ).  These events happen in a specific order.

                    // Processor adds two to the current PC
                    PC += 2;

                    // Call IRQ routine
                    DoIRQ(0xfffe, true);

                    // Whether or not the decimal flag is cleared depends on the type of 6502 CPU.
                    // The CMOS 65C02 clears this flag but the NMOS 6502 does not.
                    if( _cpuType == e6502Type.CMOS )
                        DF = false;

                    break;
                // BVC - branch on overflow clear
                case 0x50:
                    PC += _currentOP.Bytes;
                    CheckBranch(!VF, oper);
                    break;

                // BVS - branch on overflow set
                case 0x70:
                    PC += _currentOP.Bytes;
                    CheckBranch(VF, oper);
                    break;

                // CLC - clear carry flag
                case 0x18:
                    CF = false;
                    PC += _currentOP.Bytes;
                    break;

                // CLD - clear decimal mode
                case 0xd8:
                    DF = false;
                    PC += _currentOP.Bytes;
                    break;

                // CLI - clear interrupt disable bit
                case 0x58:
                    IF = false;
                    PC += _currentOP.Bytes;
                    break;

                // CLV - clear overflow flag
                case 0xb8:
                    VF = false;
                    PC += _currentOP.Bytes;
                    break;

                // CMP - compare memory with accumulator (NZC)
                // CMP, CPX and CPY are unsigned comparisions
                case 0xc5:
                case 0xc9:
                case 0xc1:
                case 0xcd:
                case 0xd1:
                case 0xd2:
                case 0xd5:
                case 0xd9:
                case 0xdd:

                    byte temp = (byte)(A - oper);

                    CF = A >= (byte)oper;
                    ZF = A == (byte)oper;
                    NF = ((temp & 0x80) == 0x80);

                    PC += _currentOP.Bytes;
                    break;

                // CPX - compare memory and X (NZC)
                case 0xe0:
                case 0xe4:
                case 0xec:
                    temp = (byte)(X - oper);

                    CF = X >= (byte)oper;
                    ZF = X == (byte)oper;
                    NF = ((temp & 0x80) == 0x80);

                    PC += _currentOP.Bytes;
                    break;

                // CPY - compare memory and Y (NZC)
                case 0xc0:
                case 0xc4:
                case 0xcc:
                    temp = (byte)(Y - oper);

                    CF = Y >= (byte)oper;
                    ZF = Y == (byte)oper;
                    NF = ((temp & 0x80) == 0x80);

                    PC += _currentOP.Bytes;
                    break;

                // DEC - decrement memory by 1 (NZ)
                case 0xc6:
                case 0xce:
                case 0xd6:
                case 0xde:
                // added by 65C02
                case 0x3a:
                    result = oper - 1;

                    ZF = ((result & 0xff) == 0x00);
                    NF = ((result & 0x80) == 0x80);

                    SaveOperand(_currentOP.AddressMode, result);

                    PC += _currentOP.Bytes;
                    break;

                // DEX - decrement X by one (NZ)
                case 0xca:
                    result = X - 1;

                    ZF = ((result & 0xff) == 0x00);
                    NF = ((result & 0x80) == 0x80);

                    X = (byte)result;
                    PC += _currentOP.Bytes;
                    break;

                // DEY - decrement Y by one (NZ)
                case 0x88:
                    result = Y - 1;

                    ZF = ((result & 0xff) == 0x00);
                    NF = ((result & 0x80) == 0x80);

                    Y = (byte)result;
                    PC += _currentOP.Bytes;
                    break;

                // EOR - XOR memory with accumulator (NZ)
                case 0x41:
                case 0x45:
                case 0x49:
                case 0x4d:
                case 0x51:
                case 0x52:
                case 0x55:
                case 0x59:
                case 0x5d:
                    result = A ^ (byte)oper;

                    ZF = ((result & 0xff) == 0x00);
                    NF = ((result & 0x80) == 0x80);

                    A = (byte)result;

                    PC += _currentOP.Bytes;
                    break;

                // INC - increment memory by 1 (NZ)
                case 0xe6:
                case 0xee:
                case 0xf6:
                case 0xfe:
                // added by 65C02
                case 0x1a:
                    result = oper + 1;

                    ZF = ((result & 0xff) == 0x00);
                    NF = ((result & 0x80) == 0x80);

                    SaveOperand(_currentOP.AddressMode, result);

                    PC += _currentOP.Bytes;
                    break;

                // INX - increment X by one (NZ)
                case 0xe8:
                    result = X + 1;

                    ZF = ((result & 0xff) == 0x00);
                    NF = ((result & 0x80) == 0x80);

                    X = (byte)result;
                    PC += _currentOP.Bytes;
                    break;

                // INY - increment Y by one (NZ)
                case 0xc8:
                    result = Y + 1;

                    ZF = ((result & 0xff) == 0x00);
                    NF = ((result & 0x80) == 0x80);

                    Y = (byte)result;
                    PC += _currentOP.Bytes;
                    break;

                // JMP - jump to new location (two byte immediate)
                case 0x4c:
                case 0x6c:
                // added for 65C02
                case 0x7c:

                    if (_currentOP.AddressMode == AddressModes.Absolute)
                    {
                        PC = GetImmWord();
                    }
                    else if (_currentOP.AddressMode == AddressModes.Indirect)
                    {
                        PC = (ushort)(GetWordFromMemory(GetImmWord()));
                    }
                    else if( _currentOP.AddressMode == AddressModes.AbsoluteX)
                    {
                        PC = GetWordFromMemory((GetImmWord() + X));
                    }
                    else
                    {
                        throw new InvalidOperationException("This address mode is invalid with the JMP instruction");
                    }

                    // CMOS fixes a bug in this op code which results in an extra clock cycle
                    if (_currentOP.OpCode == 0x6c && _cpuType == e6502Type.CMOS)
                        _extraCycles++;
                    break;

                // JSR - jump to new location and save return address
                case 0x20:
                    // documentation says push PC+2 even though this is a 3 byte instruction
                    // When pulled via RTS 1 is added to the result
                    Push((ushort)(PC+2));  
                    PC = GetImmWord();
                    break;

                // LDA - load accumulator with memory (NZ)
                case 0xa1:
                case 0xa5:
                case 0xa9:
                case 0xad:
                case 0xb1:
                case 0xb2:
                case 0xb5:
                case 0xb9:
                case 0xbd:
                    A = (byte)oper;

                    ZF = ((A & 0xff) == 0x00);
                    NF = ((A & 0x80) == 0x80);

                    PC += _currentOP.Bytes;
                    break;

                // LDX - load index X with memory (NZ)
                case 0xa2:
                case 0xa6:
                case 0xae:
                case 0xb6:
                case 0xbe:
                    X = (byte)oper;

                    ZF = ((X & 0xff) == 0x00);
                    NF = ((X & 0x80) == 0x80);

                    PC += _currentOP.Bytes;
                    break;

                // LDY - load index Y with memory (NZ)
                case 0xa0:
                case 0xa4:
                case 0xac:
                case 0xb4:
                case 0xbc:
                    Y = (byte)oper;

                    ZF = ((Y & 0xff) == 0x00);
                    NF = ((Y & 0x80) == 0x80);

                    PC += _currentOP.Bytes;
                    break;


                // LSR - shift right one bit (NZC)
                // 0 -> (76543210) -> C
                case 0x46:
                case 0x4a:
                case 0x4e:
                case 0x56:
                case 0x5e:

                    // On 65C02 (abs,X) takes one less clock cycle (but still add back 1 if page boundary crossed)
                    if (_currentOP.OpCode == 0x5e && _cpuType == e6502Type.CMOS)
                        _extraCycles--;

                    // shift bit 0 into carry
                    CF = ((oper & 0x01) == 0x01);

                    // shift operand
                    result = oper >> 1;

                    ZF = ((result & 0xff) == 0x00);
                    NF = ((result & 0x80) == 0x80);

                    SaveOperand(_currentOP.AddressMode, result);

                    PC += _currentOP.Bytes;
                    break;

                // NOP - no operation
                case 0xea:
                    PC += _currentOP.Bytes;
                    break;

                // ORA - OR memory with accumulator (NZ)
                case 0x01:
                case 0x05:
                case 0x09:
                case 0x0d:
                case 0x11:
                case 0x12:
                case 0x15:
                case 0x19:
                case 0x1d:
                    result = A | (byte)oper;

                    ZF = ((result & 0xff) == 0x00);
                    NF = ((result & 0x80) == 0x80);

                    A = (byte)result;

                    PC += _currentOP.Bytes;
                    break;

                // PHA - push accumulator on stack
                case 0x48:
                    Push(A);
                    PC += _currentOP.Bytes;
                    break;

                // PHP - push processor status on stack
                case 0x08:
                    int sr = 0x00;

                    if (NF) sr = sr | 0x80;
                    if (VF) sr = sr | 0x40;
                    sr = sr | 0x20; // bit 5 is always 1
                    sr = sr | 0x10; // bit 4 is always 1 for PHP
                    if (DF) sr = sr | 0x08;
                    if (IF) sr = sr | 0x04;
                    if (ZF) sr = sr | 0x02;
                    if (CF) sr = sr | 0x01;

                    Push((byte)sr);
                    PC += _currentOP.Bytes;
                    break;

                // PHX - push X on stack
                case 0xda:
                    Push(X);
                    PC += _currentOP.Bytes;
                    break;

                // PHY - push Y on stack
                case 0x5a:
                    Push(Y);
                    PC += _currentOP.Bytes;
                    break;

                // PLA - pull accumulator from stack (NZ)
                case 0x68:
                    A = PopByte();
                    NF = (A & 0x80) == 0x80;
                    ZF = (A & 0xff) == 0x00;
                    PC += _currentOP.Bytes;
                    break;

                // PLP - pull status from stack
                case 0x28:
                    sr = PopByte();

                    NF = (sr & 0x80) == 0x80;
                    VF = (sr & 0x40) == 0x40;
                    DF = (sr & 0x08) == 0x08;
                    IF = (sr & 0x04) == 0x04;
                    ZF = (sr & 0x02) == 0x02;
                    CF = (sr & 0x01) == 0x01;
                    PC += _currentOP.Bytes;
                    break;

                // PLX - pull X from stack (NZ)
                case 0xfa:
                    X = PopByte();
                    NF = (X & 0x80) == 0x80;
                    ZF = (X & 0xff) == 0x00;
                    PC += _currentOP.Bytes;
                    break;

                // PLY - pull Y from stack (NZ)
                case 0x7a:
                    Y = PopByte();
                    NF = (Y & 0x80) == 0x80;
                    ZF = (Y & 0xff) == 0x00;
                    PC += _currentOP.Bytes;
                    break;

                // RMBx - clear bit in memory (no flags)
                // Clear the zero page location of the specified bit
                // These instructions are only available on Rockwell and WDC 65C02 chips.
                case 0x07:
                case 0x17:
                case 0x27:
                case 0x37:
                case 0x47:
                case 0x57:
                case 0x67:
                case 0x77:

                    // upper nibble specifies the bit to check
                     check_bit = (byte)(_currentOP.OpCode >> 4);
                     check_value = 0x01;
                    for (int ii = 0; ii < check_bit; ii++)
                    {
                        check_value = (byte)(check_value << 1);
                    }
                    check_value = (byte)~check_value;
                    SaveOperand(_currentOP.AddressMode, oper & check_value);
                    PC += _currentOP.Bytes;
                    break;

                // SMBx - set bit in memory (no flags)
                // Set the zero page location of the specified bit
                // These instructions are only available on Rockwell and WDC 65C02 chips.
                case 0x87:
                case 0x97:
                case 0xa7:
                case 0xb7:
                case 0xc7:
                case 0xd7:
                case 0xe7:
                case 0xf7:

                    // upper nibble specifies the bit to check (but ignore bit 7)
                    check_bit = (byte)((_currentOP.OpCode & 0x70) >> 4);
                    check_value = 0x01;
                    for (int ii = 0; ii < check_bit; ii++)
                    {
                        check_value = (byte)(check_value << 1);
                    }
                    SaveOperand(_currentOP.AddressMode, oper | check_value);
                    PC += _currentOP.Bytes;
                    break;

                // ROL - rotate left one bit (NZC)
                // C <- 76543210 <- C
                case 0x26:
                case 0x2a:
                case 0x2e:
                case 0x36:
                case 0x3e:

                    // On 65C02 (abs,X) takes one less clock cycle (but still add back 1 if page boundary crossed)
                    if (_currentOP.OpCode == 0x3e && _cpuType == e6502Type.CMOS)
                        _extraCycles--;

                    // perserve existing cf value
                    bool old_cf = CF;

                    // shift bit 7 into carry flag
                    CF = (oper >= 0x80);

                    // shift operand
                    result = oper << 1;

                    // old carry flag goes to bit zero
                    if (old_cf) result = result | 0x01;

                    ZF = ((result & 0xff) == 0x00);
                    NF = ((result & 0x80) == 0x80);
                    SaveOperand(_currentOP.AddressMode, result);

                    PC += _currentOP.Bytes;
                    break;

                // ROR - rotate right one bit (NZC)
                // C -> 76543210 -> C
                case 0x66:
                case 0x6a:
                case 0x6e:
                case 0x76:
                case 0x7e:

                    // On 65C02 (abs,X) takes one less clock cycle (but still add back 1 if page boundary crossed)
                    if (_currentOP.OpCode == 0x7e && _cpuType == e6502Type.CMOS)
                        _extraCycles--;

                    // perserve existing cf value
                    old_cf = CF;

                    // shift bit 0 into carry flag
                    CF = (oper & 0x01) == 0x01;

                    // shift operand
                    result = oper >> 1;

                    // old carry flag goes to bit 7
                    if (old_cf) result = result | 0x80;

                    ZF = ((result & 0xff) == 0x00);
                    NF = ((result & 0x80) == 0x80);
                    SaveOperand(_currentOP.AddressMode, result);

                    PC += _currentOP.Bytes;
                    break;

                // RTI - return from interrupt
                case 0x40:
                    // pull SR
                    sr = PopByte();

                    NF = (sr & 0x80) == 0x80;
                    VF = (sr & 0x40) == 0x40;
                    DF = (sr & 0x08) == 0x08;
                    IF = (sr & 0x04) == 0x04;
                    ZF = (sr & 0x02) == 0x02;
                    CF = (sr & 0x01) == 0x01;

                    // pull PC
                    PC = PopWord();

                    break;

                // RTS - return from subroutine
                case 0x60:
                    PC = (ushort)(PopWord() + 1);
                    break;

                // SBC - subtract memory from accumulator with borrow (NZCV)
                // A-M-C -> A (NZCV)
                case 0xe1:
                case 0xe5:
                case 0xe9:
                case 0xed:
                case 0xf1:
                case 0xf2:
                case 0xf5:
                case 0xf9:
                case 0xfd:

                    if (DF)
                    {
                        result = HexToBCD(A) - HexToBCD((byte)oper);
                        if (!CF) result--;

                        CF = (result >= 0);

                        // BCD numbers wrap around when subtraction is negative
                        if (result < 0)
                            result += 100;
                        ZF = (result == 0);

                        A = BCDToHex(result);

                        // Unlike ZF and CF, the NF flag represents the MSB after conversion
                        // to BCD.
                        NF = (A > 0x7f);

                        // extra clock cycle on CMOS in decimal mode
                        if (_cpuType == e6502Type.CMOS)
                            _extraCycles++;
                    }
                    else
                    {
                        ADC((byte)~oper);
                    }
                    PC += _currentOP.Bytes;

                    break;

                // SEC - set carry flag
                case 0x38:
                    CF = true;
                    PC += _currentOP.Bytes;
                    break;

                // SED - set decimal mode
                case 0xf8:
                    DF = true;
                    PC += _currentOP.Bytes;
                    break;

                // SEI - set interrupt disable bit
                case 0x78:
                    IF = true;
                    PC += _currentOP.Bytes;
                    break;

                // STA - store accumulator in memory
                case 0x81:
                case 0x85:
                case 0x8d:
                case 0x91:
                case 0x92:
                case 0x95:
                case 0x99:
                case 0x9d:
                    SaveOperand(_currentOP.AddressMode, A);
                    PC += _currentOP.Bytes;
                    break;

                // STX - store X in memory
                case 0x86:
                case 0x8e:
                case 0x96:
                    SaveOperand(_currentOP.AddressMode, X);
                    PC += _currentOP.Bytes;
                    break;

                // STY - store Y in memory
                case 0x84:
                case 0x8c:
                case 0x94:
                    SaveOperand(_currentOP.AddressMode, Y);
                    PC += _currentOP.Bytes;
                    break;

                // STZ - Store zero
                case 0x64:
                case 0x74:
                case 0x9c:
                case 0x9e:
                    SaveOperand(_currentOP.AddressMode, 0);
                    PC += _currentOP.Bytes;
                    break;

                // TAX - transfer accumulator to X (NZ)
                case 0xaa:
                    X = A;
                    ZF = ((X & 0xff) == 0x00);
                    NF = ((X & 0x80) == 0x80);
                    PC += _currentOP.Bytes;
                    break;

                // TAY - transfer accumulator to Y (NZ)
                case 0xa8:
                    Y = A;
                    ZF = ((Y & 0xff) == 0x00);
                    NF = ((Y & 0x80) == 0x80);
                    PC += _currentOP.Bytes;
                    break;

                // TRB - test and reset bits (Z)
                // Perform bitwise AND between accumulator and contents of memory
                case 0x14:
                case 0x1c:
                    SaveOperand(_currentOP.AddressMode, ~A & oper);
                    ZF = (A & oper) == 0x00;
                    PC += _currentOP.Bytes;
                    break;

                // TSB - test and set bits (Z)
                // Perform bitwise AND between accumulator and contents of memory
                case 0x04:
                case 0x0c:
                    SaveOperand(_currentOP.AddressMode, A | oper);
                    ZF = (A & oper) == 0x00;
                    PC += _currentOP.Bytes;
                    break;

                // TSX - transfer SP to X (NZ)
                case 0xba:
                    X = SP;
                    ZF = ((X & 0xff) == 0x00);
                    NF = ((X & 0x80) == 0x80);
                    PC += _currentOP.Bytes;
                    break;

                // TXA - transfer X to A (NZ)
                case 0x8a:
                    A = X;
                    ZF = ((A & 0xff) == 0x00);
                    NF = ((A & 0x80) == 0x80);
                    PC += _currentOP.Bytes;
                    break;

                // TXS - transfer X to SP (no flags -- some online docs are incorrect)
                case 0x9a:
                    SP = X;
                    PC += _currentOP.Bytes;
                    break;

                // TYA - transfer Y to A (NZ)
                case 0x98:
                    A = Y;
                    ZF = ((A & 0xff) == 0x00);
                    NF = ((A & 0x80) == 0x80);
                    PC += _currentOP.Bytes;
                    break;

                // The original 6502 has undocumented and erratic behavior if
                // undocumented op codes are invoked.  The 65C02 on the other hand
                // are guaranteed to be NOPs although they vary in number of bytes
                // and cycle counts.  These NOPs are listed in the OpcodeList.txt file
                // so the proper number of clock cycles are used.
                //
                // Instructions STP (0xdb) and WAI (0xcb) will reach this case.
                // For now these are treated as a NOP.
                default:
                    PC += _currentOP.Bytes;
                    break;
            }
        }

        private int GetOperand(AddressModes mode)
        {
            int oper = 0;
            switch (mode)
            {
                // Accumulator mode uses the value in the accumulator
                case AddressModes.Accumulator:
                    oper = A;
                    break;

                // Retrieves the byte at the specified memory location
                case AddressModes.Absolute:             
                    oper = memory[ GetImmWord() ];
                    break;

                // Indexed absolute retrieves the byte at the specified memory location
                case AddressModes.AbsoluteX:

                    ushort imm = GetImmWord();
                    ushort result = (ushort)(imm + X);

                    if (_currentOP.CheckPageBoundary)
                    {
                        if ((imm & 0xff00) != (result & 0xff00)) _extraCycles += 1;
                    }
                    oper = memory[ result ];
                    break;
                case AddressModes.AbsoluteY:
                    imm = GetImmWord();
                    result = (ushort)(imm + Y);

                    if (_currentOP.CheckPageBoundary)
                    {
                        if ((imm & 0xff00) != (result & 0xff00)) _extraCycles += 1;
                    }
                    oper = memory[result]; break;

                // Immediate mode uses the next byte in the instruction directly.
                case AddressModes.Immediate:
                    oper = GetImmByte();
                    break;

                // Implied or Implicit are single byte instructions that do not use
                // the next bytes for the operand.
                case AddressModes.Implied:
                    break;

                // Indirect mode uses the absolute address to get another address.
                // The immediate word is a memory location from which to retrieve
                // the 16 bit operand.
                case AddressModes.Indirect:
                    oper = GetWordFromMemory(GetImmWord());
                    break;

                // The indexed indirect modes uses the immediate byte rather than the
                // immediate word to get the memory location from which to retrieve
                // the 16 bit operand.  This is a combination of ZeroPage indexed and Indirect.
                case AddressModes.XIndirect:

                    /*
                     * 1) fetch immediate byte
                     * 2) add X to the byte
                     * 3) obtain word from this zero page address
                     * 4) return the byte located at the address specified by the word
                     */

                    oper = memory[GetWordFromMemory( (byte)(GetImmByte() + X))];
                    break;

                // The Indirect Indexed works a bit differently than above.
                // The Y register is added *after* the deferencing instead of before.
                case AddressModes.IndirectY:

                    /*
                        1) Fetch the address (word) at the immediate zero page location
                        2) Add Y to obtain the final target address
                        3)Load the byte at this address
                    */

                    ushort addr = GetWordFromMemory(GetImmByte());
                    oper = memory[addr + Y];

                    if (_currentOP.CheckPageBoundary)
                    {
                        if ((oper & 0xff00) != (addr & 0xff00)) _extraCycles++;
                    }
                    break;


                // Relative is used for branching, the immediate value is a
                // signed 8 bit value and used to offset the current PC.
                case AddressModes.Relative:
                    oper = SignExtend(GetImmByte());
                    break;

                // Zero Page mode is a fast way of accessing the first 256 bytes of memory.
                // Best programming practice is to place your variables in 0x00-0xff.
                // Retrieve the byte at the indicated memory location.
                case AddressModes.ZeroPage:
                    oper = memory[GetImmByte()];
                    break;
                case AddressModes.ZeroPageX:
                    oper = memory[(GetImmByte() + X) & 0xff];
                    break;
                case AddressModes.ZeroPageY:
                    oper = memory[(GetImmByte() + Y) & 0xff];
                    break;

                // this mode is from the 65C02 extended set
                // works like ZeroPageY when Y=0
                case AddressModes.ZeroPage0:
                    oper = memory[GetWordFromMemory((GetImmByte()) & 0xff)];
                    break;

                // for this mode do the same thing as ZeroPage
                case AddressModes.BranchExt:
                    oper = memory[GetImmByte()];
                    break;
                default:
                    break;
            }
            return oper;
        }

        private void SaveOperand(AddressModes mode, int data)
        {
            switch (mode)
            {
                // Accumulator mode uses the value in the accumulator
                case AddressModes.Accumulator:
                    A = (byte)data;
                    break;

                // Absolute mode retrieves the byte at the indicated memory location
                case AddressModes.Absolute:
                    memory[GetImmWord()] = (byte)data;
                    break;
                case AddressModes.AbsoluteX:
                    memory[GetImmWord() + X] = (byte)data;
                    break;
                case AddressModes.AbsoluteY:
                    memory[GetImmWord() + Y] = (byte)data;
                    break;

                // Immediate mode uses the next byte in the instruction directly.
                case AddressModes.Immediate:
                    throw new InvalidOperationException("Address mode " + mode.ToString() + " is not valid for this operation");

                // Implied or Implicit are single byte instructions that do not use
                // the next bytes for the operand.
                case AddressModes.Implied:
                    throw new InvalidOperationException("Address mode " + mode.ToString() + " is not valid for this operation");

                // Indirect mode uses the absolute address to get another address.
                // The immediate word is a memory location from which to retrieve
                // the 16 bit operand.
                case AddressModes.Indirect:
                    throw new InvalidOperationException("Address mode " + mode.ToString() + " is not valid for this operation");

                // The indexed indirect modes uses the immediate byte rather than the
                // immediate word to get the memory location from which to retrieve
                // the 16 bit operand.  This is a combination of ZeroPage indexed and Indirect.
                case AddressModes.XIndirect:
                    memory[GetWordFromMemory((byte)(GetImmByte() + X))] = (byte)data;
                    break;

                // The Indirect Indexed works a bit differently than above.
                // The Y register is added *after* the deferencing instead of before.
                case AddressModes.IndirectY:
                    memory[GetWordFromMemory(GetImmByte()) + Y] = (byte)data;
                    break;

                // Relative is used for branching, the immediate value is a
                // signed 8 bit value and used to offset the current PC.
                case AddressModes.Relative:
                    throw new InvalidOperationException("Address mode " + mode.ToString() + " is not valid for this operation");

                // Zero Page mode is a fast way of accessing the first 256 bytes of memory.
                // Best programming practice is to place your variables in 0x00-0xff.
                // Retrieve the byte at the indicated memory location.
                case AddressModes.ZeroPage:
                    memory[GetImmByte()] = (byte)data;
                    break;
                case AddressModes.ZeroPageX:
                    memory[(GetImmByte() + X) & 0xff] = (byte)data;
                    break;
                case AddressModes.ZeroPageY:
                    memory[(GetImmByte() + Y) & 0xff] = (byte)data;
                    break;
                case AddressModes.ZeroPage0:
                    memory[GetWordFromMemory((GetImmByte()) & 0xff)] = (byte)data;
                    break;

                // for this mode do the same thing as ZeroPage
                case AddressModes.BranchExt:
                    memory[GetImmByte()] = (byte)data;
                    break;

                default:
                    break;
            }
        }

        private ushort GetWordFromMemory(int address)
        {
            return (ushort)((memory[address + 1] << 8 | memory[address]) & 0xffff);
        }

        private ushort GetImmWord()
        {
            return (ushort)((memory[PC + 2] << 8 | memory[PC + 1]) & 0xffff);
        }

        private byte GetImmByte()
        {
            return memory[PC + 1];
        }

        private int SignExtend(int num)
        {
            if (num < 0x80)
                return num;
            else
                return (0xff << 8 | num) & 0xffff;
        }

        private void Push(byte data)
        {
            memory[(0x0100 | SP)] = data;
            SP--;
        }

        private void Push(ushort data)
        {
            // HI byte is in a higher address, LO byte is in the lower address
            memory[(0x0100 | SP)] = (byte)(data >> 8);
            memory[(0x0100 | (SP-1))] = (byte)(data & 0xff);
            SP -= 2;
        }

        private byte PopByte()
        {
            SP++;
            return memory[(0x0100 | SP)];
        }

        private ushort PopWord()
        {
            // HI byte is in a higher address, LO byte is in the lower address
            SP += 2;
            ushort idx = (ushort)(0x0100 | SP);
            return (ushort)((memory[idx] << 8 | memory[idx-1]) & 0xffff);
        }

        private void ADC(byte oper)
        {
            ushort answer = (ushort)(A + oper);
            if (CF) answer++;

            CF = (answer > 0xff);
            ZF = ((answer & 0xff) == 0x00);
            NF = (answer & 0x80) == 0x80;

            //ushort temp = (ushort)(~(A ^ oper) & (A ^ answer) & 0x80);
            VF = (~(A ^ oper) & (A ^ answer) & 0x80) != 0x00;

            A = (byte)answer;
        }

        private int HexToBCD(byte oper)
        {
            // validate input is valid packed BCD 
            if (oper > 0x99)
                throw new InvalidOperationException("Invalid BCD number: " + oper.ToString("X2"));
            if ((oper & 0x0f) > 0x09)
                throw new InvalidOperationException("Invalid BCD number: " + oper.ToString("X2"));

            return ((oper >> 4) * 10) + (oper & 0x0f);
        }

        private byte BCDToHex(int result)
        {
            if (result > 0xff)
                throw new InvalidOperationException("Invalid BCD to hex number: " + result.ToString());

            if (result <= 9)
                return (byte)result;
            else
                return (byte)(((result / 10) << 4) + (result % 10));

        }

        private void DoIRQ(ushort vector)
        {
            DoIRQ(vector, false);
        }

        private void DoIRQ(ushort vector, bool isBRK)
        {
            // Push the MSB of the PC
            Push((byte)(PC >> 8));

            // Push the LSB of the PC
            Push((byte)(PC & 0xff));

            // Push the status register
            int sr = 0x00;
            if (NF) sr = sr | 0x80;
            if (VF) sr = sr | 0x40;

            sr = sr | 0x20;             // bit 5 is unused and always 1

            if(isBRK)
                sr = sr | 0x10;         // software interrupt (BRK) pushes B flag as 1
                                        // hardware interrupt pushes B flag as 0
            if (DF) sr = sr | 0x08;
            if (IF) sr = sr | 0x04;
            if (ZF) sr = sr | 0x02;
            if (CF) sr = sr | 0x01;

            Push((byte)sr);

            // set interrupt disable flag
            IF = true;

            // On 65C02, IRQ, NMI, and RESET also clear the D flag (but not on BRK) after pushing the status register.
            if (_cpuType == e6502Type.CMOS && !isBRK)
                DF = false;

            // load program counter with the interrupt vector
            PC = GetWordFromMemory(vector);
        }

        private void CheckBranch(bool flag, int oper)
        {
            if (flag)
            {
                // extra cycle on branch taken
                _extraCycles++;

                // extra cycle if branch destination is a different page than
                // the next instruction
                if ((PC & 0xff00) != ((PC + oper) & 0xff00))
                    _extraCycles++;

                PC += (ushort)oper;
            }

        }
    }
}

아무도 당신을 PPCG에 오신 것을 환영하지 않습니다.이 기회를 잡을 것 같습니다. 이것은 첫 번째 좋은 답변이며 더 자주 만나기를 바랍니다. 즐기세요!
Stan Strum

@StanStrum 감사합니다! 그것은 몇 년 전 8086 에뮬레이터에 관한 SE였습니다. 정말 재미있었습니다. 위의 것 외에도 완전한 8080 에뮬레이터와 약 90 % 완료 된 8086이 있습니다.
Adam Mensch

놀랍습니다. 에뮬레이터 및 / 또는 중간 수준의 프로그래밍 언어를 만드는 데 관심이 있었지만 그렇게 할 시간, 인내 또는 지성이 없었습니다
Stan Strum
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.