Saturday, 17 March 2018

Commodore 128 Assembly - Part 1




All the code and resource files in this post will be available on my Github page, because it's fun to share.  It's very W.I.P. though, so I will add it later when more stable.

I want to delve more into the Commodore 128. It's such a nice system. Some of the new and advanced features are not easy to program though. So I decided to create a system, a framework if you will, of re-useable parts once I had figured out the basics.

Groundwork


I use Kick Assembler, Sublime Text and VICE to develop on my PC.

Using more of Kick Assemblers power was on my to-do list and this is a start. I decided to create easy to use macro's that will generate specific object code, or make it easy to insert reusable subroutines into object code.

First thing to do was to modify the default basic startup macro to use $1c01, not $0801:

.macro BasicUpstart128(address) {
    .pc = $1c01 "C128 Basic"
    .word upstartEnd  // link address
    .word 10   // line num
    .byte $9e  // sys
    .text toIntString(address)
    .byte 0
upstartEnd:
    .word 0  // empty link signals the end of the program
    .pc = $1c0e "Basic End"
}

Then, some handy setup macros. For example:

.macro Go80 () {
lda MODE // are we in 80 columns mode?
bmi !+ // bit 7 set? then yes
jsr SWAPPER // swap mode to 80 columns
!:
}

Isn't that nice? Entering Go80() beats remembering and typing in that code. I can now also use this macro:

DoEscapeCode('X')

Which will also switch to 80 or 40 column mode. This is the macro code for that:

.macro DoEscapeCode (code) {
lda #code
jsr JESCAPE
}

And it allows easy access to the other escape codes as well.

These are one-off calls. They accept parameters and will therefore generate specific code for specific situations, mostly during setup, and they can be added into the code when required. Some code is better added as subroutines though, for example: writing to a VDC register:

.macro WriteVDC () {
stx VDCADR
!: bit VDCADR
bpl !-
sta VDCDAT
}

This can be added as a subroutine to object code like so:

WRITE_VDC:
:WriteVDC()
rts

It can then be called multiple times, like this:

ldx #26 // modify register 26, colour.
lda #010011 // left nybble is BG, right is FG
jsr WRITE_VDC

This will change the background and character colour on the 80 columns display.


Memory Management (Unit)


One of the first things to tackle when using the C128 is the use of the MMU. Especially when mixing Basic and machine code.

Check out this memory map overview. We will get back to it later:



This can be quite confusing, so I created a macro which will make selecing the proper bank configuration easier, as the values as used in the BASIC Bank command can be used, and some custom configuration when I do so choose to add them:

.macro SetBank(id) {
.if(id==0) {
lda #111111 // no roms, RAM 0
}
.if(id==1) {
lda #%01111111 // no roms, RAM 1
}
.if(id==12) {
lda #000110 // int.func. ROM, Kernal, IO, RAM 0
}
.if(id==14) {
lda #000001 // all roms, char ROM, RAM 0
}
.if(id==15) {
lda #000000  // all roms, RAM 0. default.
}
.if(id==99) {
lda #001110  // IO, kernal, RAM0. No basic
}
sta MMUCR
}

So, adding SetBank(15) to my source code will enable the default bank configuration.


First Test


This is the code for a first test I've made. It sets up some default stuff, and it then fills the 80 column display with a character, and changes the colour of the display.


#import "c128system.asm"
#import "c128macros.asm"

:BasicUpstart128(MAIN) // I like to use : to indicate a macro call.


MAIN:
:SetBank(15) // set bank 15
:Go80()

ldx #18 // register 18 = update address hi
lda #$00
jsr WRITE_VDC // write 0
inx // register 19 = update address lo
jsr WRITE_VDC // write 0

ldy #0 // byte counter
lda #8 // page counter
sta $fa

ldx #31 // we write to the data register
// data will be passed to address defined by 18-19
lda #$66 // we write this char to screen memory
!:
jsr WRITE_VDC
iny
bne !- // byte loop
dec $fa
bne !- // page loop

ldx #26 // modify register 26, colour.
lda #010011 // left nybble is BG, right is FG
jsr WRITE_VDC
rts

// assemble sub routines.

WRITE_VDC:
:WriteVDC()
rts
READ_VDC:
:ReadVDC()
rts


That's it for now. I will keep trying new things and post about them.  At this time, I am not sure what I want to program on the C128, but I have a few ideas... 

Till next time!

Commodore 128 Assembly - Part 3 : The 80-column (8563) Chip

Welcome to a new part of this series. We are going to look at the basic functions of the VDC, which enables 80 column support. It is foremos...