aboutsummaryrefslogblamecommitdiff
path: root/src/mos6502.rs
blob: 58ac2170e96829ae30c1e1899cd9a850ee7daa24 (plain) (tree)
1
2
                    
                 










































                                                                                         
                                   















                                                   


                                 















                                                   

  












                                 





















                                                               
                                    














                                                       


                                                
                    
                                          



























                                                         
                                    
































                                                                                       



                                                                             








                                                                             
         
                   

                                      






                                                                        

                    




































                                                                                       


















































                                                                               


















                                                                  








                                                                          












                                                                           
                                                    






































































                                                                           
                  



                                                         

                                    









                                                         

                                    












                                       
                           
                        
                        

                                                                              

                                                                        
                               
                                    
                                                                              

     
                             








                                                        
                       










                                                           


















                                                                          
























                                                                  
                            












                                                                        
                           

                           



                                             




























                                                              


                                                                      


          
                                 

                                               





                                              
                                            



                                          
                                              



                                          
                                       




                                                     
                                       




                                                     




                                                        



                                          
                                           



                                          



                                                                     



                                          



                                                                     



                                          
                                             




                                          


                                                            




                                          




                                                                     


                             

 



                

 




              
                    




                   
            

                        
                        
             
                                       
                
                   
                         
                         

 














                                                            
                  

                                                                              
                                                                            

                                                                             
 
                                           











                                                   
                      
                                         
                
     
 


                               

                            





                                             










                                                              













                                                         
                                          

                                
                        













                                                   
 
#![allow(dead_code)]
use memory::VMem;
macro_rules! make_optable {
    ($x:ident, $t: ty) => (pub const $x: [$t; 0x100] = [
    /*  0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf */
        brk, ora, nil, nil, nil, ora, asl, nil, php, ora, asl, nil, nil, ora, asl, nil,
        bpl, ora, nil, nil, nil, ora, asl, nil, clc, ora, nil, nil, nil, ora, asl, nil,
        jsr, and, nil, nil, bit, and, rol, nil, plp, and, rol, nil, bit, and, rol, nil,
        bmi, and, nil, nil, nil, and, rol, nil, sec, and, nil, nil, nil, and, rol, nil,
        rti, eor, nil, nil, nil, eor, lsr, nil, pha, eor, lsr, nil, jmp, eor, lsr, nil,
        bvc, eor, nil, nil, nil, eor, lsr, nil, cli, eor, nil, nil, nil, eor, lsr, nil,
        rts, adc, nil, nil, nil, adc, ror, nil, pla, adc, ror, nil, jmp, adc, ror, nil,
        bvs, adc, nil, nil, nil, adc, ror, nil, sei, adc, nil, nil, nil, adc, ror, nil,
        nil, sta, nil, nil, sty, sta, stx, nil, dey, nil, txa, nil, sty, sta, stx, nil,
        bcc, sta, nil, nil, sty, sta, stx, nil, tya, sta, txs, nil, nil, sta, nil, nil,
        ldy, lda, ldx, nil, ldy, lda, ldx, nil, tay, lda, tax, nil, ldy, lda, ldx, nil,
        bcs, lda, nil, nil, ldy, lda, ldx, nil, clv, lda, tsx, nil, ldy, lda, ldx, nil,
        cpy, cmp, nil, nil, cpy, cmp, dec, nil, iny, cmp, dex, nil, cpy, cmp, dec, nil,
        bne, cmp, nil, nil, nil, cmp, dec, nil, cld, cmp, nil, nil, nil, cmp, dec, nil,
        cpx, sbc, nil, nil, cpx, sbc, inc, nil, inx, sbc, nop, nil, cpx, sbc, inc, nil,
        beq, sbc, nil, nil, nil, sbc, inc, nil, sed, sbc, nil, nil, nil, sbc, inc, nil
    ];);
}

macro_rules! make_addrtable {
    ($x:ident, $t: ty) => (pub const $x: [$t; 0x100] = [
       nil, xin, nil, nil, nil, zpg, zpg, nil, nil, imm, acc, nil, nil, abs, abs, nil,
       rel, iny, nil, nil, nil, zpx, zpx, nil, nil, aby, nil, nil, nil, abx, abx, nil,
       abs, xin, nil, nil, zpg, zpg, zpg, nil, nil, imm, acc, nil, abs, abs, abs, nil,
       rel, iny, nil, nil, nil, zpx, zpx, nil, nil, aby, nil, nil, nil, abx, abx, nil,
       nil, xin, nil, nil, nil, zpg, zpg, nil, nil, imm, acc, nil, abs, abs, abs, nil,
       rel, iny, nil, nil, nil, zpx, zpx, nil, nil, aby, nil, nil, nil, abx, abx, nil,
       nil, xin, nil, nil, nil, zpg, zpg, nil, nil, imm, acc, nil, ind, abs, abs, nil,
       rel, iny, nil, nil, nil, zpx, zpx, nil, nil, aby, nil, nil, nil, abx, abx, nil,
       nil, xin, nil, nil, zpg, zpg, zpg, nil, nil, nil, nil, nil, abs, abs, abs, nil,
       rel, iny, nil, nil, zpx, zpx, zpy, nil, nil, aby, nil, nil, nil, abx, nil, nil,
       imm, xin, imm, nil, zpg, zpg, zpg, nil, nil, imm, nil, nil, abs, abs, abs, nil,
       rel, iny, nil, nil, zpx, zpx, zpy, nil, nil, aby, nil, nil, abx, abx, aby, nil,
       imm, xin, nil, nil, zpg, zpg, zpg, nil, nil, imm, nil, nil, abs, abs, abs, nil,
       rel, iny, nil, nil, nil, zpx, zpx, nil, nil, aby, nil, nil, nil, abx, abx, nil,
       imm, xin, nil, nil, zpg, zpg, zpg, nil, nil, imm, nil, nil, abs, abs, abs, nil,
       rel, iny, nil, nil, nil, zpx, zpx, nil, nil, aby, nil, nil, nil, abx, abx, nil
    ];);
}

const INST_LENGTH: [u8; 0x100] =  [
    1, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
    2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
    3, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
    2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
    1, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
    2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
    1, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
    2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
    2, 2, 0, 0, 2, 2, 2, 0, 1, 0, 1, 0, 3, 3, 3, 0,
    2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 0, 3, 0, 0,
    2, 2, 2, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
    2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
    2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
    2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
    2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
    2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
];

const INST_CYCLE: [u8; 0x100] = [
    7, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6,
    2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
    6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 4, 4, 6, 6,
    2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
    6, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 3, 4, 6, 6,
    2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
    6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 5, 4, 6, 6,
    2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
    2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4,
    2, 6, 2, 6, 4, 4, 4, 4, 2, 5, 2, 5, 5, 5, 5, 5,
    2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4,
    2, 5, 2, 5, 4, 4, 4, 4, 2, 4, 2, 4, 4, 4, 4, 4,
    2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6,
    2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
    2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6,
    2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
];

const NMI_VECTOR: u16 = 0xfffa;
const RESET_VECTOR: u16 = 0xfffc;
const IRQ_VECTOR: u16 = 0xfffe;
const BRK_VECTOR: u16 = 0xfffe;

const CARRY_FLAG: u8 = 1 << 0;
const ZERO_FLAG: u8 = 1 << 1;
const INT_FLAG: u8 = 1 << 2;
const DEC_FLAG: u8 = 1 << 3;
const BRK_FLAG: u8 = 1 << 4;
const OVER_FLAG: u8 = 1 << 6;
const NEG_FLAG: u8 = 1 << 7;

macro_rules! ids2strs {
    ($($x:ident), *) => {
        $(#[allow(non_upper_case_globals)]
            const $x: &str = stringify!($x);)*
    };
}

pub mod disasm {
    mod disops {
        make_optable!(OPS, &str);
        ids2strs!(adc, and, asl, bcc, bcs, beq, bit, bmi,
                  bne, bpl, brk, bvc, bvs, clc, cld, cli,
                  clv, cmp, cpx, cpy, dec, dex, dey, eor,
                  inc, inx, iny, jmp, jsr, lda, ldx, ldy,
                  lsr, nop, ora, pha, php, pla, plp, rol,
                  ror, rti, rts, sbc, sec, sed, sei, sta,
                  stx, sty, tax, tay, tsx, txa, txs, tya, nil);
    }
    
    mod disaddr {
        pub type T<'a, 'b> = &'a mut Iterator<Item=&'b u8>;
        make_addrtable!(ADDR_MODES, fn (T) -> String);
        fn acc(_code: T) -> String {
            "a".to_string()
        }
        fn imm(code: T) -> String {
            format!("#${:02x}", code.next().unwrap())
        }
        fn zpg(code: T) -> String {
            format!("${:02x}", code.next().unwrap())
        }
        fn zpx(code: T) -> String {
            format!("${:02x}, x", code.next().unwrap())
        }
        fn zpy(code: T) -> String {
            format!("${:02x}, y", code.next().unwrap())
        }
        fn rel(code: T) -> String {
            let b = *code.next().unwrap() as i8;
            if b >= 0 {
                format!("+${:02x}, x", b)
            } else {
                format!("-${:02x}, x", -b)
            }
        }
        fn abs(code: T) -> String {
            let low = *code.next().unwrap() as u16;
            let high = *code.next().unwrap() as u16;
            format!("${:04x}", (high << 8) | low)
        }
        fn abx(code: T) -> String {
            let low = *code.next().unwrap() as u16;
            let high = *code.next().unwrap() as u16;
            format!("${:04x}, x", (high << 8) | low)
        }
        fn aby(code: T) -> String {
            let low = *code.next().unwrap() as u16;
            let high = *code.next().unwrap() as u16;
            format!("${:04x}, y", (high << 8) | low)
        }
        fn ind(code: T) -> String {
            let low = *code.next().unwrap() as u16;
            let high = *code.next().unwrap() as u16;
            format!("(${:04x})", (high << 8) | low)
        }
        fn xin(code: T) -> String {
            format!("(${:02x}, x)", code.next().unwrap())
        }
        fn iny(code: T) -> String {
            format!("(${:02x}), y", code.next().unwrap())
        }
        fn nil(_code: T) -> String {
            "".to_string()
        }
    }

    pub struct Disassembler<'a, T> where T: Iterator<Item=&'a u8> {
        raw_code: T
    }
    
    impl<'a, T> Disassembler<'a, T> where T: Iterator<Item=&'a u8> {
        pub fn new(raw_code: T) -> Self {
            Disassembler{raw_code}
        }
        fn parse(opcode: u8, code: &mut T) -> String {
            format!("{} {}", disops::OPS[opcode as usize],
                             disaddr::ADDR_MODES[opcode as usize](code))
        }
    }
    
    impl<'a, T> Iterator for Disassembler<'a, T> where T: Iterator<Item=&'a u8> {
        type Item = String;
        fn next(&mut self) -> Option<Self::Item> {
            match self.raw_code.next() {
                Some(opcode) => Some(Disassembler::parse(*opcode, &mut self.raw_code)),
                None => None
            }
        }
    }
    
    pub fn parse(opcode: u8, code: &[u8]) -> String {
        Disassembler::parse(opcode, &mut code.iter())
    }
}

macro_rules! stack_addr {
    ($sp: ident, $disp: expr) => (($sp.wrapping_sub($disp) as u16) | 0x0100);
}

macro_rules! make16 {
    ($high: expr, $low: expr) => ((($high as u16) << 8) | ($low as u16));
}

macro_rules! read16 {
    ($mem: expr, $laddr: expr) => (make16!($mem.read($laddr.wrapping_add(1)),
                                                $mem.read($laddr)));
}

mod ops {
    use mos6502::*;
    make_optable!(OPS, fn (&mut CPU));

    macro_rules! check_zero {
        ($st: ident, $r: expr) => ($st |= (($r as u8 == 0) as u8) << 1);
    }
    macro_rules! check_neg {
        ($st: ident, $r: expr) => ($st |= ($r as u8 & NEG_FLAG) as u8);
    }

    /* arithmetic */

    fn adc(cpu: &mut CPU) {
        let opr1 = cpu.a as u16;
        let opr2 = match cpu.addr_mode {
                    AddrMode::Immediate => cpu.imm_val,
                    AddrMode::Accumulator => cpu.a,
                    AddrMode::EffAddr => cpu.mem.read(cpu.ea)
                    } as u16;
        let res = opr1 + opr2 + cpu.get_carry() as u16;
        let mut status = cpu.status & !(CARRY_FLAG | ZERO_FLAG | OVER_FLAG | NEG_FLAG);
        cpu.a = res as u8;
        status |= (res > 0xff) as u8; /* carry flag */
        check_zero!(status, res);
        status |= ((((opr1 ^ opr2) as u8 & 0x80) ^ 0x80) &
                     ((opr1 ^ res) as u8 & 0x80)) >> 1; /* over flag */
        check_neg!(status, res);
        cpu.status = status;
    }


    fn sbc(cpu: &mut CPU) {
        let opr1 = cpu.a as u16;
        let opr2 = match cpu.addr_mode {
                    AddrMode::Immediate => cpu.imm_val,
                    AddrMode::Accumulator => cpu.a,
                    AddrMode::EffAddr => cpu.mem.read(cpu.ea)
                    } as u16;
        let res = opr1 + (0xff - opr2) + cpu.get_carry() as u16;
        let mut status = cpu.status & !(CARRY_FLAG | ZERO_FLAG | OVER_FLAG | NEG_FLAG);
        cpu.a = res as u8;
        status |= (res > 0xff) as u8; /* carry flag */
        check_zero!(status, res);
        status |= (((opr1 ^ opr2) as u8 & 0x80) &
                    ((opr1 ^ res) as u8 & 0x80)) >> 1; /* over flag */
        check_neg!(status, res);
        cpu.status = status;
    }
    
    macro_rules! make_cmp {
        ($f: ident, $r: ident) => (fn $f(cpu: &mut CPU) {
            let opr1 = cpu.$r as u16;
            let opr2 = match cpu.addr_mode {
                        AddrMode::Immediate => cpu.imm_val,
                        _ => cpu.mem.read(cpu.ea)
                        } as u16;
            let res = opr1 - opr2;
            let mut status = cpu.status & !(CARRY_FLAG | ZERO_FLAG | NEG_FLAG);
            status |= (res < 0x100) as u8; /* carry flag */
            check_zero!(status, res);
            check_neg!(status, res);
            cpu.status = status;
        });
    }

    make_cmp!(cmp, a);
    make_cmp!(cpx, x);
    make_cmp!(cpy, y);

    /* increments & decrements */
    macro_rules! make_delta {
        ($f: ident, $d: expr) => (
            fn $f(cpu: &mut CPU) {
                let res = cpu.mem.read(cpu.ea) as u16 + $d;
                let mut status = cpu.status & !(ZERO_FLAG | NEG_FLAG);
                cpu.mem.write(cpu.ea, res as u8);
                check_zero!(status, res);
                check_neg!(status, res);
                cpu.status = status;
            });
        ($f: ident, $d: expr, $r: ident) => (
            fn $f(cpu: &mut CPU) {
                let res = cpu.$r as u16 + $d;
                let mut status = cpu.status & !(ZERO_FLAG | NEG_FLAG);
                cpu.$r = res as u8;
                check_zero!(status, res);
                check_neg!(status, res);
                cpu.status = status;
            });

    }

    make_delta!(dec, 0xff);
    make_delta!(dex, 0xff, x);
    make_delta!(dey, 0xff, y);
    make_delta!(inc, 0x01);
    make_delta!(inx, 0x01, x);
    make_delta!(iny, 0x01, y);

    /* logical */
    macro_rules! make_logic {
        ($f: ident, $op: tt) => (
        fn $f(cpu: &mut CPU) {
            let res = cpu.a $op match cpu.addr_mode {
                        AddrMode::Immediate => cpu.imm_val,
                        _ => cpu.mem.read(cpu.ea)
                    };
            let mut status = cpu.status & !(ZERO_FLAG | NEG_FLAG);
            cpu.a = res as u8;
            check_zero!(status, res);
            check_neg!(status, res);
            cpu.status = status;
        });
    }

    make_logic!(and, &);
    make_logic!(eor, ^);
    make_logic!(ora, |);

    fn bit(cpu: &mut CPU) {
        let m = cpu.mem.read(cpu.ea);
        let mut status = cpu.status & !(ZERO_FLAG | OVER_FLAG | NEG_FLAG);
        check_zero!(status, (m & cpu.a));
        status |= ((m >> 6) & 0x3) << 6; /* copy bit 6 & 7 */
        cpu.status = status;
    }

    /* shifts */
    fn asl(cpu: &mut CPU) {
        let res = match cpu.addr_mode {
                    AddrMode::Accumulator => {
                        let t = (cpu.a as u16) << 1;
                        cpu.a = t as u8;
                        t
                    },
                    _ => {
                        let t = (cpu.mem.read(cpu.ea) as u16) << 1;
                        cpu.mem.write(cpu.ea, t as u8);
                        t
                    }};
        let mut status = cpu.status & !(CARRY_FLAG | ZERO_FLAG | NEG_FLAG);
        status |= (res >> 8) as u8; /* carry flag */
        check_zero!(status, res);
        check_neg!(status, res);
        cpu.status = status;
    }
    
    fn lsr(cpu: &mut CPU) {
        let mut status = cpu.status & !(CARRY_FLAG | ZERO_FLAG | NEG_FLAG);
        let res = match cpu.addr_mode {
                    AddrMode::Accumulator => {
                        let old = cpu.a;
                        let t = old >> 1;
                        cpu.a = t as u8;
                        status |= (old & 1) as u8; /* carry flag */
                        t
                    },
                    _ => {
                        let old = cpu.mem.read(cpu.ea);
                        let t = old >> 1;
                        cpu.mem.write(cpu.ea, t as u8);
                        status |= (old & 1) as u8; /* carry flag */
                        t
                    }};
        check_zero!(status, res);
        check_neg!(status, res);
        cpu.status = status;
    }

    fn rol(cpu: &mut CPU) {
        let mut status = cpu.status & !(CARRY_FLAG | ZERO_FLAG | NEG_FLAG);
        let res = match cpu.addr_mode {
                    AddrMode::Accumulator => {
                        let old = cpu.a;
                        let t = old.rotate_left(1);
                        cpu.a = t as u8;
                        status |= old >> 7 as u8; /* carry flag */
                        t
                    },
                    _ => {
                        let old = cpu.mem.read(cpu.ea);
                        let t = old.rotate_left(1);
                        cpu.mem.write(cpu.ea, t as u8);
                        status |= old >> 7 as u8; /* carry flag */
                        t
                    }};
        check_zero!(status, res);
        check_neg!(status, res);
        cpu.status = status;
    }

    fn ror(cpu: &mut CPU) {
        let mut status = cpu.status & !(CARRY_FLAG | ZERO_FLAG | NEG_FLAG);
        let res = match cpu.addr_mode {
                    AddrMode::Accumulator => {
                        let old = cpu.a;
                        let t = old.rotate_right(1);
                        cpu.a = t as u8;
                        status |= old & 1 as u8; /* carry flag */
                        t
                    },
                    _ => {
                        let old = cpu.mem.read(cpu.ea);
                        let t = old.rotate_right(1);
                        cpu.mem.write(cpu.ea, t as u8);
                        status |= old & 1 as u8; /* carry flag */
                        t
                    }};
        check_zero!(status, res);
        check_neg!(status, res);
        cpu.status = status;
    }

    /* branches */
    macro_rules! make_branch_clear {
        ($f: ident, $e: ident) => (fn $f(cpu: &mut CPU) {
            match cpu.$e() {
                0 => {
                    cpu.pc = cpu.ea;
                    cpu.cycle += 1;
                },
                _ => ()
            }});
    }

    macro_rules! make_branch_set {
        ($f: ident, $e: ident) => (fn $f(cpu: &mut CPU) {
            match cpu.$e() {
                0 => (),
                _ => {
                    cpu.pc = cpu.ea;
                    cpu.cycle += 1;
                }
            }});
    }

    make_branch_clear!(bcc, get_carry);
    make_branch_set!(bcs, get_carry);
    make_branch_clear!(bne, get_zero);
    make_branch_set!(beq, get_zero);
    make_branch_clear!(bpl, get_neg);
    make_branch_set!(bmi, get_neg);
    make_branch_clear!(bvc, get_over);
    make_branch_set!(bvs, get_over);

    fn brk(cpu: &mut CPU) {
        let pc = cpu.pc;
        let sp = cpu.sp;
        cpu.mem.write(stack_addr!(sp, 0), (pc >> 8) as u8); /* push high pc */
        cpu.mem.write(stack_addr!(sp, 1), pc as u8); /* push low pc */
        cpu.status |= BRK_FLAG;
        cpu.mem.write(stack_addr!(sp, 2), cpu.status); /* push status */
        cpu.status |= INT_FLAG;
        cpu.sp = sp.wrapping_sub(3);
        cpu.pc = read16!(cpu.mem, BRK_VECTOR); /* load the interrupt vector */
    }

    /* status flag changes */
    fn clc(cpu: &mut CPU) { cpu.status &= !CARRY_FLAG; }
    fn cld(cpu: &mut CPU) { cpu.status &= !DEC_FLAG; }
    fn cli(cpu: &mut CPU) { cpu.status &= !INT_FLAG; }
    fn clv(cpu: &mut CPU) { cpu.status &= !OVER_FLAG; }

    fn sec(cpu: &mut CPU) { cpu.status |= CARRY_FLAG; }
    fn sed(cpu: &mut CPU) { cpu.status |= DEC_FLAG; }
    fn sei(cpu: &mut CPU) { cpu.status |= INT_FLAG; }

    /* jumps & calls */
    fn jmp(cpu: &mut CPU) { cpu.pc = cpu.ea; }

    fn jsr(cpu: &mut CPU) {
        let sp = cpu.sp;
        let pc = cpu.pc.wrapping_sub(1);
        cpu.mem.write(stack_addr!(sp, 0), (pc >> 8) as u8);
        cpu.mem.write(stack_addr!(sp, 1), pc as u8);
        cpu.sp = sp.wrapping_sub(2);
        cpu.pc = cpu.ea;
    }

    fn rts(cpu: &mut CPU) {
        let sp = cpu.sp.wrapping_add(2);
        cpu.pc = make16!(cpu.mem.read(stack_addr!(sp, 0)),
                        cpu.mem.read(stack_addr!(sp, 1))).wrapping_add(1);
        cpu.sp = sp;
    }

    /* system functions */
    fn rti(cpu: &mut CPU) {
        let sp = cpu.sp.wrapping_add(3);
        cpu.status = cpu.mem.read(stack_addr!(sp, 2));
        cpu.pc = make16!(cpu.mem.read(stack_addr!(sp, 0)),
                        cpu.mem.read(stack_addr!(sp, 1)));
        cpu.sp = sp;
    }

    fn nop(_cpu: &mut CPU) {}
    
    /* load/store operations */
    macro_rules! make_ld {
        ($f: ident, $r: ident) => (fn $f(cpu: &mut CPU) {
            let mut status = cpu.status & !(ZERO_FLAG | NEG_FLAG);
            let res = cpu.mem.read(cpu.ea);
            cpu.a = res;
            check_zero!(status, res);
            check_neg!(status, res);
            cpu.status = status;
        });
    }

    make_ld!(lda, a);
    make_ld!(ldx, x);
    make_ld!(ldy, y);

    macro_rules! make_st {
        ($f: ident, $r: ident) => (fn $f(cpu: &mut CPU) {
            cpu.mem.write(cpu.ea, cpu.$r);
        });
    }

    make_st!(sta, a);
    make_st!(stx, x);
    make_st!(sty, y);

    /* register transfers */
    macro_rules! make_trans {
        ($f: ident, $from: ident, $to: ident) => (fn $f(cpu: &mut CPU) {
            let mut status = cpu.status & !(ZERO_FLAG | NEG_FLAG);
            let res = cpu.$from;
            cpu.$to = res; 
            check_zero!(status, res);
            check_neg!(status, res);
            cpu.status = status;
        });
    }

    make_trans!(tax, a, x);
    make_trans!(tay, a, y);
    make_trans!(txa, x, a);
    make_trans!(tya, y, a);

    /* stack operations */
    make_trans!(tsx, sp, x);
    fn txs(cpu: &mut CPU) { cpu.sp = cpu.x; }

    fn pha(cpu: &mut CPU) {
        let sp = cpu.sp;
        cpu.mem.write(stack_addr!(sp, 0), cpu.a);
        cpu.sp = sp.wrapping_sub(1);
    }

    fn php(cpu: &mut CPU) {
        let sp = cpu.sp;
        cpu.mem.write(stack_addr!(sp, 0), cpu.status);
        cpu.sp = sp.wrapping_sub(1);
    }

    fn pla(cpu: &mut CPU) {
        let mut status = cpu.status & !(ZERO_FLAG | NEG_FLAG);
        let sp = cpu.sp.wrapping_add(1);
        let res = cpu.mem.read(stack_addr!(sp, 0));
        cpu.a = res;
        cpu.sp = sp;
        check_zero!(status, res);
        check_neg!(status, res);
        cpu.status = status;
    }

    fn plp(cpu: &mut CPU) {
        let sp = cpu.sp.wrapping_add(1);
        cpu.status = cpu.mem.read(stack_addr!(sp, 0));
        cpu.sp = sp;
    }

    fn nil(cpu: &mut CPU) {
        panic!("invalid instruction: 0x{:02x}", cpu.mem.read(cpu.pc));
    }
}

mod addr {
    use mos6502::{CPU, AddrMode};
    make_addrtable!(ADDR_MODES, fn (&mut CPU));

    fn acc(cpu: &mut CPU) {
        cpu.addr_mode = AddrMode::Accumulator;
    }

    fn imm(cpu: &mut CPU) {
        cpu.addr_mode = AddrMode::Immediate;
        cpu.imm_val = cpu.mem.read(cpu.opr);
    }

    fn zpg(cpu: &mut CPU) {
        cpu.addr_mode = AddrMode::EffAddr;
        cpu.ea = cpu.mem.read(cpu.opr) as u16;
    }

    fn zpx(cpu: &mut CPU) {
        cpu.addr_mode = AddrMode::EffAddr;
        cpu.ea = (cpu.mem.read(cpu.opr)
                        .wrapping_add(cpu.x)) as u16;
    }

    fn zpy(cpu: &mut CPU) {
        cpu.addr_mode = AddrMode::EffAddr;
        cpu.ea = (cpu.mem.read(cpu.opr)
                        .wrapping_add(cpu.y)) as u16;
    }

    fn rel(cpu: &mut CPU) {
        cpu.addr_mode = AddrMode::EffAddr;
        let base = cpu.pc;
        let offset = cpu.mem.read(cpu.opr) as i8 as i16;
        let sum = (base & 0xff) + offset as u16;
        cpu.ea = (base & 0xff00).wrapping_add(sum);
        cpu.cycle += (sum >> 8) as u32;
    }

    fn abs(cpu: &mut CPU) {
        cpu.addr_mode = AddrMode::EffAddr;
        cpu.ea = read16!(cpu.mem, cpu.opr);
    }

    fn abx(cpu: &mut CPU) {
        cpu.addr_mode = AddrMode::EffAddr;
        let base = read16!(cpu.mem, cpu.opr);
        let sum = (base & 0xff) + cpu.x as u16;
        cpu.ea = (base & 0xff00).wrapping_add(sum);
        cpu.cycle += (sum >> 8) as u32; /* boundary cross if carry */
    }

    fn aby(cpu: &mut CPU) {
        cpu.addr_mode = AddrMode::EffAddr;
        let base = read16!(cpu.mem, cpu.opr);
        let sum = (base & 0xff) + cpu.y as u16;
        cpu.ea = (base & 0xff00).wrapping_add(sum);
        cpu.cycle += (sum >> 8) as u32; /* boundary cross if carry */
    }

    fn ind(cpu: &mut CPU) {
        cpu.addr_mode = AddrMode::EffAddr;
        let addr = read16!(cpu.mem, cpu.opr);
        cpu.ea = read16!(cpu.mem, addr);
    }

    fn xin(cpu: &mut CPU) {
        cpu.addr_mode = AddrMode::EffAddr;
        let addr = cpu.mem.read(
                    cpu.mem.read(cpu.opr)
                        .wrapping_add(cpu.x) as u16) as u16;
        cpu.ea = read16!(cpu.mem, addr);
    }

    fn iny(cpu: &mut CPU) {
        cpu.addr_mode = AddrMode::EffAddr;
        let addr = cpu.mem.read(cpu.mem.read(cpu.opr) as u16) as u16;
        let base = read16!(cpu.mem, addr);
        let sum = (base & 0xff) + cpu.y as u16;
        cpu.ea = (base & 0xff00).wrapping_add(sum);
        cpu.cycle += (sum >> 8) as u32; /* boundary cross if carry */
    }

    fn nil(_cpu: &mut CPU) {}
}

enum AddrMode {
    Immediate,
    Accumulator,
    EffAddr
}

enum IntType {
    NMI,
    IRQ
}

pub struct CPU<'a> {
    /* registers */
    a: u8,
    x: u8,
    y: u8,
    status: u8,
    pc: u16,
    sp: u8,
    /* internal state */
    addr_mode: AddrMode,
    opr: u16,
    ea: u16,    /* effective address */
    imm_val: u8,
    pub cycle: u32,
    int: Option<IntType>,
    pub mem: &'a mut VMem
}

macro_rules! make_int {
    ($f:ident, $v: expr) => (
    fn $f(&mut self) {
        let pc = self.pc;
        let sp = self.sp;
        self.mem.write(stack_addr!(sp, 0), (pc >> 8) as u8);
        self.mem.write(stack_addr!(sp, 1), pc as u8);
        self.mem.write(stack_addr!(sp, 2), self.status);
        self.sp = sp.wrapping_sub(3);
        self.pc = read16!(self.mem, $v as u16);
        self.status |= INT_FLAG;
        self.cycle += 7;
    });
}

impl<'a> CPU<'a> {
    #[inline(always)] pub fn get_carry(&self) -> u8 { (self.status >> 0) & 1 }
    #[inline(always)] pub fn get_zero(&self) -> u8 { (self.status >> 1) & 1 }
    #[inline(always)] pub fn get_int(&self) -> u8 { (self.status >> 2) & 1 }
    #[inline(always)] pub fn get_over(&self) -> u8 { (self.status >> 6) & 1 }
    #[inline(always)] pub fn get_neg(&self) -> u8 { (self.status >> 7) & 1 }

    pub fn new(mem: &'a mut VMem) -> Self {
        let pc = read16!(mem, RESET_VECTOR as u16);
        /* nes power up state */
        let a = 0;
        let x = 0;
        let y = 0;
        let sp = 0xfd;
        let status = 0x34;
        let cycle = 0;

        CPU{a, x, y,
            pc, sp, status, cycle,
            opr: 0, ea: 0, imm_val: 0,
            int: None,
            addr_mode: AddrMode::EffAddr,
            mem}
    }

    make_int!(nmi, NMI_VECTOR);
    make_int!(irq, IRQ_VECTOR);

    pub fn step(&mut self) {
        let pc = self.pc;
        match self.int {
            Some(IntType::NMI) => self.nmi(),
            Some(IntType::IRQ) => self.irq(),
            _ => ()
        }
        self.int = None;
        let opcode = self.mem.read(pc) as usize;
        /* update opr pointing to operands of current inst */
        self.opr = pc.wrapping_add(1);
        /* update program counter pointing to next inst */
        self.pc = pc.wrapping_add(INST_LENGTH[opcode] as u16);
        /* get effective address based on addressing mode */
        addr::ADDR_MODES[opcode](self);
        /* execute the inst */
        ops::OPS[opcode](self);
        self.cycle += INST_CYCLE[opcode] as u32;
    }

    pub fn powerup(&mut self) {
        self.pc = read16!(self.mem, RESET_VECTOR as u16);
        /* nes power up state */
        self.a = 0;
        self.x = 0;
        self.y = 0;
        self.sp = 0xfd;
        self.status = 0x34;
        self.cycle = 0;
    }

    pub fn reset(&mut self) {
        self.pc = read16!(self.mem, RESET_VECTOR as u16);
        self.sp = self.sp.wrapping_sub(3);
        self.status |= INT_FLAG;
        self.cycle = 0;
        self.int = None;
    }

    pub fn trigger_nmi(&mut self) {
        self.int = Some(IntType::NMI);
    }

    pub fn trigger_irq(&mut self) {
        if self.get_int() == 0 {
            self.int = Some(match self.int {
                Some(IntType::NMI) => IntType::NMI,
                _ => IntType::IRQ
            });
        }
    }
}