As the third (and probably final) instalment in this saga I am attaching the third version of this Hydra-NES simulator. The primary upgrade for this version is the inclusion of sound. This means emulating the NES Audio Processing Unit (APU). Adding code for another cog meant that the emulation code (PPU, APU, tv driver, emulator) couldn't fit in the Hydra main memory at the same time as the ROM image (even for these 16KB games). The work-around in this version is that the game ROM is loaded into the Hydra's EEPROM and downloaded when the main emulator starts. This also allows several game ROMs to be held in the Hydra's EEPROM ready to go. I also added a "Chooser" routine which allows you to choose which ROM to play from those in the EEPROM. There are no more games included in this version (just the three in the previous version) plus one more short file which is basically an NES sound demo which I found on the Internet and which demonstrates that the APU emulator works (more or less).
For this to work you need to do the following:
1. Unzip the attached file.
2. Load the Propellor tool and click on "NES_EEPROM_loader_001.spin". This spin file includes (as an object) whatever ROM you have left uncommented in (around) line 83.
3. Load the EEPROM loader program into the Hydra with F10 and then (when it is running on the Hydra) press "A" on the game controller. This loads the ROM image that you have selected into the Hydra's EEPROM.
4. Go back to the Propellor tool and uncomment the next ROM (around line 83 again) (of course, dont forget to comment out the first one).
5. Again press F10 and then, when the EEPROM-loader program loads into the Hydra, press "A". Repeat this process until you have loaded all the ROMs that you want (or that will fit) into the available EEPROM. If something goes wrong and you want to start again, press the "B" button on the Game Controller and the number of ROMs loaded resets to zero and you have to start again. You will notice that the font changes each time you load a different ROM - this is because the font (the tilemap) is specific to each game and is loaded with the ROM image and there isn't enough space to have a separate font (tilemap) along with the EEPROM loader program and the ROM image itself.
Anyway, once you have done all this you should see the Hydra screen something like the screenshot below.
6. You are now ready to start playing the games. Click on "NES_Game_emulator_012.spin" and load it into the Hydra with F10. The screenshot below shows what should come up. You can select the game you want with the up and down arrows on the game controller. Then press "A" on the game controller and the game will download from the EEPROM into the Hydra memory and the emulation will start. You will see some garbage on the screen for a second or two (since the ROM download to main Hydra memory overwrites the memory that is temporarily used for the screen display). All going well this should disappear very quickly. Once you start playing you should be able to hear the sound.
There are also screenshots from Pacman and Galaga.
There are still some quirks and things that are not quite right. The sound emulation is good in some respects but you may hear things that don't quite sound like the original. (Particularly, the music in Donkey Kong sounds a bit odd!). Oh well... It takes so much time to get it right.
If anyone wants to persevere getting another ROM working there is the ability to run the emulator in step-by-step mode - so you can see what is happening in all of the registers and in the memory of the NES. You can also set a breakpoint and have the emulation stop when it reaches the breakpoint. However, there aren't enough cogs to run the display full speed/have sound/and run the debugger, so if you want to debug the game you have to give up either some sprites or sound(!).
Thanks·for the positive feedback and comments. I don't read this forum very often so if you want me to respond, email: darryl.biggar@stanfordalumni.org .
Awesome program...however, the rolling screen is a problem...has anyone fixed this...it looks like it the timing is off in the tv.spin program.· Are there too many vertical lines...or is the counter off?
This is the first chance I have had to try this and I was really impressed! I couldn't believe it ran at or close to full speed! I had rename a file to search for the EEPROM tile set instead of pacman tileset, but after that it worked ok. The only oddities I have noticed is that on Pacman on the intermission stage the colors are off, and in Donkey Kong the rivet level was missing the ladder graphics (maybe they were just black?) and the hammer colors were off.
I'm trying to port this over to the El' Jugador, but the assembly in the emulation core to set to read the NES controller on 3,4,5,6 instead of 23,24,25,26 as the El' Jugador uses.
Could someone fluent in Propeller assembly lend a hand?
The relevant section from: NES_emulator_core_10.spin
Joystick_read cmp ptr, #$16wz' $4016 readif_nzjmp #rdmem_ret
mov _DB, INA' read all 32-bits of input including gamepadsorOUTA, #%000001000' JOY_CLK = 1shr _DB, #5' shift joystick data to bit 1xor _DB, #1and _DB, #1'****1andOUTA,#%111110111' JOY_CLK = 0jmp #rdmem_ret
'
wrmem mov ptr, _AB '' cmp ptr,breakpoint wz' if_z mov emu_mode,#0testn ptr, RAMmask wzif_zadd ptr,RAMbase
if_zjmp #write_ret
cmp ptr,H4000 wc,wzif_aejmp #H4000_write
and ptr,#7cmp ptr,#3wz,wcif_badd ptr,PPUctrl
if_bjmp #write_ret
if_zmov OAMaddr,_DB ' $2003 _DB -> OAMoffsetif_zjmp #wrmem_ret
cmp ptr,#4wzif_zrdword ptr,sprite_data_ptr ' $2004 _DB -> SpriteMem[OAMoffset]if_zadd ptr,OAMaddr
if_zjmp #write_ret
cmp ptr,#5wzif_zrdword ptr,xscroll
if_zshl ptr,#8if_zadd ptr,_DB
if_zwrword ptr,xscroll
if_zjmp #wrmem_ret ' 2005 - not used so save the spacecmp ptr,#6wzif_zshl VRAMaddress,#8' $2006 _DB -> upper or lower byte of VRAMaddressif_zadd VRAMaddress,_DB
if_zand VRAMaddress,OOOOFFFF
if_zjmp #wrmem_ret
mov temp,VRAMaddress ' $2007 _DB -> PatternTableMem[VRAMaddress]cmp temp,H3F00 wc,wzif_aejmp #H3F00_write
' test temp,H2000 wz' if_z rdword ptr,pattern_table_ptr' if_z jmp #write1test temp,H0800 wzand temp,H03FF
if_nzadd temp,H0400
rdword ptr,name_table_ptr
write1 add ptr,temp
rdbyte temp,PPUctrl
test temp,#4wzif_zadd VRAMaddress,#1if_nzadd VRAMaddress,#32
write_ret wrbyte _DB, ptr
wrmem_ret ret
H3F00_write and temp,#31rdword ptr,pallette_ptr
' mov emu_mode,#0jmp #write1
H4000_write cmp ptr,DMAaccess wzif_nzjmp #Joystick_write
mov ptr,_DB ' DMAaccess writeshl ptr,#8add ptr,RAMbase
wrword ptr,sprite_data_ptr
jmp #wrmem_ret
Joystick_write cmp ptr,JoystickDMA wzif_nzjmp #invalid_write
orDIRA, #%000011000' JOY_CLK and JOY_SH/LDn to outputsandDIRA, #%110011111' JOY_DATAOUT0 and JOY_DATAOUT1 to inputsror _DB,#1wc'***** 1if_corOUTA, #%000010000' JOY_SH/LDn = 1if_ncandOUTA, #%111101111' JOY_SH/LDn = 0jmp #wrmem_ret
That's as far as I can go without having h/w. If you know that the previous setup worked [3..6] then simply subtract 20 from the mask shifts and adjust the shr in read again. That should restore previous functionality. Do you have test programs for this? Is there any interference from other objects (the top level object uses SPIN to read from 3..6)?
Beats me. Can you verify that the latch/clk pins do anything at all? Or are they completely static?
Note that the core file has a few variable definitions *after* res. ctr and temp look OK, not sure about the 2 address entries though. Anyway, this shouldn't affect the joystick interface.
Maybe it's too early yet ... but from the looks of it all you added was this:
Joystick_read cmp ptr, #$16wz' $4016 readif_nzjmp #rdmem_ret
mov _DB, INA' read all 32-bits of input including gamepadsorOUTA, clk_mask ' JOY_CLK = 1shr _DB, #25' shift joystick data to bit 1xor _DB, #1and _DB, #1
[COLOR="red"]anddira,clk_mask[/COLOR]
andnOUTA, clk_mask ' JOY_CLK = 0jmp #rdmem_ret
All this does is reset the load/shift output (if previously set in write) which isn't even used in this subroutine. What am I missing here? I mean the way the code was originally you had to issue at least one write before you could read as clock and load/shift are only configured during write. That's why my initial comment to do dira configuration during cog startup.
I just had a look at my first gamepad driver (Hydra), latching is done by sending a high pulse (LHL) then clock-in the data. Can you somehow verify that this sequence is used here? Looks like not driving load/shift somehow affects its level?!
Comments
For this to work you need to do the following:
1. Unzip the attached file.
2. Load the Propellor tool and click on "NES_EEPROM_loader_001.spin". This spin file includes (as an object) whatever ROM you have left uncommented in (around) line 83.
3. Load the EEPROM loader program into the Hydra with F10 and then (when it is running on the Hydra) press "A" on the game controller. This loads the ROM image that you have selected into the Hydra's EEPROM.
4. Go back to the Propellor tool and uncomment the next ROM (around line 83 again) (of course, dont forget to comment out the first one).
5. Again press F10 and then, when the EEPROM-loader program loads into the Hydra, press "A". Repeat this process until you have loaded all the ROMs that you want (or that will fit) into the available EEPROM. If something goes wrong and you want to start again, press the "B" button on the Game Controller and the number of ROMs loaded resets to zero and you have to start again. You will notice that the font changes each time you load a different ROM - this is because the font (the tilemap) is specific to each game and is loaded with the ROM image and there isn't enough space to have a separate font (tilemap) along with the EEPROM loader program and the ROM image itself.
Anyway, once you have done all this you should see the Hydra screen something like the screenshot below.
6. You are now ready to start playing the games. Click on "NES_Game_emulator_012.spin" and load it into the Hydra with F10. The screenshot below shows what should come up. You can select the game you want with the up and down arrows on the game controller. Then press "A" on the game controller and the game will download from the EEPROM into the Hydra memory and the emulation will start. You will see some garbage on the screen for a second or two (since the ROM download to main Hydra memory overwrites the memory that is temporarily used for the screen display). All going well this should disappear very quickly. Once you start playing you should be able to hear the sound.
There are also screenshots from Pacman and Galaga.
There are still some quirks and things that are not quite right. The sound emulation is good in some respects but you may hear things that don't quite sound like the original. (Particularly, the music in Donkey Kong sounds a bit odd!). Oh well... It takes so much time to get it right.
If anyone wants to persevere getting another ROM working there is the ability to run the emulator in step-by-step mode - so you can see what is happening in all of the registers and in the memory of the NES. You can also set a breakpoint and have the emulation stop when it reaches the breakpoint. However, there aren't enough cogs to run the display full speed/have sound/and run the debugger, so if you want to debug the game you have to give up either some sprites or sound(!).
Thanks·for the positive feedback and comments. I don't read this forum very often so if you want me to respond, email: darryl.biggar@stanfordalumni.org .
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
http://www.propgfx.co.uk/forum/·home of the PropGFX Lite
·
Bill
In the TV program change 'mov·· x,vf' to 'mov··· x,#2'
Just like I thought...the old program had too many scanlines
··
But as whole really cool!
Could someone fluent in Propeller assembly lend a hand?
The relevant section from: NES_emulator_core_10.spin
Joystick_read cmp ptr, #$16 wz ' $4016 read if_nz jmp #rdmem_ret mov _DB, INA ' read all 32-bits of input including gamepads or OUTA, #%000001000 ' JOY_CLK = 1 shr _DB, #5 ' shift joystick data to bit 1 xor _DB, #1 and _DB, #1 '****1 and OUTA,#%111110111 ' JOY_CLK = 0 jmp #rdmem_ret ' wrmem mov ptr, _AB ' ' cmp ptr,breakpoint wz ' if_z mov emu_mode,#0 testn ptr, RAMmask wz if_z add ptr,RAMbase if_z jmp #write_ret cmp ptr,H4000 wc,wz if_ae jmp #H4000_write and ptr,#7 cmp ptr,#3 wz,wc if_b add ptr,PPUctrl if_b jmp #write_ret if_z mov OAMaddr,_DB ' $2003 _DB -> OAMoffset if_z jmp #wrmem_ret cmp ptr,#4 wz if_z rdword ptr,sprite_data_ptr ' $2004 _DB -> SpriteMem[OAMoffset] if_z add ptr,OAMaddr if_z jmp #write_ret cmp ptr,#5 wz if_z rdword ptr,xscroll if_z shl ptr,#8 if_z add ptr,_DB if_z wrword ptr,xscroll if_z jmp #wrmem_ret ' 2005 - not used so save the space cmp ptr,#6 wz if_z shl VRAMaddress,#8 ' $2006 _DB -> upper or lower byte of VRAMaddress if_z add VRAMaddress,_DB if_z and VRAMaddress,OOOOFFFF if_z jmp #wrmem_ret mov temp,VRAMaddress ' $2007 _DB -> PatternTableMem[VRAMaddress] cmp temp,H3F00 wc,wz if_ae jmp #H3F00_write ' test temp,H2000 wz ' if_z rdword ptr,pattern_table_ptr ' if_z jmp #write1 test temp,H0800 wz and temp,H03FF if_nz add temp,H0400 rdword ptr,name_table_ptr write1 add ptr,temp rdbyte temp,PPUctrl test temp,#4 wz if_z add VRAMaddress,#1 if_nz add VRAMaddress,#32 write_ret wrbyte _DB, ptr wrmem_ret ret H3F00_write and temp,#31 rdword ptr,pallette_ptr ' mov emu_mode,#0 jmp #write1 H4000_write cmp ptr,DMAaccess wz if_nz jmp #Joystick_write mov ptr,_DB ' DMAaccess write shl ptr,#8 add ptr,RAMbase wrword ptr,sprite_data_ptr jmp #wrmem_ret Joystick_write cmp ptr,JoystickDMA wz if_nz jmp #invalid_write or DIRA, #%000011000 ' JOY_CLK and JOY_SH/LDn to outputs and DIRA, #%110011111 ' JOY_DATAOUT0 and JOY_DATAOUT1 to inputs ror _DB,#1 wc '***** 1 if_c or OUTA, #%000010000 ' JOY_SH/LDn = 1 if_nc and OUTA, #%111101111 ' JOY_SH/LDn = 0 jmp #wrmem_ret
Joystick_read cmp ptr, #$16 wz ' $4016 read if_nz jmp #rdmem_ret mov _DB, INA ' read all 32-bits of input including gamepads or OUTA, clk_mask ' JOY_CLK = 1 shr _DB, #25 ' shift joystick data to bit 1 xor _DB, #1 and _DB, #1 '****1 andn OUTA, clk_mask ' JOY_CLK = 0 jmp #rdmem_ret clk_mask long |< 23 sld_mask long |< 24 dta_mask long |< 25 | |< 26 Joystick_write cmp ptr,JoystickDMA wz if_nz jmp #invalid_write [COLOR="orange"]or DIRA, clk_mask[/COLOR] ' | [COLOR="orange"]or DIRA, sld_mask[/COLOR] ' JOY_CLK and JOY_SH/LDn to outputs [COLOR="orange"]andn DIRA, dta_mask[/COLOR] ' JOY_DATAOUT0 and JOY_DATAOUT1 to inputs ror _DB,#1 wc '***** 1 muxc OUTA, sld_mask ' JOY_SH/LDn = 1/0 jmp #wrmem_ret
Is it correct that only one data input is used during read?What are the mask definitions?
OBC
The ones in the middle? Or are you asking why they are used (9bit immediate constant limitation)?
clk_mask long |< 23 sld_mask long |< 24 dta_mask long |< 25 | |< 26
OBC
yeah. Here's the entire chunk of code I probably should have posted just in case I've missed something relevant.
OBC
NES_emulator_core_010.spin
Corrected in this version.. Not working quite yet..
OBC
Yes
Note that the core file has a few variable definitions *after* res. ctr and temp look OK, not sure about the 2 address entries though. Anyway, this shouldn't affect the joystick interface.
@Roadster, you hit the nail right on the head!
A quick review of Graham's Assembler howto showed me where it should go.
Thanks to Kuroneko & Roadster we have an easily adjustable version of the soundless version of this emulator.
Nes Emulator compatible with Propeller Platform & El' Jugador:
http://dl.dropbox.com/u/7557533/Propellerpowered/NES_Emulator_4_ElJugador_Soundless.zip
Now to see if I can get the newer version working on this setup....
Joystick_read cmp ptr, #$16 wz ' $4016 read if_nz jmp #rdmem_ret mov _DB, INA ' read all 32-bits of input including gamepads or OUTA, clk_mask ' JOY_CLK = 1 shr _DB, #25 ' shift joystick data to bit 1 xor _DB, #1 and _DB, #1 [COLOR="red"]and dira,clk_mask[/COLOR] andn OUTA, clk_mask ' JOY_CLK = 0 jmp #rdmem_ret
All this does is reset the load/shift output (if previously set in write) which isn't even used in this subroutine. What am I missing here? I mean the way the code was originally you had to issue at least one write before you could read as clock and load/shift are only configured during write. That's why my initial comment to do dira configuration during cog startup.I'm no Propeller Assembly expert (Heck I don't even play one on TV), but I do know that things didn't start clicking until I put that line in.
If you assembly masterminds can figure out why, by all means post up.. But it works now.
OBC