diff --git a/doc/notes.txt b/doc/notes.txt index 0d52a834..7d353119 100644 --- a/doc/notes.txt +++ b/doc/notes.txt @@ -152,7 +152,9 @@ TODO: - support projects with subdirectories, file list? - emulator needs reset shortcut for nes - switching platform of a repo? - +- z80 + - can't single step on PUSH insns in listings? + - order of acheader.s WEB WORKER FORMAT diff --git a/presets/astrocade/acbios.h b/presets/astrocade/acbios.h new file mode 100644 index 00000000..744f6b6a --- /dev/null +++ b/presets/astrocade/acbios.h @@ -0,0 +1,67 @@ + +#ifndef _ACBIOS_H +#define _ACBIOS_H + +// FONT DESCRIPTORS + +typedef struct { + byte base_ch; + byte frame_x; + byte frame_y; + byte pattern_x; + byte pattern_y; + const byte* chartab; +} FontDescriptor; + +const FontDescriptor __at(0x206) FNTSYS; +const FontDescriptor __at(0x20d) FNTSML; + +// STACK MANIPULATION + +#define DECSP1 __asm__("dec sp") +#define DECSP2 __asm__("dec sp"); __asm__("dec sp") +#define DECSP3 __asm__("dec sp"); __asm__("dec sp"); __asm__("dec sp") + +// BIOS COMMANDS + +#define STRDIS 0x34 + +// FUNCTIONS + +#define OPT_1x1 0x00 +#define OPT_2x2 0x40 +#define OPT_4x4 0x80 +#define OPT_8x8 0xc0 +#define OPT_XOR 0x20 +#define OPT_OR 0x10 +#define OPT_ON(n) ((n)<<2) +#define OPT_OFF(n) ((n)) + +void activate_interrupts(void); +void wait_for_vsync(void); + +void _display_string(byte x, byte y, byte options, const char* str); +#define display_string(x,y,opts,str) \ + DECSP1; \ + _display_string(x,y,opts,str); + +void _paint_rectangle(byte x, byte y, byte w, byte h, byte colormask); +#define paint_rectangle(x,y,w,h,colormask) \ + DECSP1; \ + _paint_rectangle(x,y,w,h,colormask); + +void _write_relative(byte x, byte y, byte magic, const char* pattern); +#define write_relative(x,y,magic,pattern) \ + DECSP1; \ + _write_relative(x,y,magic,pattern); + +// QUICK MACROS + +#define SYS_SETOUT(verbl,horcb,inmod)\ + __asm__("rst 0x38");\ + __asm__(".db 0x17");\ + __asm__(".db "#verbl);\ + __asm__(".db "#horcb);\ + __asm__(".db "#inmod);\ + +#endif diff --git a/presets/astrocade/acbios.s b/presets/astrocade/acbios.s new file mode 100644 index 00000000..fe1c3a2d --- /dev/null +++ b/presets/astrocade/acbios.s @@ -0,0 +1,452 @@ + +; ****** HVGLIB.H (formally called ballyequ.h) (C)1977,78 +; *** Bally Astrocade Equates and Macros Header File *** +; From the nutting_manual and reformatted using Mixed Case +; Version 3.01 - thru December 29, 2010 +; by Richard C Degler, from scratch +; +; > Retyped and proofread by Adam Trionfo and Lance F. Squire +; > Version 1.0 (as ballyequ.h) - January 17, 2002 +; > Version 2.52 (Version 1.0 of HVGLIB.H) - March 28, 2003 +; > Version 2.6 - March 2, 2004 - as seen on BallyAlley.com +; > Version 3.0 - 2009 +; > Version 3.01 - Changed "FonT BASE character" comment +; > +; > This file contains the equates and macros that Bally +; > programs require for assembly. This file has been +; > written to assemble with ZMAC 1.3 (a freely distribut- +; > able Z-80 assembler (with C source), that has a 25-year +; > history. ZMAC can be compiled under just about any O.S. +; > in existence, so try it out. This file will probably +; > require changes to be assembled under other assemblers. +; > +; > To assemble your Z-80 source code using ZMAC: +; > +; > zmac -d -o -x +; > +; > For example, assemble this Astrocade Z-80 ROM file: +; > +; > zmac -d -o BallyROM.bin -x BallyROM.lst BallyROM.asm +; > +; > Currently the Listing file is full of 'Undeclared' +; > errors. When all of the source is typed-in, these will +; > vanish. I'm leaving the others until all the source is +; > re-typed. +; > +; +; *************************** +; * Home Video Game =ates * +; *************************** +; +; ASSEMBLY CONTROL +; +XPNDON = 1 ; ** SET TO 1 WHEN HARDWARE EXP +NWHDWR = 1 ; ** SET TO 1 WHEN NEW HARDWARE +; +; General goodies (HEX and Decimal values): +NORMEM = 0x4000 ; 8192 ; NORmal MEMory start +FIRSTC = 0x2000 ; 4096 ; FIRST address in Cartridge +SCREEN = 0x0000 ; 0 ; magic SCREEN start +BYTEPL = 0x28 ; 40 ; BYTEs Per Line +BITSPL = 0xA0 ; 160 ; BITS Per Line +; +; Stuff in SYSTEM DOPE VECTOR (valid for ALL system ROMs): +STIMER = 0x0200 ; Seconds and game TIMER, music +CTIMER = 0x0203 ; Custom TIMERs +FNTSYS = 0x0206 ; FoNT descriptor for SYStem font +FNTSML = 0x020D ; FoNT descriptor for SMaLl font +ALKEYS = 0x0214 ; ALl KEYS keypad mask +MENUST = 0x0218 ; head of onboard MENU STart +MXSCR = 0x021E ; address of 'MaX SCoRe' text string +NOPLAY = 0x0228 ; address of 'Number Of PLAYers' string +NOGAME = 0x0235 ; address of 'Number Of GAMEs' string +; +; BITS in PROCESSOR FLAG byte: +PSWCY = 0 ; Processor Status Word, CarrY bit +PSWPV = 2 ; Processor Status Word, Parity or oVerflow bit +PSWZRO = 6 ; Processor Status Word, ZeRO bit +PSWSGN = 7 ; Processor Status Word, SiGN bit +; +; BITS in GAME STATUS Byte: +GSBTIM = 0 ; Game Status Byte, if TIMe is up set end bit +GSBSCR = 1 ; Game Status Byte, if SCoRe reached set end bit +GSBEND = 7 ; Game Status Byte, END flag bit +; +; Standard VECTOR DISPLACEMENTS and bits: +VBMR = 0x00 ; +0 ; Vector Block, Magic Register +VBSTAT = 0x01 ; +1 ; Vector Block, STATus byte +VBTIMB = 0x02 ; +2 ; Vector Block, TIMe Base +VBDXL = 0x03 ; +3 ; Vector Block, Delta for X Low +VBDXH = 0x04 ; +4 ; Vector Block, Delta for X Hi +VBXL = 0x05 ; +5 ; Vector Block, X coord Low +VBXH = 0x06 ; +6 ; Vector Block, X coord Hi +VBXCHK = 0x07 ; +7 ; Vector Block, X CHecK flags +VBDYL = 0x08 ; +8 ; Vector Block, Delta for Y Low +VBDYH = 0x09 ; +9 ; Vector Block, Delta for Y Hi +VBYL = 0x0A ; +10 ; Vector Block, Y coord Low +VBYH = 0x0B ; +11 ; Vector Block, Y coord Hi +VBYCHK = 0x0C ; +12 ; Vector Block, Y CHecK flags +VBOAL = 0x0D ; +13 ; Vector Block, Old Address Low +VBOAH = 0x0E ; +14 ; Vector Block, Old Address Hi +; +; DISPLACEMENTS from start of COORDINATE AREA (X or Y): +VBDCL = 0x00 ; +0 ; Vector Block, Delta for Coord Low +VBDCH = 0x01 ; +1 ; Vector Block, Delta for Coord Hi +VBCL = 0x02 ; +2 ; Vector Block, Coord Low +VBCH = 0x03 ; +3 ; Vector Block, Coord Hi +VBCCHK = 0x04 ; +4 ; Vector Block, Coord CHecK flags +; +; BITS in STATUS byte: +VBBLNK = 6 ; Vector Block status, BLaNK bit +VBSACT = 7 ; Vector Block Status, ACTive bit +; +; BITS in (X or Y) VB CHECK FLAG bit mask: +VBCLMT = 0 ; Vector Block Check, LiMiT bit +VBCREV = 1 ; Vector Block Check, REVerse delta on limit attain +VBCLAT = 3 ; Vector Block Check, coordinate Limit ATtained +; +; FONT TABLE DISPLACEMENTS for CHARACTER DESCRIPTOR BLOCK: +FTBASE = 0x00 ; +0 ; FonT BASE character (normally 0xA0) +FTFSX = 0x01 ; +1 ; FonT Frame X Size width +FTFSY = 0x02 ; +2 ; FonT Frame Y Size height +FTBYTE = 0x03 ; +3 ; FonT X size for char in BYTEs +FTYSIZ = 0x04 ; +4 ; FonT Y SIZe height in rows +FTPTL = 0x05 ; +5 ; FonT Pattern Table address Low +FTPTH = 0x06 ; +6 ; FonT Pattern Table address Hi +; +; BITS for MAGIC REGISTER (write option) byte: +MRSHFT = 0x03 ; Magic Register, mask of SHiFT amount 0-3 +MRROT = 2 ; Magic Register, write with ROTata bit +MRXPND = 3 ; Magic Register, write with eXPaND bit +MROR = 4 ; Magic Register, write with OR bit +MRXOR = 5 ; Magic Register, write with eXclusive-OR bit +MRFLOP = 6 ; Magic Register, write with FLOP bit +; +; BITS of CONTROL HANDLE Input port: +CHUP = 0 ; Control Handle, UP bit +CHDOWN = 1 ; Control Handle, DOWN bit +CHLEFT = 2 ; Control Handle, joystick LEFT bit +CHRIGH = 3 ; Control Handle, joystick RIGHT bit +CHTRIG = 4 ; Control Handle, TRIGger bit +; +; CONTEXT BLOCK Register DISPLACEMENTS: +CBIYL = 0x00 ; +0 ; Context Block, IY register Low +CBIYH = 0x01 ; +1 ; Context Block, IY register Hi +CBIXL = 0x02 ; +2 ; Context Block, IX register Low +CBIXH = 0x03 ; +3 ; Context Block, IX register Hi +CBE = 0x04 ; +4 ; Context Block, E register +CBD = 0x05 ; +5 ; Context Block, D register +CBC = 0x06 ; +6 ; Context Block, C register +CBB = 0x07 ; +7 ; Context Block, B register +CBFLAG = 0x08 ; +8 ; Context Block, FLAGs register +CBA = 0x09 ; +9 ; Context Block, A register +CBL = 0x0A ; +10 ; Context Block, L register +CBH = 0x0B ; +11 ; Context Block, H register +; +; SENTRY RETURN Codes =ates: +SNUL = 0x00 ; Sentry return NULl, nothing happened +SCT0 = 0x01 ; Sentry, Counter-Timer 0 has counted down +SCT1 = 0x02 ; Sentry, Counter-Timer 1 has counted down +SCT2 = 0x03 ; Sentry, Counter-Timer 2 has counted down +SCT3 = 0x04 ; Sentry, Counter-Timer 3 has counted down +SCT4 = 0x05 ; Sentry, Counter-Timer 4 has counted down +SCT5 = 0x06 ; Sentry, Counter-Timer 5 has counted down +SCT6 = 0x07 ; Sentry, Counter-Timer 6 has counted down +SCT7 = 0x08 ; Sentry, Counter-Timer 7 has counted down +SF0 = 0x09 ; Sentry, Flag bit 0 has changed +SF1 = 0x0A ; Sentry, Flag bit 1 has changed +SF2 = 0x0B ; Sentry, Flag bit 2 has changed +SF3 = 0x0C ; Sentry, Flag bit 3 has changed +SF4 = 0x0D ; Sentry, Flag bit 4 has changed +SF5 = 0x0E ; Sentry, Flag bit 5 has changed +SF6 = 0x0F ; Sentry, Flag bit 6 has changed +SF7 = 0x10 ; Sentry, Flag bit 7 has changed +SSEC = 0x11 ; Sentry, SEConds timer has counted down +SKYU = 0x12 ; Sentry, KeY is now Up +SKYD = 0x13 ; Sentry, KeY is now Down +ST0 = 0x14 ; Sentry, Trigger 0 for player 1 has changed +SJ0 = 0x15 ; Sentry, Joystick 0 for player 1 has changed +ST1 = 0x16 ; Sentry, Trigger 1 for player 2 has changed +SJ1 = 0x17 ; Sentry, Joystick 1 for player 2 has changed +ST2 = 0x18 ; Sentry, Trigger 2 for player 3 has changed +SJ2 = 0x19 ; Sentry, Joystick 2 for player 3 has changed +ST3 = 0x1A ; Sentry, Trigger 3 for player 4 has changed +SJ3 = 0x1B ; Sentry, Joystick 3 for player 4 has changed +SP0 = 0x1C ; Sentry, POTentiometer 0 has changed +SP1 = 0x1D ; Sentry, POTentiometer 1 has changed +SP2 = 0x1E ; Sentry, POTentiometer 2 has changed +SP3 = 0x1F ; Sentry, POTentiometer 3 has changed +; +; +; ******************************** +; * Home Video Game PORT =ates * +; ******************************** +; +; OUTPUT Ports for VIRTUAL COLOR: +COL0R = 0x00 ; &(0)= ; write COLor 0 Right +COL1R = 0x01 ; &(1)= ; write COLor 1 Right +COL2R = 0x02 ; &(2)= ; write COLor 2 Right +COL3R = 0x03 ; &(3)= ; write COLor 3 Right +COL0L = 0x04 ; &(4)= ; write COLor 0 Left +COL1L = 0x05 ; &(5)= ; write COLor 1 Left +COL2L = 0x06 ; &(6)= ; write COLor 2 Left +COL3L = 0x07 ; &(7)= ; write COLor 3 Left +HORCB = 0x09 ; &(9)= ; write HORizontal Color Boundary +VERBL = 0x0A ;&(10)= ; write VERtical Blanking Line +COLBX = 0x0B ;&(11)= ; write COLor BloCK multi-port +; +; OUTPUT Ports for MUSIC and SOUNDS: +TONMO = 0x10 ;&(16)= ; write TONe Master Oscillator +TONEA = 0x11 ;&(17)= ; write TONe A oscillator +TONEB = 0x12 ;&(18)= ; write TONe B oscillator +TONEC = 0x13 ;&(19)= ; write TONe C oscillator +VIBRA = 0x14 ;&(20)= ; write VIBRAto frequency & range +VOLC = 0x15 ;&(21)= ; write VOLume of tone C +VOLAB = 0x16 ;&(22)= ; write VOLumes of tones A & B +VOLN = 0x17 ;&(23)= ; write VOLume of Noise +SNDBX = 0x18 ;&(24)= ; write SouND BloCK multi-port +; +; INTERRUPT and CONTROL OUTPUT Ports: +CONCM = 0x08 ; &(8)= ; write 0 for CONsumer, 1 for CoMmercial mode +MAGIC = 0x0C ;&(12)= ; write MAGIC register +INFBK = 0x0D ;&(13)= ; write INterrupt FeedBacK +INMOD = 0x0E ;&(14)= ; write INterrupt MODe +INLIN = 0x0F ;&(15)= ; write INterrupt LINe +XPAND = 0x19 ;&(25)= ; eXPANDer pixel definition port +; +; INTERRUPT and INTERCEPT INPUT Ports: +INTST = 0x08 ; =&(8) ; read INTercept STatus +VERAF = 0x0E ;=&(14) ; read VERtical Address Feedback +HORAF = 0x0F ;=&(15) ; read HORizontal Address Feedback +; +; HAND CONTROL INPUT Ports: +SW0 = 0x10 ;=&(16) ; read SWitch bank 0 for player 1 hand control +SW1 = 0x11 ;=&(17) ; read SWitch bank 1 for player 2 hand control +SW2 = 0x12 ;=&(18) ; read SWitch bank 2 for player 3 hand control +SW3 = 0x13 ;=&(19) ; read SWitch bank 3 for player 4 hand control +POT0 = 0x1C ;=&(28) ; read POTentiometer 0 for player 1 knob +POT1 = 0x1D ;=&(29) ; read POTentiometer 1 for player 2 knob +POT2 = 0x1E ;=&(30) ; read POTentiometer 2 for player 3 knob +POT3 = 0x1F ;=&(31) ; read POTentiometer 3 for player 4 knob +; +; KEYBOARD INPUT Ports: +KEY0 = 0x14 ;=&(20) ; KEYboard column 0 (right side) +KEY1 = 0x15 ;=&(21) ; KEYboard column 1 (center right) +KEY2 = 0x16 ;=&(22) ; KEYboard column 2 (center left) +KEY3 = 0x17 ;=&(23) ; KEYboard column 3 (left side) +; +; +; *************************************** +; * Home Video Game SYSTEM CALL Indexes * +; *************************************** +; +; USER PROGRAM Interface: +INTPC = 0x00 ; # 0 ; INTerPret with Context create +XINTC = 0x02 ; # 2 ; eXit INTerpreter with Context +RCALL = 0x04 ; # 4 ; Real CALL asm language subroutine +MCALL = 0x06 ; # 6 ; Macro CALL interpreter subroutine +MRET = 0x08 ; # 8 ; Macro RETurn from interpreter subroutine +MJUMP = 0x0A ; # 10 ; Macro JUMP to interpreter subroutine +SUCK = 0x0C ; # 12 ; SUCK inline args into context block +; +; SCHEDULER Routines: +ACTINT = 0x0E ; # 14 ; ACTivate sub timer INTerrupts +DECCTS = 0x10 ; # 16 ; DECrement CT'S under mask +; +; MUSIC and SOUNDS: +BMUSIC = 0x12 ; # 18 ; Begin playing MUSIC +EMUSIC = 0x14 ; # 20 ; End playing MUSIC +; +; SCREEN HANDLER Routines: +SETOUT = 0x16 ; # 22 ; SET some OUTput ports +COLSET = 0x18 ; # 24 ; COLors SET +FILL = 0x1A ; # 26 ; FILL memory with data +RECTAN = 0x1C ; # 28 ; paint a RECTANgle +VWRITR = 0x1E ; # 30 ; Vector WRITe Relative +WRITR = 0x20 ; # 32 ; WRITe Relative +WRITP = 0x22 ; # 34 ; WRITe with Pattern size lookup +WRIT = 0x24 ; # 36 ; WRITe with sizes provided +WRITA = 0x26 ; # 38 ; WRITe Absolute +VBLANK = 0x28 ; # 40 ; Vector BLANK area +BLANK = 0x2A ; # 42 ; BLANK area +SAVE = 0x2C ; # 44 ; SAVE area +RESTOR = 0x2E ; # 46 ; RESTORe area +SCROLL = 0x30 ; # 48 ; SCROLL area of screen +; +CHRDIS = 0x32 ; # 50 ; CHaRacter DISplay +STRDIS = 0x34 ; # 52 ; STRing DISplay +DISNUM = 0x36 ; # 54 ; DISplay NUMber +; +RELABS = 0x38 ; # 56 ; RELative to ABSolute conversion +RELAB1 = 0x3A ; # 58 ; RELative to non-magic ABSolute +VECTC = 0x3C ; # 60 ; VECTor move single Coordinate +VECT = 0x3E ; # 62 ; VECTor move coordinate pair +; +; HUMAN INTERFACE Routines: +KCTASC = 0x40 ; # 64 ; Key Code in B To ASCii +SENTRY = 0x42 ; # 66 ; SENse TRansition Y +DOIT = 0x44 ; # 68 ; DOIT table, branch to translation handler +DOITB = 0x46 ; # 70 ; DOIT table, use B instead of A +PIZBRK = 0x48 ; # 72 ; take a PIZza BReaK +MENU = 0x4A ; # 74 ; display a MENU +GETPAR = 0x4C ; # 76 ; GET game PARameter from user +GETNUM = 0x4E ; # 78 ; GET NUMber from user +PAWS = 0x50 ; # 80 ; PAUSE +DISTIM = 0x52 ; # 82 ; DISplay TIMe +INCSCR = 0x54 ; # 84 ; INCrement SCoRe +; +; MATH Routines: +INDEXN = 0x56 ; # 86 ; INDEX Nibble by C +STOREN = 0x58 ; # 88 ; STORE Nibble in A by C +INDEXW = 0x5A ; # 90 ; INDEX Word by A +INDEXB = 0x5C ; # 92 ; INDEX Byte by A +MOVE = 0x5E ; # 94 ; MOVE block transfer +SHIFTU = 0x60 ; # 96 ; SHIFT Up digit in A +BCDADD = 0x62 ; # 98 ; BCD ADDition +BCDSUB = 0x64 ;# 100 ; BCD SUBtraction +BCDMUL = 0x66 ;# 102 ; BCD MULtiplication +BCDDIV = 0x68 ;# 104 ; BCD DIVision +BCDCHS = 0x6A ;# 106 ; BCD CHange Sign +BCDNEG = 0x6C ;# 108 ; BCD NEGate to decimal +DADD = 0x6E ;# 110 ; Decimal ADDition +DSMG = 0x70 ;# 112 ; Decimal convert to Sign MaGnitude +DABS = 0x72 ;# 114 ; Decimal ABSolute value +NEGT = 0x74 ;# 116 ; decimal NEGaTe +RANGED = 0x76 ;# 118 ; RANGED random number +QUIT = 0x78 ;# 120 ; QUIT cassette execution +SETB = 0x7A ;# 122 ; SET Byte +SETW = 0x7C ;# 124 ; SET Word +MSKTD = 0x7E ;# 127 ; MaSK joystick in B To Deltas +; +; +; *************************** +; * SYSTEM RAM MEMORY Cells * +; *************************** +WASTE = 0x0FFF +WASTER = WASTE +; +SYSRAM = 0x4FCE ; Resides at the highest possible address +BEGRAM = SYSRAM ; typically used for initial Stack Pointer +; Used by MUSIC PROCESSOR: +MUZPC = 0x4FCE ; MUSic Program Counter +MUZSP = 0x4FD0 ; MUSic Stack Pointer +PVOLAB = 0x4FD2 ; Preset VOLume for tones A and B +PVOLMC = 0x4FD3 ; Preset VOLuMe for tone C and Noise Mode +VOICES = 0x4FD4 ; music VOICES mask +; COUNTER TIMERS (used by DECCTS,ACTINT,CTIMER): +CT0 = 0x4FD5 ; Counter Timer 0 +CT1 = 0x4FD6 ; Counter Timer 1 +CT2 = 0x4FD7 ; Counter Timer 2 +CT3 = 0x4FD8 ; Counter Timer 3 +CT4 = 0x4FD9 ; Counter Timer 4 +CT5 = 0x4FDA ; Counter Timer 5 +CT6 = 0x4FDB ; Counter Timer 6 +CT7 = 0x4FDC ; Counter Timer 7 +;Used by SENTRY to track controls: +CNT = 0x4FDD ; Counter update & Number Tracking +SEMI4S = 0x4FDE ; SEMAPHORE flag bitS +OPOT0 = 0x4FDF ; Old POT 0 tracking byte +OPOT1 = 0x4FE0 ; Old POT 1 tracking byte +OPOT2 = 0x4FE1 ; Old POT 2 tracking byte +OPOT3 = 0x4FE2 ; Old POT 3 tracking byte +KEYSEX = 0x4FE3 ; KEYS-EX tracking byte +OSW0 = 0x4FE4 ; Old SWitch 0 tracking byte +OSW1 = 0x4FE5 ; Old SWitch 1 tracking byte +OSW2 = 0x4FE6 ; Old SWitch 2 tracking byte +OSW3 = 0x4FE7 ; Old SWitch 3 tracking byte +COLLST = 0x4FE8 ; COLset LaST address for P.B. A +; Used by STIMER: +DURAT = 0x4FEA ; note DURATion +TMR60 = 0x4FEB ; TiMeR for SIXTY'ths of sec +TIMOUT = 0x4FEC ; TIMer for blackOUT +GTSECS = 0x4FED ; Game Time SECondS +GTMINS = 0x4FEE ; Game Time MINuteS +; Used by MENU: +RANSHT = 0x4FEF ; RANdom number SHifT register +NUMPLY = 0x4FF3 ; NUMber of PLaYers +ENDSCR = 0x4FF4 ; END SCoRe to 'play to' +MRLOCK = 0x4FF7 ; Magic Register LOCK out flag +GAMSTB = 0x4FF8 ; GAMe STatus Byte +PRIOR = 0x4FF9 ; PRIOR music protect flag +SENFLG = 0x4FFA ; SENtry control seizure FLaG +; User UPI Routines, even numbers from 0x80 to 0xFE ( + 1 for SUCK): +UMARGT = 0x4FFB ; User Mask ARGument Table + (routine / 2) +USERTB = 0x4FFD ; USER Table Base + routine = JumP address +; +URINAL = 0x4FFF ; WASTER flushes here! +; +; + +; +; MACROs to generate SYSTEM CALLs: + .macro SYSTEM NUMBA + rst 0x38 + .db NUMBA +; .if NUMBA = INTPC +;INTPCC DEFL 1 +; .endif + .endm +; MACRO to generate SYSTEM CALL with SUCK option ON: + .macro SYSSUK UMBA + rst 0x38 + .db UMBA + 1 +; .if UMBA = INTPC +;INTPCC DEFL 1 +; .endif + .endm + +;;; C functions + + .area CODE + +; activate interrupts + .globl _activate_interrupts +_activate_interrupts: + SYSTEM ACTINT + ; set INMOD + ld a,#0x8 + out (INMOD),a + ret + +; wait for next interrupt + .globl _wait_for_vsync +_wait_for_vsync: +; this is faster than PAWS + ld hl,#TMR60 + ld a,(hl) +.1: cp a,(hl) + jp z,.1 + ret +; SYSTEM PAWS +; .db 1 + +; build a SYSSUK block on the stack +; the caller needs to dec SP before calling +; so we have room for a RET opcode + .globl __display_string +__display_string: + ld h,#(STRDIS+1) +syssuk5: + pop de ; return address + ld l,#0xff ; RST 0x38 + push hl ; SYSSUK + ld iy,#0 + add iy,sp ; SP -> IY + push de ; push return addr + ld d,#0xc9 ; ret opcode + ld 7(iy),d ; store after params + ld ix,#0x20d ; alternate font desc. + jp (iy) ; jump to RST + +; RECTAN x y w h colormask + .globl __paint_rectangle +__paint_rectangle: + ld h,#(RECTAN+1) + jp syssuk5 + +; WRITP x y magic pattern + .globl __write_relative +__write_relative: + ld h,#(WRITR+1) + jp syssuk5 + diff --git a/presets/astrocade/aclib.c b/presets/astrocade/aclib.c index e14bdb9f..8c4b3827 100644 --- a/presets/astrocade/aclib.c +++ b/presets/astrocade/aclib.c @@ -5,25 +5,24 @@ #define EXIT_CLIPDEST(addr) if ((((word)addr)&0xfff) >= 0xe10) return // clear screen and set graphics mode -void clrscr() { +void clrscr(void) { memset(vidmem, 0, VHEIGHT*VBWIDTH); // clear page 1 } // set entire palette at once (8 bytes to port 0xb) // bytes in array should be in reverse -void set_palette(byte palette[8]) __naked { +void set_palette(byte palette[8]) __z88dk_fastcall { palette; __asm ld bc,#0x80b ; B -> 8, C -> 0xb - otir ; write C bytes to B + otir ; write C bytes from HL to port[B] ret ; return __endasm; } // draw vertical line void vline(byte x, byte y1, byte y2, byte col, byte op) { - byte xb = x>>2; // divide x by 4 - byte* dest = &vmagic[y1][xb]; // destination address + byte* dest = &vmagic[y1][x>>2];// destination address byte y; hw_magic = M_SHIFT(x) | op; // set magic register col <<= 6; // put color in high pixel @@ -34,14 +33,9 @@ void vline(byte x, byte y1, byte y2, byte col, byte op) { } } -// draw a pixel -void pixel(byte x, byte y, byte col, byte op) { - vline(x, y, y, col, op); // draw line with 1-pixel height -} - // render a sprite with the given graphics operation void render_sprite(const byte* src, byte x, byte y, byte op) { - byte i,j; + byte i; byte w = *src++; // get width from 1st byte of sprite byte h = *src++; // get height from 2nd byte of sprite byte* dest = &vmagic[y][x>>2];// destination address @@ -53,21 +47,18 @@ void render_sprite(const byte* src, byte x, byte y, byte op) { } // memory copy loop if (op != M_ERASE) { - for (j=0; j>2; // divide x by 4 - byte* dest = &vmagic[y][xb]; // destination address + byte* dest = &vmagic[y][x>>2]; // destination address hw_magic = M_SHIFT(x) | M_XPAND | op; for (byte i=0; i<8; i++) { char b = *src++; @@ -98,7 +88,8 @@ void draw_char(byte ch, byte x, byte y, byte op) { } } -void draw_string(const char* str, byte x, byte y) { +void draw_string(byte x, byte y, byte options, const char* str) { + hw_xpand = XPAND_COLORS(0, options); do { byte ch = *str++; if (!ch) break; diff --git a/presets/astrocade/aclib.h b/presets/astrocade/aclib.h index f539fbf9..c2ebcca0 100644 --- a/presets/astrocade/aclib.h +++ b/presets/astrocade/aclib.h @@ -19,10 +19,14 @@ __sfr __at(0x04) hw_col0l; __sfr __at(0x05) hw_col1l; __sfr __at(0x06) hw_col2l; __sfr __at(0x07) hw_col3l; // palette 7 - +__sfr __at(0x08) hw_concm; // consumer/commercial mode __sfr __at(0x09) hw_horcb; // horiz color boundary __sfr __at(0x0a) hw_verbl; // vertical blanking line * 2 +__sfr __at(0x0b) hw_colbx; // palette transfer __sfr __at(0x0c) hw_magic; // magic register +__sfr __at(0x0d) hw_infbk; // interrupt feedback +__sfr __at(0x0e) hw_inmod; // interrupt enable, mode +__sfr __at(0x0f) hw_inlin; // interrupt line __sfr __at(0x19) hw_xpand; // expander register __sfr __at(0x08) hw_intst; // intercept test feedback @@ -32,6 +36,16 @@ __sfr __at(0x11) hw_p2ctrl; // player 2 controls __sfr __at(0x12) hw_p3ctrl; // player 3 controls __sfr __at(0x13) hw_p4ctrl; // player 4 controls +__sfr __at(0x10) hw_tonmo; // tone master oscillator +__sfr __at(0x11) hw_tonea; +__sfr __at(0x12) hw_toneb; +__sfr __at(0x13) hw_tonec; +__sfr __at(0x14) hw_vibra; +__sfr __at(0x15) hw_volc; +__sfr __at(0x16) hw_volab; +__sfr __at(0x17) hw_voln; +__sfr __at(0x18) hw_sndbx; + // flags #define M_SHIFT0 0x00 @@ -66,15 +80,16 @@ byte __at (0x4000) vidmem[VTOTAL][VBWIDTH]; /// GRAPHICS FUNCTIONS void clrscr(); -void set_palette(byte palette[8]); // palette in reverse order +void set_palette(byte palette[8]) __z88dk_fastcall; // palette in reverse order void vline(byte x, byte y1, byte y2, byte col, byte op); void pixel(byte x, byte y, byte col, byte op); void render_sprite(const byte* src, byte x, byte y, byte op); void draw_char(byte ch, byte x, byte y, byte op); -void draw_string(const char* str, byte x, byte y); +void draw_string(byte x, byte y, byte options, const char* str); void draw_bcd_word(word bcd, byte x, byte y, byte op); word bcd_add(word a, word b); +#define pixel(x,y,color,op) vline(x, y, y, color, op); #define erase_sprite(src,x,y) render_sprite(src,x,y,M_ERASE); #endif diff --git a/presets/astrocade/cosmic.c b/presets/astrocade/cosmic.c index ea6e0a0c..edd76b52 100644 --- a/presets/astrocade/cosmic.c +++ b/presets/astrocade/cosmic.c @@ -8,10 +8,8 @@ #include #include "aclib.h" - //#link "aclib.c" - -//#link "acheader.s" +//#link "hdr_autostart.s" // @@ -119,8 +117,7 @@ void draw_bunker(byte x, byte y, byte y2, byte h, byte w) { void draw_playfield() { byte i; clrscr(); - hw_xpand = XPAND_COLORS(0, COLOR_SCORE); - draw_string("PLAYER 1", 0, 0); + draw_string(0, 0, COLOR_SCORE, "PLAYER 1"); draw_score(); draw_lives(); for (i=0; i #include /*{pal:"astrocade",layout:"astrocade"}*/ const byte palette[8] = { - 0x06, 0x62, 0xF1, 0x04, - 0x07, 0xD4, 0x35, 0x00, + 0x77, 0xD4, 0x35, 0x01, + 0x07, 0xD4, 0x35, 0x01, }; -void setup_registers() { - // setup colors - set_palette(palette); - // horizontal palette split - hw_horcb = 12; - // height of screen - hw_verbl = VHEIGHT*2; -} +const byte BALL[] = { + 0, 0, // x and y offset + 1, 6, // width (bytes) and height (lines) + /*{w:8,h:6,brev:1}*/ + 0b01111000, + 0b11011100, + 0b10111100, + 0b10111100, + 0b11111100, + 0b01111000, +}; void main() { - setup_registers(); + // clear screen clrscr(); - hw_xpand = XPAND_COLORS(0, 2); - draw_string("Hello, World!", 2, 0); + // setup palette + set_palette(palette); + // set screen height + // set horizontal color split (position / 4) + // set interrupt status + SYS_SETOUT(98*2, 23, 0); + // display standard characters + display_string(2, 2, OPT_ON(1), "HELLO, WORLD!!"); + // 2x2 must have X coordinate multiple of 2 + display_string(4, 16, OPT_2x2|OPT_ON(2), "BIG TEXT!"); + // 4x4 must have X coordinate multiple of 4 + display_string(4, 36, OPT_4x4|OPT_ON(2), "4X4"); + // we can use OR mode to make a shadow + display_string(4, 38, OPT_4x4|OPT_ON(3)|OPT_OR, "4X4"); + // and XOR mode to invert existing pixels + // (careful, there's no clipping) + display_string(101, 24, OPT_8x8|OPT_ON(3)|OPT_XOR, "?"); + // small font must be aligned to multiple of 4 + display_string(4, 80, OPT_ON(1), "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9"); + // paint a rectangle with a pattern mask (0xa5) + paint_rectangle(4, 72, 90, 4, 0xa5); + // write from pattern block + write_relative(50, 80, M_XPAND, BALL); + write_relative(60, 80, M_XPAND, BALL); + write_relative(70, 80, M_XPAND, BALL); // infinite loop while (1) { } diff --git a/presets/astrocade/lines.c b/presets/astrocade/lines.c index 6784384b..f9c1d1dd 100644 --- a/presets/astrocade/lines.c +++ b/presets/astrocade/lines.c @@ -1,7 +1,7 @@ #include "aclib.h" //#link "aclib.c" -//#link "acheader.s" +//#link "hdr_autostart.s" #include #include @@ -40,7 +40,7 @@ void main() { setup_registers(); clrscr(); hw_xpand = XPAND_COLORS(0, 2); - draw_string("Hello, Lines!", 2, 80); + draw_string(2, 80, 0, "Hello, Lines!"); draw_line(0, 0, 159, 95, 1); // infinite loop srand(1); diff --git a/presets/astrocade/sprites.c b/presets/astrocade/sprites.c index 225e951d..bb0b8f9f 100644 --- a/presets/astrocade/sprites.c +++ b/presets/astrocade/sprites.c @@ -3,7 +3,7 @@ #include "aclib.h" //#link "aclib.c" -//#link "acheader.s" +//#link "hdr_autostart.s" const byte player_bitmap[] = {3,14,/*{w:12,h:16,bpp:2,brev:1}*/0x00,0x3C,0x00,0x00,0x18,0x00,0x00,0x3C,0x00,0x00,0x18,0x00,0x04,0x18,0x20,0x0C,0x3C,0x30,0x3C,0x3C,0x3C,0x1F,0xE7,0xF4,0x1F,0x66,0xF4,0x17,0xE7,0xE4,0x17,0xE7,0xE4,0x1C,0x7E,0x34,0x1C,0xFF,0x34,0x3C,0x18,0x3C,0x0C,0x18,0x30,0x04,0x18,0x20}; @@ -11,13 +11,13 @@ const byte player_bitmap[] = /*{pal:"astrocade",layout:"astrocade"}*/ const byte palette[8] = { 0x06, 0x62, 0xF1, 0x04, - 0x07, 0xD4, 0x35, 0x00, + 0x07, 0xD4, 0x35, 0x01, }; void setup_registers() { set_palette(palette); hw_horcb = 0; - hw_verbl = VHEIGHT*2; + hw_verbl = 102*2; } void main() { @@ -28,6 +28,7 @@ void main() { clrscr(); while (1) { render_sprite(player_bitmap, x, y, M_MOVE); + erase_sprite(player_bitmap, x, y); x++; y++; } diff --git a/presets/astrocade/vsync.c b/presets/astrocade/vsync.c new file mode 100644 index 00000000..a00a36e7 --- /dev/null +++ b/presets/astrocade/vsync.c @@ -0,0 +1,39 @@ + +#include + +#include "aclib.h" +//#link "aclib.c" +//#link "hdr_autostart.s" +#include "acbios.h" +//#link "acbios.s" + +const byte player_bitmap[] = +{3,14,/*{w:12,h:16,bpp:2,brev:1}*/0x00,0x3C,0x00,0x00,0x18,0x00,0x00,0x3C,0x00,0x00,0x18,0x00,0x04,0x18,0x20,0x0C,0x3C,0x30,0x3C,0x3C,0x3C,0x1F,0xE7,0xF4,0x1F,0x66,0xF4,0x17,0xE7,0xE4,0x17,0xE7,0xE4,0x1C,0x7E,0x34,0x1C,0xFF,0x34,0x3C,0x18,0x3C,0x0C,0x18,0x30,0x04,0x18,0x20}; + +/*{pal:"astrocade",layout:"astrocade"}*/ +const byte palette[8] = { + 0x06, 0x62, 0xF1, 0x04, + 0x07, 0xD4, 0x35, 0x01, +}; + +void setup_registers() { + set_palette(palette); + hw_horcb = 0; + hw_verbl = 102*2; +} + +void main() { + byte x,y; + x=10; + y=10; + setup_registers(); + clrscr(); + activate_interrupts(); + while (1) { + render_sprite(player_bitmap, x, y, M_MOVE); + wait_for_vsync(); + erase_sprite(player_bitmap, x, y); + x++; + y++; + } +} diff --git a/src/cpu/z80.coffee b/src/cpu/z80.coffee index 8d5d4e8a..b75a13cf 100644 --- a/src/cpu/z80.coffee +++ b/src/cpu/z80.coffee @@ -2349,7 +2349,7 @@ window.buildZ80 = (opts) -> l = READMEM(inttemp); inttemp = (inttemp+1) & 0xffff; h = READMEM(inttemp); - console.log(hex(interruptDataBus), hex(inttemp), hex(l), hex(h)); + /*console.log(hex(interruptDataBus), hex(inttemp), hex(l), hex(h));*/ regPairs[#{rpPC}] = (h<<8) | l; tstates += 7; break; @@ -2447,7 +2447,7 @@ window.buildZ80 = (opts) -> regPairs[#{rpIR}] = snapRegs['IR']; iff1 = snapRegs['iff1'] & 1; iff2 = snapRegs['iff2'] & 1; - im = snapRegs['im'] & 1; + im = snapRegs['im'] & 3; halted = !!snapRegs['halted']; tstates = snapRegs['T'] * 1; interruptPending = !!snapRegs['intp']; diff --git a/src/cpu/z80.js b/src/cpu/z80.js index 1535b0e9..f69977a7 100644 --- a/src/cpu/z80.js +++ b/src/cpu/z80.js @@ -1666,7 +1666,7 @@ of the host processor, as typed arrays are native-endian The indirection on 'eval' causes most browsers to evaluate it in the global scope, giving a significant speed boost */ - defineZ80JS = "window.Z80 = function(opts) {\n var self = {};\n\n " + setUpStateJS + "\n\n self.requestInterrupt = function(dataBus) {\n interruptPending = true;\n interruptDataBus = dataBus & 0xffff;\n /* TODO: use event scheduling to keep the interrupt line active for a fixed\n ~48T window, to support retriggered interrupts and interrupt blocking via\n chains of EI or DD/FD prefixes */\n }\n self.nonMaskableInterrupt = function() {\n iff1 = 1;\n self.requestInterrupt(0x66);\n }\n var z80Interrupt = function() {\n if (iff1) {\n if (halted) {\n /* move PC on from the HALT opcode */\n regPairs[" + rpPC + "]++;\n halted = false;\n }\n\n iff1 = iff2 = 0;\n\n /* push current PC in readiness for call to interrupt handler */\n regPairs[" + rpSP + "]--; WRITEMEM(regPairs[" + rpSP + "], regPairs[" + rpPC + "] >> 8);\n regPairs[" + rpSP + "]--; WRITEMEM(regPairs[" + rpSP + "], regPairs[" + rpPC + "] & 0xff);\n\n /* TODO: R register */\n\n switch (im) {\n case 0:\n regPairs[" + rpPC + "] = interruptDataBus; // assume always RST\n tstates += 6;\n break;\n case 1:\n regPairs[" + rpPC + "] = 0x0038;\n tstates += 7;\n break;\n case 2:\n inttemp = (regs[" + rI + "] << 8) | (interruptDataBus & 0xff);\n l = READMEM(inttemp);\n inttemp = (inttemp+1) & 0xffff;\n h = READMEM(inttemp);\n console.log(hex(interruptDataBus), hex(inttemp), hex(l), hex(h));\n regPairs[" + rpPC + "] = (h<<8) | l;\n tstates += 7;\n break;\n }\n }\n };\n\n self.runFrame = function(frameLength) {\n var lastOpcodePrefix, offset, opcode;\n\n while (tstates < frameLength || opcodePrefix) {\n if (interruptible && interruptPending) {\n z80Interrupt();\n interruptPending = false;\n }\n interruptible = true; /* unless overridden by opcode */\n lastOpcodePrefix = opcodePrefix;\n opcodePrefix = '';\n switch (lastOpcodePrefix) {\n case '':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS, null, opts.traps)) + "\n break;\n case 'CB':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_CB)) + "\n break;\n case 'DD':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_DD, OPCODE_RUN_STRINGS)) + "\n break;\n case 'DDCB':\n offset = READMEM(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n if (offset & 0x80) offset -= 0x100;\n CONTEND_READ(regPairs[" + rpPC + "], 3);\n opcode = memory.read(regPairs[" + rpPC + "]);\n CONTEND_READ_NO_MREQ(regPairs[" + rpPC + "], 1);\n CONTEND_READ_NO_MREQ(regPairs[" + rpPC + "], 1);\n regPairs[" + rpPC + "]++;\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_DDCB)) + "\n break;\n case 'ED':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_ED)) + "\n break;\n case 'FD':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_FD, OPCODE_RUN_STRINGS)) + "\n break;\n case 'FDCB':\n offset = READMEM(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n if (offset & 0x80) offset -= 0x100;\n CONTEND_READ(regPairs[" + rpPC + "], 3);\n opcode = memory.read(regPairs[" + rpPC + "]);\n CONTEND_READ_NO_MREQ(regPairs[" + rpPC + "], 1);\n CONTEND_READ_NO_MREQ(regPairs[" + rpPC + "], 1);\n regPairs[" + rpPC + "]++;\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_FDCB)) + "\n break;\n default:\n throw(\"Unknown opcode prefix: \" + lastOpcodePrefix);\n }\n }\n while (display.nextEventTime != null && display.nextEventTime <= tstates) display.doEvent();\n };\n\n self.reset = function() {\n regPairs[" + rpPC + "] = regPairs[" + rpIR + "] = 0;\n iff1 = 0; iff2 = 0; im = 0; halted = false;\n };\n\n self.loadState = function(snapRegs) {\n regPairs[" + rpAF + "] = snapRegs['AF'];\n regPairs[" + rpBC + "] = snapRegs['BC'];\n regPairs[" + rpDE + "] = snapRegs['DE'];\n regPairs[" + rpHL + "] = snapRegs['HL'];\n regPairs[" + rpAF_ + "] = snapRegs['AF_'];\n regPairs[" + rpBC_ + "] = snapRegs['BC_'];\n regPairs[" + rpDE_ + "] = snapRegs['DE_'];\n regPairs[" + rpHL_ + "] = snapRegs['HL_'];\n regPairs[" + rpIX + "] = snapRegs['IX'];\n regPairs[" + rpIY + "] = snapRegs['IY'];\n regPairs[" + rpSP + "] = snapRegs['SP'];\n regPairs[" + rpPC + "] = snapRegs['PC'];\n regPairs[" + rpIR + "] = snapRegs['IR'];\n iff1 = snapRegs['iff1'] & 1;\n iff2 = snapRegs['iff2'] & 1;\n im = snapRegs['im'] & 1;\n halted = !!snapRegs['halted'];\n tstates = snapRegs['T'] * 1;\n interruptPending = !!snapRegs['intp'];\n interruptDataBus = snapRegs['intd'] & 0xffff;\n };\n\n self.saveState = function() {\n return {\n AF: regPairs[" + rpAF + "],\n BC: regPairs[" + rpBC + "],\n DE: regPairs[" + rpDE + "],\n HL: regPairs[" + rpHL + "],\n AF_: regPairs[" + rpAF_ + "],\n BC_: regPairs[" + rpBC_ + "],\n DE_: regPairs[" + rpDE_ + "],\n HL_: regPairs[" + rpHL_ + "],\n IX: regPairs[" + rpIX + "],\n IY: regPairs[" + rpIY + "],\n SP: regPairs[" + rpSP + "],\n PC: regPairs[" + rpPC + "],\n IR: regPairs[" + rpIR + "],\n iff1: iff1,\n iff2: iff2,\n im: im,\n halted: halted,\n T: tstates,\n intp: interruptPending,\n intd: interruptDataBus,\n };\n };\n\n /* Register / flag accessors (used for tape trapping and test harness) */\n self.getAF = function() {\n return regPairs[" + rpAF + "];\n }\n self.getBC = function() {\n return regPairs[" + rpBC + "];\n }\n self.getDE = function() {\n return regPairs[" + rpDE + "];\n }\n self.getHL = function() {\n return regPairs[" + rpHL + "];\n }\n self.getAF_ = function() {\n return regPairs[" + rpAF_ + "];\n }\n self.getBC_ = function() {\n return regPairs[" + rpBC_ + "];\n }\n self.getDE_ = function() {\n return regPairs[" + rpDE_ + "];\n }\n self.getHL_ = function() {\n return regPairs[" + rpHL_ + "];\n }\n self.getIX = function() {\n return regPairs[" + rpIX + "];\n }\n self.getIY = function() {\n return regPairs[" + rpIY + "];\n }\n self.getI = function() {\n return regs[" + rI + "];\n }\n self.getR = function() {\n return regs[" + rR + "];\n }\n self.getSP = function() {\n return regPairs[" + rpSP + "];\n }\n self.getPC = function() {\n return regPairs[" + rpPC + "];\n }\n self.getIFF1 = function() {\n return iff1;\n }\n self.getIFF2 = function() {\n return iff2;\n }\n self.getIM = function() {\n return im;\n }\n self.getHalted = function() {\n return halted;\n }\n\n self.setAF = function(val) {\n regPairs[" + rpAF + "] = val;\n }\n self.setBC = function(val) {\n regPairs[" + rpBC + "] = val;\n }\n self.setDE = function(val) {\n regPairs[" + rpDE + "] = val;\n }\n self.setHL = function(val) {\n regPairs[" + rpHL + "] = val;\n }\n self.setAF_ = function(val) {\n regPairs[" + rpAF_ + "] = val;\n }\n self.setBC_ = function(val) {\n regPairs[" + rpBC_ + "] = val;\n }\n self.setDE_ = function(val) {\n regPairs[" + rpDE_ + "] = val;\n }\n self.setHL_ = function(val) {\n regPairs[" + rpHL_ + "] = val;\n }\n self.setIX = function(val) {\n regPairs[" + rpIX + "] = val;\n }\n self.setIY = function(val) {\n regPairs[" + rpIY + "] = val;\n }\n self.setI = function(val) {\n regs[" + rI + "] = val;\n }\n self.setR = function(val) {\n regs[" + rR + "] = val;\n }\n self.setSP = function(val) {\n regPairs[" + rpSP + "] = val;\n }\n self.setPC = function(val) {\n regPairs[" + rpPC + "] = val;\n }\n self.setIFF1 = function(val) {\n iff1 = val & 1;\n }\n self.setIFF2 = function(val) {\n iff2 = val & 1;\n }\n self.setIM = function(val) {\n im = val & 1;\n }\n self.setHalted = function(val) {\n halted = !!val;\n }\n\n self.getTstates = function() {\n return tstates;\n }\n self.setTstates = function(val) {\n tstates = val * 1;\n }\n\n self.getCarry_ = function() {\n return regs[" + rF_ + "] & " + FLAG_C + ";\n };\n self.setCarry = function(val) {\n if (val) {\n regs[" + rF + "] |= " + FLAG_C + ";\n } else {\n regs[" + rF + "] &= " + (~FLAG_C) + ";\n }\n };\n self.getA_ = function() {\n return regs[" + rA_ + "];\n };\n\n return self;\n};"; + defineZ80JS = "window.Z80 = function(opts) {\n var self = {};\n\n " + setUpStateJS + "\n\n self.requestInterrupt = function(dataBus) {\n interruptPending = true;\n interruptDataBus = dataBus & 0xffff;\n /* TODO: use event scheduling to keep the interrupt line active for a fixed\n ~48T window, to support retriggered interrupts and interrupt blocking via\n chains of EI or DD/FD prefixes */\n }\n self.nonMaskableInterrupt = function() {\n iff1 = 1;\n self.requestInterrupt(0x66);\n }\n var z80Interrupt = function() {\n if (iff1) {\n if (halted) {\n /* move PC on from the HALT opcode */\n regPairs[" + rpPC + "]++;\n halted = false;\n }\n\n iff1 = iff2 = 0;\n\n /* push current PC in readiness for call to interrupt handler */\n regPairs[" + rpSP + "]--; WRITEMEM(regPairs[" + rpSP + "], regPairs[" + rpPC + "] >> 8);\n regPairs[" + rpSP + "]--; WRITEMEM(regPairs[" + rpSP + "], regPairs[" + rpPC + "] & 0xff);\n\n /* TODO: R register */\n\n switch (im) {\n case 0:\n regPairs[" + rpPC + "] = interruptDataBus; // assume always RST\n tstates += 6;\n break;\n case 1:\n regPairs[" + rpPC + "] = 0x0038;\n tstates += 7;\n break;\n case 2:\n inttemp = (regs[" + rI + "] << 8) | (interruptDataBus & 0xff);\n l = READMEM(inttemp);\n inttemp = (inttemp+1) & 0xffff;\n h = READMEM(inttemp);\n /*console.log(hex(interruptDataBus), hex(inttemp), hex(l), hex(h));*/\n regPairs[" + rpPC + "] = (h<<8) | l;\n tstates += 7;\n break;\n }\n }\n };\n\n self.runFrame = function(frameLength) {\n var lastOpcodePrefix, offset, opcode;\n\n while (tstates < frameLength || opcodePrefix) {\n if (interruptible && interruptPending) {\n z80Interrupt();\n interruptPending = false;\n }\n interruptible = true; /* unless overridden by opcode */\n lastOpcodePrefix = opcodePrefix;\n opcodePrefix = '';\n switch (lastOpcodePrefix) {\n case '':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS, null, opts.traps)) + "\n break;\n case 'CB':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_CB)) + "\n break;\n case 'DD':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_DD, OPCODE_RUN_STRINGS)) + "\n break;\n case 'DDCB':\n offset = READMEM(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n if (offset & 0x80) offset -= 0x100;\n CONTEND_READ(regPairs[" + rpPC + "], 3);\n opcode = memory.read(regPairs[" + rpPC + "]);\n CONTEND_READ_NO_MREQ(regPairs[" + rpPC + "], 1);\n CONTEND_READ_NO_MREQ(regPairs[" + rpPC + "], 1);\n regPairs[" + rpPC + "]++;\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_DDCB)) + "\n break;\n case 'ED':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_ED)) + "\n break;\n case 'FD':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_FD, OPCODE_RUN_STRINGS)) + "\n break;\n case 'FDCB':\n offset = READMEM(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n if (offset & 0x80) offset -= 0x100;\n CONTEND_READ(regPairs[" + rpPC + "], 3);\n opcode = memory.read(regPairs[" + rpPC + "]);\n CONTEND_READ_NO_MREQ(regPairs[" + rpPC + "], 1);\n CONTEND_READ_NO_MREQ(regPairs[" + rpPC + "], 1);\n regPairs[" + rpPC + "]++;\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_FDCB)) + "\n break;\n default:\n throw(\"Unknown opcode prefix: \" + lastOpcodePrefix);\n }\n }\n while (display.nextEventTime != null && display.nextEventTime <= tstates) display.doEvent();\n };\n\n self.reset = function() {\n regPairs[" + rpPC + "] = regPairs[" + rpIR + "] = 0;\n iff1 = 0; iff2 = 0; im = 0; halted = false;\n };\n\n self.loadState = function(snapRegs) {\n regPairs[" + rpAF + "] = snapRegs['AF'];\n regPairs[" + rpBC + "] = snapRegs['BC'];\n regPairs[" + rpDE + "] = snapRegs['DE'];\n regPairs[" + rpHL + "] = snapRegs['HL'];\n regPairs[" + rpAF_ + "] = snapRegs['AF_'];\n regPairs[" + rpBC_ + "] = snapRegs['BC_'];\n regPairs[" + rpDE_ + "] = snapRegs['DE_'];\n regPairs[" + rpHL_ + "] = snapRegs['HL_'];\n regPairs[" + rpIX + "] = snapRegs['IX'];\n regPairs[" + rpIY + "] = snapRegs['IY'];\n regPairs[" + rpSP + "] = snapRegs['SP'];\n regPairs[" + rpPC + "] = snapRegs['PC'];\n regPairs[" + rpIR + "] = snapRegs['IR'];\n iff1 = snapRegs['iff1'] & 1;\n iff2 = snapRegs['iff2'] & 1;\n im = snapRegs['im'] & 3;\n halted = !!snapRegs['halted'];\n tstates = snapRegs['T'] * 1;\n interruptPending = !!snapRegs['intp'];\n interruptDataBus = snapRegs['intd'] & 0xffff;\n };\n\n self.saveState = function() {\n return {\n AF: regPairs[" + rpAF + "],\n BC: regPairs[" + rpBC + "],\n DE: regPairs[" + rpDE + "],\n HL: regPairs[" + rpHL + "],\n AF_: regPairs[" + rpAF_ + "],\n BC_: regPairs[" + rpBC_ + "],\n DE_: regPairs[" + rpDE_ + "],\n HL_: regPairs[" + rpHL_ + "],\n IX: regPairs[" + rpIX + "],\n IY: regPairs[" + rpIY + "],\n SP: regPairs[" + rpSP + "],\n PC: regPairs[" + rpPC + "],\n IR: regPairs[" + rpIR + "],\n iff1: iff1,\n iff2: iff2,\n im: im,\n halted: halted,\n T: tstates,\n intp: interruptPending,\n intd: interruptDataBus,\n };\n };\n\n /* Register / flag accessors (used for tape trapping and test harness) */\n self.getAF = function() {\n return regPairs[" + rpAF + "];\n }\n self.getBC = function() {\n return regPairs[" + rpBC + "];\n }\n self.getDE = function() {\n return regPairs[" + rpDE + "];\n }\n self.getHL = function() {\n return regPairs[" + rpHL + "];\n }\n self.getAF_ = function() {\n return regPairs[" + rpAF_ + "];\n }\n self.getBC_ = function() {\n return regPairs[" + rpBC_ + "];\n }\n self.getDE_ = function() {\n return regPairs[" + rpDE_ + "];\n }\n self.getHL_ = function() {\n return regPairs[" + rpHL_ + "];\n }\n self.getIX = function() {\n return regPairs[" + rpIX + "];\n }\n self.getIY = function() {\n return regPairs[" + rpIY + "];\n }\n self.getI = function() {\n return regs[" + rI + "];\n }\n self.getR = function() {\n return regs[" + rR + "];\n }\n self.getSP = function() {\n return regPairs[" + rpSP + "];\n }\n self.getPC = function() {\n return regPairs[" + rpPC + "];\n }\n self.getIFF1 = function() {\n return iff1;\n }\n self.getIFF2 = function() {\n return iff2;\n }\n self.getIM = function() {\n return im;\n }\n self.getHalted = function() {\n return halted;\n }\n\n self.setAF = function(val) {\n regPairs[" + rpAF + "] = val;\n }\n self.setBC = function(val) {\n regPairs[" + rpBC + "] = val;\n }\n self.setDE = function(val) {\n regPairs[" + rpDE + "] = val;\n }\n self.setHL = function(val) {\n regPairs[" + rpHL + "] = val;\n }\n self.setAF_ = function(val) {\n regPairs[" + rpAF_ + "] = val;\n }\n self.setBC_ = function(val) {\n regPairs[" + rpBC_ + "] = val;\n }\n self.setDE_ = function(val) {\n regPairs[" + rpDE_ + "] = val;\n }\n self.setHL_ = function(val) {\n regPairs[" + rpHL_ + "] = val;\n }\n self.setIX = function(val) {\n regPairs[" + rpIX + "] = val;\n }\n self.setIY = function(val) {\n regPairs[" + rpIY + "] = val;\n }\n self.setI = function(val) {\n regs[" + rI + "] = val;\n }\n self.setR = function(val) {\n regs[" + rR + "] = val;\n }\n self.setSP = function(val) {\n regPairs[" + rpSP + "] = val;\n }\n self.setPC = function(val) {\n regPairs[" + rpPC + "] = val;\n }\n self.setIFF1 = function(val) {\n iff1 = val & 1;\n }\n self.setIFF2 = function(val) {\n iff2 = val & 1;\n }\n self.setIM = function(val) {\n im = val & 1;\n }\n self.setHalted = function(val) {\n halted = !!val;\n }\n\n self.getTstates = function() {\n return tstates;\n }\n self.setTstates = function(val) {\n tstates = val * 1;\n }\n\n self.getCarry_ = function() {\n return regs[" + rF_ + "] & " + FLAG_C + ";\n };\n self.setCarry = function(val) {\n if (val) {\n regs[" + rF + "] |= " + FLAG_C + ";\n } else {\n regs[" + rF + "] &= " + (~FLAG_C) + ";\n }\n };\n self.getA_ = function() {\n return regs[" + rA_ + "];\n };\n\n return self;\n};"; defineZ80JS = defineZ80JS.replace(/READMEM\((.*?)\)/g, '(CONTEND_READ($1, 3), memory.read($1))'); defineZ80JS = defineZ80JS.replace(/WRITEMEM\((.*?),(.*?)\)/g, "CONTEND_WRITE($1, 3);\nwhile (display.nextEventTime != null && display.nextEventTime < tstates) display.doEvent();\nmemory.write($1,$2);"); if (opts.applyContention) { diff --git a/src/emu.ts b/src/emu.ts index 3d91802d..47b48169 100644 --- a/src/emu.ts +++ b/src/emu.ts @@ -132,6 +132,12 @@ export class RasterVideo { this.ctx.putImageData(this.imageData, 0, 0); } + clearRect(dx:number, dy:number, w:number, h:number) { + var ctx = this.ctx; + ctx.fillStyle = '#000000'; + ctx.fillRect(dx, dy, w, h); + } + setupMouseEvents(el? : HTMLCanvasElement) { if (!el) el = this.canvas; $(el).mousemove( (e) => { @@ -242,7 +248,7 @@ export class AnimationTimer { } catch (e) { this.running = false; this.pulsing = false; - throw e; + throw new EmuHalt(e); } } if (this.useReqAnimFrame) diff --git a/src/pixed/pixeleditor.ts b/src/pixed/pixeleditor.ts index 5b18b7df..c7d6ba7c 100644 --- a/src/pixed/pixeleditor.ts +++ b/src/pixed/pixeleditor.ts @@ -60,7 +60,7 @@ type PixelEditorMessage = { ///////////////// -var pixel_re = /([0#]?)([x$%]|\d'[bh])([0-9a-f]+)(?:;.*$)?/gim; +var pixel_re = /([0#]?)([bx$%]|\d'[bh])([0-9a-f]+)(?:;.*$)?/gim; function convertToHexStatements(s:string) : string { // convert 'hex ....' asm format @@ -316,8 +316,8 @@ var PREDEF_LAYOUTS : {[id:string]:PixelEditorPaletteLayout} = { ['Sprite 3', 0x1d, 3] ], 'astrocade':[ - ['Left', 0x04, -4], - ['Right', 0x00, -4] + ['Left', 0x00, -4], + ['Right', 0x04, -4] ], }; diff --git a/src/platform/astrocade.ts b/src/platform/astrocade.ts index ffacd20c..d202a33d 100644 --- a/src/platform/astrocade.ts +++ b/src/platform/astrocade.ts @@ -14,6 +14,7 @@ const ASTROCADE_PRESETS = [ {id:'hello.c', name:'Hello World'}, {id:'lines.c', name:'Lines'}, {id:'sprites.c', name:'Sprites'}, + {id:'vsync.c', name:'Sprites w/ VSYNC'}, {id:'cosmic.c', name:'Cosmic Impalas Game'}, ]; @@ -71,7 +72,9 @@ const _BallyAstrocadePlatform = function(mainElement, arcade) { const sheight = arcade ? 204 : 102; const swbytes = Math.floor(swidth / 4); const cpuFrequency = 1789000; - const cpuCyclesPerLine = cpuFrequency/(60*262); + const cpuCyclesPerLine = Math.floor(cpuFrequency/(60*262.5)); + const cpuCyclesPerHBlank = Math.floor(cpuCyclesPerLine*0.33); + const cpuCyclesPerVisible = cpuCyclesPerLine - cpuCyclesPerHBlank; const INITIAL_WATCHDOG = 256; const PIXEL_ON = 0xffeeeeee; const PIXEL_OFF = 0xff000000; @@ -88,13 +91,9 @@ const _BallyAstrocadePlatform = function(mainElement, arcade) { var infbk = 0; var verbl = sheight; var palette = new Uint32Array(8); - // default palette - for (var i=0; i<8; i++) - palette[i] = ASTROCADE_PALETTE[i]; - var refreshlines = 0; var vidactive = false; - + function ramwrite(a:number, v:number) { ram.mem[a] = v; ramupdate(a, v); @@ -200,7 +199,7 @@ const _BallyAstrocadePlatform = function(mainElement, arcade) { ]), write: newAddressDecoder([ [0x4000, 0x4fff, 0xfff, ramwrite], - [0x0000, 0x3fff, 0x3fff, magicwrite], + [0x0000, 0x3fff, 0xfff, magicwrite], ]), // TODO: correct values? // TODO: no contention on hblank @@ -311,6 +310,9 @@ const _BallyAstrocadePlatform = function(mainElement, arcade) { setKeyboardFromMap(video, inputs, ASTROCADE_KEYCODE_MAP); pixels = video.getFrameData(); timer = new AnimationTimer(60, this.nextFrame.bind(this)); + // default palette + for (var i=0; i<8; i++) + palette[i] = ASTROCADE_PALETTE[i]; } readAddress(addr) { @@ -323,14 +325,23 @@ const _BallyAstrocadePlatform = function(mainElement, arcade) { } advance(novideo : boolean) { - this.loadControls(); + this.scanline = 0; + var extra = 0; // keep track of spare cycles for (var sl=0; sl<131; sl++) { - this.scanline = sl; - vidactive = sl < verbl; - this.runCPU(cpu, cpuCyclesPerLine*2); // TODO? - if (sl == inlin && (inmod & 0x8)) { - this.requestInterrupt(cpu, infbk); + // double scanlines in consumer mode + for (var i=0; i<2; i++) { + // simulate contention during visible part of scanline + vidactive = sl < verbl; + extra = this.runCPU(cpu, cpuCyclesPerVisible - extra); + vidactive = false; + extra = this.runCPU(cpu, cpuCyclesPerHBlank - extra); + this.scanline++; } + // interrupt + if (sl == inlin && (inmod & 0x8)) { + cpu.requestInterrupt(infbk); + } + // refresh this line in frame buffer? if (sl < sheight && refreshlines>0) { refreshline(sl); refreshlines--; @@ -338,6 +349,8 @@ const _BallyAstrocadePlatform = function(mainElement, arcade) { } if (!novideo) { video.updateFrame(0, 0, 0, 0, swidth, verbl); + video.clearRect(0, verbl, swidth, sheight-verbl); + this.loadControls(); } /* if (watchdog_counter-- <= 0) { @@ -360,7 +373,7 @@ const _BallyAstrocadePlatform = function(mainElement, arcade) { } loadState(state) { - cpu.loadState(state.c); // TODO: this causes problems on reset+debug + cpu.loadState(state.c); ram.mem.set(state.b); palette.set(state.palette); magicop = state.magicop; @@ -372,6 +385,7 @@ const _BallyAstrocadePlatform = function(mainElement, arcade) { inlin = state.inlin; infbk = state.infbk; verbl = state.verbl; + this.scanline = state.sl; this.loadControlsState(state); refreshall(); } @@ -390,6 +404,7 @@ const _BallyAstrocadePlatform = function(mainElement, arcade) { inlin: inlin, infbk: infbk, verbl: verbl, + sl: this.scanline, }; } loadControlsState(state) { @@ -418,8 +433,42 @@ const _BallyAstrocadePlatform = function(mainElement, arcade) { reset() { cpu.reset(); cpu.setTstates(0); + // TODO? + magicop = xpand = inmod = inlin = infbk = shift2 = horcb = 0; + verbl = sheight; + xplower = false; //watchdog_counter = INITIAL_WATCHDOG; } + getDebugCategories() { + return super.getDebugCategories().concat(['Astro']); + } + getDebugInfo(category, state) { + switch (category) { + case 'Astro': return this.toLongString(state); + default: return super.getDebugInfo(category, state); + } + } + toLongString(st) { + var s = ""; + s += " Scanline: " + st.sl; + s += "\nMAGICOP: $" + hex(st.magicop); + s += "\n XPAND: $" + hex(st.xpand); + s += "\nXPLOWER: " + st.xplower; + s += "\n SHIFT2: $" + hex(st.shift2); + s += "\n HORCB: $" + hex(st.horcb); + s += "\n INMOD: $" + hex(st.inmod); + s += "\n INLIN: " + st.inlin; + s += "\n INFBK: " + st.infbk; + s += "\n VERBL: " + st.verbl; + /* + s += "\nPalette: "; + for (var i=0; i<8; i++) + s += hex(palette[i]); + */ + s += "\n"; + return s; + } + } return new BallyAstrocadePlatform(); } diff --git a/src/ui.ts b/src/ui.ts index 6c04810d..ab6b70cd 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -1412,7 +1412,7 @@ function _addLinkFile() { var fn = getCurrentMainFilename(); var tool = platform.getToolForFilename(fn); if (fn.endsWith(".c") || tool == 'sdcc' || tool == 'cc65') - addFileToProject("Linked C", ".c", (s) => { return '//#link "'+s+'"' }); + addFileToProject("Linked C (or .s)", ".c", (s) => { return '//#link "'+s+'"' }); else if (fn.endsWith("asm") || fn.endsWith(".s") || tool == 'ca65') addFileToProject("Linked ASM", ".inc", (s) => { return ';#link "'+s+'"' }); else diff --git a/test/cli/testplatforms.js b/test/cli/testplatforms.js index e4c203d5..42b19aa9 100644 --- a/test/cli/testplatforms.js +++ b/test/cli/testplatforms.js @@ -77,6 +77,7 @@ emu.RasterVideo = function(mainElement, width, height, options) { this.getFrameData = function() { return datau32; } this.getImageData = function() { return {data:datau8, width:width, height:height}; } this.updateFrame = function() {} + this.clearRect = function() {} this.setupMouseEvents = function() { } this.canvas = this; this.getContext = function() { return this; }