NEWS, EDITORIALS, REFERENCE
This is a very short, practical, follow–up to my post earlier in the week about my final direction with how to implement timers in C64 OS.
I implemented the input module a long time ago, it was one of the very first modules of code I wrote for C64 OS. It is sort of a combination of mouse and keyboard driver, and maintains queues of the three broad categories of input events: Mouse Events, Keyboard Command Events and Printable Key Events. You can read all about this, from my post The Event Model, which was published in January 2017.
The mouse events consist of mostly mouse primitives, this excludes drag and drop events, for example. It also excludes boundary crossing events such as mouseover and mouseout. So the events that it supports are:
- left down
- left tracking
- left up
- right down
- right up
Additionally, I considered to be of sufficient primitiveness, left click, left double click and right click. These are ever so slightly more than purely primitive, because a click is a not a state of a button, but rather it is a sequence of button states with a temporal aspect. If you mouse down and then later mouse up, but too much time has passed, a click will not be generated. In order to really get it right, the whole thing turned out to be more complicated then I was anticipating.
I love learning. And let me tell you, working on a big project that pushes your boundaries is just one learning experience after another. I've got quite a bit of code in C64 OS now, and so after I've been working on some areas for a while and neglecting others, I sometimes go back to code I wrote months ago, look it over and say:
What the hell was I thinking back then? Greg Naçu — 2018
I'll give you a quick example.
I probably “finished” writing the majority of the code in the screen module what feels like a year ago. The screen module implements the main event loop, fetches events from the event queues and distributes them to screen layers. The code also tracks screen layers and lets you push them and pop them to and from the screen layer stack, among other things. I looked over that code, and realized the way I was doing the pushing and popping, the way I was tracking the screen layers and accessing them, it was almost insane. In the intervening months I've just learned and improved so much that when I go back and look at my old variable naming conventions, code formatting, and comment style, they look horrible.
Not to mention the way it actually works. I've done lots of silly amateur stuff, like, loading the accumulator and then immediately comparing it to zero with CMP before doing a branch on equal to zero. In case you're unaware, that's pretty dumb. Loading the accumulator accordingly sets the Zero flag automatically. Comparing to 0, in this case, is just busy work.
Another example can be found in technique. There are two ways you can make a table of pointers. You can make a single array of 2–byte elements. But if you do this, your byte–index into the table has to increment and decrement by two to track the array offset. And to get the high byte of one of the pointers, for example, you have to set the index to: (array offset)*2 + 1. Alternatively, you can split the pointers into two arrays of 1–byte elements, a table for lo bytes and a table for hi bytes. To move through these tables you just increment and decrement the index by one. To get the high byte, (if the X index register is already set,) it's just LDA HIBYTES,X. And to get the low byte the index stays exactly the same, and it's just LDA LOBYTES,X. And so on and so forth.
Live and learn, right? Learning the practicals of programming is like learning to play pool. You can know all about geometry, but if you don't have that muscle memory and instinctive experience, your pool game is gonna suck.
We're back. This is a quick post to discuss a cool little programming technique I came up with recently. It's a "Switch" Macro. Let's just hop right in.
If you've ever written code in a high level language you're familiar with the usual flow control statements. You have if, else and else if statements, a few types of loop, for, while and do while, and a few others.1 One of the things that tripped me up when starting to learn 6502 is how to replicate some of these fairly mundane flow mechanisms.
For example, in an if–statement you can use logical operators to chain numerous checks that together determine if the code inside that block should be executed or not. But how do you do that sort of thing in 6502 when you only have branch operations on single conditions? Well, you have to do it very manually. When conditions are ANDed together you check each condition one at a time, and if any one of them is not true you use it to branch past the whole block. And you do the opposite for ORing conditions. You check a condition, and if it's true you branch past the condition checking and straight into the block of code. If you make it past all the condition checking and nothing branched you into the code, then you have a default branch at the bottom that skips the block of code.
Here's an example of 3 conditions ANDed together:
And here's an example of 3 conditions ORed together:
Note how there is a bit of a shortcut on the last branch of the OR conditions. Because it's the last one being checked, if it doesn't pass you can branch to skip, otherwise flow will fall through to the code.
Happy Easter 6502 and C64 fans! Here's some long–weekend reading, going into more detail about sorting algorithms on the C64 than you probably ever thought you wanted to know. Enjoy!
C64s are interesting computers. They have lots of character. But it's hard to put your finger on exactly why that is sometimes. What is it that endears us to this humble little machine, all these years after its commercial decline in the late 80's early 90's? My guess is that it is a confluence of artsy factors that aren't replicated on other platforms, despite their technical superiority.
Let me give you an example.
Sourced from this Twitter Moment
The above is a directory listing, probably from a 1541 disk, but could also be from any other drive that supports the standard structure. And that is just amazing! It's so artistic. It's so novel and fun, and makes the C64 artsy and nonconformant. But how is this sort of thing even possible? Why don't we see this sort of thing on other platforms?
The directory art trick, to take this one example of what's fun about a C64, is enabled by three general factors. The first is PETSCII itself. While it's missing a bunch of important business characters from ASCII, it has a brilliant set of graphical symbols that makes it possible to draw clever and complex text-based images. Check out this older post of mine, Why PETSCII Anyway?
The next factor is the simple and unvalidated structure of the directory blocks on the disk. The structure is so simple that it can easily be understood from a description in a book. And numerous tools and utilities have popped over the years that allow direct editing of the disk sectors. This allows one to make a filename have odd properties, such as a quote mid file that allows the file to be loaded by its first two characters, but still spits out the remaining characters when loading a directory. Those remaining characters can be PETSCII'd to the max, in fulfillment of your artistic desires. Or, marking files as deleted, even though they still remain in the directory. These delightful hacks are possible due to the simplicity of the structure, but also because the code that outputs it does so quite blindly and doesn't care if it deviates from a more stringent norm.
But, for the purposes of this post today, it is the third factor which is the most relevant. Directory listings on a C64 are unsorted.
I've had my head down, programming away on C64 OS, and been making some great progress. I've tweeted a few things out, some of which are revealing of things I've either never talked about here or even seem to contradict what I've said here. So I'd like to take a breather and talk about some of these developments.
When I started working on C64 OS, I was really just beginning to learn to program in 6502. I'd had a bit of experience from the past, but nothing extensive. I extended the digital audio driver in WiNGs from mono to stereo, for example. But that only involved a handful of lines of code. I played around a bit back in the day with some demo techniques, like making a raster bar. Demo coding always felt like too much work to produce something that would be a mere demonstration of some visual capability of the machine. And I plinked around with Geos and Wheels coding after getting a SuperCPU, but before falling headlong in love with WiNGs. As I've said before though, most of my WiNGs coding was done in C. And the vast resources, standard C memory management coupled with many megabytes of ram, meant that coding for WiNGs kind of felt like coding for an old PC.
This time it's different. I can now say with confidence that I'm a 6502 programmer. I don't know all the tricks yet, there is a lot to learn, especially from experience, but I know how the game is played now, and I'm knee deep in the mud.
One of the most shocking and difficult–to–get–used–to qualities of the C64, (besides low screen resolution, low clock speed and slow disk I/O) is the sheer lack of ram. 64K may have seemed big in 1982, but today 64 megabytes feels impossibly small. The small size of 64 kilobytes is hard to even fathom. To help visualize it, I just whipped up this image, 256x256, with grid lines every 16 pixels, and 16 subdivisions, one for each pixel. Then I zoomed in enough so you can see the grid lines.
Here it is:
The question driving the creation of this reference table is simple: If I press a key, or a key combined with one of the modifier keys (Shift, C=, or Control) what is going to appear on the screen?
Surprisingly, the answer is not straightforward. The keycaps of the Commodore 8-Bit family are famous for including a series of foreign–looking graphical symbols, sometimes on the key surface, sometimes printed on the front vertical face of the key. If you've ever used a C64, 128 or Vic-20, you know that in the mode immediately after turning the machine one, pressing a letter key will give you an uppercase letter, pressing shift and a letter will give you the right symbol of the two graphic symbols that appear on the key. Pressing C=Key and a letter will give you the left graphic symbol on the key.
But this is only the beginning. Many of the non–letter keys also have symbols, but not every symbol that you can generate with modifier combinations is represented on every key. And to make matters more complicated, switching the character set to uppercase/lowercase introduces other symbols that are not visible on the keycaps.
The tables below show what glyph will be produced on the screen for every key on the keyboard, in each of four possible modifications (Unmodified, Shift, C=, Control), in both character sets. See below for a full discussion.
|Row 1||Uppercase / Graphics||Lowercase / Uppercase|
|9||reverse on||reverse on|
|0||reverse off||reverse off|
|£||$1C, red||$1C, red|
|Row 2||Uppercase / Graphics||Lowercase / Uppercase|
|CONTROL||modifier key||modifier key||modifier key||modifier key||modifier key||modifier key||modifier key||modifier key|
|Q||$11, cursor down||$11, cursor down|
|E||$05, white||$05, white|
|R||$12, reverse||$12, reverse|
|T||$14, delete||$14, delete|
|I||$09, unlock||$09, unlock|
|↑||$1E, green||$1E, green|
* RESTORE generates a hardware NMI signal direct to the CPU.
Happy Valentine's Day, 6502 freaks. Here's a new genre of post, I've tagged this as: Programming Practice, because it gets right into the nitty gritty of a practical problem and the steps taken to fix it. This post includes a 30 minute video of a realtime debugging session. If a picture is worth a thousand words, well then, a video has gotta be worth something.
Late last month I posted my first video update. A video update is kind of cool because as I build out C64 OS, there will be more and more interesting things to see. And pictures are handy, but a video really gives you a flavor for how fast something is loading, or how the mouse is responding and how the screen is refreshing, etc.
In my video update, which was only a couple of minutes long, I held the camera in my left hand and worked the computer and the demo with my right. That's fine for a short clip, but to really get into the weeds you need your hands free and therefore a stand for your camera. I banged together a decent makeshift camera stand out of nothing more than a stiff metal coat–hanger, with a grippy rubber coating. My workspace is under a stairwell, so the ceiling is quite low. Hanging the coat–hanger from the ceiling works perfectly and positions it to point straight at the monitor.
Let's get into this video debugging session, and take a glance into some native coding.
I began this video knowing I had an interesting bug to find. It's interesting because it has a clear visual artifact and involves several low–level code modules that I'm working through for the first time.
Before I started my hunt for this bug, it struck me, this would be a great opportunity to catch the entire process in a video. I spend the first few minutes talking about the general layout of the source code and the convention I use for filename extensions. Then I spend maybe a minute describing the basic problem I'm having, and then we just hop right into using some tools, like DraBrowse64, Turbo Macro Pro, SuperMon64+ and JiffyDOS commands. The goal is to show how one goes about using the tools available on a C64 to hypothesize about the problem, run some tests, modify the code, reassemble and test again.
The video concludes with me being very self–satisfied, having diagnosed the problem, found the bug and fixed it, and showing that the result of the fix has removed the offending visual artifact.
WAIT, hold on. That's not the end of the story...
Shortly after concluding the video I decided I really should run it against a couple of additional test files. I very quickly noticed that I was getting unexpected results. Off video, I ended up going back into the debugging process, and after about 2 hours of testing some things out, I finally realized what the real source of the problem was. The fix shown in the video is completely wrong. It solves the visual problem by fluke, and in fact the entire on–screen display is wrong, symptomatic of the real problem, but it's only subtly wrong in a way that is hard to notice.
So, watch the video, and see how the tools work and the general process of debugging native code. And then read on below for a full description: What went wrong? How did I mis-diagnose the problem? How did my bogus fix look like it solved the problem? And what was the real problem and the real fix?
I want to talk about File References and the services provided to the developer by the File module in C64 OS. But to do that, we'll have to first take a brief tour through the IEC serial bus, the KERNAL and the C64's native I/O architecture.
To Rewrite or Not To Rewrite
First things first. C64 OS does not patch out the KERNAL ROM, it makes use of it for many low–level routines. Some of those routines include file handling. Many people who write an Operating System for the C64 make it one of their first and top priorities to rewrite the low–level file access routines. But I definitely don't want to do that. Why not?
- C64 OS is single tasking
- The C64 is already short on RAM
- Writing serial routines is hard
- JiffyDOS is already very fast
- C64 OS wants to be compatible and agnostic
If I can be allowed the pleasure of describing those points in prose, the bottom line is that writing serial routines is hard, and serial routines already exist in the KERNAL ROM. The RAM beneath the KERNAL's addressing space is still available to the machine,1 so anything you can make use of from the KERNAL, even if you only use 20% of the KERNAL, that's 20% of 8K. That's 1.6K of memory you can spend implementing something else that the KERNAL doesn't do.
Compute's 1st Book of C64: Block diagram of memory with I/O and ROM overlays.
Furthermore, the reasons people write their own file routines usually fall into a couple of categories. They want serial access to be faster, they're targeting a specific common device such as the 1541, or they're doing something more sophisticated than the KERNAL's routines can handle. These special needs might arise as a result of the the OS you're writing being more sophisticated, such as by supporting multi–tasking. C64 OS is single–tasking, that was a decision I made very early.
I want C64 OS to be able to use any type of device, IDE64, SD2IEC, 1541 Ultimate, 1541/71/81, CMD FD/HD/RL, etc. JiffyDOS, which if you don't have, you should get, already has routines that have been carefully thought out to get a good balance of greatly improved speed without sacrificing compatibility. I had a discussion with Jim Brain about how JiffyDOS's speed loading routines work, relative to how the custom speed loaders in some games work. Yes, some custom speed loaders can get more speed than JiffyDOS, but some of them also require you to have only one drive on the IEC bus, or will only work with a 1541 or 100% compatible clone. Those limitations might make sense for a demo or a game, but they are the sorts of tradeoffs that I would never make for a platform like C64 OS.
Check out this speed comparison between JiffyDOS and the original KERNAL rom. It's pretty stark.
I've been plugging away in the new year, trying to get the booter booting properly, and the ability to launch an app from an application bundle. So here's a quick video update of my progress so far.
What this video shows
The booter has run, it loads in all of the OS modules. It runs a drive detection routine and constructs a drive type to device number table in workspace memory.
The booter sets itself up as a standard app with a "quit app" jump table entry. Then it calls service routines to quit itself. This actually puts a vector to the "loadhome" service routine into the main event loop's EventLoopBreakVector. Typically this will cause the main event loop to end, and will jump to the routine to load the designated "home" application. But because we're just booting, the main event loop is not yet actually running. The quit app routine returns the pointer to loadhome in the X and Y registers. I refer to these in C64 OS now as a RegPtr. That's a standard 16 bit pointer, low byte in X, high byte in Y. The booter takes that returned pointer and jumps to it manually. This kicks off the load home routine.
Load home starts by allocating a page to be the application file reference (AppFileRef) for the about–to–be–launched app. It allocates new space because it needs to still have access to the AppFileRef for the about–to–be–quit app. Load Home then configures this new file reference with the device, partition and path to the currently designated home app's bundle. It then calls the loadapp service routine, with a pointer to the newly configured AppFileRef. In this way, the home app gets loaded just like any other app. And any app can load any other app directly, without needing to go back to the home app first. However, if you want to just quit the current app, there is a service routine that configures loading the home app for you. So it's very easy to leave your app and have the system return to the default home app.
The very first thing the loadapp routine does, is quit's the currently running app. In order to do this it jumps to the application's standard quit routine. The booter configured itself to have a standard quit jump table entry specifically so that things would go smoothly when loadapp tries to quit the booter. The booter's own quit routine doesn't need to do anything so it just RTSs.
Typically the quit routine gives the application one last chance to clean up, before it is forcibly removed from memory. It might be that it closes network connections, or saves open files to disk, or saves its own state back to its application bundle. In fact, that's the reason for maintaining the current app's AppFileRef, so it can use it during the quit phase to write data back to its own bundle.
After the previous app has completed it's quit routine, the current AppFileRef is deallocated and AppFileRef is pointed to the configured file reference for the app to be loaded. Then all application–level allocated memory pages are deallocated. Then the new app's menu file is loaded in, and the new menus are initialized. The menu initialization process deallocates the previous application's menu allocations. Memory allocations for the menu system are done at the system–level. So your application never needs to worry about setting up or taking down its own menus.
Then the application's main.o file is loaded in. And the pages that it occupies get marked as allocated (application–level.) Next, just as every application has a standard quit jump table entry, every application also has an initialization jump table entry. Load app then jumps to the freshly loaded main.o's init routine. At this early stage, I've setup app launcher as the default home app, but file manager will be next. App launcher's init routine is called. Typically it would configure its Toolkit widget UI, but I'm not there yet. It allocates space for a PETSCII image, which it then populates from a file background.pet which it finds as a resource in its own app bundle.
Next the init method sets up a screen layer struct which it pushes onto the screen layer stack. This defines its mouse and keyboard event handling routines, and its draw routine. The draw routine doesn't do much, except call textblit to copy its background PETSCII image to the screen. The mouse event routine is not yet forwarding to the Toolkit, but is instead just doing some primitive checks for where the clicks are happening. Clicking in the first column changes the foreground/background colors. Clicking in the middle of the screen changes the colors back.
Clicking in the second column puts the app into an infinite loop. This lets us test how the interrupt routine handles the situation where the app fails to return to the main event loop for a prolonged period of time. It kicks into action the CPU Busy animation, top left corner. We can see that working. It is animated by the interrupt routine. But as long as the the main event loop keeps running in a timely fashion, and events keep getting distributed, it continually updates a variable that prevents that IRQ handler from animating the CPU busy indicator. Pretty cool.
Once the app goes into an infinite loop, the main event loop is no longer running, and so events are no longer being distributed by their normal means. The interrupt handler is still updating the mouse and keyboard, though, and it's still buffering input events. The infinite loop code is scanning the key command buffer. As soon as there is a key command in the buffer it breaks the loop.
Most of these things are just a way for me to test various low–level features of the system. And things seem pretty stable. I'm making progress.
This table appears in The Complete Commodore Inner Space Anthology, pages 37 and 38. The data are rearranged here to best suit the format of a webpage, and a couple of pretty big errors in the Inner Space Anthology listing have been corrected. Corrections are noted below each block.
It is called a SuperChart because it lists the PETSCII, ScreenCode (both character sets), BASIC character/token, and 6502 operation for every value in the 8-bit range. The numeric values are conveniently given in both decimal and hexadecimal. I'm digitizing it so I can have access to it without having the reference text open on the desk in front of me, and tagging it here as Programming Reference for others to find. Hopefully someone out there will find this chart as useful as I do.
PETSCII, ScreenCodes and BASIC codes are roughly divided into 8 blocks of 32 characters each. For this reason, the chart is presented below as eight 32-row tables. See the end discussion for an explanation of PETSCII's block structure.
Bit 7 Low: %0xxx xxxx
- Block 1: 000 - 031: $00 - $1F
- Block 2: 032 - 063: $20 - $3F
- Block 3: 064 - 095: $40 - $5F
- Block 4: 096 - 127: $60 - $7F
Bit 7 High: %1xxx xxxx
- Block 5: 128 - 159: $80 - $9F
- Block 6: 160 - 191: $A0 - $BF
- Block 7: 192 - 223: $C0 - $DF
- Block 8: 224 - 255: $E0 - $FF
|29||1D||cur right||ORA X|
Corrections: Inner Space Anthology lists the block–1 alphabetic SCREEN CODES as only uppercase. They should be uppercase in the upper/graphics character set, and lowercase in the lower/upper character set, as shown above.
Corrections: Inner Space Anthology lists the SCREEN CODE $1C as a backslash. It should be the british pound symbol (£) as shown above.
Above are previews of the 10 most recent posts.
Below are titles of the 5 next most recent posts.