From 007311415ca84d7cc17af9525e97600d1dcc4027 Mon Sep 17 00:00:00 2001 From: Bartek Zbytniewski Date: Sun, 19 Apr 2026 20:46:16 +0200 Subject: [PATCH 01/16] new target: X65 and fix for it --- examples/x65/plasma.mfk | 153 ++++++++++++++++++ include/platform/x65.ini | 22 +++ include/x65_cgia.mfk | 26 +++ include/x65_hardware.mfk | 6 + include/x65_ria.mfk | 7 + .../millfork/output/OutputPackager.scala | 2 +- 6 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 examples/x65/plasma.mfk create mode 100644 include/platform/x65.ini create mode 100644 include/x65_cgia.mfk create mode 100644 include/x65_hardware.mfk create mode 100644 include/x65_ria.mfk diff --git a/examples/x65/plasma.mfk b/examples/x65/plasma.mfk new file mode 100644 index 00000000..d35a8e9d --- /dev/null +++ b/examples/x65/plasma.mfk @@ -0,0 +1,153 @@ +//----------------------------------------------------------------------------- +// Plasma effect for X65 https://x65.zone/ +//----------------------------------------------------------------------------- + +byte b @ 0 +word w @ 1 +pointer p @ 3 + +//----------------------------------------------------------------------------- + +const byte SCR_W = 48; +const byte SCR_H = 30; + +const word LMS = $2000; // Memory address for character data (first text row) +const word LFS = $2600; // Memory address for character color data (first text row) +const word LBS = $2c00; // Memory address for background color data (first text row) +const word LCG = $3800; // Character generator memory address (8x8 font, font file must include a header) + +const array(byte) DL align(fast) = [ + $f3, // LMS + LFS + LBS + LCG + @word[LMS], + @word[LFS], + @word[LBS], + @word[LCG], + $a,$a,$a,$a,$a,$a,$a,$a,$a,$a, // MODE2 + $a,$a,$a,$a,$a,$a,$a,$a,$a,$a, // MODE2 + $a,$a,$a,$a,$a,$a,$a,$a,$a,$a, // MODE2 + $82,@word[DL.addr] // JMP to begin of DL and wait for Vertical BLank +] + +const array(byte) CHARSET @ LCG = [ + $00, $00, $00, $00, $00, $00, $00, $00, + $00, $00, $00, $10, $00, $00, $00, $00, + $00, $00, $18, $18, $00, $00, $00, $00, + $00, $00, $38, $38, $38, $00, $00, $00, + $00, $00, $3c, $3c, $3c, $3c, $00, $00, + $00, $7c, $7c, $7c, $7c, $7c, $00, $00, + $00, $7e, $7e, $7e, $7e, $7e, $7e, $00, + $fe, $fe, $fe, $fe, $fe, $fe, $fe, $00, + $00, $7f, $7f, $7f, $7f, $7f, $7f, $7f, + $00, $7e, $7e, $7e, $7e, $7e, $7e, $00, + $00, $7c, $7c, $7c, $7c, $7c, $00, $00, + $00, $00, $3c, $3c, $3c, $3c, $00, $00, + $00, $00, $38, $38, $38, $00, $00, $00, + $00, $00, $18, $18, $00, $00, $00, $00, + $00, $00, $00, $08, $00, $00, $00, $00, + $00, $00, $00, $00, $00, $00, $00, $00 + ] + +const array SINUS_TABLE @ $4000 = [for i, 0, until, 256 [sin((i * 256) / 256, 100)]] + +//----------------------------------------------------------------------------- + +byte c1A +byte c1B + +pointer screen @ $60 +pointer column @ $62 + +array(byte) lookupDiv16[256] @ $4100 +array(byte) xbuf[48] @ $4200 + +//----------------------------------------------------------------------------- + +macro asm void x65_init() { + sei ; disable IRQ + cld ; clear decimal mode + + ldx #$ff + txs ; initialize stack pointer + + lda #%10000000 + sta cgia_int_enable ; trigger NMI on VBL +} + +//----------------------------------------------------------------------------- + +macro void cgia_init() { + cgia_planes = 0; // disable all planes + + screen = LMS + // parallelto - risky thing + // paralleluntil - risky thing + for w, ($600 * 3) - 1, downto, 0 { screen[w] = 0 } // clear screen + + p = cgia_plane0.addr + for b, 9, downto, 0 { p[b] = 0 } // clear CGIA_PLANE0 registers + + cgia_plane0_row_height = 7 // 8 rows per character + cgia_offset0 = DL.addr // point plane0 to DL + + cgia_planes = 1; // activate plane0 +} + +//----------------------------------------------------------------------------- + +void plasma() { + byte _c1a, _c1b + byte i, ii, tmp + + screen = LMS + column = LFS + _c1a = c1A + _c1b = c1B + + for i, SCR_W - 1, downto, 0 { + xbuf[i] = SINUS_TABLE[_c1a] + SINUS_TABLE[_c1b]; + _c1a += 3 + _c1b += 7 + } + + for ii, SCR_H - 1, downto, 0 { + tmp = SINUS_TABLE[_c1a] + SINUS_TABLE[_c1b]; + + _c1a += 4 + _c1b += 9 + + for i, SCR_W - 1, downto, 0 { + screen[i] = lookupDiv16[xbuf[i] + tmp]; + column[i] = screen[i]; + } + + screen += SCR_W + column += SCR_W + } + + c1A += 3 + c1B -= 5 +} + +//----------------------------------------------------------------------------- + +interrupt asm void vbl() { + jsr plasma + sta cgia_int_status + rti +} + +//----------------------------------------------------------------------------- + +void main() { + x65_init() + + c1A = 1 + c1B = 5 + + cgia_init() + + // store values divided by 16 + for b:lookupDiv16 { lookupDiv16[b] = b >> 4 } + + while(true) {} +} diff --git a/include/platform/x65.ini b/include/platform/x65.ini new file mode 100644 index 00000000..ef51c408 --- /dev/null +++ b/include/platform/x65.ini @@ -0,0 +1,22 @@ +[compilation] +arch=w65c02 +encoding=petscii +screen_encoding=petscr +modules=x65_hardware,default_panic + +[allocation] +zp_bytes=$0-$ff +segment_default_start=$200 +segment_default_end=$fbff + +[define] +X65=1 +WIDESCREEN=1 +KEYBOARD=1 +JOYSTICKS=2 +HAS_BITMAP_MODE=1 + +[output] +style=single +format=$FF,$FF,startaddr,endaddr,allocated,$E0,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,addr:vbl,startaddr,$00,$00 +extension=xex diff --git a/include/x65_cgia.mfk b/include/x65_cgia.mfk new file mode 100644 index 00000000..72f463ef --- /dev/null +++ b/include/x65_cgia.mfk @@ -0,0 +1,26 @@ +// X65 CGIA hardware + +#if not(X65) +#warn x65_cgia module should be used only on X65 computer-compatible targets +#endif + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +const byte CGIA_DL_MODE_BIT = %00001000 +const byte CGIA_DL_DLI_BIT = %10000000 +const byte CGIA_DL_STORE_BIT = %01000000 + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +volatile byte cgia_raster @$FF10 // Raster Counter +volatile byte cgia_int_enable @$FF1A // RST Interrupt enabled +volatile byte cgia_int_status @$FF1B // RST Interrupt status +volatile byte cgia_planes @$FF30 +volatile byte cgia_back_color @$FF31 +volatile word cgia_offset0 @$FF38 +volatile byte cgia_plane0 @$FF40 +volatile byte cgia_plane0_flags @$FF40 +volatile byte cgia_plane0_row_height @$FF42 +volatile byte cgia_plane0_stride @$FF43 +volatile byte cgia_plane0_shared_color1 @$FF48 +volatile byte cgia_plane0_shared_color2 @$FF49 \ No newline at end of file diff --git a/include/x65_hardware.mfk b/include/x65_hardware.mfk new file mode 100644 index 00000000..fc1c7c8e --- /dev/null +++ b/include/x65_hardware.mfk @@ -0,0 +1,6 @@ +#if not(X65) +#warn x65_hardware module should be used only on X65 computer-compatible targets +#endif + +import x65_cgia +import x65_ria diff --git a/include/x65_ria.mfk b/include/x65_ria.mfk new file mode 100644 index 00000000..4c9ccb59 --- /dev/null +++ b/include/x65_ria.mfk @@ -0,0 +1,7 @@ +// X65 RIA hardware + +#if not(X65) +#warn x65_ria module should be used only on X65 computer-compatible targets +#endif + +volatile byte gpio_in0 @$FF80 \ No newline at end of file diff --git a/src/main/scala/millfork/output/OutputPackager.scala b/src/main/scala/millfork/output/OutputPackager.scala index dc113a3f..1b20a162 100644 --- a/src/main/scala/millfork/output/OutputPackager.scala +++ b/src/main/scala/millfork/output/OutputPackager.scala @@ -137,7 +137,7 @@ case class SymbolAddressOutput(symbol: String, bonus: Int) extends OutputPackage def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = { val b = mem.banks(bank) val x = mem.getAddress(symbol) + bonus - Array(b.end.toByte, b.end.>>(8).toByte) + Array(x.toByte, x.>>(8).toByte) } } From 7fec7cc3f3b5f67726a418efb49a218d88c1d539 Mon Sep 17 00:00:00 2001 From: Bartek Zbytniewski Date: Sun, 19 Apr 2026 20:50:06 +0200 Subject: [PATCH 02/16] template project for X65 --- examples/x65/template.mfk | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 examples/x65/template.mfk diff --git a/examples/x65/template.mfk b/examples/x65/template.mfk new file mode 100644 index 00000000..498ab148 --- /dev/null +++ b/examples/x65/template.mfk @@ -0,0 +1,27 @@ +byte tmp0 @ 0 +byte tmp1 @ 1 + +macro asm void x65_init() { + sei ; disable IRQ + cld ; clear decimal mode + + ldx # + txs ; initialize stack pointer + + lda #%10000000 + sta cgia_int_enable ; trigger NMI on VBL +} + +interrupt asm void vbl() { + inc tmp0 + sta cgia_int_status + rti +} + +void main() { + x65_init() + + while(true) { + tmp1 += 1 + } +} From f525336d22c3eb9e13384a89205f262795b0f364 Mon Sep 17 00:00:00 2001 From: Bartek Zbytniewski Date: Sun, 19 Apr 2026 21:04:54 +0200 Subject: [PATCH 03/16] syntax highlighting for Sublime Text --- Millfork.sublime-syntax | 110 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 Millfork.sublime-syntax diff --git a/Millfork.sublime-syntax b/Millfork.sublime-syntax new file mode 100644 index 00000000..9470f858 --- /dev/null +++ b/Millfork.sublime-syntax @@ -0,0 +1,110 @@ +%YAML 1.2 +--- +name: Millfork +scope: source.mfk +file_extensions: [mfk] + +contexts: + main: + - include: comments + - include: preprocessor + - include: numbers + - include: strings + - include: keywords + - include: operators + - include: punctuation + + comments: + - match: '//' + scope: punctuation.definition.comment.mfk + push: + - meta_scope: comment.line.double-slash.mfk + - match: $\n? + pop: true + - match: ';' + scope: punctuation.definition.comment.mfk + push: + - meta_scope: comment.line.semicolon.mfk + - match: $\n? + pop: true + - match: '/\*' + scope: punctuation.definition.comment.begin.mfk + push: + - meta_scope: comment.block.mfk + - match: '\*/' + scope: punctuation.definition.comment.end.mfk + pop: true + + preprocessor: + # Poprawiona reguła obejmująca wszystkie warianty #elseif / #elsif / #else if + - match: '^\s*#\s*(if|else|else\s*if|elseif|elsif|endif|use|warn|error|info|fatal|pragma|infoeval|define)\b' + scope: keyword.control.preprocessor.mfk + + numbers: + - match: '\b0[bB][01]+\b|\%[01]+\b' + scope: constant.numeric.binary.mfk + - match: '\b0[oOqQ][0-7]+\b' + scope: constant.numeric.octal.mfk + - match: '\b0[xX][0-9A-Fa-f]+\b|\$[0-9A-Fa-f]+\b|\b[0-9A-Fa-f]+[hH]\b' + scope: constant.numeric.hex.mfk + - match: '\b[0-9]+\b' + scope: constant.numeric.decimal.mfk + - match: ':-+|:\++' + scope: constant.language.unnamed-label.mfk + + strings: + - match: '"' + scope: punctuation.definition.string.begin.mfk + push: + - meta_scope: string.quoted.double.mfk + - match: '\\.' + scope: constant.character.escape.mfk + - match: '"' + scope: punctuation.definition.string.end.mfk + pop: true + + keywords: + # Typy danych + - match: '\b(void|bool|byte|sbyte|ubyte|word|farword|pointer|farpointer|long|word_be|word_le|long_be|long_le|file|int[0-9]+|signed[0-9]+|unsigned[0-9]+|set_carry|set_zero|set_overflow|set_negative|clear_carry|clear_zero|clear_overflow|clear_negative)\b' + scope: storage.type.mfk + + # Modyfikatory + - match: '\b(const|volatile|register|static|stack|asm|inline|noinline|interrupt|kernal_interrupt|macro|reentrant|extern|segment|align|fast)\b' + scope: storage.modifier.mfk + + # Kontrola przepływu + - match: '\b(if|else|for|while|do|until|to|downto|parallelto|paralleluntil|return|break|continue|goto|default)\b' + scope: keyword.control.mfk + + # Inne słowa kluczowe + - match: '\b(import|array|struct|union|enum|alias|call|label)\b' + scope: keyword.other.mfk + + # Wbudowane funkcje i stałe + - match: '\b(not|hi|lo|nonet|sizeof|sin|cos|tan|defined|true|false|nullptr|nullchar)\b' + scope: support.function.mfk + + # Kodowania znaków + - match: '\b(default|defaultz|scr|scrz|petscii|ascii|petscr|pet|atascii|atari|bbc|sinclair|apple2|jis|jisx|iso_de|iso_yu|iso_no|iso_dk|iso_se|iso_fi|petsciiz|asciiz|petscrz|petz|atasciiz|atariz|bbcz|sinclairz|apple2z|jisz|jisxz|iso_dez|iso_yuz|iso_noz|iso_dkz|iso_sez|iso_fiz|utf8|utf16le|utf16be|latin0|latin9|iso8859_15|zx80|zx81|vectrex|koi7n2|short_koi|msx_intl|msx_us|msx_uk|msx_de|msx_fr|msx_es|msx_ru|msx_jp|msx_br|utf8z|utf16lez|utf16bez|latin0z|latin9z|iso8859_15z|zx80z|zx81z|vectrexz|koi7n2z|short_koiz|msx_intlz|msx_usz|msx_ukz|msx_dez|msx_frz|msx_esz|msx_ruz|msx_jpz|msx_brz)\b' + scope: constant.language.mfk + + operators: + # Operatory specyficzne Millforka + - match: '(\$\+|\$\-|\$\*|\$\<{2}|\$\>{2}|\$\+\=|\$\-\=|\$\*\=|\$\<{2}\=|\$\>{2}\=)' + scope: keyword.operator.millfork.special + - match: '(\+''|\-''|\*''|\<{2}''|\>{2}''|\+''\=|\-''\=|\*''\=|\<{2}''\=|\>{2}''\=)' + scope: keyword.operator.millfork.special + + # Operatory asemblera + - match: '#' + scope: keyword.operators.immediate.mfk + - match: '\.(?i:mod|bitnot|bitand|bitor|shl|shr|and|or|not|xor)\b' + scope: keyword.operators.word.mfk + + # Standardowe operatory + - match: '(\+|\-|\*|/|%|<{2}|>{2}|==|!=|<=|>=|<|>|=|\+=|-=|\*=|/=|&&|&|\|{2}|\||\^|\^=)' + scope: keyword.operator.mfk + + punctuation: + - match: '(\{|\}|\(|\)|\[|\]|@|#|;|,|\.)' + scope: punctuation.separator.mfk From 648370dd26bcc29170ae50d4485288f516bf84f2 Mon Sep 17 00:00:00 2001 From: Bartek Zbytniewski Date: Sun, 19 Apr 2026 21:13:35 +0200 Subject: [PATCH 04/16] refactor --- examples/x65/plasma.mfk | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/x65/plasma.mfk b/examples/x65/plasma.mfk index d35a8e9d..7c5bebea 100644 --- a/examples/x65/plasma.mfk +++ b/examples/x65/plasma.mfk @@ -14,7 +14,7 @@ const byte SCR_H = 30; const word LMS = $2000; // Memory address for character data (first text row) const word LFS = $2600; // Memory address for character color data (first text row) const word LBS = $2c00; // Memory address for background color data (first text row) -const word LCG = $3800; // Character generator memory address (8x8 font, font file must include a header) +const word LCG = $3800; // Character generator memory address (8x8 font) const array(byte) DL align(fast) = [ $f3, // LMS + LFS + LBS + LCG @@ -116,8 +116,8 @@ void plasma() { _c1b += 9 for i, SCR_W - 1, downto, 0 { - screen[i] = lookupDiv16[xbuf[i] + tmp]; - column[i] = screen[i]; + screen[i] = lookupDiv16[xbuf[i] + tmp]; + column[i] = screen[i]; } screen += SCR_W From 585e9536a6a84b1ebd57e412181d764eacba7c84 Mon Sep 17 00:00:00 2001 From: Bartek Zbytniewski Date: Sun, 19 Apr 2026 22:16:56 +0200 Subject: [PATCH 05/16] refactor --- examples/x65/plasma.mfk | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/x65/plasma.mfk b/examples/x65/plasma.mfk index 7c5bebea..a001cc2b 100644 --- a/examples/x65/plasma.mfk +++ b/examples/x65/plasma.mfk @@ -55,7 +55,7 @@ byte c1A byte c1B pointer screen @ $60 -pointer column @ $62 +pointer color @ $62 array(byte) lookupDiv16[256] @ $4100 array(byte) xbuf[48] @ $4200 @@ -99,7 +99,7 @@ void plasma() { byte i, ii, tmp screen = LMS - column = LFS + color = LFS _c1a = c1A _c1b = c1B @@ -117,11 +117,11 @@ void plasma() { for i, SCR_W - 1, downto, 0 { screen[i] = lookupDiv16[xbuf[i] + tmp]; - column[i] = screen[i]; + color[i] = screen[i]; } screen += SCR_W - column += SCR_W + color += SCR_W } c1A += 3 From dc7a12a82a603835d429915b7964499444108aaf Mon Sep 17 00:00:00 2001 From: Bartek Zbytniewski Date: Sun, 19 Apr 2026 22:25:19 +0200 Subject: [PATCH 06/16] refactor --- examples/x65/plasma.mfk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/x65/plasma.mfk b/examples/x65/plasma.mfk index a001cc2b..296ecc18 100644 --- a/examples/x65/plasma.mfk +++ b/examples/x65/plasma.mfk @@ -45,7 +45,7 @@ const array(byte) CHARSET @ LCG = [ $00, $00, $18, $18, $00, $00, $00, $00, $00, $00, $00, $08, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 - ] +] const array SINUS_TABLE @ $4000 = [for i, 0, until, 256 [sin((i * 256) / 256, 100)]] From 84568df34e1c9dbed68c4f3c4b76692cfc6d8ade Mon Sep 17 00:00:00 2001 From: Bartek Zbytniewski Date: Sun, 19 Apr 2026 22:28:17 +0200 Subject: [PATCH 07/16] refactor --- examples/x65/plasma.mfk | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/x65/plasma.mfk b/examples/x65/plasma.mfk index 296ecc18..7719866f 100644 --- a/examples/x65/plasma.mfk +++ b/examples/x65/plasma.mfk @@ -8,13 +8,13 @@ pointer p @ 3 //----------------------------------------------------------------------------- -const byte SCR_W = 48; -const byte SCR_H = 30; +const byte SCR_W = 48 +const byte SCR_H = 30 -const word LMS = $2000; // Memory address for character data (first text row) -const word LFS = $2600; // Memory address for character color data (first text row) -const word LBS = $2c00; // Memory address for background color data (first text row) -const word LCG = $3800; // Character generator memory address (8x8 font) +const word LMS = $2000 // Memory address for character data (first text row) +const word LFS = $2600 // Memory address for character color data (first text row) +const word LBS = $2c00 // Memory address for background color data (first text row) +const word LCG = $3800 // Character generator memory address (8x8 font) const array(byte) DL align(fast) = [ $f3, // LMS + LFS + LBS + LCG @@ -76,7 +76,7 @@ macro asm void x65_init() { //----------------------------------------------------------------------------- macro void cgia_init() { - cgia_planes = 0; // disable all planes + cgia_planes = 0 // disable all planes screen = LMS // parallelto - risky thing @@ -89,7 +89,7 @@ macro void cgia_init() { cgia_plane0_row_height = 7 // 8 rows per character cgia_offset0 = DL.addr // point plane0 to DL - cgia_planes = 1; // activate plane0 + cgia_planes = 1 // activate plane0 } //----------------------------------------------------------------------------- @@ -104,20 +104,20 @@ void plasma() { _c1b = c1B for i, SCR_W - 1, downto, 0 { - xbuf[i] = SINUS_TABLE[_c1a] + SINUS_TABLE[_c1b]; + xbuf[i] = SINUS_TABLE[_c1a] + SINUS_TABLE[_c1b] _c1a += 3 _c1b += 7 } for ii, SCR_H - 1, downto, 0 { - tmp = SINUS_TABLE[_c1a] + SINUS_TABLE[_c1b]; + tmp = SINUS_TABLE[_c1a] + SINUS_TABLE[_c1b] _c1a += 4 _c1b += 9 for i, SCR_W - 1, downto, 0 { - screen[i] = lookupDiv16[xbuf[i] + tmp]; - color[i] = screen[i]; + screen[i] = lookupDiv16[xbuf[i] + tmp] + color[i] = screen[i] } screen += SCR_W From 03b314f8413036cfcbc35fa22efe672685cbd077 Mon Sep 17 00:00:00 2001 From: Bartek Zbytniewski Date: Mon, 20 Apr 2026 03:29:51 +0200 Subject: [PATCH 08/16] refactor --- examples/x65/plasma.mfk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/x65/plasma.mfk b/examples/x65/plasma.mfk index 7719866f..2a46f6ef 100644 --- a/examples/x65/plasma.mfk +++ b/examples/x65/plasma.mfk @@ -81,7 +81,7 @@ macro void cgia_init() { screen = LMS // parallelto - risky thing // paralleluntil - risky thing - for w, ($600 * 3) - 1, downto, 0 { screen[w] = 0 } // clear screen + for w, ($600 * 3) - 1, downto, 0 { screen[w] = 0 } // clear screen p = cgia_plane0.addr for b, 9, downto, 0 { p[b] = 0 } // clear CGIA_PLANE0 registers From 3a0ee5bda20a12e7819bc6767871e6f3cebf33fe Mon Sep 17 00:00:00 2001 From: Bartek Zbytniewski Date: Mon, 20 Apr 2026 04:22:06 +0200 Subject: [PATCH 09/16] refactor --- examples/x65/plasma.mfk | 2 +- examples/x65/template.mfk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/x65/plasma.mfk b/examples/x65/plasma.mfk index 2a46f6ef..8f99f2d5 100644 --- a/examples/x65/plasma.mfk +++ b/examples/x65/plasma.mfk @@ -149,5 +149,5 @@ void main() { // store values divided by 16 for b:lookupDiv16 { lookupDiv16[b] = b >> 4 } - while(true) {} + while true {} } diff --git a/examples/x65/template.mfk b/examples/x65/template.mfk index 498ab148..380d5d6d 100644 --- a/examples/x65/template.mfk +++ b/examples/x65/template.mfk @@ -21,7 +21,7 @@ interrupt asm void vbl() { void main() { x65_init() - while(true) { + while true { tmp1 += 1 } } From c1fe0bb42a40d48a73410e6c9d9d2ff7fa3e028d Mon Sep 17 00:00:00 2001 From: Bartek Zbytniewski Date: Mon, 20 Apr 2026 10:07:20 +0200 Subject: [PATCH 10/16] refactor --- examples/x65/plasma.mfk | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/examples/x65/plasma.mfk b/examples/x65/plasma.mfk index 8f99f2d5..e361103b 100644 --- a/examples/x65/plasma.mfk +++ b/examples/x65/plasma.mfk @@ -51,8 +51,7 @@ const array SINUS_TABLE @ $4000 = [for i, 0, until, 256 [sin((i * 256) / 256, 10 //----------------------------------------------------------------------------- -byte c1A -byte c1B +byte c1A = 1, c1B = 5 pointer screen @ $60 pointer color @ $62 @@ -140,10 +139,6 @@ interrupt asm void vbl() { void main() { x65_init() - - c1A = 1 - c1B = 5 - cgia_init() // store values divided by 16 From ade1ba39aa982412ce770a3375ca625ce46ebefb Mon Sep 17 00:00:00 2001 From: Bartek Zbytniewski Date: Mon, 20 Apr 2026 11:39:03 +0200 Subject: [PATCH 11/16] refactor --- examples/x65/template.mfk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/x65/template.mfk b/examples/x65/template.mfk index 380d5d6d..f1f8295f 100644 --- a/examples/x65/template.mfk +++ b/examples/x65/template.mfk @@ -5,7 +5,7 @@ macro asm void x65_init() { sei ; disable IRQ cld ; clear decimal mode - ldx # + ldx #$ff txs ; initialize stack pointer lda #%10000000 From bffb7150a6872b171f8c791ba7cb6d51d3beabb0 Mon Sep 17 00:00:00 2001 From: Bartek Zbytniewski Date: Mon, 20 Apr 2026 13:54:07 +0200 Subject: [PATCH 12/16] X65 target - improvements --- examples/x65/plasma.mfk | 16 +-- examples/x65/template.mfk | 6 +- include/platform/x65.ini | 2 +- include/x65_cgia.mfk | 216 +++++++++++++++++++++++++++++++++++--- 4 files changed, 214 insertions(+), 26 deletions(-) diff --git a/examples/x65/plasma.mfk b/examples/x65/plasma.mfk index e361103b..1b31b2cb 100644 --- a/examples/x65/plasma.mfk +++ b/examples/x65/plasma.mfk @@ -69,26 +69,26 @@ macro asm void x65_init() { txs ; initialize stack pointer lda #%10000000 - sta cgia_int_enable ; trigger NMI on VBL + sta cgia.int_enable ; trigger NMI on VBL } //----------------------------------------------------------------------------- macro void cgia_init() { - cgia_planes = 0 // disable all planes + cgia.planes = 0 // disable all planes screen = LMS // parallelto - risky thing // paralleluntil - risky thing for w, ($600 * 3) - 1, downto, 0 { screen[w] = 0 } // clear screen - p = cgia_plane0.addr + p = cgia.plane0.addr for b, 9, downto, 0 { p[b] = 0 } // clear CGIA_PLANE0 registers - cgia_plane0_row_height = 7 // 8 rows per character - cgia_offset0 = DL.addr // point plane0 to DL + cgia.plane0.row_height = 7 // 8 rows per character + cgia.offset0 = DL.addr // point plane0 to DL - cgia_planes = 1 // activate plane0 + cgia.planes = 1 // activate plane0 } //----------------------------------------------------------------------------- @@ -131,7 +131,7 @@ void plasma() { interrupt asm void vbl() { jsr plasma - sta cgia_int_status + stz cgia.int_status rti } @@ -146,3 +146,5 @@ void main() { while true {} } + +//----------------------------------------------------------------------------- \ No newline at end of file diff --git a/examples/x65/template.mfk b/examples/x65/template.mfk index f1f8295f..d5aa0bc6 100644 --- a/examples/x65/template.mfk +++ b/examples/x65/template.mfk @@ -9,19 +9,19 @@ macro asm void x65_init() { txs ; initialize stack pointer lda #%10000000 - sta cgia_int_enable ; trigger NMI on VBL + sta cgia.int_enable ; trigger NMI on VBL } interrupt asm void vbl() { inc tmp0 - sta cgia_int_status + stz cgia.int_status rti } void main() { x65_init() - while true { + while true { tmp1 += 1 } } diff --git a/include/platform/x65.ini b/include/platform/x65.ini index ef51c408..9397e93d 100644 --- a/include/platform/x65.ini +++ b/include/platform/x65.ini @@ -7,7 +7,7 @@ modules=x65_hardware,default_panic [allocation] zp_bytes=$0-$ff segment_default_start=$200 -segment_default_end=$fbff +segment_default_end=$ffff [define] X65=1 diff --git a/include/x65_cgia.mfk b/include/x65_cgia.mfk index 72f463ef..bd5b47ba 100644 --- a/include/x65_cgia.mfk +++ b/include/x65_cgia.mfk @@ -1,26 +1,212 @@ // X65 CGIA hardware +// https://docs.x65.zone/ +// https://docs.google.com/spreadsheets/d/1mADeuKo_zZCQmT42eyEhKunW5CIMZ9LZTW8EDv7rU7w/ #if not(X65) #warn x65_cgia module should be used only on X65 computer-compatible targets #endif //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// Multicolor encoding +// https://csbruce.com/cbm/hacking/hacking12.txt +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// 00 background color +// 01 same as "off" color in hires mode +// 10 same as "on" color in hires mode +// 11 another "background" color +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// Hold-And-Modify MODE6 +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// HAM commands are 6bit each, 4 screen pixels packed in 3 bytes. +// [CCCDDD] - C -command bit, D - data bit +// +// 000 - load base color index at DDD (one of 8 base colors) +// 001 - blend current color with color at DDD +// +// CCS - CC: +// 01 - Modify Red channel +// 10 - Modify Green channel +// 11 - Modify Blue channel +// +// S: sign, 0 +delta, 1 -delta +// DDD: delta (2's complement, with above sign bit) +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// Display List Values +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// bit 3 unset (0-7) - instructions: +// Bits 0-2 encode the instruction: +// 0 - empty lines filled with fill color +// bits 6-4 - how many +// bit 7 - DLI +// 1 - [TBD] +// 2 - JMP Display List (Load DL offset address) +// - DLI bit set - wait for Vertical Blank +// 3 - Load Memory - bits 4-7 flag which offsets will follow +// 4 - LMS - memory scan +// 5 - LFS - color scan +// 6 - LBS - background scan +// 7 - LCG - character generator address +// 4 - Load 8 bit value to Register Offset +// 5 - Load 16 bit value to Register Offset +// bits 6-4 - register index +// 6 - [TBD] +// 7 - [TBD] +// +// bit 3 set (8-F) - generate mode row: +// Bits 0-2 encode the mode: +// 0 - palette text/tile mode +// 1 - palette bitmap mode +// 2 - attribute text/tile mode +// 3 - attribute bitmap mode +// 4 - [TBD] +// 5 - [TBD] +// 6 - Hold-and-Modify (HAM) mode +// 7 - affine transform chunky pixel mode +// +// bit 7 - trigger DLI - Display List Interrupt +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +const byte CGIA_MODE_HIRES_BIT = %00000001 +const byte CGIA_MODE_INTERLACE_BIT = %00000010 + +const byte CGIA_REG_INT_FLAG_VBI = %10000000 +const byte CGIA_REG_INT_FLAG_DLI = %01000000 +const byte CGIA_REG_INT_FLAG_RSI = %00100000 + +// --- DISPLAY LIST INSTRUCTIONS --- +const byte CGIA_DL_INS_EMPTY_LINES = $00 +const byte CGIA_DL_INS_RESERVED_1 = $01 +const byte CGIA_DL_INS_JUMP = $02 +const byte CGIA_DL_INS_DL_INTERRUPT = %10000000 +const byte CGIA_DL_INS_LOAD_MEMORY = $03 +const byte CGIA_DL_INS_LM_MEMORY_SCAN = %00010000 +const byte CGIA_DL_INS_LM_FOREGROUND_SCAN = %00100000 +const byte CGIA_DL_INS_LM_BACKGROUND_SCAN = %01000000 +const byte CGIA_DL_INS_LM_CHARACTER_GENERATOR = %10000000 +const byte CGIA_DL_INS_LOAD_REG8 = $04 +const byte CGIA_DL_INS_LOAD_REG16 = $05 +const byte CGIA_DL_INS_RESERVED_6 = $06 +const byte CGIA_DL_INS_RESERVED_7 = $07 + +const byte CGIA_DL_MODE_PALETTE_TEXT = $08 +const byte CGIA_DL_MODE_PALETTE_BITMAP = $09 +const byte CGIA_DL_MODE_ATTRIBUTE_TEXT = $0A +const byte CGIA_DL_MODE_ATTRIBUTE_BITMAP = $0B +const byte CGIA_DL_MODE_HOLD_AND_MODIFY = $0E +const byte CGIA_DL_MODE_AFFINE_TRANSFORM = $0F -const byte CGIA_DL_MODE_BIT = %00001000 -const byte CGIA_DL_DLI_BIT = %10000000 -const byte CGIA_DL_STORE_BIT = %01000000 +const byte CGIA_DL_MODE_BIT = %00001000 +const byte CGIA_DL_DOUBLE_WIDTH_BIT = %00010000 +const byte CGIA_DL_MULTICOLOR_BIT = %00100000 +const byte CGIA_DL_RESERVED_BIT = %01000000 +const byte CGIA_DL_DLI_BIT = %10000000 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// sprite flags: +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// 0-2 - width in bytes +// 3 - [RESERVED] +// 4 - double-width +// 5 - multicolor +// 6 - mirror X +// 7 - mirror Y +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +const byte SPRITE_MASK_WIDTH = %00000111 +const byte SPRITE_MASK_RESERVED = %00001000 +const byte SPRITE_MASK_DOUBLE_WIDTH = %00010000 +const byte SPRITE_MASK_MULTICOLOR = %00100000 +const byte SPRITE_MASK_MIRROR_X = %01000000 +const byte SPRITE_MASK_MIRROR_Y = %10000000 + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// plane flags: +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// 0 - color 0 is transparent +// 1-2 - [RESERVED] +// 3 - border is transparent +// 4 - double-width pixel +// 5 - multicolor-pixel +// 6,7 - pixel bits: 00 - 1bit, 2 colors; 01 - 2bit, 4 colors; +// 10 - 3bit, 8 colors; 11 - 4bit, 8 colors + half-bright +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +const byte PLANE_MASK_TRANSPARENT = %00000001 +const byte PLANE_MASK_BORDER_TRANSPARENT = %00001000 +const byte PLANE_MASK_DOUBLE_WIDTH = %00010000 +const byte PLANE_MASK_MULTICOLOR = %00100000 +const byte PLANE_MASK_PIXEL_BITS = %11000000 + +const byte PLANE_MASK_FROM_DL = PLANE_MASK_DOUBLE_WIDTH | PLANE_MASK_MULTICOLOR + +const byte PLANE_BITS_1BPP = (%00 << 6) +const byte PLANE_BITS_2BPP = (%01 << 6) +const byte PLANE_BITS_3BPP = (%10 << 6) +const byte PLANE_BITS_4BPP = (%11 << 6) + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// 16 registers for plane use +// interpretation depends on plane type and active display list mode +// BACKGROUND | HAM | AFFINE | SPRITES +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +struct plane_t { + byte flags // active_sprites + byte border_columns + byte row_height // start_y + byte stride // texture_bits | stop_y + byte scroll_x // u + byte offset_x // u + byte scroll_y // v + byte offset_y // v + byte shared_color0 // du + byte shared_color1 // du + byte color2 // dv + byte color3 // dv + byte color4 // dx + byte color5 // dx + byte color6 // dy + byte color7 // dy +} + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// CGIA registers +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +struct cgia_t { + byte mode // Chip mode | currently reserved for future use - must be 0 + byte bckgnd_bank // Background Layers bank no | third byte (23-16 bits) of generated address + byte sprite_bank // Sprites bank no | third byte (23-16 bits) of generated address + array(byte) ctl_reserved[13] + word raster // Raster Counter + byte rst_status // Raster Status Bits | [EVEN/ODD x x x x x x x] + array(byte) rst_reserved1[5] + word int_raster // Interrupt register | Write to set raster line generating RSI + byte int_enable // Interrupt enabled flags | [VBI DLI RSI x x x x x] + byte int_status // Interrupt status | [VBI DLI RSI x x x x x] + array(byte) rst_reserved2[4] + array(byte) reserved[16] + byte planes // Plane enable and type | [TTTTEEEE] EEEE - enable bits, TTTT - type (0 bckgnd, 1 sprite) + byte order // Plane order | plane order permutation - SJT ordering + array(byte) pln_reserved1[2] + byte back_color // Backdrop/Border color | palette index + array(byte) pln_reserved2[3] + word offset0 // PLANE0 | DisplayList or SpriteDescriptor table start, interpretation depends on plane type in PLANES register + word offset1 // PLANE1 | DisplayList or SpriteDescriptor table start, interpretation depends on plane type in PLANES register + word offset2 // PLANE2 | DisplayList or SpriteDescriptor table start, interpretation depends on plane type in PLANES register + word offset3 // PLANE3 | DisplayList or SpriteDescriptor table start, interpretation depends on plane type in PLANES register + plane_t plane0 + plane_t plane1 + plane_t plane2 + plane_t plane3 +} + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +volatile cgia_t cgia @ $FF00 -volatile byte cgia_raster @$FF10 // Raster Counter -volatile byte cgia_int_enable @$FF1A // RST Interrupt enabled -volatile byte cgia_int_status @$FF1B // RST Interrupt status -volatile byte cgia_planes @$FF30 -volatile byte cgia_back_color @$FF31 -volatile word cgia_offset0 @$FF38 -volatile byte cgia_plane0 @$FF40 -volatile byte cgia_plane0_flags @$FF40 -volatile byte cgia_plane0_row_height @$FF42 -volatile byte cgia_plane0_stride @$FF43 -volatile byte cgia_plane0_shared_color1 @$FF48 -volatile byte cgia_plane0_shared_color2 @$FF49 \ No newline at end of file +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ \ No newline at end of file From c10e032d7f7fa56ce96b29a9df8bda0bbe2c3ae3 Mon Sep 17 00:00:00 2001 From: Bartek Zbytniewski Date: Mon, 20 Apr 2026 15:40:38 +0200 Subject: [PATCH 13/16] X65 target - improvements --- examples/x65/plasma.mfk | 2 +- include/x65_cgia.mfk | 187 +++++++++++++++++++++++++--------------- 2 files changed, 119 insertions(+), 70 deletions(-) diff --git a/examples/x65/plasma.mfk b/examples/x65/plasma.mfk index 1b31b2cb..63ecdeef 100644 --- a/examples/x65/plasma.mfk +++ b/examples/x65/plasma.mfk @@ -85,7 +85,7 @@ macro void cgia_init() { p = cgia.plane0.addr for b, 9, downto, 0 { p[b] = 0 } // clear CGIA_PLANE0 registers - cgia.plane0.row_height = 7 // 8 rows per character + cgia.plane0.bckgnd.row_height = 7 // 8 rows per character cgia.offset0 = DL.addr // point plane0 to DL cgia.planes = 1 // activate plane0 diff --git a/include/x65_cgia.mfk b/include/x65_cgia.mfk index bd5b47ba..91a19dec 100644 --- a/include/x65_cgia.mfk +++ b/include/x65_cgia.mfk @@ -1,38 +1,20 @@ // X65 CGIA hardware // https://docs.x65.zone/ // https://docs.google.com/spreadsheets/d/1mADeuKo_zZCQmT42eyEhKunW5CIMZ9LZTW8EDv7rU7w/ +// https://github.com/X65/examples/blob/main/src/cgia.asm #if not(X65) #warn x65_cgia module should be used only on X65 computer-compatible targets #endif -//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -// Multicolor encoding -// https://csbruce.com/cbm/hacking/hacking12.txt -//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -// 00 background color -// 01 same as "off" color in hires mode -// 10 same as "on" color in hires mode -// 11 another "background" color //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -// Hold-And-Modify MODE6 -//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -// HAM commands are 6bit each, 4 screen pixels packed in 3 bytes. -// [CCCDDD] - C -command bit, D - data bit -// -// 000 - load base color index at DDD (one of 8 base colors) -// 001 - blend current color with color at DDD -// -// CCS - CC: -// 01 - Modify Red channel -// 10 - Modify Green channel -// 11 - Modify Blue channel -// -// S: sign, 0 +delta, 1 -delta -// DDD: delta (2's complement, with above sign bit) -//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +const byte CGIA_MODE_HIRES_BIT = %00000001 +const byte CGIA_MODE_INTERLACE_BIT = %00000010 + +const byte CGIA_REG_INT_FLAG_VBI = %10000000 +const byte CGIA_REG_INT_FLAG_DLI = %01000000 +const byte CGIA_REG_INT_FLAG_RSI = %00100000 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Display List Values @@ -70,13 +52,6 @@ // bit 7 - trigger DLI - Display List Interrupt //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -const byte CGIA_MODE_HIRES_BIT = %00000001 -const byte CGIA_MODE_INTERLACE_BIT = %00000010 - -const byte CGIA_REG_INT_FLAG_VBI = %10000000 -const byte CGIA_REG_INT_FLAG_DLI = %01000000 -const byte CGIA_REG_INT_FLAG_RSI = %00100000 - // --- DISPLAY LIST INSTRUCTIONS --- const byte CGIA_DL_INS_EMPTY_LINES = $00 const byte CGIA_DL_INS_RESERVED_1 = $01 @@ -106,23 +81,15 @@ const byte CGIA_DL_RESERVED_BIT = %01000000 const byte CGIA_DL_DLI_BIT = %10000000 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -// sprite flags: +// Multicolor encoding +// https://csbruce.com/cbm/hacking/hacking12.txt //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -// 0-2 - width in bytes -// 3 - [RESERVED] -// 4 - double-width -// 5 - multicolor -// 6 - mirror X -// 7 - mirror Y +// 00 background color +// 01 same as "off" color in hires mode +// 10 same as "on" color in hires mode +// 11 another "background" color //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -const byte SPRITE_MASK_WIDTH = %00000111 -const byte SPRITE_MASK_RESERVED = %00001000 -const byte SPRITE_MASK_DOUBLE_WIDTH = %00010000 -const byte SPRITE_MASK_MULTICOLOR = %00100000 -const byte SPRITE_MASK_MIRROR_X = %01000000 -const byte SPRITE_MASK_MIRROR_Y = %10000000 - //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // plane flags: //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -148,36 +115,118 @@ const byte PLANE_BITS_2BPP = (%01 << 6) const byte PLANE_BITS_3BPP = (%10 << 6) const byte PLANE_BITS_4BPP = (%11 << 6) +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// sprite flags: +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// 0-2 - width in bytes +// 3 - [RESERVED] +// 4 - double-width +// 5 - multicolor +// 6 - mirror X +// 7 - mirror Y +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +const byte SPRITE_MASK_WIDTH = %00000111 +const byte SPRITE_MASK_RESERVED = %00001000 +const byte SPRITE_MASK_DOUBLE_WIDTH = %00010000 +const byte SPRITE_MASK_MULTICOLOR = %00100000 +const byte SPRITE_MASK_MIRROR_X = %01000000 +const byte SPRITE_MASK_MIRROR_Y = %10000000 + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +struct cgia_bckgnd_regs { + byte flags + byte border_columns + byte row_height + byte stride + byte scroll_x + byte offset_x + byte scroll_y + byte offset_y + byte shared_color0 + byte shared_color1 + byte color2 + byte color3 + byte color4 + byte color5 + byte color6 + byte color7 +} + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// Hold-And-Modify MODE6 +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// HAM commands are 6bit each, 4 screen pixels packed in 3 bytes. +// [CCCDDD] - C -command bit, D - data bit +// +// 000 - load base color index at DDD (one of 8 base colors) +// 001 - blend current color with color at DDD +// +// CCS - CC: +// 01 - Modify Red channel +// 10 - Modify Green channel +// 11 - Modify Blue channel +// +// S: sign, 0 +delta, 1 -delta +// DDD: delta (2's complement, with above sign bit) +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +struct cgia_ham_regs { + byte flags + byte border_columns + byte row_height + array(byte) reserved[5] + byte color0 + byte color1 + byte color2 + byte color3 + byte color4 + byte color5 + byte color6 + byte color7 +} + +struct cgia_affine_regs { + byte flags + byte border_columns + byte row_height + byte texture_bits // 2-0 texture_width_bits, 6-4 texture_height_bits + word u + word v + word du + word dv + word dx + word dy +} + +struct cgia_sprite_regs { + byte active // bitmask for active sprites + byte border_columns + byte start_y + byte stop_y + array(byte) reserved[12] +} + + //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // 16 registers for plane use // interpretation depends on plane type and active display list mode // BACKGROUND | HAM | AFFINE | SPRITES //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -struct plane_t { - byte flags // active_sprites - byte border_columns - byte row_height // start_y - byte stride // texture_bits | stop_y - byte scroll_x // u - byte offset_x // u - byte scroll_y // v - byte offset_y // v - byte shared_color0 // du - byte shared_color1 // du - byte color2 // dv - byte color3 // dv - byte color4 // dx - byte color5 // dx - byte color6 // dy - byte color7 // dy +union plane { + cgia_bckgnd_regs bckgnd + cgia_ham_regs ham + cgia_affine_regs affine + cgia_sprite_regs sprite } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // CGIA registers //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -struct cgia_t { +struct cgia_regs { byte mode // Chip mode | currently reserved for future use - must be 0 byte bckgnd_bank // Background Layers bank no | third byte (23-16 bits) of generated address byte sprite_bank // Sprites bank no | third byte (23-16 bits) of generated address @@ -199,14 +248,14 @@ struct cgia_t { word offset1 // PLANE1 | DisplayList or SpriteDescriptor table start, interpretation depends on plane type in PLANES register word offset2 // PLANE2 | DisplayList or SpriteDescriptor table start, interpretation depends on plane type in PLANES register word offset3 // PLANE3 | DisplayList or SpriteDescriptor table start, interpretation depends on plane type in PLANES register - plane_t plane0 - plane_t plane1 - plane_t plane2 - plane_t plane3 + plane plane0 + plane plane1 + plane plane2 + plane plane3 } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -volatile cgia_t cgia @ $FF00 +volatile cgia_regs cgia @ $FF00 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ \ No newline at end of file From 02cd5a6f7935e45b6810b22463c25facfddba7f4 Mon Sep 17 00:00:00 2001 From: Bartek Zbytniewski Date: Mon, 20 Apr 2026 15:43:56 +0200 Subject: [PATCH 14/16] X65 target - refactor --- examples/x65/plasma.mfk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/x65/plasma.mfk b/examples/x65/plasma.mfk index 63ecdeef..9237c66c 100644 --- a/examples/x65/plasma.mfk +++ b/examples/x65/plasma.mfk @@ -85,7 +85,7 @@ macro void cgia_init() { p = cgia.plane0.addr for b, 9, downto, 0 { p[b] = 0 } // clear CGIA_PLANE0 registers - cgia.plane0.bckgnd.row_height = 7 // 8 rows per character + cgia.plane0.bckgnd.row_height = 7 // 8 rows per character cgia.offset0 = DL.addr // point plane0 to DL cgia.planes = 1 // activate plane0 From 8e901d1b0df686259cf143753b1593e5a349de66 Mon Sep 17 00:00:00 2001 From: Bartek Zbytniewski Date: Mon, 20 Apr 2026 16:14:38 +0200 Subject: [PATCH 15/16] X65 target - refactor --- include/x65_cgia.mfk | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/x65_cgia.mfk b/include/x65_cgia.mfk index 91a19dec..83c94ae0 100644 --- a/include/x65_cgia.mfk +++ b/include/x65_cgia.mfk @@ -102,18 +102,18 @@ const byte CGIA_DL_DLI_BIT = %10000000 // 10 - 3bit, 8 colors; 11 - 4bit, 8 colors + half-bright //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -const byte PLANE_MASK_TRANSPARENT = %00000001 -const byte PLANE_MASK_BORDER_TRANSPARENT = %00001000 -const byte PLANE_MASK_DOUBLE_WIDTH = %00010000 -const byte PLANE_MASK_MULTICOLOR = %00100000 -const byte PLANE_MASK_PIXEL_BITS = %11000000 +const byte PLANE_MASK_TRANSPARENT = %00000001 +const byte PLANE_MASK_BORDER_TRANSPARENT = %00001000 +const byte PLANE_MASK_DOUBLE_WIDTH = %00010000 +const byte PLANE_MASK_MULTICOLOR = %00100000 +const byte PLANE_MASK_PIXEL_BITS = %11000000 -const byte PLANE_MASK_FROM_DL = PLANE_MASK_DOUBLE_WIDTH | PLANE_MASK_MULTICOLOR +const byte PLANE_MASK_FROM_DL = PLANE_MASK_DOUBLE_WIDTH | PLANE_MASK_MULTICOLOR -const byte PLANE_BITS_1BPP = (%00 << 6) -const byte PLANE_BITS_2BPP = (%01 << 6) -const byte PLANE_BITS_3BPP = (%10 << 6) -const byte PLANE_BITS_4BPP = (%11 << 6) +const byte PLANE_BITS_1BPP = (%00 << 6) +const byte PLANE_BITS_2BPP = (%01 << 6) +const byte PLANE_BITS_3BPP = (%10 << 6) +const byte PLANE_BITS_4BPP = (%11 << 6) //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // sprite flags: From 3f1cd29831654399ac4dff0ec59e0042c9f2057c Mon Sep 17 00:00:00 2001 From: Bartek Zbytniewski Date: Sat, 25 Apr 2026 20:19:42 +0200 Subject: [PATCH 16/16] X65 MODE1 4bpp example --- examples/x65/data/image.4bpp | Bin 0 -> 46080 bytes examples/x65/mode1_4bpp.mfk | 171 +++++++++++++++++++++++++++++++++++ include/x65_cgia.mfk | 48 +++++----- 3 files changed, 195 insertions(+), 24 deletions(-) create mode 100644 examples/x65/data/image.4bpp create mode 100644 examples/x65/mode1_4bpp.mfk diff --git a/examples/x65/data/image.4bpp b/examples/x65/data/image.4bpp new file mode 100644 index 0000000000000000000000000000000000000000..9ab2040a51d4c5e29946277825bd17dadc88bdcb GIT binary patch literal 46080 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