Tuesday, March 12, 2019

Speeding up formatting and freezing with multi-sector SD card writes

The MEGA65 format utility can take a terribly long time to run, making you feel rather much more like you are in the 1980s than you might like to, especially when formatting a large capacity 64GB SDXC card or similar.   This is because until now we have not supported multi-sector writes to the SD card.  As a result, the SD card controller must read an entire flash block (often 64KB or larger), modify the 512 byte sector we have written, erase the entire 64KB block in the flash, and then put the new data back down.  The end result is that it is about 10x slower or worse than it could be.

This problem also affects the freeze menu that has to save ~512KB of data every time you freeze.  Because the SD cards are smart internally, they tend to ignore writes with unchanged data, but even then, you have the penalty of the ~64KB block read internally in the card.  Thus freezing a problem that had very different memory contents than the last frozen program could take a number of seconds.  This was quite annoying when all you might want to do in the freeze menu is toggle between PAL and NTSC, for example.

Thus I figured the time had come to attack the root cause of these problems and implement multi-sector writes on the SD card.  But first I had to figure out exactly how they work. This was not as easy as it sounded, as there is a lot of contradictory information out there.

The only certain thing, was that CMD25 should be used instead of CMD24 to initiate a multi-block write. Some sources said you should also use a CMD18 to say how many blocks you are going to write, and others said you should use a CMD23 after to flush the SD card's internal cache.  Then everyone seemed to disagree on the actual mechanics of multi-sector writes.

What I figured out was:  You don't need CMD18 or CMD23, just CMD25.  Then use the 0xFC data start token instead of 0xFE when writing the first sector. Then just keep writing extra sectors, this time with an 0xFC token until done, after which you should write an 0xFD token (without a sector of data following it) to mark the end of the multi-sector write.  If you try to write data after the 0xFD, then really bad/weird things happen that require you to physically power things off and on again.

In the end, the main part of the change to the low-level SD card controller was quite small:

if write_multi = '0' then
+                -- Normal CMD24 write
                 txCmd_v := WRITE_BLK_CMD_C & addr_i & FAKE_CRC_C;  -- Use address supplied by host.
-                addr_v  := unsigned(addr_i);  -- Store address for multi-block operations.
+                state_v    := START_TX;  -- Go to this FSM subroutine to send the command ...
+                rtnState_v := WR_BLK;  -- then go to this state to write the data block.
+              elsif write_multi = '1' and write_multi_first='1' then
+                -- First block of multi-block write
+                -- We should begin things with CMD25 instead of CMD24, but
+                -- then we don't need to repeat it for each extra block
+                txCmd_v := WRITE_MULTI_BLK_CMD_C & addr_i & FAKE_CRC_C;  -- Use address supplied by host.
+                state_v    := START_TX;  -- Go to this FSM subroutine to send the command ...
+                rtnState_v := WR_BLK;  -- then go to this state to write the data block.
+              else
+                -- We are in a multi-block write.  So just go direct to WR_BLK
+                state_v    := WR_BLK;       -- Go to this FSM subroutine to send the command ...               
               end if;

Essentially the change was just to write the correct command for starting a multi-block write, and to write subsequent blocks with the correct data token at the start, and without re-sending any CMD25 or anything else. I actually managed to get that part correct on almost the first go, once I had figured the mechanics out, which rather surprised me.  But it took quite a bit more effort to get FDISK/FORMAT and the hypervisor freeze routines to work properly with it.  Indeed there are still a few wrinkles to work out. For example, when copying the frozen program to a new freeze slot, trying to use multi-sector writes causes things to go haywire. So for now, I have just disabled that.

That said, the changes to the various programs are actually quite small as well.  It's just the usual problem of writing assembly language and low-level C code that works reliably.  Add in the fun of CC65 not telling you when you have run out of memory during compilation, and life remains "interesting".  For example, here are the changes to the freeze routine:

       jsr copy_sdcard_regs_to_scratch

        ; Save current SD card sector buffer contents
-       jsr freeze_write_sector_and_wait
+       jsr freeze_write_first_sector_and_wait

        ; Save each region in the list
        ldx #$00
@@ -31,6 +31,8 @@ freeze_next_region:
        cmp #$ff
        bne freeze_next_region

+       jsr freeze_end_multi_block_write
+
        rts


        jsr sd_wait_for_ready_reset_if_required

-       ; Trigger the write
-       lda #$03
+       ; Trigger the write (subsequent sector of multi-sector write)
+       lda #$05
        sta $d680

        jsr sd_wait_for_ready
@@ -168,6 +206,13 @@ freeze_write_sector_and_wait:
        sec
        rts

+freeze_end_multi_block_write:
+       jsr sd_wait_for_ready
+       lda #$06
+       sta $d680
+       jsr sd_wait_for_ready
+       rts
+

In short, use the new commands $04 (begin multi-sector write), $05 (write next sector of multi-sector write) and $06 (finish multi-sector write) when writing to $D680, the SD card control register. The changes to FDISK/FORMAT were similarly simple, again, just changing the bulk-erase function to use these new commands.  While I was there, I also implemented a little SD card reading speed test, because I was curious how fast (or slow) the SD card was going.  My 8GB class 4 card can read more than 700KB/second. One day I will implement the 4-wire interface, which will allow speeds 8x faster, but for now 700KB/sec is ample.




All up, the result is already very pleasing:  Formatting an SD card is easily 10x faster, taking less than 1 minute for an 8GB card now, instead of 10 minutes or more.  You can see how fast it is in this video:



Freezing is now also MUCH faster, reliably now only about 1 second to get to the menu on my machine here.  The biggest delay when freezing is now waiting for the monitor to resync if the frozen program was PAL (as the freeze menu is always 60Hz NTSC for better monitor compatibility).  You can see how fast the freeze menu is in the videos below:

This time, we will have the English video first, and the German one below:


Und auf Deutsch:




Sunday, March 3, 2019

Introduction to using the MEGA65 as at 3 March 2019

Hello all!

While lots of things might change, we thought it would be helpful to post a short video showing how to get around the MEGA65 with the current state of the bitstream and supporting software.

The video was filmed in the last couple of hours before I had to fly home from Germany, and is provided in both German and English.  English version begins at about 5:25.


Here is a random frame from the video, so that things that want to use an image from the blog work properly :)





Saturday, March 2, 2019

Auto-Detecting Required Revision of DMAgic Chip, improving default audio mixer settings

In the last hour or two before I fly home from Germany, we decided to tackle a couple of little things to make the system easier to use:

The Commodore 65 prototypes have one of two different revisions of the DMAgic DMA chip, which are not entirely compatible with one another, because the format of the DMA lists differs.  The revision B list has an extra sub-command byte between the destination bank and modulo bytes, as these example lists show:

        ; F018A DMA list
        .byte $04   ; COPY + chained request
        .word 1996  ; 40x25x2-4 = 1996
        .word $0400 ; copy from start of screen at $0400
        .byte $00   ; source bank 00
        .word $0404 ; ... to screen at $0402
        .byte $00   ; screen is in bank $00
        .word $0000 ; modulo (unused)





        ; F018B DMA list
        .byte $04   ; COPY + chained request
        .word 1996  ; 40x25x2-4 = 1996
        .word $0400 ; copy from start of screen at $0400
        .byte $00   ; source bank 00
        .word $0404 ; ... to screen at $0402
        .byte $00   ; screen is in bank $00

        .byte $00   ; F018B sub-command
        .word $0000 ; modulo (unused)



Basically having the wrong mode makes MEGA65 BASIC do all sorts of odd things, like produce the delightful ? PROGRAM MANGLED  ERROR.  We should try to avoid those, so it would be great for the boot ROM to automatically recognise when a C65 ROM is loaded, and to know which DMAgic revision the ROM needs.

Fortunately, this is fairly easy to do, because at offset $16 in the C65 ROM there is a string like "V910111" that indicates the date of the ROM.  So we can just check for the V there, an then test the date.  If it isn't a C65 ROM, then it doesn't matter which DMAgic mode we use, so there is no problem from false positives.  Then all we need to know, is from which date the new DMAgic was required, and that turns out to be from V910523, so a little bit of code to test the date, like the following is called for:

syspart_dmagic_autoset:
        ; Set DMAgic revision based on ROM version
        ; $20017-$2001D = "V9xxxxx" version string.
        ; If it is 900000 - 910522, then DMAgic revA, else revB
        lda #$16
        sta zptempv32
        lda #$00
        sta zptempv32+1
        sta zptempv32+3   
        lda #$02
        sta zptempv32+2
        ldz #$00
        nop
        lda (<zptempv32),z
        cmp #$56
        beq @hasC65ROMVersion
        rts
@hasC65ROMVersion:
        ; Check first digit is 9
        inz
        nop
        lda (<zptempv32),z
        cmp #$39
        bne @useDMAgicRevB
        ; check if second digit is 0, if so, revA
        inz
        nop
        lda (<zptempv32),z
        cmp #$30
        beq @useDMAgicRevA
        ; check if second digit != 1, if so, revB
        cmp #$31
        bne @useDMAgicRevB
        ; check 3rd digit is 0, if not, revB
        inz
        nop
        lda (<zptempv32),z
        cmp #$30
        bne @useDMAgicRevB
        ; check 4th digit is >5, if so, revB
        inz
        nop
        lda (<zptempv32),z
        cmp #$36
        bcs @useDMAgicRevB
        ; check 4th digit is <5, if so, revA
        cmp #$35
        bcc @useDMAgicRevA
        ; check 5th digit <=> 2
        inz
        nop
        lda (<zptempv32),z
        cmp #$32
        bcc @useDMAgicRevA
        cmp #$33
        bcs @useDMAgicRevB
        ; check 6th digit <3
        inz
        nop
        lda (<zptempv32),z
        cmp #$33
        bcc @useDMAgicRevA
@useDMAgicRevB:
        ldz #$00
        lda #$01
        tsb $d703

        ldx #<msg_dmagicb
        ldy #>msg_dmagicb
        jmp printmessage

@useDMAgicRevA:
        ldz #$00
        lda #$01
        trb $d703
       
        ldx #<msg_dmagica
        ldy #>msg_dmagica
        jmp printmessage

And then the boot ROM can now automatically work out the correct DMAgic version, and tells you at boot time which it has selected, as can be seen in the messages below:


While we were fiddling with that, we also decided to improve the default audio-mixer settings, so that the microphones are not connected to the line out by default, but are for the cellular modems, and generally improve the audio line levels for the SIDs.  The output volume is now much better, and it all sounds nice and clear and loud.







Friday, March 1, 2019

Multiple Freeze Slots

Recently we were excited to be able to begin to show-off the freeze menu of the MEGA65, but at that time we had only one freeze slot working.

So I have spent some time this week while with the MEGA65 team here in Germany to figure out what was going wrong, and how to fix it.

The problem basically was that we were not calculating the address on the SD card of the freeze slots correctly, and then were passing the 16-bit slot number with high and low bytes swapped around.  All quite annoying, but nice little bugs that could be solved without wasting lots of time resynthesising multiple times.

The result is now as it should be: We can freeze a program, choose whichever slot to save it in, and then load it back up as often as you want later on.  So quite quickly we had frozen a few games and even a MEGA65 demo, which we can then easily flick through.

So here are a couple of short videos of using the freeze menu to access various things saved in different freeze slots.

You can play spot the differences between the thumbnail images.  Because we are in Germany, the top version of the video is in German, and the lower one is in English for those of you who are unable to understand Australo-deutsch ;)





Also, here are some screenshots of the freeze menu looking at some of the different slots with things saved in them.  I continue to be very happy that I implemented the hardware thumbnail generator, so that it is relatively easily to see what is saved in a slot. We will add the ability to name the slots, search for them, jump to specific slot numbers by typing the numbers etc, once we get a bit of spare time.




And here is how a slot with nothing saved in it can look:


Audio Mixer in Freeze Menu and Fixing SID Problems

The audio cross bar switch that we have implemented is a delight, allowing tuning of the input levels of all audio sources, as well as control of master output levels.  But until now, there has been no way for a user to easily control the audio levels. This was a bit of a pain, as the microphone input on the Nexys boards is by default active, and thus as we have worked to work out the source of some bugs in the SIDs, having feedback via the microphones was, shall we say, rather unhelpful.

Thus I finally got around to making a control interface for the audio cross-bar switch.  This has been built into the freeze menu to make it easy to change the audio levels when running a program, without having the program to know about the cross-bar.  The first step was to add an "A" option to go to the audio mixer in the freeze menu.  "A" was used for enabling cartridges, so that function has been changed to "T".


This then takes you to a screen like this, where you can modify all the coefficients for the cross bar:



This looks quite complicated, but is in reality not too bad.  The columns of 4-digit hex numbers (we will eventually make a friendlier display of the volume levels) are the level for the input on the left for the output at the top.  There is also a pseudo input which is the master volume input. The other inputs down the left, from top to bottom are the left SID, right SID, first and second phone modems (for the MEGAphone, which supports dual cellular radios), bluetooth left and right microphone/audio inputs (again for the MEGAphone), line in left and right, the left and right 16-bit digi channels of the MEGA65, then up to four microphone channels (again, mostly for the MEGAphone), then one input channel that is spare, and the master volume level.

The output channels are left and right speaker output, then outputs for the two phone radios, stereo output for bluetooth, and finally wired head-phones stereo channels.  For the desktop version we can of course remove the majority of these from the menu, and make it a lot friendlier, the main thing for now is that we have a facility that works, and that we can improve upon.

So, finally we were able to start investigating what was wrong with the SIDs.  We have known for a while that some things sound quite wrong with the new VHDL SID implementation we are using, despite the fact that technically, it should sounds really great, with all internal features of the SIDs implemented carefully by the author.  For example, the Trap demo shown below, had very muffled drums, and just generally sounded wrong:

 I'm not very musically inclined, so couldn't alone even work out what was wrong.  But this week I am not working alone, but rather with the MEGA65 crew here in Germany, so together with Deft and Libi we started investigating. After about an hour of fiddling and comparing audio output from the VICE with the output from the MEGA65 (with the microphone input on the MEGA65 nicely muted using the audio mixer interface in the freeze menu), we realised that the problem was actually quite simple: The SID was producing audio one octave too low, and the ADSR behaviour was also half-speed.  Thus it seemed that the frequency input to the audio engines of the SIDs needed doubling.

After months of worrying about how hard the problem would be to find, and then to fix by fiddling with low-level signal processing algorithms in the SID implementation, it ended up taking only about a further hour to fix.

You can hear the difference between the old broken audio, and the new fixed audio. (The lower wave form in each video is the old broken audio, and the upper waveform is the fixed one, for those wanting to interpret the images in the videos)




The difference is noticeable in all sorts of games, and it really does now sound simply great.  This is the joy of the power of open-source projects -- thanks to the SID work of Alvaro Lopes (SID filters in VHDL) and Jan Derogee (SID VHDL implementation), the MEGA65 now has really, really nice sound.



Towards Dual SD-Card Support

The MEGA65 was intended from the outset to support multiple SD Cards / storage devices, and there is considerable provision made for this in our boot ROM, if not actual current support.  This takes the form of allowing multiple drives, and for each drive to indicate which storage device it has come from.

Full active support is still a while off, but what we have done today is to add support for two SD cards, and to have the boot ROM work out which one has an SD Card inserted.  This will get used in the R2 PCBs that are being designed at the moment.  But the pressing reason for this was to get the R1 PCB that Falk is using to develop GEOS back working, as a couple of FPGA pins have died on it that were responsible for the SD card interface.  We have rerouted a couple of unused pins that connect to the HDMI controller to get a physical connection:


I have then also modified the SD controller so that it has a multiplexer to select which of two SD Card busses is active at any point in time.  This is controlled in software by writing $C0 (for Card 0) or $C1 (for Card 1) to $D680, the SD Card command register.  This turned out to be more of a pain than it should have been, because things kept going strange for no apparent reason.  I had to refactor the multiplexer a couple of times until it was working reliably, even though the problems, as far as I can see, were nothing to do with the SD card interface.

This kind of thing happens more often than I would like when working with VHDL. I am sure some of the problems are subtle (or not so subtle) things that I have done wrong, but others seem to defy explanation.  This was one of those: I added simply a multiplexer for the SD card busses, and suddenly the MEGA65 had keyboard problems.

Anyway, after some considerable effort, we managed to get it mostly working, but then suddenly the keyboard stopped working at 40MHz again -- a problem we have seen before. But then after doing some other unrelated fixes to the SID, suddenly the keyboard is again working at 40MHz.  Hardware is annoying, sometimes.

What was then left was to add support to the boot ROM to work out which SD card slot to use (as mentioned, support for using both at once will come later). Basically we try to reset the SD card in slot 0, and if that fails, then we try resetting the other one:

   ; Work out if we are using primary or secondard SD card

                ; First try resetting card 0
                lda #$c0
                sta $d680
                lda #$00
                sta $d680
                lda #$01
                sta $d680

                ldx #$0f
@morewaiting:
                jsr sdwaitawhile

                lda $d680
                and #$03
                bne trybus1

                phx

                ldx #<msg_usingcard0
                ldy #>msg_usingcard0
                jsr printmessage

                plx            

                jmp tryreadmbr
trybus1:
                dex
                bne @morewaiting

                lda #$c1
                sta $d680
                ldx #<msg_tryingcard1
                ldy #>msg_tryingcard1
                jsr printmessage

tryreadmbr:


Whichever we choose is then the SD card used by the system until next reboot.  The freeze menu and FDISK needed to be patched to handle the bit that indicates which SD card is being used, but other than that, it was pretty uneventful, and now when you boot, you get a message that indicates which SD card bus is being used, in this case, bus 0:


So now we can send Falk his board back, so that he can finish working on the GEOS port for the MEGA65, which we are all very much looking forward to.

Floppys, floppys everywhere!

A number of you will recall that we have been asking questions about floppy drives, and even asking people to hunt through their old floppy drive collections, so that we can get enough for trying out which will fit best etc.

First though, is the question of whether we will include a floppy drive in the MEGA65.  The survey confirmed our existing belief, that more people would like the MEGA65 to have a floppy drive than those against:



We had a total of 184 valid answers from mostly Europe, North America and Australia, with special mentions for Argentina and Iceland.  I was a bit surprised to not see any entries from New Zealand, but that's okay.  Basically we see a general matching of interest to where the Commodore 8-bit computers were well known.  What is most interesting is that on a per-capita basis, it is Denmark and then Australia and Germany that have the highest number of responses.  But enough of me in academic data analysis mode! 


So given that more people want the drive in than out, we had to figure out how and where we could find enough floppy drives for those who want them (we will still likely make the inclusion of the drives optional).

Of course floppy drives are no longer manufacturered, and although as recently as 2016 or 2017 we were able to find brand-new stock in Chinese warehouses, we have had no such luck this time around.  

We even called the German headquarters of ALPS who still make various switches and things, but no longer floppy drives, if they had a few palettes of them hidden away some where.  This was quite a nice call with them, and they were very sympathetic, but unfortunately knew of no stock anywhere.  They did reveal something interesting though: Apparently they are being asked about floppy drives more frequently of late.  Could it be that floppies will make a come back like vinyl records? Probably not.

Anyway, so we had to try to find a large quantity of floppies, but recognised that they would likely have to be used ones, rather than new ones.  But even if they were used, we wanted to find a single supplier with a single model of drive, so that we don't have to worry about eject buttons with different mechanisms or locations to interface to the case etc.

After some searching, we found what we were after: A local supplier here in Germany who has some quantity of different models, including a large number of ALPS 3.5" 1.44MB drives.  This was as good as we could hope for, as the ALPS drives are built like little tanks.  They kindly sent us some very fancy photos of an example drive for us to look at:


 They even took the top off one to show us the internals:

Note that this drive had the front panel removed, which we had requested, so that we could see the eject button mechanism etc, since in the MEGA65 only the slot will be visible.

So, we were satisfied we had a solution, and have thus bravely ordered our first shipment of floppy drives, which is also the first component we have purchased in bulk for the first production run of the MEGA65, which is a little milestone in itself.  After a tense couple of weeks of not receiving a tracking number for the shipment, they suddenly arrived yesterday.  Here are some shots of me starting to unpack several hundred floppy drives:





So now we need to make sure that everything will work together as expected, and have one of these drives connected to the R1 PCB prototypes of the MEGA65 here in Darmstadt:

Thursday, February 28, 2019

Fixing Sprite Rendering Problem

After all the fiddling around with video modes a while back, a bug with sprite handling has crept in, where gaps would appear between sprites that were placed side-by-side, e.g., as in the big planet display on Gyrrus:



Or between the halves of the 16-colour sprites in Daniel's Beach Demo test program for the MEGA65:



At first I thought it was pixel edge timing funniness, but it turns out after a little inspection that the left column of pixels in a sprite are only one pixel wide instead of the correct two pixels wide (when at 320x200), as I was able to confirm by setting the first few pixels in the left and right column to check for clues.  So, the question is what is causing this problem?

To figure out the likely cause, I first checked to make sure that the problem happens whether or not sprites are horizontally expanded, and indeed, it does. Also, it is still exactly one physical pixel that is missing, i.e., the gap does not get wider if the sprites are horizontally expanded.

This suggests something about the logic that triggers when on a raster line a sprite should begin being drawn is to blame.  Quite possibly it triggers the logic to advance the pixels one cycle before it actually begins outputting sprite data.

There is some evidence in support of this, in that if the sprites are set to un-expanded H640 mode, i.e., where each sprite pixel is only one physical pixel wide, then the left pixels simply don't appear.


So I started looking at the logic for the sprite pixel advancing, and found that the sprite left edge could be triggered on a cycle when it is not a visible pixel edge.  As a result, an update happens to the sprite X pixel position on this partially wide pixel.  The fix turned out to be quite simple:

@@ -299,11 +300,19 @@ begin  -- behavioural
 --      else
 --        report "x_in = " & integer'image(x_in) & ", != sprite_x = " & integer'image(to_integer(sprite_x));
 --      end if;
+
+      -- Make sure we don't start a sprite edge except on an output pixel edge,
+      -- to stop the left pixel column being trimmed by one clock tick
+      if (x_in_sprite_soon= '1') and (pixel_strobe = '1')  then
+        x_in_sprite_soon <= '0';
+        x_in_sprite <= '1';
+      end if;
+
       if (x_in = to_integer(sprite_x))
         and (x_in /= x_last)
         and (sprite_enable='1')
         and ((y_top='1') or (sprite_drawing = '1')) then
-        x_in_sprite <= '1';
+        x_in_sprite_soon <= '1';         x_expand_toggle <= '0';
         report "SPRITE: drawing row " & integer'image(y_offset)
           & " of sprite " & integer'image(sprite_number)


Basically I have added an extra signal that notes when the sprite X starting position is reached, but then delays it until the next physical pixel edge.  I like it when things are simple to fix.  The result is that the problem is corrected, as the following screen-shots show:


Now if I only I can fix the remaining bitplane problems so quickly.  But that will have to wait for another post.

Wednesday, January 23, 2019

The MEGA65 at Linux Conf AU

This week I am at Linux Conf AU, where I have, among other things, given a presentation about the MEGA65, using the MEGA65:


The cover slide was produced using a PNG to MEGA65 full-colour image converstion utility I wrote, and the main slides using MegaWAT, which I wrote with Lucas, a student, over the past few months in preparation for this, and in general, for being able to use the MEGA65 to introduce itself.  The source is at github.com/MEGA65/MegaWAT.


Wednesday, January 16, 2019

Livening CPU speed, video mode and monitor in freeze menu

Continuing the work on the freeze menu, I have been able to make quite a bit of progress lately. In the last post, I had the freezer and freeze menu working in a minimalistic kind of way. However, there were a lot of things not there, and still plenty of bugs to cause trouble. 

For a start, I wasn't saving the state of the CIAs.  This turned out to be a bit more of a pain than I first expected.  Fortunately, I was able to read through the information on C64 freezing by Gideon, as well as get a bunch of useful tips from Groepaz and others.  This opened a whole can of worms, and basically reminded me of a fact I had either previously forgotten, or had failed to fully grasp:  Perfect freezing is practically impossible, and requires in the very least hardware support if you are to have any chance at getting it right. 

The CIAs are a big part of the reason behind this.  The problem is that the CIAs have timers that keep on running while you are trying to freeze them, and there is certain internal state that you can't read from the registers, but requires that you try to work out how far the timers have run down, wait for them to run out, and then read out the latched information.  Of course, one or more of the four timers of the 2 CIAs will probably run out before you spot it, and the accuracy you can achieve when trying to watch four timers with a single 1MHz CPU is rather limited -- probably of the order of 50 or 100 cycles.

My solution to deal with the CIAs was to make the CIAs effectively freeze themselves when in Hypervisor mode: The CIA timers stop ticking, and acknowledging interrupts via $DC0D etc has no effect.  I also added 16 extra registers in hypervisor mode of the CIA that allows us to directly read out the latched and current values of the timers, as well as the current and alarm times of the time-of-day clocks. It is still not perfect, but it pretty much works.

As I worked on the freeze menu, I wanted to provide an indication of which ROM you are running, since there are a variety of C65 ROMs available, and people will probably want to try some out.  So I wrote a little routine that makes an educated guess as to which ROM you are running. For the C65 ROMs, this is quite simple: they have a fairly reliable version string near the start of the ROM, and so if I find one of those, I indicate it is a C65 ROM and show the relevant version.  It can also detect a wide range of C64 and even PET 4064 ROMs. I did this by making a utility that reads in all the ROMs I could find, and looks for unique bytes in the KERNAL ROM part, and makes its decision on that basis. It turns out if you do the tests in the right order, a single PEEK is all you need to test for each known ROM:

  // Check for C65 ROM via version string
  if ((freeze_peek(0x20016L)=='V')
      &&(freeze_peek(0x20017L)=='9')) {
    c65_rom_name[0]=' ';
    c65_rom_name[1]='C';
    c65_rom_name[2]='6';
    c65_rom_name[3]='5';
    c65_rom_name[4]=' ';
    for(i=0;i<6;i++)
      c65_rom_name[5+i]=freeze_peek(0x20017L+i);
    c65_rom_name[11]=0;
    return c65_rom_name;
   
  }

  if (freeze_peek(0x2e47dL)=='J') {
    // Probably jiffy dos
    if (freeze_peek(0x2e535L)==0x06)
      return "sx64 jiffy ";
    else
      return "c64 jiffy  ";
  }
 
  // Else guess using detection routines from detect_roms.c
  // These were built using a combination of the ROMs from zimmers.net/pub/c64/firmware,
  // the RetroReplay ROM collection, and the JiffyDOS ROMs
  if (freeze_peek(0x2e449L)==0x2e) return "C64GS      ";
  if (freeze_peek(0x2e119L)==0xc9) return "C64 REV1   ";
  if (freeze_peek(0x2e67dL)==0xb0) return "C64 REV2 JP";
  if (freeze_peek(0x2ebaeL)==0x5b) return "C64 REV3 DK";
  if (freeze_peek(0x2e0efL)==0x28) return "C64 SCAND  ";
  if (freeze_peek(0x2ebf3L)==0x40) return "C64 SWEDEN ";
  if (freeze_peek(0x2e461L)==0x20) return "CYCLONE 1.0";
  if (freeze_peek(0x2e4a4L)==0x41) return "DOLPHIN 1.0";
  if (freeze_peek(0x2e47fL)==0x52) return "DOLPHIN 2AU";
  if (freeze_peek(0x2eed7L)==0x2c) return "DOLPHIN 2P1";
  if (freeze_peek(0x2e7d2L)==0x6b) return "DOLPHIN 2P2";
  if (freeze_peek(0x2e4a6L)==0x32) return "DOLPHIN 2P3";
  if (freeze_peek(0x2e0f9L)==0xaa) return "DOLPHIN 3.0";
  if (freeze_peek(0x2e462L)==0x45) return "DOSROM V1.2";
  if (freeze_peek(0x2e472L)==0x20) return "MERCRY3 PAL";
  if (freeze_peek(0x2e16dL)==0x84) return "MERCRY NTSC";
  if (freeze_peek(0x2e42dL)==0x4c) return "PET 4064   ";
  if (freeze_peek(0x2e1d9L)==0xa6) return "SX64 CROACH";
  if (freeze_peek(0x2eba9L)==0x2d) return "SX64 SCAND ";
  if (freeze_peek(0x2e476L)==0x2a) return "TRBOACS 2.6";
  if (freeze_peek(0x2e535L)==0x07) return "TRBOACS 3P1";
  if (freeze_peek(0x2e176L)==0x8d) return "TRBOASC 3P2";
  if (freeze_peek(0x2e42aL)==0x72) return "TRBOPROC US";
  if (freeze_peek(0x2e4acL)==0x81) return "C64C 251913";
  if (freeze_peek(0x2e479L)==0x2a) return "C64 REV2   ";
  if (freeze_peek(0x2e535L)==0x06) return "SX64 REV4  ";
  return "UNKNOWN ROM";

This routine is written in C, because the whole freeze menu is written in C using the CC65 compiler.  This makes it quite easy to change, which is how we want it: If you want to make your own replacement or modified freeze menu, then it should be possible to do.   We are progressively building up a library of functions that are helpful, such as the freeze_peek() routine, which the freezer uses to retrieve a byte of memory from the frozen program.  To do this, it needs to know the layout of the freeze slots as they are saved on the SD card, because on the MEGA65 freezing always happens with the result written to the SD card, rather than being squished around in memory like with the original freeze cartridges.  The freeze_peek() routine itself is fairly simple, basically consisting of working out where the byte lives on the SD card, then reading the relevant sector, and returning the appropriate byte of data:

unsigned char freeze_peek(uint32_t addr)
{
  // Find sector
  uint32_t freeze_slot_offset=address_to_freeze_slot_offset(addr);
  unsigned short offset;

  offset=freeze_slot_offset&0x1ff;
  freeze_slot_offset=freeze_slot_offset>>9L;
 
  if (freeze_slot_offset==0xFFFFFFFFL) {
    // Invalid / unfrozen memory
    return 0x55;
  }
 

  // Read the sector
  sdcard_readsector(freeze_slot_start_sector+freeze_slot_offset);

  // Return the byte
  return sector_buffer[offset&0x1ff];

}

Working out where the byte lives is a little more complicated, as we have to ask the Hypervisor for the layout of the freeze regions, and then iterate through those regions to work out if the requested address falls in one of those regions.  It is possible that it doesn't, because the MEGA65 has a lot more address space (256MB) than it has populated with actual memory.

/* Convert a requested address to a location in the freeze slot,
   or to 0xFFFFFFFF if the address is not present.
*/
uint32_t address_to_freeze_slot_offset(uint32_t address)
{
  uint32_t freeze_slot_offset=1;  // Skip the initial saved SD sector at the beginning of each slot
  uint32_t relative_address=0;
  uint32_t region_length=0;
  char skip,i;

  for(i=0;i<freeze_region_count;i++) {
    skip=0;
    if (address<freeze_region_list[i].address_base) skip=1;
    relative_address=address-freeze_region_list[i].address_base;
    if (freeze_region_list[i].address_base==0x1000L) {
      // Thumbnail region: Treat specially so that we can examine it
      // We give the fictional mapping of $FF54xxx
      if ((address&0xFFFF000L)==0xFF54000L)
    { relative_address=address&0xFFF;
      freeze_slot_offset=freeze_slot_offset<<9;
      freeze_slot_offset+=(relative_address&0xFFF);
      return freeze_slot_offset;
    }
    }
    region_length=freeze_region_list[i].region_length&REGION_LENGTH_MASK;
    if (relative_address>=region_length) skip=1;
    if (skip) {
      // Skip this region if our address is not in it
      freeze_slot_offset+=region_length>>9;
      // If region is not an integer number of sectors long, don't forget to count the partial sector
      if (region_length&0x1ff) freeze_slot_offset++;
    } else {
      // The address is in this region.

      // Firsts add the number of sectors to get to the one with the content we want
      freeze_slot_offset+=relative_address>>9;

      // Now multiply it by the length of a sector (512 bytes), and add the offset in the sector
      // This gives us the absolute byte position in the slot of the address we want.
      freeze_slot_offset=freeze_slot_offset<<9;
      freeze_slot_offset+=(relative_address&0x1FF);
      return freeze_slot_offset;
    }
  }
  return 0xFFFFFFFFL;
}

Anyway, back to the ROM auto-detection: The result of the ROM auto-detection is displayed in the freeze menu, as shown in this screenshot:


While we are here, let's walk through the various elements, starting with most obvious part: the picture of the computer itself!  Here the results of the ROM auto-detection are used a second time, to pick the correct surround to show around the thumbnail image of the frozen program: If it is a C65 ROM, you see a C65 and 1084S monitor depicted, otherwise a C64 and 1702:


These surrounding images can be drawn as a 152x96 pixel PNG file, and converted to the correct format using the thumbnail-surround-formatter from thte MEGA65 Freeze Menu repository, so if you want to make it look different, perhaps like Gus the snail, you are totally free to do so! The only limitation is that the screen position for the thumbnail is fixed, and the colour palette is a 256 colour colour cube, with the first 16 entries replaced by the C64's standard palette. I might do something to improve the palette at some point, because it is a bit annoying.  It might be possible, for example, to have a separate palette for the surrounding image versus the thumbnail by selecting the alternate palette bit in the thumbnail graphics tiles. But that will have to wait for another day.

The thumbnails are 80x50 images generated automatically by the MEGA65 hardware, exactly for the freeze menu.  I wrote about the thumbnail generator hardware a long time ago, and it has finally taken until now before it got to be used for its intended purpose.  The only real change to the thumbnail generator was to move it to $D640 instead of $D630 to avoid a recurring address resolution problem in the VHDL, which I think was due to glitching of the chip select line.

While on the one hand just a bit of a fun cosmetic touch, the thumbnails serve the very useful purpose of making it easier for you to find previously frozen programs.  From in the freeze menu you can use the cursor keys to navigate through the set of freeze slots configured in the MEGA65 system partition.  The MEGA65 FDISK+FORMAT utility will normally allocate upto 2GB for the systesm partition, with about half of that being freeze slots, giving a total of just over 2,000 freeze slots under the current design. 

That should be more than enough to have games and programs you like to use pre-frozen ready for an instant meal whenever you want! It also means if you need to interrupt a favourite game at a critical point, it should be easy enough to find a free slot and save it for reheating later.  In fact, with ~2,000 slots, searching through them linearly will be sure to become a chore, so I will add some kind of search facility in the future.  This is a great thing about writing the freeze menu as a separate program in C, is that it is pretty easy to work on and extend.

Going further through the freeze menu, we find the bank of six quite configuration settings: CPU mode, ROM, CPU Frequency (speed), write-protection of the ROM area, cartridge enable/disable and PAL/NTSC select.  With each off these, the setting can be changed by pressing the indicated letter on the screen.  The only exception is R for ROM selection, which is not yet implemented.

Below that we have a set of typical Freeze utilities: A monitor to inspect and modify the memory of a frozen program, a mechanism to enter poke codes, e.g., for game cheats, a disk image chooser to let you switch disks while running a program (or to get ready to run one), a sprite viewer, poke finder and sprite collision killer (also for cheating in gamems).  At the moment only the monitor and disk select options are implemented. 

The monitor uses 80-column mode, and works like your typical machine code monitor on the C64.  At the moment only M to inspect memory and S to set contents of memory are supported. The syntax of these mirrors that of the MEGA65's hardware monitor, except that with the S command you can use " and ' to indicate either ASCII strings or screen poke codes.  So for example S400 'HELLO would put the word HELLO at the top left of the screen in C64 mode, by writing $08 $05 $0C $0C $0F to locations $0400 - $0404.

The disk selector scans the SD card for D81 files, and then displays a list of up to 1,000 of them, and lets you choose one to use.  Again, at some point I will add some kind of sorting and/or searching function to make browsing through long lists of disk images easier.  But what I have already implemented, is that if you highlight a disk image, and then don't press any keys for a second or more, it will retrieve the directory from the disk image.  In this way you can more easily find the disk you are looking for.  Here is an example of this in action:

This menu will also refuse to let you select a disk image if it can't be mounted for some reason. The most common cause at the moment is if the .D81 file is fragmented on the SD card.  The reason this isn't allowed is that the hardware support for D81 image access requires the D81 file to exist as a single linear 800KB block, so as to avoid the hardware needing to know about FAT file systems.  What I will likely do at some point is add support for automatically de-fragmenting image files, so that the user doesn't need to think about it.

Talking about disk images, whenever you load a frozen program, it goes through the disk image attachment process again, so if you have changed the disk image, e.g., by creating a new file in a different session, then they will show up.  There are some hazards with this, for example, if the frozen program isn't clever enough to notice the disk-change line, but this will hopefully not be a big problem. Also, of course you could have deleted the disk image.  In that case, when you resume the frozen program there will be no disk image attached.

That's the bulk of the function of the freeze menu for now.  I have skipped over a whole pile of little niggly problems I have had to solve along the way, and there are still a number of bugs and missing features to be solved, and the freeze/unfreeze process still sometimes messes up the frozen program in some way causing it to crash, but it is already functional enough to be very useful.  In short, you can now easily use the MEGA65, including switching disks, CPU speed and other things, without having to do anything awkward.  The following video shows me using the freeze menu to do various things.  I particularly like how cute the little thumbnails of the frozen programs look :)


Thursday, January 3, 2019

More work on the MEGA65 built-in freezer

Yesterday I posted the progress on the built-in freezer for the MEGA65, and explained a bit how it works.  However, at that point in time, the freezer was not really functional -- it could save and restore some memory and IO registers, but not without problems, and thus it wasn't possible to actually resume a program after freezing.  That has changed today!  After quite a bit of fiddling, the freeze and unfreeze routines are now much better, and generally work.

The main progress is that I am able to save the main memory, the colour RAM, the VIC-IV registers (including the colour palettes), the MEGA65 Hypervisor saved state (which is really the saved state of the program being frozen, since it was saved on entry to the Hypervisor, which is what is actually doing the freezing), along with most of the new MEGA65 registers, e.g., those at $D7xx.

The result is that the program gets fairly convincingly frozen. But this is no good, if the program can't be unfrozen after.  But this also works just fine now, as the following video of me playing Krakout and freezing and resuming it multiple times shows.  (Apologies for the shaky video, I don't have my good camera and tripod here at home.  Similarly the general lack of audio due to the Zoom recorder also not being here.)



What is clear is that we can freeze and unfreeze a real game, and it resumes without any noticeable problems.  Even multiple times, is not a problem. It also works fine to freeze BASIC, as the following freezing, frozen and un-frozen images show:



Just to prove that it was still alive after, I typed some rubbish:

(Note the fun feature of the later C65 ROMs of showing error messages in red, regardless of what the cursor colour was before).


While I would like the freeze and unfreeze time to be a little faster, it is already quite acceptable.  Once we have the 8MB expansion RAM in the MEGA65 working, we will be able to freeze to expansion RAM instead of the SD card in the first instance, which should make freezing and unfreezing several times faster.

In fact, the main limitations at the moment are relatively few:

1. Like most C64 freezers, we can't really freeze the state of the SIDs, because of all those SID registers being write-only, and even if they were readable, they would only show what you wrote, not the current ADSR state of the voices etc.  I'll likely add some support for saving and resuming the internal state of the SIDs, so that freezing doesn't mess up music.

2. The CIAs are not currently backed up.  This is really just a little oversight, and should be quite trivial to fix.

3. The Hypervisor doesn't sanity-check the state of any previously mounted disk image(s), and re-mount them if still available.  Similarly, it doesn't check any other bits and pieces in the process descriptor block after loading it back in.

4. I noticed that by blindly restoring the VIC-IV registers that it is bad if the freeze occurred at a high raster line, because it is possible for the raster compare register to be programmed to an impossibly high raster number.  This would cause the program to effectively not resume after unfreezing, unless you manually modified $D011 to clear the high-bit of the raster compare register. Thus I should probably and $D011 with $7F after restoring the machine state.


Wednesday, January 2, 2019

Working on the MEGA65 Freeze Menu

For a long time, the planned primary interface for controlling the MEGA65 has been planned to be a kind of "freeze menu".  While this will be easy for folks to change, our rationale for this is that it allows the machine to boot the BASIC as expected, but still have all the features you want to commonly use, e.g., mounting disk images, loading programs from a menu etc, a single button press away.

A while back, I mentioned that we were planning on having a double-tap of RESTORE trigger this.  This has evolved a bit into a long-press of RESTORE (anywhere from ~0.5 seconds to 5 seconds.  Longer than that will reset the machine in stead, which we might remove to avoid accidents, especially since the M65 will come with a reset button).

Quite a lot of work has gone on in the background to actually get to the point of having a freeze menu appear and be useful.  While it isn't quite there yet, it is now getting much closer.  A lot of that work has been on getting functional(ish) freeze and unfreeze routines working, as well as the hypervisor hooks to actually trigger the freeze and load the freeze menu itself.

So let's walk through how this all pulls together, beginning with pressing the RESTORE key, and detecting if it is a normal press of the RESTORE key, a long-press that should trigger the Hypervisor trap that launches the freeze process, or whether it should reset the CPU.  This is all in src/vhdl/keymapper.vhdl

  -- 0= restore down (pressed), 1 = restore up (not-pressed)
        if restore_state='0' and last_restore_state='1' then
          -- Restore has just been pressed, do nothing special.
          -- (Events happen on rising edge)
        elsif restore_state='1' and last_restore_state='0' then
          -- Restore has just been released
          if restore_down_ticks < 8 then
            -- <0.25 seconds = quick tap = trigger NMI
            restore_out <= '0';
          elsif restore_down_ticks < 32 then
            -- 0.25 - ~ 1 second hold = trigger hypervisor trap
            hyper_trap <= '0';

            hyper_trap_count <= hyper_trap_count_internal + 1;
            hyper_trap_count_internal <= hyper_trap_count_internal + 1;
          elsif restore_down_ticks < 128 then
            -- Long hold = do RESET instead of NMI
            -- But holding it down for >4 seconds does nothing,
            -- incase someone holds it by mistake, and wants to abort doing a reset.
            reset_drive <= '0';
            report "asserting reset via RESTORE key";
          end if;
        else
          hyper_trap <= '1';
          restore_out <= '1';
          reset_drive <= '1';
        end if;


When hyper_trap goes to zero, then this tells the CPU to trigger the freezer Hypervisor trap.  This really just means that the CPU enters Hypervisor mode after saving register state, and then jumps to a certain location in the Hypervisor programme.  To make writing the freeze menu easy, after saving the state of the machine to freeze slot #0, the hypervisor loads in the standard C64 character set and a C65 ROM, and assumes that the freeze menu is a program made for C64 mode with entry point at SYS 2061.  This means we can write the freeze menu using CC65, the C compiler for the C64, for example.  In the following snippet from kickstart_task.a65 we can see that the Hypervisor already implements a bunch of very handy routines, that make it easy to load the ROM files, and then the freeze menu itself.  Loading the freeze menu is performed by setting the name of the file we want to load from the SD card ("FREEZER.M65"), and then providing the 32-bit load address. We load it to $07FF instead of $0800 or $0801 as you might have otherwise expected, because we expect the program to have a normal C64-style $01 $08 header on it, and thus we need to pretend it loads at $07FF so that the first real byte of data is placed at $0801.  Otherwise, there is nothing too surprising here. We set the C64 memory map to make life easier for the program, and we also provide a dummy NMI vector, as we have seen race conditions where an NMI can be triggered before a proper NMI vector has been installed. Since we don't enter via the C64/C65 ROM's normal entry point, the NMI vector at $0316 won't get setup automatically, thus requiring this precaution.  Finally we set the value of the PC on exit from the Hypervisor, and actually exit the Hypervisor itself:

restore_press_trap:

    ; Freeze to slot 0
    ldx #<$0000
    ldy #<$0000
    jsr freeze_to_slot

    ; Load freeze program
    jsr attempt_loadcharrom
    jsr attempt_loadc65rom

    ldx #<txt_FREEZER
    ldy #>txt_FREEZER
    jsr dos_setname

    ; Prepare 32-bit pointer for loading freezer program ($000007FF)
    ; (i.e. $0801 - 2 byte header, so we can use a normal PRG file)
    ;
    lda #$00
    sta <dos_file_loadaddress+2
    sta <dos_file_loadaddress+3
    lda #$07
    sta <dos_file_loadaddress+1
    lda #$ff
    sta <dos_file_loadaddress+0

    jsr dos_readfileintomemory
    jsr task_set_c64_memorymap
    jsr task_dummy_nmi_vector
   
    ; set entry point and memory config
    lda #<2061
    sta hypervisor_pcl
    lda #>2061
    sta hypervisor_pch

    ; return from hypervisor, causing freeze menu to start
    ;
    sta hypervisor_enterexit_trigger


The actual freezing happens in the Hypevisor in the freeze_to_slot routine, rather than in the freeze menu. Similarly, unfreezing happens in the Hypervisor as well.  This actually solves a lot of problems all at the same time. First, the freeze menu doesn't need to know about changing on-SD formats for the freeze slots.  Second, it makes sure that there is a single freeze and a single unfreeze routine used in all situations. Third, it allows use of the extra memory of the Hypervisor, to allow for near-perfect freezing, without corrupting the stack or any other memory.  It also means that we can provide a nice simple abstracted interface to allow one program to get itself replaced by another in memory, similar to exec() on UNIX-like systems.

The freeze and unfreeze routines are naturally very similar. They basically consist of a loop that iterates through a range of memory areas that have to be loaded or saved, with an optional pre-save or post-load hook.  This allows us to define pseudo regions that save some tricky bits of machine state that we can't just DMA to the SD card.  It also makes it quite easy to modify what gets saved.  Here is the definition of the list of regions to be saved as they currently stand.  We know there are some missing bits, and we have removed some bits to make this easier to read.

freeze_mem_list:
    ; start address (4 bytes), length (3 bytes),
    ; preparatory action required before reading/writing (1 byte)
    ; Each segment will live in its own sector (or sectors if
    ; >512 bytes) when frozen. So we should avoid excessive
    ; numbers of blocks.

    ; SDcard sector buffer + SD card registers
    ; We have to save this before anything much else, because
    ; we need it for freezing.
    .dword $ffd6000
    .word $0290
    .byte 0
    .byte freeze_prep_stash_sd_buffer_and_regs

    ; 384KB RAM (includes the 128KB "ROM" area)
    .dword $0000000
    .word $0000     
    .byte 6          ; =6x64K blocks = 384KB
    .byte freeze_prep_none   

    ; 32KB colour RAM
    .dword $ff80000
    .word $8000
    .byte $00
    .byte freeze_prep_none

    ; VIC-IV palette block 0
    .dword $ffd3100
    .word $0400
    .byte 0
    .byte freeze_prep_palette0

    ; VIC-IV palette block 1
    .dword $ffd3100
    .word $0400
    .byte 0
    .byte freeze_prep_palette1

    ; VIC-IV palette block 2
    .dword $ffd3100
    .word $0400
    .byte 0
    .byte freeze_prep_palette2

    ; VIC-IV palette block 3
    .dword $ffd3100
    .word $0400
    .byte 0
    .byte freeze_prep_palette3   
 

    ; Process scratch space
    .dword currenttask_block
    .word $0100
    .byte 0
    .byte freeze_prep_none
    
    ; $D640-$D67E hypervisor state registers
    ; XXX - These can't be read by DMA, so we need to have a
    ; prep routine that copies them out first?
    .dword $ffd3640
    .word $003F
    .byte 0
    .byte freeze_prep_none

    ; VIC-IV, F011 $D000-$D0FF
    .dword $ffd3000
    .word $0100
    .byte 0
    .byte freeze_prep_none

    ; $D700-$D7FF CPU registers

    .dword $ffd3700
    .word $0100
    .byte 0
    .byte freeze_prep_none

    ; XXX - Other IO chips!

    ; End of list
    .dword $FFFFFFFF
    .word $FFFF
    .byte $FF
    .byte $FF

There are four lots of the VIC-IV palette, because the MEGA65 has four palette banks that can be dynamically selected, but are mapped to the same region of memory, therefore the freeze_prep_paletten routines make sure the correct one is mapped before the area is saved/loaded. These routines are typically quite simple, e.g.:

do_unfreeze_prep_palette_select:
    ; We do the same memory map setup during freeze and unfreeze
do_freeze_prep_palette_select:
    ; X = 6, 8, 10 or 12
    ; Use this to pick which of the four palette banks
    ; is visible at $D100-$D3FF
    txa
    clc
    sbc #freeze_prep_palette0
    asl
    asl
    asl
    asl
    asl
    ora #$3f  ; keep displaying the default palette
    sta $d070
    rts
 

 Now if we turn our attention to the freeze menu, this basically consists of a normal program that can do whatever we want.  The current version just displays a simple set of options (most of which aren't yet implemented), and selects one of them based on key input.  Key input is done using the MEGA65's super-easy ASCII keyboard input abstraction layer, where you can basically just read $D610 to get the next key from the keyboard, with all modifiers like SHIFT and CONTROL already applied.  Function keys map to $F1 - $FE, making life super simple for menus.  Here is the important bit of freezer.c:

  // Flush input buffer
  while (PEEK(0xD610U)) POKE(0xD610U,0);
 
  // Main keyboard input loop
  while(1) {
    //    POKE(0xD020U,PEEK(0xD020U)+1);
    if (PEEK(0xD610U)) {
      // Process char
      switch(PEEK(0xD610U)) {
      case 0xf1: // F1 = backup
    break;
      case 0xf3: // F3 = resume
    // Load memory from freeze slot $0000, i.e., the temporary save space
    // This implicitly restarts the frozen program
    __asm__("LDX #<$0000");
    __asm__("LDY #>$0000");
    __asm__("LDA #$12");
    __asm__("STA $D642");
    __asm__("NOP");
    break;
      case 0xf7: // F7 = show screen of frozen program
    // XXX for now just show we read the key
    POKE(0xD020U,PEEK(0xD020U)+1);
    break;
      }
     
      // Flush char from input buffer
      POKE(0xD610,0);
    }
  }


 The highlighted snippet of code makes a Hypervisor call asking for whatever currently lives in freeze slot 0 to be loaded back into memory.  This by definition will replace the freeze menu in memory, so there is nothing more to be done.  We have gone to quite some effort to make calling the Hypervisor really painless, which I think shows here:  All you have to do is prepare the register values for the call, where the accumulator usually indicates the sub-function of the Hypervisor call, and then write to the correct Hypervisor trap address between $D640-$D67F.  It doesn't matter what you write, or from which register, as the act of asking the CPU to write to these registers tells it you want to trap to the Hypervisor.  The Hypervisor automatically (in just one clock cycle!) saves all process or flags, registers and memory mapping settings, and switches to the Hypervisor memory context.  This makes Hypervisor calls very simple and efficient.  The only gotcha at the moment is the need to put a NOP or other single-byte junk instruction after the write that triggers the Hypervisor call.  This is to work around a bug where sometimes the PC value on exit from the Hypervisor call is incremented by one.

But enough theory already. We want pictures!

Here is the MEGA65 mid-freeze, with border colour action telling you something is happening:



After a couple of seconds, this is replaced with the freeze menu, which is currently rather spartan. You can probably tell I used to use an Action Replay as my preferred freeze cartridge ;) This program will get a thorough pimping as time goes on.


Finally, here is the view after resuming:


If you want to see it as moving pictures:


There are a few obvious things to point out here:

1. We can clearly trigger loading of the freeze menu program.
2. We can (at least partly) save and restore memory contents and IO registers, as shown by how we manage to restore the C65 BASIC boot screen on un-freeze, complete with switching back to 80 column mode, and restoring colour RAM (so that the bars are different colours etc.
3. The palette is seriously messed up.  It turns out I have a bug in the DMAgic implementation when reading the palette, where it gets it one byte late.  It might be that we need to have an extra wait-state on reading the palette memory.
4. The frozen program doesn't actually resume after being unfrozen.  I'll have to look at the saved registers etc, and see why they aren't getting restored correctly. Actually, it looks like the unfreeze process never quite completes, but is instead stuck loading a sector from the SD card. I'll have to investigate that.

Anyway, that's where things are upto right now.  It shouldn't hopefully be too much longer before we can correctly unfreeze with the right colours, and with a running program after.