The Boston Diaries

The ongoing saga of a programmer who doesn't live in Boston, nor does he even like Boston, but yet named his weblog/journal “The Boston Diaries.”

Go figure.

Sunday, October 13, 2024

Unit testing from inside an assembler, part IV

I'm not terribly happy with how running unit tests inside my assembler work. I mean, it works, as in, it tests the code and show problems during the assembly phase, but I don't like how you write the tests in the first place. Here's one of the tests I added to my maze generation program (and the routine it tests):

getpixel        bsr     point_addr      ; get video address
                comb                    ; reverse mask (since we're reading
                stb     ,-s             ; the screen, not writing it)
                ldb     ,x              ; get video data
                andb    ,s+             ; mask off the pixel
                tsta                    ; any shift?
                beq     .done
.rotate         lsrb                    ; shift color bits
                deca
                bne     .rotate
.done           rts                     ; return color in B

        .test
	.opt	test	pokew	ECB.beggrp , $0E00
        .opt    test    poke    $0E00 , %11_11_11_11
                lda     #0
                ldb     #0
                bsr     getpixel
        .assert /d = 3
        .assert /x = @@ECB.beggrp
                lda     #1
                ldb     #0
                bsr     getpixel
        .assert /d = 3
        .assert /x = @@ECB.beggrp
                lda     #2
                ldb     #0
                bsr     getpixel
        .assert /d = 3
        .assert /x = @@ECB.beggrp
                lda     #3
                ldb     #0
                bsr     getpixel
        .assert /d = 3
        .assert /x = @@ECB.beggrp
                rts
        .endtst

The problem is the machine code for the test is included in the final binary output, which is bad because I can't just set an option to run the tests in addition to assembling the code into its final output, which I don't want (and that means when I use the test backend, I tend to generate the output to /dev/null). I've also found that I prefer table-style tests to writing code (for reasons way beyond the scope of this entry). For example, for a C function like this:

int max_monthday(int year,int month)
{
  static int const days[] = { 31,0,31,30,31,30,31,31,30,31,30,31 } ;
  
  assert(year  > 1969);
  assert(month >    0);
  assert(month <   13);
  
  if (month == 2)
  {
    /*----------------------------------------------------------------------
    ; in case you didn't know, leap years are those years that are divisible
    ; by 4, except if it's divisible by 100, then it's not, unless it's
    ; divisible by 400, then it is.  1800 and 1900 were NOT leap years, but
    ; 2000 is.
    ;----------------------------------------------------------------------*/
    
    if ((year % 400) == 0) return 29;
    if ((year % 100) == 0) return 28;
    if ((year %   4) == 0) return 29;
    return 28;
  }
  else
    return days[month - 1];
}

I would prefer to write test code like:

Test code for max_monthday()
output year month
28 1900 2
29 2000 2
28 2100 2
29 1904 2
29 2104 2
28 2001 2

Just specify the inputs and outputs for some corner cases, and let the computer do what is necessary to call the function in question.

But it's not so easy with assembly language, given the large number of ways to pass data into a function, and the number of output results one can have. How would I specify that the inputs come in registers A and B, and the outputs come in A, B and X? The above could be done in a table format, I guess. It might not be pretty, but it's doable.

Then there's these subroutines and their associated tests:

;***********************************************************************
;       RND4            Generate a random number 0 .. 3
;Entry: none
;Exit:  B - random number
;***********************************************************************

rnd4            dec     rnd4.cnt        ; any more cached random #s?
                bpl     .cached         ; yes, get next cached number
                ldb     #3              ; else reset count
                stb     rnd4.cnt
                bsr     random          ; get random number
                stb     rnd4.cache      ; save in the cache
                bra     .ret            ; and return the first number
.cached         ldb     rnd4.cache      ; get cached value
                lsrb                    ; get next 2-bit random number
                lsrb
                stb     rnd4.cache      ; save ermaining bits
.ret            andb    #3              ; mask off our result
                rts

;***********************************************************************
;       RANDOM          Generate a random number
;Entry: none
;Exit:  B - random number (1 - 255)
;***********************************************************************

random          ldb     lfsr
                andb    #1
                negb
                andb    #$B4
                stb     ,-s             ; lsb = -(lfsr & 1) & taps
                ldb     lfsr
                lsrb                    ; lfsr >>= 1
                eorb    ,s+             ; lfsr ^=  lsb
                stb     lfsr
                rts

        .test
                ldx     #.result_array
                clra
                clrb
.setmem         sta     ,x+
                decb
                bne     .setmem
                ldx     #.result_array + 128
                lda     #1
                sta     lfsr
                lda     #255
.loop           bsr     random
        .assert /b <> 0         , "degenerate LFSR"
        .assert @/b,x = 0       , "non-repeating LFSR"
                inc     b,x
                deca
                bne     .loop

                clr     ,x
                clr     1,x
                clr     2,x
                clr     3,x
                lda     #255
.chk4           bsr     rnd4
        .assert /b >= 0
        .assert /b <= 3
                inc     b,x
                deca
                bne     .chk4
        .tron
                ldb     ,x      ; to check the spread
                ldb     1,x     ; of results, basically
                ldb     2,x     ; these should be roughly
                ldb     3,x     ; 1/4 of 256
        .troff
        .assert @/,x + @/1,x + @/2,x + @/3,x = 255
                rts
.result_array   rmb     256
        .endtst

        .test   "whole program"
        .opt    test    pokew   $A000 , KEYIN
        .opt    test    pokew   $FFFE , END
        .opt    test    prot    r,$A000,$A001

                lbsr    start
KEYIN           lda     #'Q'
END             rts

        .endtst

And … just uhg. I mean, this checks that the 8-bit LFSR I'm using to generate random numbers actually doesn't repeat within it's 255-period cycle, and that the number of 2-bit random numbers I generate from RND4 is more or less evenly spread, and for both of those, I use an array to store the intermediate results. I leary about including an interpreter just for the tests, because I don't think it would be any better. At least the test code is largely written in the target language of 6809 assembly.

Then again, I could embed Lua, and write the tests like:

	.test
		local array = {}
		for i = 0 , 255 do array[i] = 0 end

		mem['lfsr'] = 1
		for i = 0 , 255 do
		  call 'random'
		  assert(cpu.B ~= 0)
		  assert(array[cpu.B] == 0)
		  array[cpu.B] = 1
		end

		array[0] = 0
		array[1] = 0
		array[2] = 0
		array[3] = 0

		for i = 0 , 255 do
		  call 'rnd4'
		  assert(cpu.B >= 0)
		  assert(cpu.B <= 3)
		  array[cpu.B] = array[cpu.B] + 1
		end

		assert(array[0] + array[1] + array[2] + array[3] == 255)
	.endtst

I suppose? I would still need to somehow code the fake KEYIN and END routines required for the test. And the first test at the start of this post would then look like:

	.test
		memw['ECB.beggrp'] = 0x0E00
		mem[0x0E00] = '%11_11_11_11'
		cpu.A = 0
		cpu.B = 0
		call 'getpixel'
		assert(cpu.D == 3)
		assert(cpu.X == memw['ECB.beggrp'])
		cpu.A = 1
		cpu.B = 0
		call 'getpixel'
		assert(cpu.D == 3)
		assert(cpu.X == memw['ECB.beggrp'])
		cpu.A = 2
		cpu.B = 0
		call 'getpixel'
		assert(cpu.D == 3)
		assert(cpu.X == memw['ECB.beggrp'])
		cpu.A = 3
		cpu.B = 0
		call 'getpixel'
		assert(cpu.D == 3)
		assert(cpu.X == memw['ECB.beggrp'])
	.endtst

which isn't any longer than the original test, but still … uhg. But doing this means I won't have 6809 code for testing in the final output, which means I could run tests with any backend.

I'll have to think on this.


A benchmark of three different floating point packages for the 6809

I recently came across another floating point package for the 6809 (written by Lennart Benschop) and I wanted to see how it stacked up against IEEE-754 and BASIC floating point math. To do this, I wanted to add support to my 6809 assembler, but it required some work. There was no support to switch floating point formats—if you picked the rsdos output format, you got the Microsoft floating point, and for the other output formats, you got IEEE-754 support.

The other issue, the format used by the new floating point package I found is ever-so-slightly different from the Microsoft format. It's just a single bit difference—Microsoft uses an exponential bias of 129, whereas this package uses a bias of 128 (and why do floating point packages use an exponential bias? I'm not entirely sure why). But other than this one small difference, they are basially the same.

It turned out not to be that hard to support all three floating point formats. The output formats still select a default format like before, but now, you can use the .OPT directive to select the floating point formats:

	.opt	* real ieee
	.float	3.14159265358979323846
	.opt	* real msfp
	.float	3.14159265358979323846
	.opt	* real lbfp
	.float	3.14159265358979323846

And you get three different formats as output:

                         | FILE p.asm
                       1 |                 .opt    * real ieee
0000: 40490FDB         2 |                 .float  3.14159265358979323846
                       3 |                 .opt    * real msfp
0004: 82490FDAA2       4 |                 .float  3.14159265358979323846
                       5 |                 .opt    * real lbfp
0009: 81490FDAA2       6 |                 .float  3.14159265358979323846

I added some code to my floating point benchmark program, which now uses all three formats to calculate -2π3/3! and times each one. The new code:

        .opt    * real lbfp
        .tron   timing
lb_fp           ldu     #lb_fp.fpstack
                ldx     #.tau
                jsr     fplod   ; t0 = .tau
                ldx     #.tau
                jsr     fplod   ; t1 = .tau
                jsr     fpmul   ; t2 = .t0 * t1
                ldx     #.tau
                jsr     fplod   ; t3 = .tau
                jsr     fpmul   ; t4 = .t2 * t3
                ldx     #.fact3
                jsr     fplod
                jsr     fpdiv
                jsr     fpneg
                ldx     #.answer
                jsr     fpsto
        .troff
		rts

.tau            .float  6.283185307
.fact3          .float  3!
.answer         .float  0
                .float  -(6.283185307 ** 3 / 3!)

.fpstack        rmb     4 * 10

The results are interesting (the IEEE-754 results are from the same package which support both single and double formats):

Benchmark of three floating point packages for the 6809
format cycles instructions
Microsoft 8752 2124
Lennart 7465 1326
IEEE-754 single 14204 2932
IEEE-754 double 31613 6865

The new code is the fastest so far. I think the reason it's faster than Microsoft's is (I think) because Microsoft uses a single codebase for all their various BASIC interpreters, so it's not really “written in 6809 assembler” as much as it is “written in 8080 assembler and semi-automatically converted to 6809 assembly,” which explains why Microsoft BASIC was so ubiquitous for 80s machines.

It's also smaller than the IEEE-754 package, a bit over 2K vs. the 8K for the IEEE-754 package. It's hard to tell how much bigger it is than Microsoft's, because Microsoft's is buried inside a BASIC interpreter, but it wouldn't surprise me it's smaller given the number of instructions executed.


Discussions about this entry

Tuesday, October 01, 2024

“What were the skies like when you were young?” was not asked by LeVar Burton

I always thought that the opening question in the song “Little Fluffy Clouds” by the Orb (music video) was a sample of LeVar Burton, maybe from his show “Reading Rainbow, but apparently, it's not! (link via Jason Kottke) Upon relistening to the song, it's now clear to me that it's not Levar Burton. It's very close though.

And while I like the song, my favorite one from The Orb is “A Huge Ever Growing Pulsating Brain That Rules from the Centre of the Ultraworld,” which is like a fever dream but in a good way [You're insane! —Bunny] [Yes, but in a good way! —Sean].

Monday, September 30, 2024

Ch-ch-ch-changes

Last week I got a letter in the mail from my ISP. In it, they said:

In a recent communication, we informed you that we are discontinuing the use of static IP adresses with our home internet service. We need to move your service to a dynamic IP address to avoid service ineterruption.

Please call before 9/30/2024 to avoid any interruption of your internet service. If you do not call, you internet service will be distrupted after this date.

I recall that previous communication. A few months ago I received a letter from my ISP trying to upsell me on a new plan, one that did not support static IP addresses. I called, and when I said that wasn't an option because of work (a slight embellishment on my part) they replied “how unfortunate” since they had no plans to offer one with the new plan. So I did nothing. I was happy with the plan I had (well, kind of still have).

And then last week, the letter above.

Now, I thought, given the wording the in letter, that I had until October 1st, you know, a date after their cut-off date of September 30th. And even then, I probably had several months, given my ISP is a large Enterprise that Does Not Move Fast™. Nope. Their definition of “after this date” was … today!

I only noticed when I couldn't log into my public server. The connection was still up, it's just that I no longer had the same IP address that I've had for the past … um … fourteen? years. I guess they can move fast when they want to.

I also found amusing this line in the letter: “We will switch your service to a dynamic IP address at no additional cost.” Wow! How nice of them. It's almost as if I wasn't already paying more for a static IP address!

Boy, how the Internet has changed over the years. Going from 32 publically addressible IP addresses aacross a dedicated ISDN link (years before DSL was a thing) to now, a dynamic IP address. Yes, I know, First World Problem™. It still doesn't make it less annoying from being a real peer on the Internet to just a cog in the client-server nature of the modern Internet.

And then, just three hours after the change, the company I host my public server with called—they're changing where the physical servers reside and that the public IP address I have for my public server will probably change, along with the DNS servers.

Boy, when it rains, it pours.

Sunday, September 29, 2024

“It's 250 miles inland! What do you mean it was hit by a hurricane?”

I've always thought of Brevard as a place safe from natural disasters, nestled as it is in the Appalachian Mountains. It's not along a fault line so no earthquakes … oh wait a second … there is a fault line nearby. But still, it's too far inland for anything horrible like a hurricane to worry about … oh … wait a second … Hurricane Helene is causing issues in the area.

Perhaps it's not as safe from natural disasters as I once thought it was.

Sigh.

Wednesday, September 11, 2024

This could maybe explain some of the emails I received, but not all of them

I received some responses to yesterday's post. The first was from Lionel Dricot who reported that he, too, has received emails for other Lionel Dricot's that lived near him. He also stated that it may be a bug in Gmail where one person can register “seanconner@gmail.com”; someone else could register “sean.conner@gmail.com” but when receiving emails, Gmail condenses the two addresses into one. That's possible, but I would suspect that would have been an issue caught early on. I've had my Gmail account for twenty years now [Has it been that long? —Sean] [Yes, it has. —Editor] [Shut up! —Sean]. and it's only been in the past few years that this has been an issue.

He also stated that people could just really be that bad with email addresses.

The second response has an explanation that is rather dire:

From
XXXXX­XXXXX­XXXXX­XXX
To
seanconner@gmail.com
Subject
People non knowing their email
Date
Tue, 10 Sep 2024 08:24:51 +0200

Hi, considering you've been shared on Hacker News, I'm afraid sooner or later some script kiddie will start abusing your address just to annoy you. I hope this will never happen ;) Thanks for sharing your blog, ave [sic] a nice day, White_Rabbit

That could explain maybe some of the email I get, which to me, I consider spam that Gmail hasn't filtered yet. But it doesn't explain emails sent to “sean.conner@gmail.com” that are obviously not a “Sean Conner,” and yet definitely contains private information. It also seems excessive to register a TikTok account or even an Instagram account.

And speaking of Tiktok, I finally have an account name associated with the TikTok account, so I have to wonder why “mommakmiller” decided to use my Gmail account? Perhaps they don't have an email account and need one to sign up for TikTok? Perhaps they didn't want to use their real email account? Perhaps they don't realize the danger in that?

Monday, September 09, 2024

How? How do people not know their own email addresses?

I'm still receiving emails from strangers who for some reason or another, think their email address is “seanconner@gmail.com” (no, really, I get so many emails). This time, it's from someone who's TikTok account was accessed on a new device:

From
TikTok <noreply@account.tiktok.com>
To
seanconner@gmail.com
Subject
New device login detected
Date
Tue, 03 Sep 2024 04:56:22 +0000 (UTC)

Hi xs,

We’re writing to inform you that we detected a login to your account from a new device.

When: 09/03 00:56 EDT
Device: iPhone 11
Near: Louisiana

If this was you, you can ignore this message.

If this wasn’t you, open the TikTok app and go to “Settings and privacy” > “Security and login” > “Security alerts” and review unauthorized logins. If you’re unable to access your account, contact TikTok support.

You can also set up 2-step verification to secure your account by going to “Security and login” > “2-step verification”.
Learn more about 2-step verification

This is an automatically generated email.
Replies to this email address aren’t monitored.

This email was generated for xs
Privacy Policy
TikTok 5800 Bristol Pkwy, Culver City, CA 90230

It's not a phishing email as the raw message doesn't contain a link to “login” to TikTok, and the links that are in the email all point to the TikTok domain. So I'm at a loss. “xs” created a TikTok account, using my email address …

Why?

I don't know why this should surprise me any more, but it still does. I just … why? How? What's the angle here?

I can't even contact anyone, because TikTok send email from an address that isn't checked. I don't know who “xs” is, nor do I have any way to contact “xs” to inform them of their error.

Sigh.

Tuesday, August 27, 2024

The programmer's obsession with version numbers

It's yet another round of hand wringing about software versioning. Woot!

Over the years, I've found that semantic versioning works for me, but only for code code that is to be used in larger projects, like libraries, modules, or classes. Yes, a code base using semantic versioning doesn't always mean the code base follows semantic versioning to the degree that some would like (like any bug fix should automatically update the major version number, because bug fixes could break some code, somewhere). But in my mind, it signals intent, which, sans an extremely obnoxious and overly bureaucratic process, is the best we can expect.

So for me, the MAJOR.MINOR.PATCH of semantic versioning breaks down like this:

MAJOR
Some change in the code base was made; either a change in API behavior, removal of some portion of the API, file format, or otherwise any visible change (except for bug fixes) in how the code works. Work will probably be required to update to this version of the library/module/class.
MINOR
Only additions to the API were made, in a backward compatible way. No work is expected, unless you were already using a name used in the updated library.
PATCH
A bug fix. The intent is for no work required to upgrade, unless you were relying upon the buggy behavior, or used a workaround that might now interfere with the library.

For applications, I've found that semantic versioning doesn't work. At least, it doesn't work for me. I've switched to either using a monotonically increasing number (mod_blog is now at version 60—but given the five releases in just the past week because of a misplaced obsession with version numbers, I might entirely stop with the version numbers—especially since I seem to be the only one using it) or skip it entirely (my Gemini server has no version number, but it's had 322 commits over its five year life so far).

The worst form of versioning I've enountered is “named versions.” A “named version” give no semantic information about the version and, at least to me, leads to confustion. Is “Bulldog” newer than ”Beagle?” Or is “Bloodhound” the latest version? Oh, it's “Berzoi” that's less than 20 minutes old. Sheesh.

But if I had to apply “semantic versioning” to an application, I would like information about any breaking changes to either work flow or file formats. To me, an incompatable change to a file format, or any change in workflow (even a change in location of a menu item) is a breaking change (muscle memory is an incredible drug). Hell, even a change in color scheme is enough to possibly break my workflow (I'm looking at you, Google Maps, who changed the color of all the roads to the same color! Why? I liked the distinction between highways, major roads and minor roads. Did you see a 0.001% increase in engagement for that change? Did someone get promoted just because of that change? Okay, I'll stop ranting now).


Discussions about this entry

Obligatory Picture

An abstract representation of where you're coming from]

Obligatory Contact Info

Obligatory Feeds

Obligatory Links

Obligatory Miscellaneous

Obligatory AI Disclaimer

No AI was used in the making of this site, unless otherwise noted.

You have my permission to link freely to any entry here. Go ahead, I won't bite. I promise.

The dates are the permanent links to that day's entries (or entry, if there is only one entry). The titles are the permanent links to that entry only. The format for the links are simple: Start with the base link for this site: https://boston.conman.org/, then add the date you are interested in, say 2000/08/01, so that would make the final URL:

https://boston.conman.org/2000/08/01

You can also specify the entire month by leaving off the day portion. You can even select an arbitrary portion of time.

You may also note subtle shading of the links and that's intentional: the “closer” the link is (relative to the page) the “brighter” it appears. It's an experiment in using color shading to denote the distance a link is from here. If you don't notice it, don't worry; it's not all that important.

It is assumed that every brand name, slogan, corporate name, symbol, design element, et cetera mentioned in these pages is a protected and/or trademarked entity, the sole property of its owner(s), and acknowledgement of this status is implied.

Copyright © 1999-2024 by Sean Conner. All Rights Reserved.