Friday, 8 August 2014

De jevu: C65 startup banner and dead BASIC, again.

The title says it all: The 48MHz CPU is almost working, but there is clearly something fruity going on.  Sadly it isn't the same issue as when I got to this point last time, but I have some extra clues, like BASIC in C64 mode also behaves similarly. There are also some other nuisances, like IRQs don't seem to be getting masked properly.  But it is getting pleasingly close.

I am also interested to know whether people think the display is better with little side borders, or whether people would prefer no side borders in 80 column mode.  I personally think that having little ones adds to the authentic feel, but without sacrificing acres of screen.  Also, the real C65 has quite narrow side borders compared with the C64.  But I would like to hear what you think


Oh, and if you got this far, you might be able to spot the display glitch that I have to fix.  If you can, and can accurately explain the cause (I already know it), I'll greet you on the next screen shot I post on the blog.

Thursday, 7 August 2014

80 column now has side borders and other misc progress

I am conscious that I haven't had many fun screenshots to share lately as the CPU redesign drags on.

Fortunately, the CPU design is almost complete, and I am now shaking out the bugs that are still lurking so that I can get back to where I was up to before the redesign.

Today I made some changes so that I could simulate booting the C65 ROM.  This requires disabling Kickstart, since that would look for the SD card, and I don't have the means to simulate that (yet).  So instead I made a model for the slowram that holds the ROM after it has been loaded by Kickstart, and made a control flag to supress Kickstart when in simulation.

This means I can easily simulate the system booting the C65 ROM, and look for anything odd with nice cycle-by-cycle memory access information at my fingertips.  This has already allowed me to fix some DMAgic bugs in the redesigned CPU (DMAgic lives in the CPU in the C65GS).

Simulation is nice, because with tools I created and described in an earlier post, I can capture the VGA output digitally which is nice for screenshots here.  

However, simulation is REALLY slow -- taking between 20 minutes and an hour to simulate one frame of activity.  This isn't quite as bad as it sounds, because the C65GS can do a LOT in one frame. With the CPU at 48MHz and a 60Hz display, this means 800,000 CPU cycles per frame.

The slowness of simulation means that it is a good idea to avoid any unnecessary delays in the startup process of the ROM.  The main problem in this area is the PAL/NTSC detection routine, which has to wait until the end of the frame to work out if the machine is PAL or NTSC based on whether it has more than 263 raster lines.  

Fortunately, when you control the hardware, you can tweak things, and so on power up, the VIC-IV's VIC-II raster number register is purposely incorrectly set to raster 264 at the start of the frame. This means that the PAL/NTSC detection routine takes just a couple of dozen cycles to decide that it is on a PAL machine.  The register gets reset at the bottom of the frame, and then works as per normal in all successive frames, so the solution is a nice one.

The end result is that the C65 ROM does all its preliminary work in <100 physical rasters.  You get an idea for how fast this is in the following screen shot. The black part is the rest of the frame that would have been drawn, except that I stopped the simulation before it got that far.  

If you make it full size, you can see a couple of rasters of junk at the very top.  That is the time from when the CPU powers up until the ROM sets the VIC-II/VIC-III video registers for 80-column mode and sets the border colour. 

Then it is all border until the 80-column text starts, during which time various bits of memory are being setup by the Kernal and possibly internal drive DOS, and then uses the DMAgic to clear the screen.  The first line of text shows the wrong contents either because the badline happened at the very top of the display, and no badline happens because of the changing value of $D011 in the middle, or because the CPU does actually take a few dozen rasters to set everything up.  I haven't looked into which is actually the case, and it doesn't really matter.  What is clear is that by the second row of text the CPU has already cleared the screen.  Of course, it should also be showing the C65 start-up banner, which it isn't, and so I have some more bugs to hunt.

The other interesting feature of the screen shot is that it shows the reworked 80-column mode using the new horizontal hardware scaler that allows non-integer numbers of physical pixels per logical pixel.  By judicious selection of the scale factor, we now have some little side borders so that it feels much more 8-bit than when it occupied the full width of the display.


Monday, 4 August 2014

Ethernet receiving now has hardware CRC check, and IRQs are close

I managed to get a little time this evening to work on the remaining bugs in ethernet reception.

The main bug was caused by using a bad example ethernet frame that had reversed bit-order in every byte.  I confirmed this by capturing a real ethernet frame, and using that as the test vector in simulation.  CRC check failed, so I tried reversing the bit order, and viola, I had valid CRC detection.

Of course, I could have left CRC calculation to software, but it is a rather boring thing to implement, and modern network cards all do this, so it seemed like a good idea to do.  After all, if I can make it work once in the hardware, then all software can ignore it forever after.

This is one of the nice things about FPGA design, in that it is much more realistic to make the hardware nice to program, because the incremental cost of making an interface that bit nicer is quite low.

Another thing I have almost working for the ethernet is IRQ triggering on packet sending and receiving, so that you don't have to poll the ethernet adapter all the time to know if you there is a packet to receive, or if you can send another frame yet.

To give an idea of how simplified the interface to this ethernet adapter is, here are the register addresses that matter:

$DE800 - $DEFFF - 2KB received frame buffer.  First two bytes are the length of the frame.  If bit 15 is high, then the frame failed the CRC check.  Frames with a bad CRC don't trigger IRQs.
$DE041 -  bit 7 - Enable IRQ on frame RX
$DE041 -  bit 6 - Enable IRQ on completion of frame TX
$DE041 -  bit 5 - A frame has been received since $DE041 was last written.
$DE041 -  bit 4 - A frame has been sent since $DE041 was last written.
$DE041 -  bit 2 - Which RX buffer was last written to by the ethernet adapter.
$DE041 -  bit 1 - Which RX buffer is mapped at $DE800 - $DEFFF for reading.
$DE041 -  bit 0 - Set to 1 to force ethernet PHY into reset state.  Reset to 0 to allow normal operation.

As described above, there are two receive buffers, so that you don't have to worry too much about losing frames while reading them out, or similarly having the part of the frame overwritten in the buffer while you are reading it out.

To clear any IRQs write anything to $DE041, although this should normally be whatever you read from it, so an LDA / STA pair or similar is the best bet for now.  I might change the interface in time so that DEC can be used to do it a little quicker, but not just yet.

To send a frame, you write the bytes to $DE800 - $DEFFF, write the frame length to $DE043/$DE044, and then write $01 to $DE045.  Note that the TX buffer is mapped to the same address range as the RX buffer.   In other words, the TX buffer is write-only, while the RX buffers are read-only.  When you transmit a frame, the ethernet adapter automatically calculates and appends the ethernet CRC to the end of the frame.  This still has some bugs, so the CRC is incorrect, but you hopefully get an idea of how easy it is to send and receive ethernet frames with this ethernet adapter.  I might add automatic IP checksum calculation later on, too.

Thursday, 31 July 2014

Ethernet transmission

In the last post I described how receiving ethernet frames was working.

Transmitting ethernet frames looked like it should follow a fairly similar process, so I spent a little time working on this.

I have structured it so that full-duplex operation is possible, with separate state machines controlling transmission and reception.

The first step was to see if I could cause anything to come out of the ethernet port at all.  Here I hit a bit of a problem, because all modern ethernet cards automatically check the ethernet Frame Check Sequence (FCS), and discard frames that are bad.  This meant that I would need to implement the FCS, and form completely valid ethernet frames first-up.

I much prefer to be able to make incremental advances, knowing that I have addressed particular steps as I go along, so I really wanted something that would let me receive invalid ethernet frames.  Then it dawned on me that the solution was to connect two C65GS's together, since the ethernet receive side doesn't (yet) check the FCS or pretty much anything else about the frames when receiving them.


This also means that I can check things without having to worry about one end feeding crazy bonjour packets all the time.

This let me quickly confirm that in fact nothing was coming out of the ethernet port with my first attempt.

I took a guess that the transmit side might not start transmitting if you don't immediately start with the ethernet preamble code.  After fixing that, and with back-to-back Nexys4 boards running as C65GSs, I was able to cause simple frames to be sent from one to the other.  I just stuffed a sequence of bytes in to the transmit buffer at $FFDE800, and then set the frame length in $FFDE043 - $FFDE044, and then wrote $01 to $FFDE045, and voila, the frame was sent to the other side with a reassuring blink on the ethernet led:

.sffde043 ff 00 01

You can see the momentus frame as received at the other end here.  Sadly no "Watson, come here" or "One small step" here.

.Mffde800                                                       
 :FFDE800 FE 00 5C C2 76 86 0A 0B 0D 0D 0E 0F 07 08 09 01
 :FFDE810 02 03 04 05 07 07 08 09 02 02 03 04 05 06 00 00
 :FFDE820 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 :FFDE830 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 :FFDE840 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 :FFDE850 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 :FFDE860 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 :FFDE870 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 :FFDE880 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 :FFDE890 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 :FFDE8A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 :FFDE8B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 :FFDE8C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 :FFDE8D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 :FFDE8E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 :FFDE8F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 :FFDE900 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

The first two bytes are the received frame length (I can see that there is an out-by-one error here somewhere, my guess is on the transmit side), then the next four are the FCS as I calculate it as the packet is received, then the packet data starts after, from 0A onwards.  I think I am calculating the FCS incorrectly, as it never matches that of any real received frame sent by an actual computer, so I will need to look into that.

But anyway, this is good progress.

Wednesday, 30 July 2014

Starting work on the ethernet adapter

As I mentioned in an earlier post we have a student working on the ethernet controller for the C65GS using the on-board 10/100mbit ethernet adapter on the Nexys4 board.

We spent a bit of time yesterday understanding how it works, and it is very pleasing that less than 24 hours later I was able to receive this ethernet frame with the C65GS connected by ethernet to my Mac:

 :FFDE800 FF FF FF FF FF FF C8 2A 14 08 DA E2 08 00 45 00 ................
 :FFDE810 01 4E 9C 70 00 00 FF 11 FC DC A9 FE CD 54 A9 FE ................
 :FFDE820 FF FF EB 5D 13 8A 01 3A 33 D6 44 52 49 4E 45 54 ..........DRINET
 :FFDE830 54 4D A9 FE CD 54 C0 0E 00 00 00 3E 39 63 30 31 TM..............
 :FFDE840 61 38 63 30 2D 31 36 37 36 30 39 38 38 39 31 00 ................
 :FFDE850 00 00 00 00 00 00 00 00 00 00 00 00 69 71 6E 2E ............iqn.
 :FFDE860 31 39 39 35 2D 31 32 2E 63 6F 6D 2E 61 74 74 6F 1995.12.com.atto
 :FFDE870 74 65 63 68 3A 78 74 65 6E 64 73 61 6E 3A 73 65 tech:xtendsan:se
 :FFDE880 72 2E 63 30 32 66 38 72 6D 78 64 68 32 68 0A 00 r.c02f8rmxdh2h..
 :FFDE890 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 

Clearly this is not a very interesting ethernet frame for our purposes, but what is clear is that it is locking onto the frame preamble and receiving the bits and putting them together all correctly.

There is no ethernet checksum being performed yet, and it isn't possible to send frames, either.  These are things I will likely work on when I get a chance.

In the meantime the student, now joined by another student, who will work on writing some test software for the ethernet controller.

Once it is all working, then we will look at adding RR.net emulation registers so that existing software can use the ethernet interface.

Why hardware is hard

I have finally found and fixed the problem that was stopping most (but not all) CIA IRQs in the redesigned CPU.  The result was the cursor would blink very slowly.

I had bashed away for several days on the CIA, to try to figure out what was going wrong, with no luck.  I wasn't able to reproduce the problem in simulation, so I was pulling my hair out.

So I progressively added more and more instrumentation that revealed that the CIA was resetting the interrupt status register, and that it seemed to be doing so at the request of the CPU.  This should only happen if the CPU is reading from $DC0D.

It was about then that the realisation dawned on me that because the memory controller on my CPU has separate channels for RAM, IO and other types of memory, it is possible for the IO bus to still be presenting the instruction to read from the last accessed IO address.  Indeed $DC0D is the last IO address touched in the C64's IRQ routine, and thus the problem.

This also explains why the problem would go away if I accessed any IO-mapped memory, even from the serial monitor.  In retrospect, I should probably have reflected on this a little more deeper, especially the fact that the CIA design hadn't changed, but my CPU design HAD changed since it was last all working.

Anyway, this was a reminder of why hardware design is hard: it does exactly what you ask it, and keeps on doing it until you ask it to stop.  Getting that right all of the time takes a lot of careful attention and testing.

Sunday, 27 July 2014

First speed test of 48MHz CPU

I am continuing to fight with getting the reimplemented CPU and VIC-IV all settled down, however things are getting much closer.

As the following image implies, it can run the C64 ROM (not yet C65 ROM -- it gets stuck in the DOS somewhere).  It should be noted that the CPU performance here is not final, and some instructions might end up faster or slower than depicted here.  That said, the CPU is certainly quite a bit faster than the old 32MHz one.


44.36x is almost exactly 48mhz/32mhz = 150% the speed of the old CPU at 28.93x.  Pleasingly this is before I do anything to optimise the performance.  Also, whereas the old CPU filled the FPGA to capacity, with the new CPU about two-thirds of the FPGA remains free -- space for implementing sprites, a 1541 and other goodies as I get the chance.

Speaking of optimisations, one that I may attack in the not too distant future is a stack cache that allows RTS to execute in just 1 cycle.  While it will make some impact on the SynthMark64 score, it is more interesting for real-life work loads where JSR/RTS are very common instructions.  But otherwise there is no caching anywhere in this -- it is all raw, predictable cycle times, which helps make it feel like a simple 8-bit computer, albeit a very fast one.

What isn't entirely obvious here is that keyboard input has broken for some reason, with my PS/2 keyboard reader failing to detect key-release events.  Also, for some reason the CIA interrupts are not always happening as often as they should.  Combined with not being able to use the C65 ROM, this means I had to side-load SynthMark64 via the serial monitor, start it directly from the serial monitor, and then use the serial-monitor to stuff the ENTER key-press into the keyboard buffer.  So there is still a bit to go, but at least it feels like I am getting somewhere.