diff --git a/demo.html b/demo.html index aaef0107..21257951 100644 --- a/demo.html +++ b/demo.html @@ -156,7 +156,7 @@ We're always adding new things here, so subscribe to our mailing list for update
- +
@@ -167,7 +167,7 @@ We're always adding new things here, so subscribe to our mailing list for update
- +
diff --git a/doc/notes.txt b/doc/notes.txt index 38903948..b63f2152 100644 --- a/doc/notes.txt +++ b/doc/notes.txt @@ -56,8 +56,9 @@ TODO: - VCS skips step on lsr/lsr after run to line - update window list after building 2nd time? - stop debugging where mouse clicked +- bring back the profiler! +- vcs disasm is broken -FYI: Image links for the books on http://8bitworkshop.com/ are broken On the website the additional grey spacing next to the program line numbers is not dynamically resized when the web browser window size is changed. Intentional? diff --git a/images/book_a2600_med.jpg b/images/book_a2600_med.jpg new file mode 100644 index 00000000..bf7ad3a6 Binary files /dev/null and b/images/book_a2600_med.jpg differ diff --git a/images/book_arcade_med.jpg b/images/book_arcade_med.jpg new file mode 100644 index 00000000..fdb6efee Binary files /dev/null and b/images/book_arcade_med.jpg differ diff --git a/index.html b/index.html index 76b0afde..2c5904ba 100644 --- a/index.html +++ b/index.html @@ -309,6 +309,7 @@ function require(modname) { + diff --git a/presets/vcs/6digit.inc b/presets/vcs/6digit.inc index ea2938a2..b2f8cf9b 100644 --- a/presets/vcs/6digit.inc +++ b/presets/vcs/6digit.inc @@ -17,35 +17,6 @@ THREE_COPIES equ %011 ; for NUSIZ registers seg Code -; setup 6-digit score -Digit6Setup: subroutine - lda #$18 - sta COLUP0 - lda #$28 - sta COLUP1 - lda #THREE_COPIES - sta NUSIZ0 - sta NUSIZ1 -; set horizontal position of player objects - sta WSYNC - SLEEP 26 - sta RESP0 - sta RESP1 - lda #$10 - sta HMP1 - sta WSYNC - sta HMOVE - sta HMCLR - lda #0 - sta REFP0 - sta REFP1 - sta GRP0 - sta GRP1 - lda #1 - sta VDELP0 - sta VDELP1 - rts - ; Adds value to 6-BCD-digit score. ; A = 1st BCD digit ; X = 2nd BCD digit @@ -97,13 +68,40 @@ GetDigitPtrs: subroutine ; Display the resulting 48x8 bitmap ; using the Digit0-5 pointers. +; A = color of scoreboard DrawDigits: subroutine .temp equ Temp1 .count equ Temp2 - sta WSYNC - SLEEP 40 ; start near end of scanline - lda #7 - sta .count +; clear player bitmaps first + jsr ClearGRP +; set colors + sta COLUP0 + sta COLUP1 +; set horizontal position of player objects + sta WSYNC +; instead of SLEEP 26... + lda #THREE_COPIES + sta NUSIZ0 + sta NUSIZ1 + lda #1 + sta VDELP0 + sta VDELP1 + lda #$10 + sta HMP1 + stx HMP0 ; x = 0 + nop +; set player positions (26 cycles later) + stx RESP0 + stx RESP1 + stx REFP0 + stx REFP1 +; apply fine adjustment + sta WSYNC + sta HMOVE + sta HMCLR + lda #8 + sta .count ; 8 scanlines + jsr ClearGRP ; just killing some time... .bigloop ldy .count ; counts backwards lda (Digit0),y ; load B0 (1st sprite byte) @@ -119,18 +117,19 @@ DrawDigits: subroutine tax ; -> X lda (Digit3),y ; load B3 -> A ldy .temp ; load B5 -> Y +.storegrp sta GRP1 ; B3 -> [GRP1]; B2 -> GRP0 stx GRP0 ; B4 -> [GRP0]; B3 -> GRP1 sty GRP1 ; B5 -> [GRP1]; B4 -> GRP0 sta GRP0 ; ?? -> [GRP0]; B5 -> GRP1 dec .count ; go to next line bpl .bigloop ; repeat until < 0 - - lda #0 ; clear the sprite registers - sta GRP0 - sta GRP1 - sta GRP0 - sta GRP1 +ClearGRP + ldx #0 ; empty out player bitmaps + stx GRP0 + stx GRP1 + stx GRP0 + stx GRP1 rts ; Font table for digits 0-9 (8x8 pixels) @@ -141,4 +140,4 @@ Digit6Font: hex 0006067f661e0e06003c6606067c607e hex 003c66667c60663c00181818180c667e hex 003c66663c66663c003c66063e66663c - + hex 00 ; so top of 9 is clean diff --git a/presets/vcs/examples/testlibrary.a b/presets/vcs/examples/testlibrary.a index 07f42566..c6125acf 100644 --- a/presets/vcs/examples/testlibrary.a +++ b/presets/vcs/examples/testlibrary.a @@ -40,7 +40,7 @@ InitLoop sta YPos0,x sta Flags0,x clc - adc #13 + adc #23 iny iny iny @@ -68,6 +68,9 @@ InitLoop sta PF1Ptr+1 lda #>Start+1 sta PF2Ptr+1 + lda YPos0+1 + adc #9 + sta YPos0+1 ; Next frame loop NextFrame @@ -76,12 +79,12 @@ NextFrame ; Do joystick movement ldy #1 jsr MoveJoystick - jsr Digit6Setup jsr GetDigitPtrs jsr MSpritePre TIMER_WAIT ; end of VBLANK +; Needed for single-line kernel TIMER_TABLE_SETUP ; Scoreboard jsr DrawDigits @@ -90,7 +93,6 @@ NextFrame sta COLUBK lda #22 jsr MSpriteDraw2 - ; Overscan TIMER_SETUP 28 ; Clear all colors to black before overscan @@ -99,6 +101,11 @@ NextFrame stx COLUP0 stx COLUP1 stx COLUPF +; add score + ldy #0 + ldx #0 + lda #1 + jsr AddScore ; 30-2 lines of overscan TIMER_WAIT ; Go to next frame diff --git a/presets/vcs/multisprite.inc b/presets/vcs/multisprite.inc index 585466d2..a325e242 100644 --- a/presets/vcs/multisprite.inc +++ b/presets/vcs/multisprite.inc @@ -45,16 +45,20 @@ ; MSpriteDraw1 - Single-line sprites, no playfield. ; This kernel requires TIMER_TABLE_SETUP at the 0th ; scanline, as it uses the timer to figure out the -; current scanline. +; current scanline. No vertical clipping is performed. +; In fact, if sprites go past the bottom, it will +; mess up the vertical sync. ; MSpriteDraw2 - Double-line sprites with playfield. ; The playfield is updated and the sprites are positioned ; in 8-scanline segments. ; This kernel uses the timer internally, but does not ; use the timer table or TIMER_TABLE_SETUP. +; Sprites are clipped on the bottom edge, but disappear +; off the top edge. ; Your program must call MSpriteInit when it starts, -; and MSpritePre between every frame. +; and MSpriteFrame between every frame. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -119,7 +123,7 @@ MSpriteInit subroutine rts ; call between frames -MSpritePre subroutine +MSpriteFrame subroutine ; Do one iteration of bubble sort on sprite indices ldx #NSprites-2 .SortLoop @@ -127,20 +131,18 @@ MSpritePre subroutine dex bpl .SortLoop ; loop until <= 0 ; Reset scanline counter and sprite objects - ldx #0 + jsr ResetCounters stx CurIndex - stx SIndx0 - stx SIndx1 - stx SSize0 - stx SSize1 rts -; single-line kernel routine +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; SINGLE-LINE KERNEL + MSpriteDraw1 subroutine .NextFindSprite ; Try to schedule sprites to both players jsr FindAnotherSprite - jsr FindAnotherSprite ; Apply fine offsets sta WSYNC ; start next scanline sta HMOVE ; apply the previous fine position(s) @@ -157,6 +159,106 @@ MSpriteDraw1 subroutine lda #191 jmp WaitForScanline +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; DOUBLE-LINE KERNEL WITH BACKGROUND + +MSpritePrefill2 subroutine + lda #0 + sta Scanline + jsr FindAnotherSprite2 + jsr FindAnotherSprite2 + sta WSYNC + sta HMOVE ; apply the previous fine position(s) + sta HMCLR ; clear motion registers + rts + +MSpriteDraw2 subroutine + sta WSYNC + sta PFIndex ; 24 * 4 scanlines = 96 2xlines + lda #0 + sta PFCount + sta VDELP1 + lda #1 + sta VDELP0 ; updates to GRP0 will be delayed + jmp .NewSprites +.Draw8Lines +; Phase 0: Fetch PF0 byte + jsr DrawSprites2 + ldy PFIndex + lda (PF0Ptr),y ; load PF0 + sta tmpPF0 +; Phase 1: Fetch PF1 byte + jsr DrawSprites2 + ldy PFIndex + lda (PF1Ptr),y ; load PF1 + sta tmpPF1 +; Phase 2: Fetch PF2 byte + jsr DrawSprites2 + ldy PFIndex + lda (PF2Ptr),y ; load PF2 + sty PFIndex + sta tmpPF2 +; Phase 3: Write PF0/PF1/PF2 registers + jsr DrawSprites2 + lda tmpPF0 + sta PF0 + lda tmpPF1 + sta PF1 + lda tmpPF2 + sta PF2 +; Go to next scanline, unless playfield is done +; or unless this segment is done + dec PFIndex + bmi .NoMoreLines ; playfield done + dec PFCount + bpl .Draw8Lines ; keep drawing +; done drawing, reset player counters + jsr ResetCounters +.NewSprites + lda PFIndex + asl + asl + eor #$7f + sbc #34 + sta Scanline ; Scanlines = 127 - PFIndex*4 +; Set up 0-2 player objects taking up to 8 scanlines + TIMER_SETUP 7 + jsr FindAnotherSprite2 + jsr CalcSpriteEnd +; Update playfield + ldy PFIndex + lda (PF0Ptr),y ; load PF0 -> X + tax + lda (PF1Ptr),y ; load PF1 -> tmp + sta tmpPF1 + lda (PF2Ptr),y ; load PF2 -> Y + tay +; Apply fine offsets + TIMER_WAIT ; wait for 8th scanlines and WSYNC + sta HMOVE ; apply the previous fine position(s) + sta HMCLR ; clear motion registers +; Store playfield registers + stx PF0 + lda tmpPF1 + sta PF1 + sty PF2 + dec PFIndex ; no more playfield? + bmi .NoMoreLines + lda PFCount + bne .Draw8Lines + sta WSYNC ; one more line + beq .NewSprites +.NoMoreLines + rts + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; SPRITE DRAWING ROUTINES +; used by kernels +; need to set SIndx0/1, SSize0/1, PData0/1, PColr0/1 +; all players must already be setup + ; called by single-line kernel DrawSprites1 subroutine ; Wait for next precise scanline @@ -166,6 +268,7 @@ DrawSprites1 subroutine lda Timer2Scanline,y ; lookup scanline # beq .AnotherScanline ; not if zero! sta Scanline ; save it +DrawSprites1a ; Calculate # of lines to draw for each sprite ; Sprite Y - current scanline + sprite height lda SIndx0 @@ -193,6 +296,7 @@ DrawSprites1 subroutine tax ; X = # of lines left to draw beq .NoSprites ; X = 0? we're done sta WSYNC ; next scanline +DrawSprites1b .DrawNextScanline ; Make sure player 0 index is within bounds ldy SIndx0 @@ -227,10 +331,7 @@ DrawSprites1 subroutine dex bne .DrawNextScanline ; Free up both player objects by zeroing them out - stx SIndx0 - stx SIndx1 - stx SSize0 - stx SSize1 + jsr ResetCounters sta WSYNC stx GRP0 stx GRP1 @@ -245,86 +346,41 @@ DrawSprites1 subroutine sta COLUP0 beq .DrawSprite1 ; always taken due to lda #0 -MSpriteDraw2 subroutine - sta WSYNC - sta PFIndex ; 24 * 4 scanlines = 96 2xlines - lda #0 - sta PFCount - sta VDELP1 - lda #1 - sta VDELP0 ; updates to GRP0 will be delayed -.KernelLoop - dec PFCount - bmi .NewSprites -.Draw8Lines -; Phase 0: Fetch PF0 byte - jsr DrawSprites2 - ldy PFIndex - lda (PF0Ptr),y ; load PF0 - sta tmpPF0 -; Phase 1: Fetch PF1 byte - jsr DrawSprites2 - ldy PFIndex - lda (PF1Ptr),y ; load PF1 - sta tmpPF1 -; Phase 2: Fetch PF2 byte - jsr DrawSprites2 - ldy PFIndex - lda (PF2Ptr),y ; load PF2 - sty PFIndex - sta tmpPF2 -; Phase 3: Write PF0/PF1/PF2 registers - jsr DrawSprites2 - lda tmpPF0 - sta PF0 - lda tmpPF1 - sta PF1 - lda tmpPF2 - sta PF2 -; Go to next scanline, unless playfield is done - dec PFIndex - bpl .KernelLoop -.NoMoreLines - rts -.NewSprites -; lda PFIndex -; sta COLUBK - lda PFIndex - asl - asl - eor #$7f - sbc #34 - sta Scanline ; Scanlines = 127 - PFIndex*4 -; Set up 0-2 player objects taking up to 8 scanlines - TIMER_SETUP 8 - ldx #0 - stx SSize0 - stx SSize1 - stx SIndx0 - stx SIndx1 - jsr FindAnotherSprite2 -; jsr FindAnotherSprite2 - jsr CalcSpriteEnd -; Update playfield - ldy PFIndex - lda (PF0Ptr),y ; load PF0 -> X +; called by 2-line kernel +DrawSprites2 subroutine +; Fetch sprite 0 values + lda SSize0 ; height in 2xlines + sec + isb SIndx0 ; INC yp0, then SBC yp0 + bcs DoDraw0 ; inside bounds? + lda #0 ; no, load the padding offset (0) +DoDraw0 + tay ; -> Y + lda (PColr0),y ; color for both lines + sta Colp0 ; -> colp0 + lda (PData0),y ; bitmap for first line + sta GRP0 ; -> [GRP0] (delayed due to VDEL) +; Fetch sprite 1 values + lda SSize1 ; height in 2xlines + sec + isb SIndx1 ; INC yp0, then SBC yp0 + bcs DoDraw1 ; inside bounds? + lda #0 ; no, load the padding offset (0) +DoDraw1 + tay ; -> Y + lda (PColr1),y ; color for both lines tax - lda (PF1Ptr),y ; load PF1 -> tmp - sta tmpPF1 - lda (PF2Ptr),y ; load PF2 -> Y + lda (PData1),y ; bitmap for first line tay -; Apply fine offsets - TIMER_WAIT ; wait for 8th scanlines and WSYNC - sta HMOVE ; apply the previous fine position(s) - sta HMCLR ; clear motion registers -; Store playfield registers - stx PF0 - lda tmpPF1 - sta PF1 - sty PF2 - dec PFIndex ; no more playfield? - bpl .KernelLoop - jmp .NoMoreLines +; WSYNC and store sprite values + lda Colp0 +; still have about 30 cycles left... + sta WSYNC + sty GRP1 ; GRP0 is also updated due to VDELP0 flag + stx COLUP1 + sta COLUP0 +; Return to caller + rts CalcSpriteEnd subroutine ; Calculate # of lines to draw for each sprite @@ -355,16 +411,24 @@ CalcSpriteEnd subroutine ; Compute the number of 8x lines in this section eor #$ff clc - adc #5 + adc #1 lsr lsr sta PFCount rts -;;; -;;; COMMON ROUTINES -;;; - +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; COMMON ROUTINES + +ResetCounters subroutine + ldx #0 + stx SSize0 + stx SSize1 + stx SIndx0 + stx SIndx1 + rts + ; We were too late to display a sprite. ; Put it earlier in the sort order and try next frame. ; X = sort index @@ -406,6 +470,7 @@ FindAnotherSprite2 ; alternate entry point when scanline known ; Due to timing issues, we have artifacts if player 1 is ; too close to the left edge of the screen. So we'd prefer to ; put those sprites in the player 0 slot. +; TODO: disable for 2-line sprite kernel cmp #34 ; X < 34 bcc .Plyr1NotReady ; First let's set its horizontal offset @@ -436,7 +501,7 @@ FindAnotherSprite2 ; alternate entry point when scanline known ldy #0 lda (PColr1),y sta SSize1 - inc SSize1 ; +1 to size + inc SSize1 ; +1 to height jmp .SetupDone .Plyr1NotReady ldx SIndx0 @@ -465,47 +530,12 @@ FindAnotherSprite2 ; alternate entry point when scanline known ldy #0 lda (PColr0),y sta SSize0 - inc SSize0 ; +1 to size + inc SSize0 ; +1 to height .SetupDone inc CurIndex ; go to next sprite in sort order .NoNearSprite rts -; called by 2-line kernel -DrawSprites2 subroutine -; Fetch sprite 0 values - lda SSize0 ; height in 2xlines - sec - isb SIndx0 ; INC yp0, then SBC yp0 - bcs DoDraw0 ; inside bounds? - lda #0 ; no, load the padding offset (0) -DoDraw0 - tay ; -> Y - lda (PColr0),y ; color for both lines - sta Colp0 ; -> colp0 - lda (PData0),y ; bitmap for first line - sta GRP0 ; -> [GRP0] (delayed due to VDEL) -; Fetch sprite 1 values - lda SSize1 ; height in 2xlines - sec - isb SIndx1 ; INC yp0, then SBC yp0 - bcs DoDraw1 ; inside bounds? - lda #0 ; no, load the padding offset (0) -DoDraw1 - tay ; -> Y - lda (PColr1),y ; color for both lines - tax - lda (PData1),y ; bitmap for first line - tay -; WSYNC and store sprite values - lda Colp0 - sta WSYNC - sty GRP1 ; GRP0 is also updated due to VDELP0 flag - stx COLUP1 - sta COLUP0 -; Return to caller - rts - ; Perform one sort iteration ; X register contains sort index (0 to NSprites-1) SwapSprites subroutine diff --git a/presets/verilog/switches.v b/presets/verilog/switches.v new file mode 100644 index 00000000..26a50159 --- /dev/null +++ b/presets/verilog/switches.v @@ -0,0 +1,40 @@ + +`include "hvsync_generator.v" + +/* +Player 1 Keys: arrow keys + space + shift +Player 2 Keys: A/D/W/S + Z + X +*/ +module switches_top(clk, reset, hsync, vsync, + switches_p1, switches_p2, + rgb); + + input clk, reset; + input [7:0] switches_p1; + input [7:0] switches_p2; + output hsync, vsync; + output [2:0] rgb; + wire display_on; + wire [8:0] hpos; + wire [8:0] vpos; + + hvsync_generator hvsync_gen( + .clk(clk), + .reset(reset), + .hsync(hsync), + .vsync(vsync), + .display_on(display_on), + .hpos(hpos), + .vpos(vpos) + ); + + // select p1 bit based on vertical position + wire p1gfx = switches_p1[vpos[7:5]]; + // select p2 bit based on horizontal position + wire p2gfx = switches_p2[hpos[7:5]]; + + assign rgb = {1'b0, + display_on && p1gfx, + display_on && p2gfx}; + +endmodule diff --git a/src/baseplatform.ts b/src/baseplatform.ts index 3b0db7fe..7367ff6e 100644 --- a/src/baseplatform.ts +++ b/src/baseplatform.ts @@ -2,6 +2,8 @@ import { RAM, RasterVideo, dumpRAM, lookupSymbol } from "./emu"; import { hex } from "./util"; import { CodeAnalyzer } from "./analysis"; +import { disassemble6502 } from "./cpu/disasm6502"; +import { disassembleZ80 } from "./cpu/disasmz80"; declare var Z80_fast, jt, CPU6809; @@ -523,6 +525,9 @@ export abstract class BaseZ80Platform extends BaseDebugPlatform { } } } + disassemble(pc:number, read:(addr:number)=>number) : DisasmLine { + return disassembleZ80(pc, read(pc), read(pc+1), read(pc+2), read(pc+3)); + } } export function getToolForFilename_z80(fn) { diff --git a/src/cpu/disasm6502.ts b/src/cpu/disasm6502.ts index 92660a7a..7a7cfb60 100644 --- a/src/cpu/disasm6502.ts +++ b/src/cpu/disasm6502.ts @@ -1,4 +1,6 @@ +import { hex } from "../util"; + var OPS_6502 = [ {mn:"BRK",am:"",nb:1,il:0,c1:7,c2:0}, // 00 {mn:"ORA",am:"(aa,x)",nb:2,il:0,c1:6,c2:0}, // 01 @@ -258,24 +260,7 @@ var OPS_6502 = [ {mn:"ISB",am:"AAAA,x",nb:3,il:1,c1:7,c2:1}, // FF ]; -function disassemble6502(pc:number, b0:number, b1:number, b2:number) : {line:string, nbytes:number} { - - function formatHex(number, len) { - if (typeof number === "undefined" || number === null || isNaN(number)) { - throw new Error("Invalid value \"" + number + "\" passed to formatHex()"); - } - var str = number.toString(16).toUpperCase(); - if (!len) { - if (str.length % 2 == 1) { - len = str.length+1; - } - } - while (str.length < len) { - str = "0" + str; - } - return str; - } - +export function disassemble6502(pc:number, b0:number, b1:number, b2:number) : {line:string, nbytes:number} { var op = OPS_6502[b0]; var s = op.mn; @@ -283,10 +268,10 @@ function disassemble6502(pc:number, b0:number, b1:number, b2:number) : {line:str if (am == 'branch') { var offset = (b1 < 0x80) ? (pc+2+b1) : (pc+2-(256-b1)); offset &= 0xffff; - am = '$'+formatHex(offset, 4); + am = '$'+hex(offset, 4); } else { - am = am.replace('aa','$'+formatHex(b1, 2)); - am = am.replace('AAAA','$'+formatHex(b1+(b2<<8), 4)); + am = am.replace('aa','$'+hex(b1, 2)); + am = am.replace('AAAA','$'+hex(b1+(b2<<8), 4)); } return {line:op.mn + " " + am, nbytes:op.nb}; }; diff --git a/src/cpu/disasmz80.ts b/src/cpu/disasmz80.ts new file mode 100644 index 00000000..8aaf7bb1 --- /dev/null +++ b/src/cpu/disasmz80.ts @@ -0,0 +1,72 @@ + +import { hex } from "../util"; + +const Z80_OPS = ["nop","ld bc,xx","ld (bc),a","inc bc","inc b","dec b","ld b,x","rlca","ex af,af'","add hl,bc","ld a,(bc)","dec bc","inc c","dec c","ld c,x","rrca","djnz x","ld de,xx","ld (de),a","inc de","inc d","dec d","ld d,x","rla","jr x","add hl,de","ld a,(de)","dec de","inc e","dec e","ld e,x","rra","jr nz,x","ld hl,xx","ld (xx),hl","inc hl","inc h","dec h","ld h,x","daa","jr z,x","add hl,hl","ld hl,(xx)","dec hl","inc l","dec l","ld l,x","cpl","jr nc,x","ld sp,xx","ld (xx),a","inc sp","inc (hl)","dec (hl)","ld (hl),x","scf","jr c,x","add hl,sp","ld a,(xx)","dec sp","inc a","dec a","ld a,x","ccf","ld b,b","ld b,c","ld b,d","ld b,e","ld b,h","ld b,l","ld b,(hl)","ld b,a","ld c,b","ld c,c","ld c,d","ld c,e","ld c,h","ld c,l","ld c,(hl)","ld c,a","ld d,b","ld d,c","ld d,d","ld d,e","ld d,h","ld d,l","ld d,(hl)","ld d,a","ld e,b","ld e,c","ld e,d","ld e,e","ld e,h","ld e,l","ld e,(hl)","ld e,a","ld h,b","ld h,c","ld h,d","ld h,e","ld h,h","ld h,l","ld h,(hl)","ld h,a","ld l,b","ld l,c","ld l,d","ld l,e","ld l,h","ld l,l","ld l,(hl)","ld l,a","ld (hl),b","ld (hl),c","ld (hl),d","ld (hl),e","ld (hl),h","ld (hl),l","","ld (hl),a","ld a,b","ld a,c","ld a,d","ld a,e","ld a,h","ld a,l","ld a,(hl)","ld a,a","add a,b","add a,c","add a,d","add a,e","add a,h","add a,l","add a,(hl)","add a,a","adc a,b","adc a,c","adc a,d","adc a,e","adc a,h","adc a,l","adc a,(hl)","adc a,a","sub b","sub c","sub d","sub e","sub h","sub l","sub (hl)","sub a","sbc a,b","sbc a,c","sbc a,d","sbc a,e","sbc a,h","sbc a,l","sbc a,(hl)","sbc a,a","and b","and c","and d","and e","and h","and l","and (hl)","and a","xor b","xor c","xor d","xor e","xor h","xor l","xor (hl)","xor a","or b","or c","or d","or e","or h","or l","or (hl)","or a","cp b","cp c","cp d","cp e","cp h","cp l","cp (hl)","cp a","ret nz","pop bc","jp nz,xx","jp xx","call nz,xx","push bc","add a,x","rst 00h","ret z","","jp z,xx","xxBITxx","call z,xx","call xx","adc a,x","rst 08h","ret nc","pop de","jp nc,xx","out (x),a","call nc,xx","push de","sub x","rst 10h","ret c","exx","jp c,xx","in a,(x)","call c,xx","xxIXxx","sbc a,x","rst 18h","ret po","pop hl","jp po,xx","ex (sp),hl","call po,xx","push hl","and x","rst 20h","ret pe","jp (hl)","jp pe,xx","ex de,hl","call pe,xx","xx80xx","xor x","rst 28h","ret p","pop af","jp p,xx","di","call p,xx","push af","or x","rst 30h","ret m","ld sp,hl","jp m,xx","ei","call m,xx","xxIYxx","cp x","rst 38h"]; +const Z80_OPS_ED = ["in b,(c)","out (c),b","sbc hl,bc","ld (xx),bc","neg","retn","im 0","ld i,a","in c,(c)","out (c),c","adc hl,bc","ld bc,(xx)","neg","reti","","ld r,a","in d,(c)","out (c),d","sbc hl,de","ld (xx),de","neg","retn","im 1","ld a,i","in e,(c)","out (c),e","adc hl,de","ld de,(xx)","neg","retn","im 2","ld a,r","in h,(c)","out (c),h","sbc hl,hl","ld (xx),hl","neg","retn","","rrd","in l,(c)","out (c),l","adc hl,hl","ld hl,(xx)","neg","retn","","rld","in f,(c)","out (c),f","sbc hl,sp","ld (xx),sp","neg","retn","","","in a,(c)","out (c),a","adc hl,sp","ld sp,(xx)","neg","reti","","","ldi","cpi","ini","outi","","","","","ldd","cpd","ind","outd","","","","","ldir","cpir","inir","otir","","","","","lddr","cpdr","indr","otdr","","","",""]; +const Z80_OPS_CB = ["rlc b","rlc c","rlc d","rlc e","rlc h","rlc l","rlc (hl)","rlc a","rrc b","rrc c","rrc d","rrc e","rrc h","rrc l","rrc (hl)","rrc a","rl b","rl c","rl d","rl e","rl h","rl l","rl (hl)","rl a","rr b","rr c","rr d","rr e","rr h","rr l","rr (hl)","rr a","sla b","sla c","sla d","sla e","sla h","sla l","sla (hl)","sla a","sra b","sra c","sra d","sra e","sra h","sra l","sra (hl)","sra a","sll b","sll c","sll d","sll e","sll h","sll l","sll (hl)","sll a","srl b","srl c","srl d","srl e","srl h","srl l","srl (hl)","srl a","bit 0,b","bit 0,c","bit 0,d","bit 0,e","bit 0,h","bit 0,l","bit 0,(hl)","bit 0,a","bit 1,b","bit 1,c","bit 1,d","bit 1,e","bit 1,h","bit 1,l","bit 1,(hl)","bit 1,a","bit 2,b","bit 2,c","bit 2,d","bit 2,e","bit 2,h","bit 2,l","bit 2,(hl)","bit 2,a","bit 3,b","bit 3,c","bit 3,d","bit 3,e","bit 3,h","bit 3,l","bit 3,(hl)","bit 3,a","bit 4,b","bit 4,c","bit 4,d","bit 4,e","bit 4,h","bit 4,l","bit 4,(hl)","bit 4,a","bit 5,b","bit 5,c","bit 5,d","bit 5,e","bit 5,h","bit 5,l","bit 5,(hl)","bit 5,a","bit 6,b","bit 6,c","bit 6,d","bit 6,e","bit 6,h","bit 6,l","bit 6,(hl)","bit 6,a","bit 7,b","bit 7,c","bit 7,d","bit 7,e","bit 7,h","bit 7,l","bit 7,(hl)","bit 7,a","res 0,b","res 0,c","res 0,d","res 0,e","res 0,h","res 0,l","res 0,(hl)","res 0,a","res 1,b","res 1,c","res 1,d","res 1,e","res 1,h","res 1,l","res 1,(hl)","res 1,a","res 2,b","res 2,c","res 2,d","res 2,e","res 2,h","res 2,l","res 2,(hl)","res 2,a","res 3,b","res 3,c","res 3,d","res 3,e","res 3,h","res 3,l","res 3,(hl)","res 3,a","res 4,b","res 4,c","res 4,d","res 4,e","res 4,h","res 4,l","res 4,(hl)","res 4,a","res 5,b","res 5,c","res 5,d","res 5,e","res 5,h","res 5,l","res 5,(hl)","res 5,a","res 6,b","res 6,c","res 6,d","res 6,e","res 6,h","res 6,l","res 6,(hl)","res 6,a","res 7,b","res 7,c","res 7,d","res 7,e","res 7,h","res 7,l","res 7,(hl)","res 7,a","set 0,b","set 0,c","set 0,d","set 0,e","set 0,h","set 0,l","set 0,(hl)","set 0,a","set 1,b","set 1,c","set 1,d","set 1,e","set 1,h","set 1,l","set 1,(hl)","set 1,a","set 2,b","set 2,c","set 2,d","set 2,e","set 2,h","set 2,l","set 2,(hl)","set 2,a","set 3,b","set 3,c","set 3,d","set 3,e","set 3,h","set 3,l","set 3,(hl)","set 3,a","set 4,b","set 4,c","set 4,d","set 4,e","set 4,h","set 4,l","set 4,(hl)","set 4,a","set 5,b","set 5,c","set 5,d","set 5,e","set 5,h","set 5,l","set 5,(hl)","set 5,a","set 6,b","set 6,c","set 6,d","set 6,e","set 6,h","set 6,l","set 6,(hl)","set 6,a","set 7,b","set 7,c","set 7,d","set 7,e","set 7,h","set 7,l","set 7,(hl)","set 7,a"]; + +// TODO: DD, etc -- http://z80-heaven.wikidot.com/opcode-reference-chart + +export function disassembleZ80(pc:number, b0:number, b1:number, b2:number, b3:number) : {line:string, nbytes:number} { + + var op,n,am; + var bytes = [b0,b1,b2,b3]; + n=1; + switch (b0) { + case 0xcb: + am = Z80_OPS_CB[b1]; + n++; + break; + case 0xed: + if (b1 >= 0x40 && b1 <= 0x7f) am = Z80_OPS_ED[b1 - 0x40]; + if (b1 >= 0xa0 && b1 <= 0xbf) am = Z80_OPS_ED[b1 - 0xa0 + 0x40]; + n++; + break; + case 0xdd: + am = Z80_OPS[b1].replace(/\bhl\b/, 'ix'); + n++; + break; + default: + am = Z80_OPS[b0]; + break; + } + if (!am || !am.length) am = "??"; + if (am.indexOf('xx') >= 0) { + am = am.replace(/\bxx\b/,'$'+hex(bytes[n]+(bytes[n+1]<<8), 4)); + n += 2; + } else if (am.indexOf('x') >= 0) { + if (am.startsWith('j')) { + var offset = (b1 < 0x80) ? (pc+2+b1) : (pc+2-(256-b1)); + offset &= 0xffff; + am = am.replace(/\bx\b/,'$'+hex(offset, 4)); + } else { + am = am.replace(/\bx\b/,'$'+hex(bytes[n], 2)); + } + n += 1; + } + /* + if (b0 == 0xcd && (s = cb_prefixed_ops[b1])) { + n = 2; + } else if (s = one_byte_instructions[b0]) { + n = 1; + } else if (s = two_byte_instructions[b0]) { + n = 2; + } else if (s = three_byte_instructions[b0]) { + n = 3; + } else { + s = "??"; + n = 1; + } + /* + TODO: http://z80-heaven.wikidot.com/opcode-reference-chart + if (am == 'branch') { + var offset = (b1 < 0x80) ? (pc+2+b1) : (pc+2-(256-b1)); + offset &= 0xffff; + am = '$'+formatHex(offset, 4); + } else { + am = am.replace('aa','$'+formatHex(b1, 2)); + am = am.replace('AAAA','$'+formatHex(b1+(b2<<8), 4)); + } + */ + return {line:am.toUpperCase(), nbytes:n}; +}; diff --git a/src/platform/vcs.ts b/src/platform/vcs.ts index 256e5a19..0c5844e4 100644 --- a/src/platform/vcs.ts +++ b/src/platform/vcs.ts @@ -4,11 +4,16 @@ import { Platform, BasePlatform, cpuStateToLongString_6502, BaseMAMEPlatform, Em import { PLATFORMS, RAM, newAddressDecoder, dumpRAM } from "../emu"; import { hex, lpad, tobin, byte2signed } from "../util"; import { CodeAnalyzer_vcs } from "../analysis"; +import { disassemble6502 } from "../cpu/disasm6502"; declare var platform : Platform; // global platform object declare var Javatari : any; declare var jt : any; // 6502 +// TODO: import or put in platform +declare var symbolmap : {[ident:string]:number}; +declare var addr2symbol : {[addr:number]:string}; + const VCS_PRESETS = [ {id:'examples/hello', chapter:4, name:'Hello 6502 and TIA'}, {id:'examples/vsync', chapter:5, name:'Painting on the CRT', title:'Color Bars'}, @@ -38,7 +43,7 @@ const VCS_PRESETS = [ {id:'examples/wavetable', chapter:36, name:'Wavetable Sound'}, {id:'examples/fracpitch', name:'Fractional Pitch'}, {id:'examples/pal', name:'PAL Video Output'}, - {id:'examples/testlibrary', name:'VCS Library Demo'}, +// {id:'examples/testlibrary', name:'VCS Library Demo'}, // {id:'examples/music2', name:'Pitch-Accurate Music'}, // {id:'examples/fullgame', name:'Thru Hike: The Game', title:'Thru Hike'}, ]; @@ -246,6 +251,22 @@ class VCSPlatform extends BasePlatform { disassemble(pc:number, read:(addr:number)=>number) : DisasmLine { return disassemble6502(pc, read(pc), read(pc+1), read(pc+2)); } + inspect(ident : string) : string { + if (this.isRunning()) return; // only inspect when stopped + var result; + var addr = symbolmap && (symbolmap[ident]); // || symbolmap['_'+ident]); + if (addr >= 0x80 && addr < 0x100) { // in RAM? + var size=4; + result = "$" + hex(addr,4) + ":"; + for (var i=0; i:(\d+):/i, /^\s*([0-9A-F]{4})/i); putWorkFile(fn, rstout); + // TODO: you have to get rid of all source lines to get asm listing listings[fn] = { asmlines:srclines.length ? asmlines : null, lines:srclines.length ? srclines : asmlines, diff --git a/test/cli/6502/test6502disasm.js b/test/cli/6502/test6502disasm.js index cb319d8c..737ce3ea 100644 --- a/test/cli/6502/test6502disasm.js +++ b/test/cli/6502/test6502disasm.js @@ -1,13 +1,8 @@ var assert = require('assert'); -var vm = require('vm'); var fs = require('fs'); -var includeInThisContext = function(path) { - var code = fs.readFileSync(path); - vm.runInThisContext(code, path); -}; -includeInThisContext("gen/cpu/disasm6502.js"); +var disassemble6502 = require("gen/cpu/disasm6502.js").disassemble6502; describe('6502 disassembler', function() { it('Should work', function() { diff --git a/test/cli/z80/testz80disasm.js b/test/cli/z80/testz80disasm.js new file mode 100644 index 00000000..cba9046e --- /dev/null +++ b/test/cli/z80/testz80disasm.js @@ -0,0 +1,18 @@ + +var assert = require('assert'); +var fs = require('fs'); + +var disassembleZ80 = require("gen/cpu/disasmz80.js").disassembleZ80; + +describe('Z80 disassembler', function() { + it('Should work', function() { + assert.deepEqual({line:"LD SP,$E800",nbytes:3}, disassembleZ80(0, 0x31, 0x00, 0xe8, 0)); + assert.deepEqual({line:"DI",nbytes:1}, disassembleZ80(0, 0xF3, 0, 0, 0)); + assert.deepEqual({line:"JP $0007",nbytes:3}, disassembleZ80(0, 0xC3, 0x07, 0x00, 0)); + assert.deepEqual({line:"LD A,$01",nbytes:2}, disassembleZ80(0, 0x3E, 0x01, 0, 0)); + assert.deepEqual({line:"LDIR",nbytes:2}, disassembleZ80(0, 0xED, 0xB0, 0, 0)); + assert.deepEqual({line:"JR C,$0027",nbytes:2}, disassembleZ80(0x4e, 0x38, 0xD7, 0, 0)); + assert.deepEqual({line:"XOR A",nbytes:2}, disassembleZ80(0, 0xaf, 0xd3, 0, 0)); + assert.deepEqual({line:"LD IX,$41E0",nbytes:4}, disassembleZ80(0, 0xdd, 0x21, 0xe0, 0x41)); + }); +});