Working with PLASMA – RTFM failure and banging my head

I mentioned in an earlier post that I would post about getting timing routines into my PLASMA test code as well as the C code. Needless to say, it took me way more banging my head and a late night to get it in and working. But, that was all me. I failed to RTFM and then tried to figure out why it wasn’t working

But, let’s back up a bit to give credit where credit is due. I stole/borrowed/adapted the clock routines I’m using in my code (both for PLASMA and C). I finally found a post on comp.sys.apple2.programer from Bill Buckels that had the code in raw opcode format which was then memcpy()’d into the right location in memory (in this case $0260) and then accessed via inline assembly via a JSR call to the right spot.  Brilliant!

But, with my lack of understanding of how PLASMA is laid out, I figured I had better do something a little more portable.  I tried several things trying to convert it to inline assembly on my own, I tried taking the assembly spit out from the monitor and converting the raw memory locations to logical offsets which involved using Virtual ][, printing the ML from the monitor to the virtual printer, saving as PDF and copy/pasting from there.  Which was a nightmare as the output in the PDF is not sequenced how I would have expected:

Screen Shot 2016-04-11 at 11.27.14 AM

Thanks to a tip from David Schmidt, I took a look at the code in ADTPro for the clock routines.  That’s all in assembly with logical offsets!  Woohoo!  I converted it into the assembly style that PLASMA wants and gave it a shot.  No go.  Time to figure out why.

At this point, I wish I has taken some screenshots of what I was doing as it would be nice to have.  I’ll be better about that in the future.

I had my PLASMA code print out the memory location ($4047, I think it was) for the function that as the inline assembly in it and went into the monitor and took a look.  If you look at the code in the picture above, you can see that the first STA instruction is $7e after the start of the routine ($260).  You’d expected to see the STA of this new code to be $7e past $4047, right?  Nope!  It was at $10B2. Well, there’s your problem.  I could get the offsets to be right in that code if I used “–setpc 16401” on the call to the ACME assembler, but then the entrance location to my PLASMA code was off and nothing would run.

After hours of digging around and trying various things, I decided I needed to reach out to see if I hit a bug (unlikely) or if I was doing something wrong (very likely).   After posting to comp.sys.apple2.programmer, David Schmenk got be straightened around.

Here is where the RTFM failure part comes in.  Here is a section from the PLASMA readme about Native Assembly Functions:

Lastly, PLASMA modules are re-locatable, but labels inside assembly functions don’t get flagged for fix-ups. The assembly code must use all relative branches and only accessing data/code at a fixed address.

Then I set off on a “damn fool idealistic crusade” to implement the code code in C (and then PLASMA) directly.  I tried.  Boy, did I try. But, apparently my reading of the assembly and trying to do it in something else was failing miserably. I tend to do that.  Wanting to do things the “right” or “best” way instead of doing it the “working way”.  Sometimes, it’s best to just use the “working way”.  Especially, since I only wanted it to do some performance testing.

Back to using the raw code and memcpy()’ing it in.  That was working fine, except my loop from 1 to 10 in my test program ran way more than 10 times.  I realize now, this was RTFM failure #2:

Data passed in on the PLASMA evaluation stack is readily accessed with the X register and the zero page address of the ESTK. The X register must be properly saved, incremented, and/or decremented to remain consistent with the rest of PLASMA. Parameters are popped off the evaluation stack with INX, and the return value is pushed with DEX.

David to  the rescue again.  Added in the code to save/restore X and DEX and good to go!

Here is the code for the timers. It’s basically a simple stopwatch with one lap timer included. Start the timer then you can ask for the elapsed time. You can do a lap reset to get individual times while the main timer is unaffected.

PLASMA code

import cmdsys
    predef memcpy
end

const nscdata = $303

byte timer_year, timer_month, timer_date, timer_day, timer_hour, timer_minute, timer_second, timer_hundredth
byte lap_year, lap_month, lap_date, lap_day, lap_hour, lap_minute, lap_second, lap_hundredth
byte tmp_year, tmp_month, tmp_date, tmp_day, tmp_hour, tmp_minute, tmp_second, tmp_hundredth

byte nsccode[] = $a9,$00,$8d,$de,$02,$a9,$03,$09,$c0,$8d,$1f,$03,$8d,$22,$03,$8d,$31,$03,$8d,$3f,$03,$a9,$03,$8d,$df,$02,$d0,$16,$00,$00,$00,$00
byte           = $00,$00,$2f,$00,$00,$2f,$00,$00,$20,$00,$00,$3a,$00,$00,$3a,$00,$00,$8d,$20,$0b,$03,$a2,$07,$bd,$03,$03,$dd,$e0,$02,$90,$0f,$dd
byte           = $e8,$02,$b0,$0a,$ca,$10,$f0,$ce,$df,$02,$d0,$e6,$18,$60,$ee,$de,$02,$ad,$de,$02,$c9,$08,$90,$af,$d0,$1d,$a9,$c0,$a0,$15,$8d,$1b
byte           = $03,$8c,$1a,$03,$a0,$07,$8d,$1f,$03,$8c,$1e,$03,$88,$8d,$6f,$03,$8c,$6e,$03,$a9,$c8,$d0,$95,$a9,$4c,$8d,$16,$03,$38,$60,$00,$00
byte           = $00,$01,$01,$01,$00,$00,$00,$00,$64,$0d,$20,$38,$98,$3c,$3c,$64,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
byte           = $18,$90,$09,$00,$00,$00,$00,$00,$00,$00,$00,$38,$08,$78,$a9,$00,$8d,$04,$03,$8d,$80,$02,$ad,$a3,$03,$ad,$ff,$cf,$48,$8d,$00,$c3
byte           = $ad,$04,$c3,$a2,$08,$bd,$bf,$03,$38,$6a,$48,$a9,$00,$2a,$a8,$b9,$00,$c3,$68,$4a,$d0,$f4,$ca,$d0,$ec,$a2,$08,$a0,$08,$ad,$04,$c3
byte           = $6a,$66,$42,$88,$d0,$f7,$a5,$42,$9d,$7f,$02,$4a,$4a,$4a,$4a,$a8,$a5,$42,$c0,$00,$f0,$08,$29,$0f,$18,$69,$0a,$88,$d0,$fb,$9d,$02
byte           = $03,$ca,$d0,$d7,$ad,$80,$02,$8d,$83,$02,$68,$30,$03,$8d,$ff,$cf,$a0,$11,$a2,$06,$bd,$c7,$03,$99,$80,$02,$bd,$80,$02,$48,$29,$0f
byte           = $09,$30,$88,$99,$80,$02,$68,$4a,$4a,$4a,$4a,$d0,$0c,$e0,$01,$f0,$04,$e0,$04,$d0,$04,$a9,$20,$d0,$02,$09,$30,$88,$99,$80,$02,$88
byte           = $ca,$d0,$d1,$28,$b0,$19,$20,$be,$de,$20,$e3,$df,$20,$6c,$dd,$85,$85,$84,$86,$a9,$80,$a0,$02,$a2,$8d,$20,$e9,$e3,$20,$9a,$da,$60
byte           = $5c,$a3,$3a,$c5,$5c,$a3,$3a,$c5,$2f,$2f,$20,$3a,$3a,$8d

asm _initnsc
        txa
        pha
        jsr $0260
        pla
        tax
        dex
        rts
end

asm _readnsc
        txa
        pha
        jsr $030B
        pla
        tax
        dex
        rts
end

export def loadnsccode
    memcpy($0260, @nsccode, $16e);
end

export def initnsc
    loadnsccode
    _initnsc
end

export def gettime(timedata)
    _readnsc
    memcpy(timedata, nscdata, 8)
end

export def timer_start
    gettime(@timer_year)
    memcpy(@lap_year, @timer_uear, 8)
end

export def timer_elapsed
    word d, h, m, s, hd
    gettime(@tmp_year)
    d = tmp_date - timer_date; h = tmp_hour - timer_hour; m = tmp_minute - timer_minute; s = tmp_second - timer_second; hd = tmp_hundredth - timer_hundredth;

    return (((d*24+h)*60+m)*60+s)*100+hd
end

export def timer_lap_reset
    gettime(@lap_year)
end

export def timer_lap_elapsed
    word d, h, m, s, hd
    gettime(@tmp_year)
    d = tmp_date - lap_date; h = tmp_hour - lap_hour; m = tmp_minute - lap_minute; s = tmp_second - lap_second; hd = tmp_hundredth - lap_hundredth;

    return (((d*24+h)*60+m)*60+s)*100+hd
end

initnsc
done

C Code

Adapted from a post by Bill Buckels

#include <stdio.h>
#include <string.h>
#include <conio.h>
#include "realtime.h"

#define READ_TIME_ADDR 0x260
#define READ_TIME_LEN  366

/* The READ.TIME program Version 1.4 (C) Copyright Craig Peterson 1991 */
char _read_time[READ_TIME_LEN] = {
0xa9,0x00,0x8d,0xde,0x02,0xa9,0x03,0x09,0xc0,0x8d,0x1f,0x03,0x8d,0x22,0x03,0x8d,0x31,0x03,0x8d,0x3f,0x03,0xa9,0x03,0x8d,0xdf,0x02,0xd0,0x16,0x00,0x00,0x00,0x00,
0x00,0x00,0x2f,0x00,0x00,0x2f,0x00,0x00,0x20,0x00,0x00,0x3a,0x00,0x00,0x3a,0x00,0x00,0x8d,0x20,0x0b,0x03,0xa2,0x07,0xbd,0x03,0x03,0xdd,0xe0,0x02,0x90,0x0f,0xdd,
0xe8,0x02,0xb0,0x0a,0xca,0x10,0xf0,0xce,0xdf,0x02,0xd0,0xe6,0x18,0x60,0xee,0xde,0x02,0xad,0xde,0x02,0xc9,0x08,0x90,0xaf,0xd0,0x1d,0xa9,0xc0,0xa0,0x15,0x8d,0x1b,
0x03,0x8c,0x1a,0x03,0xa0,0x07,0x8d,0x1f,0x03,0x8c,0x1e,0x03,0x88,0x8d,0x6f,0x03,0x8c,0x6e,0x03,0xa9,0xc8,0xd0,0x95,0xa9,0x4c,0x8d,0x16,0x03,0x38,0x60,0x00,0x00,
0x00,0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x64,0x0d,0x20,0x38,0x98,0x3c,0x3c,0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x18,0x90,0x09,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x08,0x78,0xa9,0x00,0x8d,0x04,0x03,0x8d,0x80,0x02,0xad,0xa3,0x03,0xad,0xff,0xcf,0x48,0x8d,0x00,0xc3,
0xad,0x04,0xc3,0xa2,0x08,0xbd,0xbf,0x03,0x38,0x6a,0x48,0xa9,0x00,0x2a,0xa8,0xb9,0x00,0xc3,0x68,0x4a,0xd0,0xf4,0xca,0xd0,0xec,0xa2,0x08,0xa0,0x08,0xad,0x04,0xc3,
0x6a,0x66,0x42,0x88,0xd0,0xf7,0xa5,0x42,0x9d,0x7f,0x02,0x4a,0x4a,0x4a,0x4a,0xa8,0xa5,0x42,0xc0,0x00,0xf0,0x08,0x29,0x0f,0x18,0x69,0x0a,0x88,0xd0,0xfb,0x9d,0x02,
0x03,0xca,0xd0,0xd7,0xad,0x80,0x02,0x8d,0x83,0x02,0x68,0x30,0x03,0x8d,0xff,0xcf,0xa0,0x11,0xa2,0x06,0xbd,0xc7,0x03,0x99,0x80,0x02,0xbd,0x80,0x02,0x48,0x29,0x0f,
0x09,0x30,0x88,0x99,0x80,0x02,0x68,0x4a,0x4a,0x4a,0x4a,0xd0,0x0c,0xe0,0x01,0xf0,0x04,0xe0,0x04,0xd0,0x04,0xa9,0x20,0xd0,0x02,0x09,0x30,0x88,0x99,0x80,0x02,0x88,
0xca,0xd0,0xd1,0x28,0xb0,0x19,0x20,0xbe,0xde,0x20,0xe3,0xdf,0x20,0x6c,0xdd,0x85,0x85,0x84,0x86,0xa9,0x80,0xa0,0x02,0xa2,0x8d,0x20,0xe9,0xe3,0x20,0x9a,0xda,0x60,
0x5c,0xa3,0x3a,0xc5,0x5c,0xa3,0x3a,0xc5,0x2f,0x2f,0x20,0x3a,0x3a,0x8d};

struct nsctm timer, lap, tmp;

#pragma optimize (push,off)
void initnsc(void)
{

    char *brunptr = (char *)READ_TIME_ADDR;

    /* bload read.clock to $260 */
	memcpy(brunptr,_read_time,READ_TIME_LEN);

	asm("JSR $260"); /* call init clock */

}
#pragma optimize (pop)

/* read the current date time and time from the NSC */
#pragma optimize (push,off)
void gettime(struct nsctm *output)
{
	asm("JSR $30B"); /* call read clock */

    memcpy(output, (char *)0x303, 8);
}
#pragma optimize (pop)

void timer_start()
{
    gettime(&timer);
    memcpy(&lap, &timer, 8);
}

int timer_elapsed()
{
    int d, h, m, s, hd;
    gettime(&tmp);
    d = tmp.date - timer.date; h = tmp.hour - timer.hour; m = tmp.minute - timer.minute; s = tmp.second - timer.second; hd = tmp.hundredth - timer.hundredth;

    return (((d*24+h)*60+m)*60+s)*100+hd;
}

void timer_lap_reset()
{
    gettime(&lap);
}

int timer_lap_elapsed()
{
    int d, h, m, s, hd;
    gettime(&tmp);
    d = tmp.date - lap.date; h = tmp.hour - lap.hour; m = tmp.minute - lap.minute; s = tmp.second - lap.second; hd = tmp.hundredth - lap.hundredth;

    return (((d*24+h)*60+m)*60+s)*100+hd;
}

Again, strikingly similar, but that is what I was after. Comparing apples to apples (pun intended!)

Next I’m going to take a look at some more comparisons. Thing I was to look at (some based on suggestions) are things like timings for different routines, cycles for different operations and size comparisons.

2 comments

  1. Hi Mike-
    I wanted to follow up now that the second PLASMA article is out in Juiced.GS. You did a great job getting PLASMA up and running – no mean feat! I was actually quite impressed that PLASMA stacked up so well against C, as it is interpreted byte code (as you point out). The reason I created it was to satisfy a few constraints, like code density, dynamic loading and relocatability. PLASMA can also execute byte code out of AUX memory on a 128K //e and //c, adding to its flexibility. If you ever choose to continue on your PLASMA journey, I’d love to see some of the other benefits of PLASMA be taken into consideration.

    Regards,
    Dave…

    1. Dave,
      Yes, thanks for the comments. I do plan on continuing my PLASMA journey. I got sidetracked with personal things (coaching, etc) but that has cleared now, so I hope to get back to it soon.

      Don’t get me wrong, I think PLASMA is great and gives a lot of benefits. These are no meant as a slam on it at all.

Leave a Reply

Your email address will not be published. Required fields are marked *