About Archive Tags RSS Feed

 

The CP/M emulator is good enough, I think.

9 July 2024 20:00

My previous post mentioned that I'd added some custom syscalls to my CP/M emulator and that lead to some more updates, embedding a bunch of binaries within the emulator so that the settings can be tweaked at run-time, for example running:

!DEBUG 1
!CTRLC 1
!CCP ccpz
!CONSOLE adm-3a

Those embedded binaries show up on A: even if they're not in the pwd when you launch the emulator.

Other than the custom syscalls I've updated the real BDOS/BIOS syscalls a bit more, so that now I can run things like the Small C compiler, BBC BASIC, and more. (BBCBasic.com used to launch just fine, but it turned out that the SAVE/LOAD functions didn't work. Ooops!)

I think I've now reached a point where all the binaries I care about run, and barring issues I will slow down/stop development. I can run Turbo Pascal, WordStar, various BASIC interpreters, and I have a significantly improved understanding of how CP/M works - a key milestone in that understanding was getting SUBMIT.COM to execute, and understanding the split between the BDOS and the BIOS.

I'd kinda like to port CP/M to a new (Z80-based) system - but I don't have such a thing to hand, and I guess there's no real need for it. Perhaps I can say I'm "done" with retro stuff, and go back to playing Super Mario Bros (1985) with my boy!

| No comments

 

The CP/M emulator is working well

25 May 2024 12:00

In my recent posts I've talked about implementing BDOS and BIOS syscalls for my cp/m emulator. I've now implemented enough of the calls that I can run many of the standard binaries:

  • The Aztech C Compiler
  • Microsoft BASIC
  • Turbo Pascal
  • Wordstar
  • etc

Of course I've not implemented all the syscalls, so the emulation isn't 100% perfect and many binaries won't run. But I sent myself on a detour by implementing extra syscalls, custom syscalls.

Traditionally CP/M systems are "rebooted" by pressing Ctrl-C at the CCP prompt. I thought that was something I'd press by accident so I implemented the restart behaviour only when the user pressed Ctrl-C twice in a row. But then I added a custom syscall that lets you change hte value:

A>ctrlc
The Ctrl-C count is currently set to 2
A>ctrlc 1
The Ctrl-C count is currently set to 1
A>

So you can now change the value at runtime. Similarly there is support for switching CCP at runtime, and even changing the default output-device from ADM-3A to ANSI, or vice-versa. It's kinda neat to make these kind of extensions, and happily the traditional BIOS has two syscalls reserved for custom use so I just used one of those.

I've added support for testing whether a binary is running under my emulator, or not, using a custom syscall. So I can run:

A>test
This binary is running under cpmulator:

cpmulator unreleased
https://github.com/skx/cpmulator/

On another emulator I see this:

A>test
Illegal BIOS call 31
No, this binary is not running under cpmulator.

Anyway I'm happy with the current state of things, and I fixed a couple of bugs which means I now have support for SUBMIT.COM which is a real time-saver.

| No comments

 

The CP/M emulator development continues

4 May 2024 12:00

In my previous post I introduced a toy CP/M Emulator I'd been working on.

At the time it was capable of running the Infocom text-based adventure games, so I thought it was done. Of course I also wanted to run Microsoft's original BASIC and it turned out that was a challenge because the coding of their interpreter didn't use the standard CP/M entry-point for making syscalls (call 0x0005).

Instead of calling 0x0005 to invoke the BDOS/BIOS functions the BASIC interpreter used the single-byte CALL instructions which are available on the Z80 processor. There are a bunch of these instructions:

  • RST 00
  • RST 08
  • RST 10
  • RST 18
  • RST 20
  • RST 28
  • RST 30
  • RST 38

Each of those instructions is equivalent to a call instruction with a fixed offset, "call 0x0010", "call 0x0020", etc. I had to rework the emulator to cope with this approach, which causes repetition but nothing too surprising. The end result is that now my emulator can run Microsoft Basic, Tasty Basic, and some more programs.

Things work but a couple of the syscalls are of the form "Return true if there is a pending keystroke", or "wait until there is keyboard input present and return the first character". I have some busy-loops which peg the CPU, which sucks but works. On the downside running the code on a MacOS machine has some weird issues with repeated keys and similar. So I need to look into fixing that for my own sense of peace.

I put together a little repository of binaries for playing with though, and that's been helpful. My emulator has a special flag which treats sub-directories as "Drives". So A: points to A/, B: points to B/, etc. That makes distributing and working with things easy!

| No comments

 

A simple golang CP/M emulator

15 April 2024 21:00

A couple of years ago I wrote a simple text-based adventure game in Z80 assembly language, to amuse our child. The game was written for CP/M, because that is the operating system my single-board Z80-based computer runs upon.

Later I ported the game to the ZX Spectrum 48k.

Recently I went through a burst of enthusiasm and started to overhaul the code a little, adding word-wrapping and fixing a couple of bugs. That lead to a new release, and also a brief amount of (positive) feedback on hacker news.

After mulling it over I realized that the number of CP/M BIOS functions I was using was very minimal, almost only the minimum you'd expect:

  • Write a character to STDOUT.
  • Write a $-terminated string to STDOUT.
  • Read a character from STDIN.
  • Read a line from STDIN.

It crossed my mind that implementing those syscalls should be trivial, and if I bundled implementations with a Z80 emulator library I'd have a means of running the game without a real CP/M installation, and without using the ZX Spectrum port.

So I picked a golang-based Z80 emulator, and started hacking.

After a day I had a working system, and I added a few more syscalls:

  • Open File, Create File, Delete File, Close File.
  • Console I/O.
  • Read Record.

After that? I can now play Zork 1, Zork 2, Zork 3, and The Hitchhiker's guide to the Galaxy, from Infocom.

I suspect I'm "done" for now, though it might be nice to add WriteRecord and the other missing functions there's no obvious use for yet another CP/M, especially with a CCP.

Still CP/M. In Golang. For text-based adventures:

| No comments

 

A simple package for running many linters

3 March 2024 13:00

I used to configure Emacs to run a linter when saving some specific type of files. For example I'd have a perl-utilities package to reformat perl code, and run the perl-linter on saving, then I'd have a hook to do the same thing for Dockerfiles, etc, etc.

It occurred to me recently that I should have a linter for both JSON and YAML files, since I have to edit those filetypes so damn often, and that there wasn't a great solution for those - Until it occurred to me I wrote sysbox which is a simple collection of tools in one binary, and that supports some validation commands:

sysbox validate-json /path/to/file
sysbox validate-yaml /path/to/file
sysbox validate-xml  /path/to/file

With that in mind it became obvious that what I want to do is pretty much always the same:

  • Run an external command, when the file is saved.
    • If the exit-code of that command is "success" (i.e. zero):
      • Do nothing.
    • If the exit-code is "failure" (i.e. non-zero):
      • Show the output.

And this process is the same for ANY of the linters I run. The only thing that changes is the command to run, based on the mode/type of file in question.

That lead to the following configuration:

(defvar save-check-config
      '(

        (:mode cperl-mode
         :exec "perl -wc -I. %f"
         :cond (executable-find "perl"))

        (:mode dockerfile-mode
         :exec "hadolint --no-color %f"
         :cond (executable-find "hadolint"))

        (:mode json-mode
         :exec "sysbox validate-json %f"
         :cond (executable-find "sysbox"))

        (:mode nxml-mode
         :exec "sysbox validate-xml %f"
         :cond (executable-find "sysbox"))

        (:mode perl-mode
         :exec "perl -wc -I. %s"
         :cond (executable-find "perl"))

        ;; This avoids creating .pyc files, which would happen if we had
        ;; used the more natural/obvious "python3 -m py_compile %s" approach
        (:mode python-mode
         :exec "python3 -c 'import ast; ast.parse(open(\"%f\").read())'"
         :cond (executable-find "python3"))

        (:mode sh-mode
         :exec "shellcheck %f"
         :cond (executable-find "shellcheck"))

        (:mode terraform-mode
         :exec "tflint --no-color --chdir %d"
         :cond (executable-find "tflint"))

        (:mode yaml-mode
         :exec "sysbox validate-yaml %f"
         :cond (executable-find "sysbox"))
        )
     )

Basically a list of things:

  • We have the mode of files to which the linter/validator applies.
  • We have the command to run
    • %f is changed to the filename which has just been saved.
    • %d is changed to the directory-name containing that file.
  • We add a :cond key to decide if we should run.
    • Which basically is used for "if the binary is found .. run it, otherwise silently do nothing".

I'm quite pleased with how simple the package was to write, and now I have all my linting configuration in one-place.

I'd be tempted to do the same for "format on save", but to be honest with LSP most of the code I care about has that in-place already.

Should I rename to "multi-lint[er].el"? Probably, but I guess we'll see in the future.

| 2 comments

 

Falsehoods I used to believe about shoes

10 February 2024 13:00

Once upon a time I used to divide shoes, boots, and other footwear, into two categories:

  • Things that had good soles and a good grip.
  • Things that were slippery and treacherous.

Nowadays I know better. When walking on snow, ice, or slush it isn't the sole of the shoe that causes you to slip, slide, and fall. It is you.

The shoes do make a difference, which is why people use icebugs (i.e. shows with little nails sticking out) and similar things. But really it's all about walking "properly":

  • One leg directly below you.
  • Put your feet down flat.
  • Shorter strides.
  • Horizontal movement is dangerous.

TLDR; be like a penguin.

Also - I seem to no longer appear on "Planet Debian". Weird. A problem for another day.

| No comments

 

Please to meet you, hope you guessed my name?

9 October 2023 09:00

"Hello, my name is Steve" - those are words I've said a million times in my life, however they are not true words.

If you want to get all technical about things, my name has always been SteveN.

Mostly this hasn't mattered to me, or anybody else, I introduce myself as Steve, people call me Steve, and Steve is the name that makes me turn my head, when shouted across a bar. However things changed when I moved to Finland.

In Finland I had to open new bank accounts, sign mortgages, hand over IDs, and there were many many pieces of paper I signed, or forms I filled out. Unfortunately I screwed up:

  • If I were thinking clearly I'd think "Oh, this is something official, I'd best write SteveN".
  • If I were distracted, or not being careful I'd write my name as "Steve", and then sign it as Steve.

The end result? I've been in Finland for approximately eight years, and I have some official documentation calling me Steve, and some other official documentation calling myself Steven. (For example my "Permanent Residency Permit" calls me Steve, but my Social Security ID knows me as Steven.)

Every now and again somebody queries the mismatch, and there are daily moments of pain where I have to interact with different agencies, so I made the obvious decision: I'm gonna change my name.

A fee of €60 and a simple online form was sufficient to initiate the process. The processing time was given as "one to five months" on the official forename changing page, but happily the process was complete in a month.

I will now need to do a little housekeeping by getting updated bank-cards, etc, and then complete the process by changing my UK passport to match. Hopefully this won't take too long - but I guess if Finland knows me as Steve and the UK knows me as Steven I'll still be in a bit of a screwed up state, albeit one that is consistent in each country!

Not a big change really, but also it feels weird to suddenly say "Hello, my name is Steve" and mean it.

People are weird.

Names are interesting.

The end.

Fin.

| 2 comments

 

Old-School CGI Scripts!

24 September 2023 19:00

I'm not sure if I've talked about my job here, but I recently celebrated my one year anniversary - whilst on a company offsite trip to Sweden. When I joined the company there were approximately 100 people employed by it. Nowadays the numbers are much higher.

Having more people around is pretty awesome, but I realized that there were a lot of people wandering around the office who I didn't recognize so it occurred to me to make a game of it.

I had the idea I could write a slack bot to quiz me on my colleagues:

  • Show a random face, using the Slack profile picture.
  • Give a list of 5 names.
  • Ask me which was correct.

I spent an hour messing around with various Slack APIs, and decided the whole thing was too much of a hassle. Instead I wrote a simple script to download the details of all members of the workspace:

  • Name.
  • Email address.
  • Profile picture URL.

Then using that data, users.json, I hacked up a simple web application in Python, using the flask API. There only needed to be two pages:

  • A page ("/") to show five random images, each with five random names beneath them.
  • A page ("/quiz") to receive the HTTP POST, and score.

All in all this took only two hours or so. Old-school CGI is pretty awesome like that - Hidden values meant the whole thing could be stateless:

 <input type="hidden" name="1answer" value="Bob Smith" ..
 <input type="hidden" name="1profile" value="Sales" ..
 <input type="hidden" name="1url" value="https://.." ..

 <input type="hidden" name="2answer" value="Sally Smith" ..
 <input type="hidden" name="2profile" value="Sales" ..
 <input type="hidden" name="2url" value="https://.." ..

The only downside is that I don't have any authentication, so there is no ability to have a leaderboard. I've looked at the Okta samples and I think it would be easy to add, but I guess that would make it more complex and less portable. That said I'm not sharing the code this time, so who cares if it is tied to the company?

Anyway sometimes I forget how fast and easy it is to spinup a random virtual machine and present a HTTP(S) service for interactive use. This is one of those times when I remembered.

| 2 comments

 

Simple REPL for CP/M, in Z80 assembly

24 June 2023 13:00

So my previous post documented a couple of simple "scripting languages" for small computers, allowing basic operations in a compact/terse fashion.

I mentioned that I might be tempted to write something similar for CP/M, in Z80 assembly, and the result is here:

To sum up it allows running programs like this:

0m 16k{rP _ _}
C3 03 EA 00 00 C3 06 DC 00 00 00 00 00 00 00 00

Numbers automatically get saved to the A-register, the accumulator. In addition to that there are three dedicated registers:

  • M-register is used to specify which RAM address to read/write from.
    • The instruction m copies the value of accumulator to the M-register.
    • The instruction M copies the value of the M-register to the accumulator.
  • K-register is used to execute loops.
    • The instruction k copies the value of accumulator to the K-register.
    • The instruction K copies the value of the K-register to the accumulator.
  • U-register is used to specify which port to run I/O input and output from.
    • The instruction u copies the value of accumulator to the U-register.
    • The instruction U copies the value of the U-register to the accumulator.

So the program above:

  • 0m
    • 0 is stored in the accumulator.
    • m copies the value of the accumulator to the M-register.
  • 16k
    • 16 is stored in the accumulator.
    • k copies the value of the accumulator (16) to the K-register, which is used for looping.
  • { - Starts a loop.
    • The K-register is decremented by one.
    • If the K-register is greater than zero the body is executed, up to the closing brace.
  • Loop body:
    • r Read a byte to the accumulator from the address stored in the M-register, incrementing that register in the process.
    • P: Print the contents of the accumulator.
    • _ _ Print a space.
  • } End of the loop, and end of the program.

TLDR: Dump the first sixteen bytes of RAM, at address 0x0000, to the console.

Though this program allows delays, RAM read/write, I/O port input and output, as well as loops it's both kinda fun, and kinda pointless. I guess you could spend hours flashing lights and having other similar fun. But only if you're like me!

All told the code compiles down to about 800 bytes and uses less than ten bytes of RAM to store register-state. It could be smaller with some effort, but it was written a bit adhoc and I think I'm probably done now.

| No comments

 

Simple toy languages

22 June 2023 13:00

Recently I was looking around the internet and looking for something to do with some ESP8266 devices, which I've been neglecting over recent years.

When I was on paternity-leave, five years ago, I decided I wanted a new hobby for my "down" time. I had two obvious choices a) developing applications for mobiles, or b) working with "hardware". I chose the latter.

By accident I came across a couple of simple scripting languages, FORTH-esque. Sample usage looks something like this (which obviously sends the command over a serial-device to the connected-board):

$ echo '5{ 6d 1o 100m 0o 100m }' >/dev/cu.usbmodem12341

That's a little terse, but briefly:

  • 5{ ... }
    • Execute the block five times.
  • 6d
    • Set the output pin, in the example "D6".
  • 1o
    • Output "1" to the pin we've selected, D6 in this example.
  • 100m
    • Delay for 100 milliseconds.
  • 0o
    • Output "0" to the pin we've selected, D6 in this example.

The end result is a blinking LED, for five iterations anyway. The code for this interpreter is described in the following link, with the code in the linked gist:

This is derived from an older, and simpler, project which has a similar focus but slightly different built-in operations (and which lacks loops/conditionals):

Both of these implementations are very similar, I guess due to the shared history and obvious FORTH-inspiration. Each allows port I/O, delays, and simple math opertions. We can pretend they're stack-based, though there are some differences and some niggles.

I'm kinda tempted to port one of them to Z80 assembly, and see if I can get it running under CP/M. I guess I could add a REPL for interactive use, though without actual hardware connected to my single-board computer it might all feel a little pointless. Then again I have Turbo Pascal, and even a tiny C-compiler, so I guess with those in mind any toy-language is pointless in a completely different regard.

| No comments