Saturday, February 4, 2023

Structured ca65hl Macro Code: Option to Output to Standard Assembly Code.

Clock Test

As a method of troubleshooting, or if there was a need to export code to standard assembly langauge, the customSyntax and ca65hl macros now support a simple method of printing the assembly code that the macros generate to the console. It is possible to coax ca65 to print to a file, but this would require a custom linker config and more macro code, and for the limited use this might see, I plan to leave it as is for now. This feature has been added to https://github.com/Movax12/ca65hl. To understand how to use this feature, please see the following example, which is using code from the previous post.


The code enclosed by the

ca65hl_Listing on
and
ca65hl_Listing off
will be printed to the console as standard assembly code.

; -------------------------------------------------------------------------------------------- ; File: mainGameLoopState ; -------------------------------------------------------------------------------------------- ; this State is one level above rootState ; -------------------------------------------------------------------------------------------- ; required headers: .include "config.h" .include "header/memory.h" .include "header/funcCall.h" .include "header/ca65hl.h" ; -------------------------------------------------------------------------------------------- ; Define State: identifyState "mainGameLoopState" loadState "mainGameLoopState" ; -------------------------------------------------------------------------------------------- ; Constants for State: ; -------------------------------------------------------------------------------------------- ; Headers for State: ; standard library .include "header/string.h" .include "header/nmi.h" .include "header/controller.h" .include "header/condes.h" .include "header/nes_constants.h" .include "header/ppu.h" .include "header/clock.h" ; -------------------------------------------------------------------------------------------- ; Code segment for State: .segment "CODE" ; -------------------------------------------------------------------------------------------- ; global memory settings: resBSS clockRunning .byte ; -------------------------------------------------------------------------------------------- ; Imports: ; -------------------------------------------------------------------------------------------- ; Exports: declarefunc nmiEnd ; -------------------------------------------------------------------------------------------- ; States: declarefunc mainGameLoopState ; -------------------------------------------------------------------------------------------- ; Forward Declarations: .pushseg .segment "RODATA" palette: .byte $01,$36,$05,$16,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F cursorPos: .byte 0, 3, 6, 9 .popseg func mainGameLoopState resBSS timeStr .byte 4 ; 3 byte string plus terminator resBSS cursor .byte ; position as 0 to 3 resBSS i .byte ; loop counter mb cursor = #0 mb clockRunning = #1 call nmi::waitForNMI ; wait for vblank to write palette call ppu::setBackgroundPalette, { &palette } jPrintRenderMode off jPrintSetMaxCol 32 jPrintSetCursor 9, 6 jPrint "Clock Test!" jPrint jPrint jPrint jPrint "00:00:00:00" jPrintSetCursor 1, 14 jPrint "Press START to Pause" jPrint "Press UP/DOWN to change value" jPrint "Press A to reset value" jPrintEnd PPU_RENDER_ON ca65hl_Listing on, "Main Loop" repeat ; draw the time: ; i = 0 -> hours ; i = 1 -> minutes ; i = 2 -> seconds ; i = 3 -> hundredths for ( mb i := #3, !N, dec i ) ; i: 3 to 0 ldx i call str::byteToDecimal, { clock::time[ x ], &timeStr } ldx i : mb a = cursorPos[ x ] + #9 call str::print, { a , #10, &(timeStr + 1) } ; only print the last 2 digits next ; draw cursor jPrintRenderMode on jPrintSetCursor 10, 11 jPrint " " ; create a blank buffer jPrintEnd ldy cursor txa ; x holds vramBufferIndex mb x = a + cursorPos[ y ] mb ppu::vramBuffer[ x ] = #$1F ; arrow symbol ; process input: lda controller::pressedNew : tay ; store a copy of buttons in y to quickly restore a ldx cursor if ( !zero && a & #BUTTON_LEFT ) dex elseif ( tya : a & #BUTTON_RIGHT && x < #3 ) inx elseif ( tya : a & #BUTTON_START ), !Z inc clockRunning elseif ( tya : a & #BUTTON_A ) mb clock::time[ x ] = #0 elseif ( tya : a & #BUTTON_UP && clock::time[ x ] <> #59 ), Z inc clock::time[ x ] elseif ( tya : a & #BUTTON_DOWN && clock::time[ x ] ), !Z dec clock::time[ x ] endif stx cursor call nmi::waitForNMI forever ca65hl_Listing off endfunc ; -------------------------------------------------------------------------------------------- func nmiEnd call controller::readPort, { #0 } call controller::autoRepeatButtons, { #0, #$FF } ; allow all to auto repeat if ( clockRunning & #1 ) call clock::clockTick endif rts endfunc ; --------------------------------------------------------------------------------------------

The output captured from the console:

; Main Loop DO_WHILE_LOOP_LABEL_0000: lda #$03 sta i FOR_STATEMENT_LABEL_0000: ldx i lda clock::time, x ldx #<timeStr ldy #>timeStr jsr str::byteToDecimal ldx i lda cursorPos, x clc adc #$09 sta ___functionMacro::parameterAddress lda #$0A ldx #<( timeStr + $01) ldy #>( timeStr + $01) jsr str::print dec i bpl FOR_STATEMENT_LABEL_0000 lda #<( jPrintStringData_01 ) ldx #>( jPrintStringData_01 ) ldy #jusPrint::bufferByteCount jsr ppu::vramBufferPutBlock ldy cursor txa clc adc cursorPos, y tax lda #$1F sta ppu::vramBuffer, x lda controller::pressedNew tay ldx cursor beq IF_STATEMENT_0001_ENDIF_LABEL and #BUTTON_LEFT beq IF_STATEMENT_0001_ENDIF_LABEL dex jmp IF_STATEMENT_0001_ELSE_ENDIF_LABEL IF_STATEMENT_0001_ENDIF_LABEL: tya and #BUTTON_RIGHT beq IF_STATEMENT_0001_ELSEIF_LABEL_0000 cpx #$03 bcs IF_STATEMENT_0001_ELSEIF_LABEL_0000 inx bne IF_STATEMENT_0001_ELSE_ENDIF_LABEL IF_STATEMENT_0001_ELSEIF_LABEL_0000: tya and #BUTTON_START beq IF_STATEMENT_0001_ELSEIF_LABEL_0001 inc clockRunning jmp IF_STATEMENT_0001_ELSE_ENDIF_LABEL IF_STATEMENT_0001_ELSEIF_LABEL_0001: tya and #BUTTON_A beq IF_STATEMENT_0001_ELSEIF_LABEL_0002 lda #$00 sta clock::time, x beq IF_STATEMENT_0001_ELSE_ENDIF_LABEL IF_STATEMENT_0001_ELSEIF_LABEL_0002: tya and #BUTTON_UP beq IF_STATEMENT_0001_ELSEIF_LABEL_0003 lda clock::time, x cmp #$3B beq IF_STATEMENT_0001_ELSEIF_LABEL_0003 inc clock::time, x bne IF_STATEMENT_0001_ELSE_ENDIF_LABEL IF_STATEMENT_0001_ELSEIF_LABEL_0003: tya and #BUTTON_DOWN beq IF_STATEMENT_0001_ELSEIF_LABEL_0004 lda clock::time, x beq IF_STATEMENT_0001_ELSEIF_LABEL_0004 dec clock::time, x IF_STATEMENT_0001_ELSE_ENDIF_LABEL: IF_STATEMENT_0001_ELSEIF_LABEL_0004: stx cursor jsr nmi::waitForNMI jmp DO_WHILE_LOOP_LABEL_0000

The output is able to capture all labels generated by the ca65hl macros, as well as all instructions processed by the assembler. It will not capture user labels or data (such as values generated by the .byte statement).