• Third day using the Apple Journal app. If I keep it up until the New Year, I might consider subscribing to Day One or Momento, which offer more features.

  • Getting a foothold into 6502 machine language, part 2

    I mentioned in part 1 that there’s a convenient routine to convert a real number into a signed 16-bit integer. However, Basic line numbers are 16-bit unsigned numbers when parsed into Basic code. So there has to be a routine to turn a real value in fac1 into a 16-bit integer as part of the interpretation of Basic text that starts with a line number.

    [ part 1 | part 3 | part 4 | part 5 | bonus ]

    And indeed there is. It is called getadr and is located at $b7f7. It supposedly is to turn a real value between zero and 65536 into a 16-bit unsigned integer. For both values the floating point accumulator fac1 is used. The resulting integer value is stored in locations $64 and $65 of fac1, high byte first.

    Since the usr machine language routine stores the function value entered in Basic into fac1 as a real number, the next steps should be easy:

    • call getadr ($b7f7) to convert a real into a unsigned 16-bit integer
    • read $64 in .A and $65 in .Y, both part of fac1
    • process the integer into a value stored in .A and .Y
    • subtract 32768 to convert the unsigned integer into a signed integer value between -32768 and 32767, which givayf needs
    • turn .A and .Y into a real, by calling the givayf ($b391) routine

    To not over-complicate things at this point, we should interpret the “process the integer” as “do nothing”, effectively skipping the processing step. We will develop our “is this number prime” routine later. All we want to know if the supplied value between 0 and 65536 is accepted and can be used as such.

    This method is certainly somewhat error-prone (never underestimate the power of typos), because it requires some workaround in Basic to deal with the signed integer value that our usr machine language routine returns to Basic.

    After entering the source code in the assembly listing “usrfunction 0.2”, I copied the hexdump from Virtual 6502 Assembler and wrote the Basic program “testusrf 0.2” in the next listing to see if I got it right. This was what I wanted to test:

    • boundaries 0 and 65535
    • fractional numbers between 0 and 65536, exclusive—use a few values of the expression 65536*rnd(ti)
    • errors with numbers that are < 0 or >= 65536 (use -1 and 65536)
    *= $c000
    
    ; usrfunction 0.2
    ; use an unsigned integer
    ; and, for now, do nothing with it
    
    getadr = $b7f7
    givayf = $b391
    fac1int = $64
    
            jsr getadr      ;convert real into unsigned int
            lda fac1int     ;load high byte in .A
            ldy fac1int+1   ;load low byte in .Y
            nop             ;do nothing for now
            sec
            sbc #%10000000  ;subtract $8000 (32678) to make it a signed integer
            jmp givayf      ;make it real and return to Basic
    0 rem testusrf 0.2
    10 poke 785,0:poke 786,192:rem set usr address to $c000
    20 forn=49152to49165:readb:poken,b:next:rem read in machine code
    30 data 32,247,183,165,100,164,101,234
    40 data 56,233,128,76,145,179
    70 x=0:gosub 100
    80 x=65535:gosub 100
    90 for i=0 to 9:x=rnd(ti)*65536:gosub 100:next
    99 end
    100 print "x =";x;", usr(x) =";usr(x)+32768:return

    Here is the output of that Basic program, with two values entered by hand, which should throw error messages:

    run
    x = 0 , usr(x) = 0
    x = 65535 , usr(x) = 65535
    x = 12161.1233 , usr(x) = 12161
    x = 3073.54893 , usr(x) = 3073
    x = 54247.0177 , usr(x) = 54247
    x = 36356.0453 , usr(x) = 36356
    x = 58801.1164 , usr(x) = 58801
    x = 37546.6392 , usr(x) = 37546
    x = 54977.7024 , usr(x) = 54977
    x = 61029.0648 , usr(x) = 61029
    x = 12345.8034 , usr(x) = 12345
    x = 63762.592 , usr(x) = 63762
    
    ready.
    ?usr(-1)
    
    ?illegal quantity  error
    ready.
    ?usr(65536)
    
    ?illegal quantity  error
    ready.

    It worked, which is always a relief.

    Now the routine we want (“is this number prime?") doesn’t have to result into an unsigned 16-bit value. We only want to be able to input such a value. The output we expect is either a “Yes” (-1) or “No” (0). So a signed 16-bit integer is exactly what we need.

    So why did I do this workaround? I wanted to know if it is at all possible to work around this limitation, in case I ever needed that. It certainly is, as the Basic example shows.

    In part 3 I will (finally) start coding for the question whether or not the supplied number is a prime number. This should be interesting.

  • To quote myself, in my latest article on my blog:

    Now we can tackle more complicated matters, but that has to wait until part 2, because this article is already much longer than I anticipated, and I started to make mistakes. I need a break.

    I had to strip out a big chunck, because it was full of errors. We can’t have that!

  • Getting a foothold into 6502 machine language, part 1

    It seems in Commodore Basic version 2 the preferred way to get a value into a user-defined 6502 machine language routine (and to get a value back), is the usr function. It took me some trial and error to get it to work. Luckily, Google is Your Friend, or, in my case DuckDuckGo. I also used “Mapping the Commodore 64”, by Sheldon Leemon, which can be downloaded as PDF on Archive.org. If you’re serious about 6502 assembly language on the Commodore 64, I highly recommend this book.

    [ part 2 | part 3 | part 4 | part 5 | bonus ]

    First of all, how do we get the usr function to work? Simply running print usr(42) results in an error message.

    Apparently, one has to set the address of the start of one’s own machine language routine. After (re)booting the operating system the value is set to $b248 (fcerr routine, which prints an illegal quantity error). Taking a step back, the final step of the routine in Basic ROM that interprets the usr function is jumping to address $0310 in RAM:

    0310  4c 48 b2    jmp $b248

    So all one has to do is overwrite the address of this jmp instruction so it points to the user defined machine language routine. This address is located at $311 and $312, in the usual low byte high byte order of the 6502 machine language. If the user defined routine is, for example, located at $c000 (49152), the values $00 and $c0 have to be written in $311 and $312, respectively.

    In Basic we use decimal instead of hexadecimal:

    • $311 is 785 decimal
    • $312 is 786 decimal
    • $c0 is 192 decimal
    10 poke 785,0:poke 786,192

    We always have to keep in mind that Commodore Basic uses real values, never integers. The value we supplied to the usr function in our Basic code is stored into fac1, which consists of six bytes, starting at location $61. Luckily, this isn’t all that important here, since there are two handy functions to convert real values back and forth into 16-bit signed integer values, using fac1. They are:

    • $b1aa – Convert a Floating Point Number to a Signed Integer in .A and .Y Registers, which calls the ayint routine (located at $b1bf), and loads the resulting signed 16-bit integer value into registers .Y and .A (lo/hi).
    • $b391 – givayf Convert 16-Bit Signed Integer in registers .Y and .A (lo/hi) to a Floating Point Number

    In both instances the registers .Y and .A contain the low byte, and high byte of the 16-bit signed integer, respectively.

    But, wait, what is a “16-bit signed integer” exactly? Well, in the 6502 architecture, this is the 2’s-complement representation of a whole number between -32768 and 32767, inclusive. It simplifies addition and subtraction of whole numbers, either positive, negative, or zero. If you want to know more, read the relevant Wikipedia article.

    The thing is that if you pass a zero or a positive value into the usr function and convert it into an integer in your machine language routine, it has to be between 0 and 32767, inclusive. If your machine code routine can deal with that restriction, you’re okay. Of course, you could use the real value instead, or use some trickery to use integers between 0 and 65535, inclusive. Whatever the routine does exactly is up to you, since you are defining it.

    Perhaps I should give an example of how to use the usr function in a useful manner 😉

    What if I wanted to know if a value I put into the usr function is a prime number, divisible only by itself or one, but no other whole positive number? That is a tricky problem to solve, since the 6502 has no division, nor multiplication instructions built in (it has to be done in software instead, using a set of instructions). Other than that, finding out a whole number is prime isn’t straightforward either, at least, if you want the method to be efficient and correct.

    To not reinvent the wheel, I looked for someone who had done the work before, and found Geeks For Geeks - Prime Numbers as a resource. Let’s go with that!

    Anyway, the result of our usr function should be either true or false, which is in Commodore Basic represented by a -1 and 0, respectively:

    print 1=1,1=0
    -1         0
    
    ready.

    To understand all this, I used a minimal viable solution. My code should test if the value put into the usr function and converted into a signed integer is even. If so, it should return a -1, else a 0. We still need to convert the result of parity test into a real number before exiting our routine in 6502 assembly language.

    *= $c000
    
    ; usrfunction 0.1
    ; test if the usr function even works
    ; as a test, check for even parity
    
    getayf = $b1aa
    givayf = $b391
    
            jsr getayf
            tya               ;lo byte
            and #%00000001    ;mask out every bit except bit 0
            cmp #0
            beq iseven
            lda #$00          ;false, which is 0 in 16-bit signed integer
            tay               ;corresponding to the hex value $0000
            jmp givayf        ;make it real, return to Basic
            iseven:
            lda #$ff          ;true, which is -1 in 16-bit signed integer
            tay               ;corresponding to the hex value $ffff
            jmp givayf        ;make it real, return to Basic

    I wrote this routine in the Textastic app on my iPad, used the Virtual 6502 assembler to create a hex dump, used it to create the Basic program below, typed that into the V.I.C.E. C64 emulator on my Raspberry PI-400 running Raspberry Pi OS, and ran it.

    10 poke 785,0:poke 786,192: rem set usr address to $c000 (49152)
    20 forn=49152to49173:readb:poken,b:next:rem read in machine code
    30 x=int(rnd(ti)*100):rem random number between 0 and 99, inclusive
    40 print x;" is ";
    50 if usr(x) then print "even":goto 70
    60 print "odd"
    70 data 32,170,177,152,41,1,201,0,240,6
    80 data 169,0,168,76,145,179,169,255,168,76,145,179

    It worked. Pfew!

    Now we can tackle more complicated matters, but that has to wait until part 2, because this article is already much longer than I anticipated, and I started to make mistakes. I need a break.

  • I wrote my second post on Apple Journal, hopeful that in time it will be of some use, if and when Apple improves the app with more than the current bare features. I’m tempted by the Day One app, but fear €40 per year isn’t worth it, if it turns out I’m not a “journaling person.”

  • I’m confused. After writing an entry in the Journal app I can’t do anything with it. Am I supposed to read it back later, only in the app? That seems rather pointless.

  • Currently reading: Mapping the Commodore 64 by Sheldon Leemon 📚

    Why would you even want to use the C64, it’s an old computer? It isn’t old, it’s retro!

    book cover of Mapping the Commodore 64 by Sheldon Leemon
  • I got a taste of what it is to write a computer program on the C64 (see here and here). For now, that is enough for me, and I’ll refrain from doing any more for the Advent of Code. It’s just too hardcore for me! I need something less intimidating.

  • Advent of Code 2023, day 1, part one—Hunting down the bug

    This is a continuation of this article I wrote earlier today.

    There was a bug in my Basic program when calculating the value of the two-digit number:

    140 p=f+10*l:sm=sm+p:print n,p,sm

    That should of course be:

    140 p=f*10+l:sm=sm+p:print n,p,sm

    After rewriting the code I got this answer:

    999 : 33 , 53194

    So adding 999 two-digit numbers gave me 53194. Would that be the correct answer?

    Yes!

    What have I learned?

    Always use simple examples to test your algorithm.

  • Advent of Code 2023, day 1, part one—Basic language approach

    Day 1 of the Advent of Code consisted of two parts. You had to finish part one before you could advance to part two, plus you had to log in using OAuth (with a Google, Twitter, or Reddit account).

    In part one there was a list of character strings, which I separated by commas (to avoid character return issues between platforms), and for which the first and last digit in each character string formed a two-digit number. All the two-digit numbers had to be added up into a total sum, which was the solution to part one.

    As a first attempt, I compiled the list as follows:

    *= $2000

    .byte "…"
    .byte 0

    where the stands for the list of comma separated items.

    I compiled the source code file to a .PRG file, using Virtual 6502 Assembler, and loaded the resulting file into the V.I.C.E. emulator on my Raspberry Pi 400, running Raspberry Pi OS. This took a couple of minutes, since it was a large file (21 Kb) for such a small retro computer.

    Next, I lowered the end of Basic memory, and wrote the following CBM Basic 2.0 program:

    poke 56,32:new: rem lower end of basic memory to $2000

    10 ad=8192:rem decimal of $2000
    20 sm=0:n=0:rem total sum of 2-digit values, number of items
    30 f=-1:l=-1:p=0:rem first, last digit, 2-digit value
    40 c=peek(ad)
    50 if c=0 then end:rem zero value signals end of file
    60 if c=asc(",") then 120:rem comma separated list
    70 if c<asc("0") or c>asc("9") then 110:skip for non-digits
    80 v=c-asc("0")
    90 if f<0 then f=v:rem -1 signals no digit found
    100 l=v
    110 ad=ad+1:goto 40:rem do next character
    120 n=n+1:rem count number of items
    130 if f<0 then 150:rem skip non-digits
    140 p=f+10*l:sm=sm+p:print n,p,sm
    150 ad=ad+1:goto 30:rem do next item

    Running in Warp mode took a few minutes, and the resulting last line was:

    999 66 54727

    ready.

    Apparently, there were 999 items, where the sum of the two-digit numbers was 54727. I checked my code, and reran the Basic program, same result (of course).

    Anxiously, I entered the result into the answer box on the website. It turned out my answer was wrong. It was too high.

    Ah well, this was to be expected. Now I have to reread the instructions to see if I perhaps misunderstood, then debug and find a better solution. Maybe I need to start smaller, instead of using the entire list.

    I think it was a valiant first attempt. Coding is hard, after all.

  • So I wrote some Commodore 64 assembly code in Textastic on iPad, assembled in an online 6502 assembler, into a .PRG file, and loaded that into the V.I.C.E. C64 emulator. You can see the ML monitor output and the output of the program. Note that clearing the screen isn’t needed, simply:

    SYS 49176
    
    random maze machine code monitor in V.I.C.E. random maze output in V.I.C.E.
  • The difference between how I think the world is, and how it appears to me couldn’t be further apart. I suppose this applies to most people. It is hope (or belief) that makes us see a better world. Too bad so few (including myself) act on that belief/hope in any substantial way to cross the gap.

    stylized blue cat on Christmassy background
  • It is said that Practice makes perfect, but I think it should be Passionate practice makes improvement. Being dispassionate and detached from the world is a good defense mechanism against a cruel world, but it does squat for making art. Hence, artists must suffer, from mostly indifference.

    blue tabby kitten on a Christmassy background
  • It amazes me how much can be done in one sitting, and yet how little it seems compared to what one can do in multiple sittings. However, much of the spontaneity is lost over the sittings.

    sketch of kitten on Christmassy card in blueprint
    After drawing a new background, all I had left in me was a simple monochrome sketch from reference.
  • AI is made of people…

    Letters A and I stylized into a antropomorphic cat, with red skulls as a filling
    Funnily enough, this drawing was scaled using AI.
  • I sometimes wonder why the world is the way it is. Then I realize it’s hard to change what has been developing over billions of years. The most one can do is tweak some things. Free will is an illusion, to keep humans sane and seemingly in control.

    tabby and white cat sitting on wall
    Sometimes cats find you, instead of you them.
  • My code is almost twice as fast as the original Print Maze routine,

    10 printchr$(205.5+rnd(1));:goto 10
    
    0 d=205.5:fori=.to39:printchr$(d+rnd(.));:next:goto
    
    screenshot of Commodore 64 Basic code for a random maze
  • It turned out I was over-exhausted from all the running lately. After a restday it was much easier to draw.

    colored drawing of a calico cat looking up
    I once adopted a calico cat as a kitten from a nearby cat colony. She had a wonderful life indoors.
  • Having paid for a year subscription for ibis Paint X didn’t make me want to draw more. After the free month was up, I struggled to draw daily. Maybe it’s the shortening of days making me gloomy.

    drawing of lying cat surrounded by Christmassy garland
    I suppose this is considered rather cute, isn't it?
  • I found a proper and informal method to add a description to an image,

    1. figcaption
    2. emphasis after a line break
    two cats play-fighting on a bed
    One play-fights, the other often takes it too far.

    two cats lying on a bed, one resting its head on the body of the other
    However, they still trust each other to snuggle up.

    Alas, markdown has no code for captions, _nor_ does it work inside an html container.