It is currently Mon Oct 16, 2017 7:12 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 19 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: I created an emulator...
PostPosted: Wed Dec 07, 2011 9:40 am 
Offline
User avatar

Joined: Fri Oct 14, 2011 1:09 am
Posts: 248
I created a NES emulator.

Target: YouTube.

Umm, what?

My goal was to produce an emulator that has as compact source code as possible, so that it -- the source code -- can be showcased within a 15 minutes of a YouTube video that is not too much sped up that it cannot be read without pausing.

Regular readers of this forum have already seen bits and pieces of it in my posts.

The finished* emulator is 940 lines long, and has the following features:
- Cycle accurate and memory access accurate CPU emulation with unofficial opcodes
- Cycle accurate PPU which passes a satisfying number of tests by Blargg (though not all)
- Accuracy-oriented emulation of the five-channel APU (actual accuracy not verified, but it sounds good)
- Additional FCEUX compatibility by preinitializing RAM in a certain manner
- iNES mappers 0, 1, 2, 3 and 7
- RGB colors synthesized through NTSC modem (modulation + demodulation), with subpixel-precision NTSC artifacts (color/luma leak, marching ants) (window resizing / scaling not implemented)
- Input is read from a Famtasia movie file (joypad/keyboard is ignored completely)
- Sound is produced through an external program / combination of external programs (synchronization may be an issue)

*) Finished, as in the version that I felt is good enough to publish on YouTube given the goals stated earier.

You can see the emulator here. In order to acquire a copy, you will have to type the source code as seen in the video, or look at the movie description and spot the URL.

- http://youtu.be/y71lli8MS8s -- Part 1/2: Programming
- http://youtu.be/XZWw745wPXY -- Part 2/2: Compiling & demonstration (replays a few TASes)
Part 2/2 is still uploading as I type this post, but it should be watchable in about 6 hours (at the turn of December 8th in UTC).

Screenshot (YouTube caps at 30 fps, so I used motion blur for fps reduction):

Image


Last edited by Bisqwit on Wed Dec 07, 2011 10:56 am, edited 2 times in total.

Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 07, 2011 10:24 am 
Offline
User avatar

Joined: Wed Dec 06, 2006 8:18 pm
Posts: 2799
Interesting. I will have to see Part 2 when it's uploaded.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 07, 2011 11:51 am 
Offline

Joined: Wed Jul 04, 2007 8:40 am
Posts: 36
That's a very entertaining video! Much better than the source code credit scroll I was expecting. The on-screen explanations are a very nice touch.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 07, 2011 3:26 pm 
Offline
User avatar

Joined: Mon Apr 04, 2011 11:49 am
Posts: 1905
Location: WhereverIparkIt, USA
This is VERY impressive. I would have never imagined it could be done in under 1000 lines of code. It's pretty memorizing to watch makes it LOOK easy and fun.

Nice work!


Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 07, 2011 5:07 pm 
Offline
User avatar

Joined: Thu Sep 23, 2010 7:28 pm
Posts: 232
bad ass, Bisqwit! this is pro.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Dec 08, 2011 12:50 am 
Offline
User avatar

Joined: Mon Jan 03, 2005 10:36 am
Posts: 2961
Location: Tampere, Finland
Very cool, sir. BTW what TAS movies/games depend on the RAM initialization state?

_________________
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: kkfos.aspekt.fi


Top
 Profile  
 
 Post subject:
PostPosted: Thu Dec 08, 2011 12:59 am 
Offline
User avatar

Joined: Fri Oct 14, 2011 1:09 am
Posts: 248
thefox wrote:
Very cool, sir. BTW what TAS movies/games depend on the RAM initialization state?

I seem to remember that I could not get the Wizards & Warriors TAS to sync before I added the RAM initialization. It could have been caused by something else, but that's what I did that made the difference.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Dec 08, 2011 1:13 am 
Offline
User avatar

Joined: Fri Nov 19, 2004 7:35 pm
Posts: 3940
Metroid refuses to boot if the CHR-RAM is really dirty. For a while, FCEUX was not initializing CHR-RAM to anything, so Metroid would often refuse to boot.

_________________
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!


Top
 Profile  
 
 Post subject:
PostPosted: Thu Dec 08, 2011 1:20 am 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 3192
Location: Mountain View, CA, USA
http://bisqwit.iki.fi/jutut/kuvat/progr ... nesemu1.cc

All I have to say is this, as I believe it speaks for itself (I hope you get my sarcasm -- and yes, I am well aware that you stated your goal was "to produce an emulator that has as compact source code as possible" -- I'm hearing you on FM):

Code:
void Ins()       // With template magic, the compiler will literally synthesize >256 different functions.
    {
        // Note: op 0x100 means "NMI", 0x101 means "Reset", 0x102 means "IRQ". They are implemented in terms of "BRK".
        // User is responsible for ensuring that WB() will not store into memory while Reset is being processed.
        unsigned addr=0, d=0, t=0xFF, c=0, sb=0, pbits = op<0x100 ? 0x30 : 0x20;

        // Define the opcode decoding matrix, which decides which micro-operations constitute
        // any particular opcode. (Note: The PLA of 6502 works on a slightly different principle.)
        enum { o8 = op/8, o8m = 1 << (op%8) };
        // Fetch op'th item from a bitstring encoded in a data-specific variant of base64,
        // where each character transmits 8 bits of information rather than 6.
        // This peculiar encoding was chosen to reduce the source code size.
        // Enum temporaries are used in order to ensure compile-time evaluation.
        #define t(s,code) { enum { \
            i=o8m & (s[o8]>90 ? (130+" (),-089<>?BCFGHJLSVWZ[^hlmnxy|}"[s[o8]-94]) \
                              : (s[o8]-" (("[s[o8]/39])) }; if(i) { code; } }

        /* Decode address operand */
        t("                                !", addr = 0xFFFA) // NMI vector location
        t("                                *", addr = 0xFFFC) // Reset vector location
        t("!                               ,", addr = 0xFFFE) // Interrupt vector location
        t("zy}z{y}zzy}zzy}zzy}zzy}zzy}zzy}z ", addr = RB(PC++))
        t("2 yy2 yy2 yy2 yy2 XX2 XX2 yy2 yy ", d = X) // register index
        t("  62  62  62  62  om  om  62  62 ", d = Y)
        t("2 y 2 y 2 y 2 y 2 y 2 y 2 y 2 y  ", addr=u8(addr+d); d=0; tick())              // add zeropage-index
        t(" y z!y z y z y z y z y z y z y z ", addr=u8(addr);   addr+=256*RB(PC++))       // absolute address
        t("3 6 2 6 2 6 286 2 6 2 6 2 6 2 6 /", addr=RB(c=addr); addr+=256*RB(wrap(c,c+1)))// indirect w/ page wrap
        t("  *Z  *Z  *Z  *Z      6z  *Z  *Z ", Misfire(addr, addr+d)) // abs. load: extra misread when cross-page
        t("  4k  4k  4k  4k  6z      4k  4k ", RB(wrap(addr, addr+d)))// abs. store: always issue a misread
        /* Load source operand */
        t("aa__ff__ab__,4  ____ -  ____     ", t &= A) // Many operations take A or X as operand. Some try in
        t("                knnn     4  99   ", t &= X) // error to take both; the outcome is an AND operation.
        t("                9989    99       ", t &= Y) // sty,dey,iny,tya,cpy
        t("                       4         ", t &= S) // tsx, las
        t("!!!!  !!  !!  !!  !   !!  !!  !!/", t &= P.raw|pbits; c = t)// php, flag test/set/clear, interrupts
        t("_^__dc___^__            ed__98   ", c = t; t = 0xFF)        // save as second operand
        t("vuwvzywvvuwvvuwv    zy|zzywvzywv ", t &= RB(addr+d)) // memory operand
        t(",2  ,2  ,2  ,2  -2  -2  -2  -2   ", t &= RB(PC++))   // immediate operand
        /* Operations that mogrify memory operands directly */
        t("    88                           ", P.V = t & 0x40; P.N = t & 0x80) // bit
        t("    nink    nnnk                 ", sb = P.C)       // rol,rla, ror,rra,arr
        t("nnnknnnk     0                   ", P.C = t & 0x80) // rol,rla, asl,slo,[arr,anc]
        t("        nnnknink                 ", P.C = t & 0x01) // lsr,sre, ror,rra,asr
        t("ninknink                         ", t = (t << 1) | (sb * 0x01))
        t("        nnnknnnk                 ", t = (t >> 1) | (sb * 0x80))
        t("                 !      kink     ", t = u8(t - 1))  // dec,dex,dey,dcp
        t("                         !  khnk ", t = u8(t + 1))  // inc,inx,iny,isb
        /* Store modified value (memory) */
        t("kgnkkgnkkgnkkgnkzy|J    kgnkkgnk ", WB(addr+d, t))
        t("                   q             ", WB(wrap(addr, addr+d), t &= ((addr+d) >> 8))) // [shx,shy,shs,sha?]
        /* Some operations used up one clock cycle that we did not account for yet */
        t("rpstljstqjstrjst - - - -kjstkjst/", tick()) // nop,flag ops,inc,dec,shifts,stack,transregister,interrupts
        /* Stack operations and unconditional jumps */
        t("     !  !    !                   ", tick(); t = Pop())                        // pla,plp,rti
        t("        !   !                    ", RB(PC++); PC = Pop(); PC |= (Pop() << 8)) // rti,rts
        t("            !                    ", RB(PC++))  // rts
        t("!   !                           /", d=PC+(op?-1:1); Push(d>>8); Push(d))      // jsr, interrupts
        t("!   !    8   8                  /", PC = addr) // jmp, jsr, interrupts
        t("!!       !                      /", Push(t))   // pha, php, interrupts
        /* Bitmasks */
        t("! !!  !!  !!  !!  !   !!  !!  !!/", t = 1)
        t("  !   !                   !!  !! ", t <<= 1)
        t("! !   !   !!  !!       !   !   !/", t <<= 2)
        t("  !   !   !   !        !         ", t <<= 4)
        t("   !       !           !   !____ ", t = u8(~t)) // sbc, isb,      clear flag
        t("`^__   !       !               !/", t = c | t)  // ora, slo,      set flag
        t("  !!dc`_  !!  !   !   !!  !!  !  ", t = c & t)  // and, bit, rla, clear/test flag
        t("        _^__                     ", t = c ^ t)  // eor, sre
        /* Conditional branches */
        t("      !       !       !       !  ", if(t)  { tick(); Misfire(PC, addr = s8(addr) + PC); PC=addr; })
        t("  !       !       !       !      ", if(!t) { tick(); Misfire(PC, addr = s8(addr) + PC); PC=addr; })
        /* Addition and subtraction */
        t("            _^__            ____ ", c = t; t += A + P.C; P.V = (c^t) & (A^t) & 0x80; P.C = t & 0x100)
        t("                        ed__98   ", t = c - t; P.C = ~t & 0x100) // cmp,cpx,cpy, dcp, sbx
        /* Store modified value (register) */
        t("aa__aa__aa__ab__ 4 !____    ____ ", A = t)
        t("                    nnnn 4   !   ", X = t) // ldx, dex, tax, inx, tsx,lax,las,sbx
        t("                 !  9988 !       ", Y = t) // ldy, dey, tay, iny
        t("                   4   0         ", S = t) // txs, las, shs
        t("!  ! ! !!  !   !       !   !   !/", P.raw = t & ~0x30) // plp, rti, flag set/clear
        /* Generic status flag updates */
        t("wwwvwwwvwwwvwxwv 5 !}}||{}wv{{wv ", P.N = t & 0x80)
        t("wwwv||wvwwwvwxwv 5 !}}||{}wv{{wv ", P.Z = u8(t) == 0)
        t("             0                   ", P.V = (((t >> 5)+1)&2))         // [arr]
        /* All implemented opcodes are cycle-accurate and memory-access-accurate.
         * [] means that this particular separate rule exists only to provide the indicated unofficial opcode(s).
         */
    }


EDIT: Oh, I guess I'll include another bit.

Code:
    void Op()
    {
        /* Check the state of NMI flag */
        bool nmi_now = nmi;

        unsigned op = RB(PC++);

        if(reset)                              { op=0x101; }
        else if(nmi_now && !nmi_edge_detected) { op=0x100; nmi_edge_detected = true; }
        else if(intr && !P.I)                  { op=0x102; }
        if(!nmi_now) nmi_edge_detected=false;

        // Define function pointers for each opcode (00..FF) and each interrupt (100,101,102)
        #define c(n) Ins<0x##n>,Ins<0x##n+1>,
        #define o(n) c(n)c(n+2)c(n+4)c(n+6)
        static void(*const i[0x108])() =
        {
            o(00)o(08)o(10)o(18)o(20)o(28)o(30)o(38)
            o(40)o(48)o(50)o(58)o(60)o(68)o(70)o(78)
            o(80)o(88)o(90)o(98)o(A0)o(A8)o(B0)o(B8)
            o(C0)o(C8)o(D0)o(D8)o(E0)o(E8)o(F0)o(F8) o(100)
        };
        #undef o
        #undef c
        i[op]();

        reset = false;
    }


Top
 Profile  
 
 Post subject:
PostPosted: Thu Dec 08, 2011 3:14 am 
Offline
Formerly Fx3
User avatar

Joined: Fri Nov 12, 2004 4:59 pm
Posts: 3064
Location: Brazil
Image
I know some of you don't like such images, but it's just a bit of humor, needless to say, eh?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Dec 08, 2011 1:47 pm 
Offline
User avatar

Joined: Mon Sep 27, 2004 2:13 pm
Posts: 1667
Location: .ma.us
http://www.ioccc.org/ :P


Top
 Profile  
 
 Post subject:
PostPosted: Thu Dec 08, 2011 2:17 pm 
Offline
User avatar

Joined: Fri Oct 14, 2011 1:09 am
Posts: 248
kyuusaku wrote:
http://www.ioccc.org/ :P

2006 winner entries are finally available? Oh goodie! Thanks for telling.

EDIT: Oh, and in all honesty, I was partially inspired by the 2005 winner that does a 6502 emulator (Commodore PET). But both algorithmically and structurally my emulator is very different. Mr. Sykes's emulator used addressing mode & operation tables and switch-cases, albeit constructed from ?: operators. I studied his submission in great detail some time in 2009, deobfuscating it entirely. I doubt his approach would have worked well with my goals of accuracy and feature-completeness.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Dec 09, 2011 12:26 am 
Offline

Joined: Fri Aug 22, 2008 10:04 am
Posts: 81
cry


Top
 Profile  
 
 Post subject:
PostPosted: Fri Dec 09, 2011 4:58 am 
Offline
Formerly Fx3
User avatar

Joined: Fri Nov 12, 2004 4:59 pm
Posts: 3064
Location: Brazil
sleepy9090 wrote:
cry


I AM ERROR.

_________________
Zepper
RockNES developer


Top
 Profile  
 
 Post subject:
PostPosted: Tue Dec 13, 2011 9:14 pm 
Offline
User avatar

Joined: Sat Jun 27, 2009 11:05 pm
Posts: 710
Location: New Mexico, USA
This is incredibly cool - and very impressive.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 19 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: *Spitfire_NES* and 6 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group