Tuesday 30 January 2024

MEGA65 R5 Cartridge Port VHDL Fixes

With the new R5 board, we spotted a problem where some cartridges aren't detected when inserted. This doesn't happen if the C64 core is launched, so we know it's not a hardware issue, but rather a VHDL issue, i.e., a bug in our FPGA "program" that is the MEGA65.  So time to quickly get to the bottom of it and fix it.

One of the cartridges that used to work fine, but is no longer detected is Sam's Journey, which I have one of here.  I have an R5 board here, so that's the key ingredients.

To add to the recipe, I have ordered and assembled a couple of cartridge port break-out boards that will let me probe all signals on the cartridge port much more easily than in the past.  I'm hoping that with the R5's new bi-directional controllable expansion port pins, that I am just driving one of those incorrectly, and that with this break-out board, the solution of the problem will be swift and decisive. Let's see if my expectations play out in reality...

The PCBs were only US$5 from PCBway for 10 (I don't have any special deal with them, they are just who I use to build PCBs), and as I was already getting some other PCBs manufactured, didn't even cost me any extra postage, which was nice.

As you can see above, I have assembled two of them, one with a pass-through cartridge port connector to plug Sam's Journey into, and one without. The one without is in case I decide to make a special bitstream for this debugging, that would let me read all the cartridge port lines at high-speed using a 2nd MEGA65 board.  I'm hopeful that I won't need that right now, though. But it might still be handy for debugging some other cartridges that we never got working with the MEGA65 core, but work with the C64 core, as we will be able to see what has gone wrong.

So let's hook it all up:

And sure enough, it doesn't detect, and my MEGA65 boots straight into MEGA65 mode BASIC.

Let's start by probing the /GAME and /EXROM lines. Well, it would help if I connected the jumpers on the cartridge port break-out board to enable them first...

Okay, with that connected, I now get a black screen. So the CBM80 signature of the cartridge must be being detected.  But presumably either the IO1 or IO2 area is not working properly, causing the cartridge to fail to change ROM banks. Or at least that's my theory.

I can't see IO1 or IO2 go low when accessing $DExx or $DFxx.  I've just checked on the FPGA pin assignments for those against the schematic, and they match.  They do go through a level-converter buffer that has an /OE line. Is that correctly handled? That's controlled by F_CTRL_EN on FPGA pin G18. And that's a change from the R3A board, in that G18 now has a pull-up on it, which was not there on the R3A. Thus it has to be actively driven low by the FPGA.

Let's synthesise that, and see if it fixes the problem.  I'm pretty hopeful that it will.

In the process, I also noticed another improvement we could make for any future R7 board: R/W line is currently not open-collector, and it would be easy to make it so.

And, with that fix, Sam's Journey starts again :)


So that probably fixes the C64 carts not being detected.  This will of course also apply to the electrically identical R6 boards that are being produced by Trenz Electronic right now to clear the entire back-order queue.

In short, as hoped, by having the right tools (the cartridge port break-out boards) this was a much quicker and simpler job than it would have otherwise been.

I'll leave it there, for an uncharacteristically short blog post.

Monday 22 January 2024

Switching from CC65 to LLVM-MOS for the MEGA65 browser

Up until now, I have been using CC65 for writing the MEGA65 browser. But while CC65 has been a thing of tremendous value for the 6502 community, as one of the only freely available and robust C compilers that can target the 6502, it produces code that is quite large, and quite slow. This was becoming critical for GRAZE, the browser-like thing I am writing for the MEGA65.  I've already turned to using overlays, but even so, the TCP/IP stack alone is about 35KB when compiled using CC65, which doesn't leave a great deal of free space in the first 64KB for the rest of the code, and CC65 doesn't natively support splitting code over multiple segments, so far as I can tell.

Recently, however, a competent port of the LLVM compiler system to target the 6502 has been created. This has really changed the lay of the land for three key reasons in my view:

1. It supports almost all C, C++ and Rust syntax and features in a totally "normal" way, e.g., int is 32 bits and so on;

2. It almost always produces much better and smaller code than CC65 did, which means I can fit more into a given program;

and perhaps almost as importantly as (2):

3. It brings LLVM's fantastic error and warning messages and static analysis to the table. This has already let me trivially fix four sneaky bugs in GRAZE, and that was just during initial porting!

So let's talk about the initial porting process.

I started by modifying the Makefile of GRAZE to support either CC65 or LLVM-mos:

#COMPILER=cc65
#CC65=  cc65
#LD65=  ld65 -t none
#CL65=  cl65 --config src/tests/vicii.cfg
#MAPFILE=       --mapfile $*.map
#HELPERS=       src/helper-cc65.s

COMPILER=llvm
CC65=   llvm-mos/bin/mos-c64-clang -mcpu=mos45gs02
LD65=   llvm-mos/bin/ld.lld
CL65=   llvm-mos/bin/mos-c64-clang -DLLVM -mcpu=mos45gs02
MAPFILE=
HELPERS=        src/helper-llvm.c

As the MEGA65 libc has also advanced considerably since I last worked on GRAZE, I also reworked how I pull that in:

M65LIBC_INC=-I $(SRCDIR)/mega65-libc/include
M65LIBC_SRCS=$(wildcard $(SRCDIR)/mega65-libc/src/*.c) $(wildcard $(SRCDIR)/mega65-libc/src/$(COMPILER)/*.c) $(wildcard $(SRCDIR)/mega65-libc/src/$(COMPILER)/*.s)
CL65+=-I include $(M65LIBC_INC)

This let me refactor the Makefile rules to be much simpler and shorter:


fetchh65.prg:       $(TCPSRCS) src/fetchh65.c $(HELPERS) include/shared_state.h
        git submodule init
        git submodule update
        $(CL65) -O -o $@ $(MAPFILE) $(TCPSRCS) src/fetchh65.c  $(M65LIBC_SRCS) $(HELPERS)


Apart from that, it's just been a process of fixing a few places where CC65 would automatically promote types (or perhaps wasn't, and it was generating silent bugs), and make sure that the main() function in each program returns int instead of void, and a few other "normalisations" of the code. This was not particularly hard, but did take a little while to go through all the warnings and errors that LLVM was (quite correctly) throwing, and figure out how to fix them in a way that CC65 would still be able to handle.

In the process, I had to fork off the helper assembly routines that are used to make some hypervisor calls, all used to load the overlays.  These are a bit more involved to port, because I don't yet know how handle C calling convention argument passing into assembly routines.  Probably what I will do is make them C functions with inline assembly blocks for the bits that can't be done in C.  

This leads into the related area of all the POKEs and PEEKs that the GRAZE code does to IO registers.  Because LLVM is so smart about optimising, it is liable to optimise these (and any in-line assembly) away, unless it understands that it's vital.  For POKE and PEEK, I can probably deal with this by making them volatile. POKE and PEEK have already been fixed upstream in mega65-libc, which is good.  

Actually, lcopy() and lfill() might also need some special treatment, as they modify memory contents in ways that LLVM doesn't know is happening.  I'm not yet sure what will be needed, if anything.

For the helper routines, it's a bit more complex. There is some helpful information on the LLVM-mos website about this, which in turn does document the C calling convention in LLVM-mos. So it looks like the arguments are passed using imaginary registers in zero-page, which I assume I can just refer to by name. Specifically rc2 and rc3 should hold the pointer to the first argument, if it is a pointer to char.

A bit of a set-back: I thought I had pulled the latest changes to the development branch, but hadn't, so I had to reapply them all, which took a little while. In the process, I can see that the head of development now properly compiles the mega65 libc as a library, rather than just compiling the source files.  It turns out that CC65 produces smaller binaries this way, because it won't exclude functions that are compiled directly from source files, but it only includes functions that are used when they come from a library.  With that change, CC65 now typically produces smaller binaries -- although I am suspecting that it might be a bit of a mirage. I think the LLVM might include global symbols in the binary, while CC65 does not.

Yes, this is indeed what happens: If I declare a global array in a program, when I compile it with LLVM, the binary gets bigger.  All in all, this means that LLVM is probably still producing code that is smaller than CC65's, but it's a bit harder to actually measure the difference. And LLVM still has way better warnings and errors and things, that make development easier, and bugs less likely to go uncaught. So I'm still planning to continue with LLVM.

Now, onto implementing the helper functions.  Having found the calling convention documentation for LLVM-mos, I've swung back to deciding to keep the helpers purely in assembly.  With a little work, I have them compiling -- the question is whether they work correctly, and whether I have interpreted the calling convention correctly.  I can check that by modifying the helper routines to infinite-loop on entry, so that I can check that the arguments are being passed in how I expect.

So let's start by doing that to the read_file_from_sdcard(char *filename, uint32_t load_address) function. This one has the most complicated call signature, and one that does not have a direct equivalent in the LLVM-mos calling convention documentation.  I believe that it will put the pointer to filename in rc2 and rc3, and the 32-bit value in A, X, rc4 and rc5. So let's see if that's the case.  Yes, A and X have the bottom 16-bits. And that is what I am seeing. After fixing a couple bugs, I am now able to use read_file_from_sdcard() to load the font file.  This routine is also the core of the mega65_dos_exechelper() function, which is the next one to test.

That might be working, but I've been waylaid by a problem with DHCP, which is probably due to changing our home internet router between the last time I worked on this and now.  The new router is a fancy Telstra 5G one, and I've seen it be a bit picky in the past, so I'm guessing that's what is going on now, as well.

Actually, it might be an indirect result of that, as the way my MEGA65 connects to the WiFi is, shall we say, a little odd: It goes through a dumb ethernet switch (so far, so sensible), and then to a 40cm Ubiquiti PowerBeam to make the jump from the office to the lounge room, where the router is.  That's a total distance of about 8 metres, somewhat less than the approximately 80,000 metres that the PowerBeam is rated to ;) As a result, the signal strength is about -23dB, instead of the more typical -65dB to -90dB you would see on a wireless link. On the up-side, I should never see a slow link due to WiFi signal strength, since I have 18dB or better of gain.  

What I think is going wrong, is that the Ubiquiti dish is probably locked to the MAC address of our old WiFi router. So I will need to reconfigure it.  I've logged in, and pointed it at the access point, and it seems to connect briefly, before disconnecting. I'm not yet sure which end is disconnecting the other.  This is apparently a known issue with these units, and the solutions seem to largely consist of a lot of rebooting and reconfiguring the AP and PowerBeam until it magically starts working again.  I'm going to start by updating the firmware on the PowerBeam, which will also reboot it, and see if that doesn't solve it. Nope. Next stop, 15 seconds without power.

Well, that's 4 hours of my life I'll never get back.  Basically the PowerBeam won't talk to the Telstra router, so far as I can tell. After considering and trying a whole pile of alternative approaches, I have connected the MEGA65 to the ethernet port of my build box, and set that up to share internet to the ethernet port from it's WiFi connection.  That all works, and ethtest.prg is now able to get an IP address by DHCP, so it looks like that's working.

However the mega65_dos_loadhelper() routine doesn't seem to be working properly.  So on to debugging that. I think the problem here is that LLVM-mos doesn't use a stable SYS entry point for programs it compiles. The range I have seen is like this:

bbs-client.prg:      CBM BASIC, SYS 2065
ethtest.prg:         CBM BASIC, SYS 2068
graze.prg:           CBM BASIC, SYS 2070
grazeerr.prg:        CBM BASIC, SYS 2072
grazeh65.prg:        CBM BASIC, SYS 2072
grazem.prg:          CBM BASIC, SYS 2072
haustierbegriff.prg: CBM BASIC, SYS 2065

So between 2065 and 2072, a range of 8 bytes, inclusive.  I'm guessing LLVM-mos is being clever and making use of the bytes of the BASIC header to match initial values of global variables or some other skullduggery.  

The frustrating part is that I can't for the life of me find where LLVM-mos generates the BASIC header. I can see where the configuration for C64 binaries is when generating a binary. This would suggest that there is a REGION_INIT section that gets built containing the SYS etc. However, that file is in a directory of tests, rather than wherever LLVM-mos generates it.  Ah, found it! It's in the llvm-mos-sdk repository instead. This suggests that the symbol _start gets set with the start address.

Anyway, I could chase my way further down this rabbit hole, or just pragmatically deal with it, by reading the last digit of the SYS address, and using that to determine the correct start address. Ideally I'd parse the entire address, but that's even more annoying, as then I will need a multiply by 10 routine for the decimal and do all the shuffling about required for that.  But, it's probably still the best way. This is the routine that does it in the end, which is a bit easier on the MEGA65 since the 65CE02 added nice instructions like ASW that rotate a 16-bit value in one instruction.

process_digit:
    
    lda $0800,x
    cmp #$39
    bcs got_digits
    cmp #$2f
    bcc got_digits

    ;; Multiply accumulated value by 10

    ;; multiply by 2
    asw $0101
    ;; stash in $0103-$0104
    lda $0101
    sta $0103
    lda $0102
    sta $0104
    ;; multiply by 4, to get x8
    asw $0101
    asw $0101

    ;; Now add the x2 value
    lda $0101
    clc
    adc $0103
    sta $0101
    lda $0102
    adc $0104
    sta $0102

    ;; Now add the digit
    lda $0800,x
    and #$0f
    clc
    adc $0101
    sta $0101
    lda $0102
    adc #0
    sta $0102

    inx
    bne process_digit


With that in place, I can now start helper programs, so that's the key functionality of the assembly language helper routines re-established.

The next step is to get it actually loading and displaying H65 "web" pages again. I have seen it go through the DHCP and TCP/IP connection process once or twice now, but it's quite unreliable.  What is frustrating, is that I don't know if it's the router or the compiler, or a bit of both.  I guess I can disambiguate those by compiling it again with CC65, and seeing how it goes with that.

Okay, with CC65 I get it making the TCP/IP connection 3 times in a row, while with LLVM-mos, it's a pretty rare event. Thus there is a compiler related issue. There is also something else going on, perhaps an incorrect fixing of one of the LLVM-compatibility problems I had to tackle to get it compiling with LLVM-mos, that is affecting the CC65 builds.

But first, let's get the LLVM-mos built binary going as far as the CC65-built one.  A quick bit of probing reveals that the LLVM one is getting stuck in some hypervisor routine, possibly waiting for the SD card to complete some job. Specifically, the call to load GRAZEH65.M65 from disk. The filename has been setup correctly, so it's not that. 

Hmmm... I wonder if LLVM is optimising away the follow-up hypervisor trap that closes all open files, since it would look like two writes to $D640 in quick succession? Nope, that's all intact. This is really weird: I'd expect the code to be all tied in knots outside of the hypervisor, but not inside it.

It might not be inside the hypervisor that the problem is happening, as it looks like the same sectors are being read again and again. It looks more like the loading of the file is happening over and over. That makes a bit more sense.

Yup! Found the stupid error where I was jumping back into the load file routine instead of actually starting the newly loaded file.  Now it reliably gets to the same place as with CC65, so that's a good bit of progress.

It looks like the TCP/IP connection doesn't actually get established: In fact, no packets flow back from the host that is being connected to.  Is this a problem with the internet sharing, or with the cantankerous Telstra 5G router, or something else? Looks like the internet sharing on the build box isn't forwarding the IP packets to the internet.  How annoying! Fortunately, rebooting the build box sorted it. Most likely the Linux kernel setting for packet forwarding wasn't previously enabled.  

So now it gets a bit further, as in packets come back. But now I am getting HTTP error 400s from the remote webserver.  Suspiciously, I am not seeing the hostname in the HTTP GET request. This might be the first instance of a bug in my corrections, or LLVM optimising something away. Again, let's try with CC65 built binaries and see if it's the same.

With CC65, I get a 404, and the HTTP request looks like this:

GET /showdown65.h65 HTTP/1.1
    Host: www.badgerpunch.com
    Accept: */*
    User-Agent: FETCH MEGA65-WeeIP/20220703

hmm... and now I can't get the LLVM-built one to load a helper program anymore.  Two steps forward, one step back. I wonder if when it was working before, I had a mix of CC65 and LLVM binaries? Anyway, it looks to be the loading of the helper programs that is failing, so I'll just go back to adding some debug facilities into that helper routine, and see what shows up... and magically it worked *sigh*.

One thing I did spot, though: LLVM-mos is using ASCII by default, while CC65 uses PETSCII. This meant that the case of the strings in the HTTP request were being inverted. Maybe that can cause the 400 errors? Nope.

But it does look like the request is being mangled in the LLVM-mos builds, with part of the request string being repeated. Tcpdump of the packet in question looks like this:

10:45:24.531798 IP (tos 0x8, ttl 63, id 5050, offset 0, flags [none], proto TCP (6), length 177)
    192.168.0.254.1088 > 77.111.240.141.80: Flags [P.], cksum 0x869b (correct), seq 1:114, ack 1, win 65535, options [eol], length 113: HTTP, length: 113
    GET /showdown65.h65 HTTP/1.1
    0x0000:  4508 00b1 13ba 0000 3f06 67e2 c0a8 00fe  E.......?.g.....
    0x0010:  4d6f f08d 0440 0050 3a77 306b 1909 98ed  Mo...@.P:w0k....
    0x0020:  b018 ffff 869b 0000 0000 0000 0000 0000  ................
    0x0030:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x0040:  4745 5420 2f73 686f 7764 6f77 6e36 352e  GET./showdown65.
    0x0050:  6836 3520 4854 5450 2f31 2e31 0a0d 486f  h65.HTTP/1.1..Ho
    0x0060:  7374 3a20 7777 772e 6261 6467 6572 7075  st:.www.badgerpu
    0x0070:  6e63 682e 636f 6d0a 0d41 6363 6570 743a  nch.com..Accept:
    0x0080:  202a 2f2a 0a0d 5573 6572 2d41 6765 6e74  .*/*..User-Agent
    0x0090:  3a20 4752 415a 4520 4d45 4741 3635 2d57  :.GRAZE.MEGA65-W
    0x00a0:  6565 4950 2f32 3032 3430 3132 310a 0d0a  eeIP/20240121...
    0x00b0:  0d     

Which doesn't have the mangling showing... so maybe it was a tcpdump funniness in display. Anyway, that request if we write out the ASCII lines for it looks like this:

GET /showdown65.h65 HTTP/1.1
Host: www.badgerpunch.com
Accept: */*
User-Agent: GRAZE MEGA65-WeeIP/20240121

Which looks pretty fine to me... Indeed, if I feed that in via telnet on Linux to the webserver, I get a 404, rather than a 400.

So what exactly is going on here? Are the carriage-return/line-feed bytes in the wrong order perhaps? Yes, that seems to have been the problem.

Okay, so now let's remove my helper loader debug code, and see if it still works. And it does.. Weird. Maybe that previous build was wonky in some way.

Anyway, unfortunately the H65 page that the folks at badgerpunch were hosting isn't there any more, so I'll setup a local web server on the lan, and point it at pages on there, to confirm that I can actually load pages.

Then I think I will focus on having ASCII vs PETSCII strings for all the messages that matter, and start re-factoring stuff out that is common in multiple of the helper programs, and moving the network initialisation code out, so that it happens only once on initial launch, so that loading subsequent pages becomes much faster.

Well, pages are now loading again, which is great :) There is something funny going on with proportional text. But I'm fairly sure that that problem is in the md2h65 generation of the page data, as plain-text pages look fine, and embedded images in pages are also loading fine, and without corruption.

It looks like I got part-way through adding support for NCM characters in md2h65, but that it still has problems. It looks specifically as though the number of pixels to trim is too great, resulting in bit 3 of the pixel trim being set when it shouldn't be. Found and fixed the bug. Now pages display properly again, like this:

While there are still a bunch of things to be fixed in the H65 page generation (like reducing vertical space between lines of proportional text, and moving the underline a pixel down, perhaps), it's not working more or less as it did under CC65. So now let's deal with some of the more general issues that we should tackle.

First, I want to improve page loading speed by moving the network initialisation code to the loader, which would do ethernet hardware initialisation and DHCP setup exactly once, so that successive pages can be loaded without big delays.

To do this, I have started refactoring out some of the common routines as well.  Quite a bit of fiddling things around, so that there isn't too much junk visible between page loads, and that the messages that appear during that time have inverted PETSCII/ASCII upper/lower case. It's still not perfect, but much less bad than it was.

Testing the browser I discovered that links that are behind text are now not working, while those behind images are still working.  I'll have to look at the cause of that.  But that will have to wait for the next blog post.

Monday 15 January 2024

Working on the Expansion Board: User-Port bring-up and other bits and pieces

In the last blog post, I got the tape port working, which was nice.  Now to get the user port working, and also deal with a number of other bits and pieces that need attacking.

First, I have added de-coupling capacitors to all the ICs in the schematic and PCB design, since they should be used, even though I have tested the board fine without them.

Anyway, let's get testing the user port data lines... and nothing shows up.  At least one problem was that the CIA data direction register is active high, while the expansion board uses active-low.  So I have fixed that, which requires a resynthesis.  With that, I am able to set the data lines. To test reading them I really need a user port connector so that I can rig up a switch or something. But in the absence of that I am probing the lines using a jumper wire, and I can see the input value change, but because of the lack of pull-ups it's a bit wonky, because once the lines are left floating they can settle on whatever they like. But it looks like they work.

The other lines that I need to test are:

RESET (output and input)

ATN (output)

PA2 (output and input)

CNT1 (output and input)

SP1 (output and input)

CNT2 (output and input)

SP2 (output and input)

PC2 (output)

FLAG2 (input)

Starting with the RESET line, this one is a bit tricky because it's bi-directional: We want the MEGA65 to pull it low when the system is reset, but we also want external devices to be able to trigger reset by pulling it low.  The trouble comes because the reset line isn't really connected to the reset line of the MEGA65, so we have to fake it, and know when we are faking pulling it low, so that we know not to read it.  This also has to handle the latency of the expansion board controller.  So the logic looks something like this:

      -- Export RESET generated by MEGA65 to the userport
      user_port_o.reset <= reset;
      -- But when doing so, ignore the RESET pin's value on the
      -- user port, as otherwise once reset were triggered, you
      -- wouldn't be able to release it.
      -- Because of the latency of the user port reading, we need
      -- to remember that /RESET has been asserted for a while.
      -- User port scans at a rate of ~150KHz, so at 40.5MHz this means we need
      -- to remember it for ~270 cycles. We'll do 1,000 to be sure
      if reset='1' then
        if cycles_since_reset = 1000 then
          reset_out_user_port <= user_port_i.reset_n;
        else
          cycles_since_reset <= cycles_since_reset + 1;
          reset_out_user_port <= '1';
        end if;
      else
        cycles_since_reset <= 0;
        reset_out_user_port <= '1';
      end if;
      reset_out <= reset_out_keyboard and reset_out_user_port;

That'll need a resynthesis to test, as will the ATN line, that needs to be connected to the IEC ATN signal from the CIA.  I could also connect it to the ATN line of the hardware IEC accelerator that is being added to the MEGA65, but it doesn't really make sense, since the rest of the IEC lines are not exposed on the user port, and it would be a bit of a pain in the bum to plumb it through. So instead, the user port ATN line will just follow the CIA line for it.

Okay, resynthesis is done, and the RESET pin works in both directions: If you hold the reset button on the MEGA65, /RESET on the user port goes to 0V, and if you temporarily ground /RESET on the user port, the MEGA65 resets -- and then when you release it, it continues to boot as it should -- so that fancy logic works... provided the expansion board is fitted.

The ATN line works, but has reversed sense, so I'll have to fix that: Done and confirmed fixed.

The PA2 line should be easier, because that's just bit 2 of $DD00 / $DD02, so should be quite easy to test. Except it seems that I have totally stuffed it up in the schematic: The data-direction bit for it is not even connected, nor is the output value.  It does work for input, though. So I need to fix the schematic, and then also fix the connection to drive the output in the expansion board ring controller.

The trouble is that I don't have 2 spare outputs, only 1. What I will do to work around that, is make PA2 open-collector instead of CMOS push-pull logic.  For 99% of use-cases that shouldn't matter.  I've updated the schematic and PCB design, as well as the VHDL. On my prototype I will just need to connect the following 2 pins:

U6 pin 3 to U9 pin 4

They look pretty easy to patch, so I'll do that while the VHDL synthesises.

Ok, in theory both are now done. I can now control PA2 from the CIA, so that's good. And I can still read it, so that's PA2 all done and dusted.

So RESET, ATN and PA2 are all good now.

PC2 is a pain: It's supposed to be a strobe whenever port B of the CIA is read or written.  The trick is, if we hold it only for 1 cycle, then our relatively slow expansion board bus will miss the strobe. Actually, looking at our CIA implementation, we currently strobe it only for 1 cycle at 40.5MHz, rather than 1 cycle at 1MHz. Nothing else in the MEGA65 uses it, so I'll just make the CIA make the strobe last longer than the 33 phases x 10 cycles that the expansion board bus takes.  So 330 should be enough, but we'll make it 511 to be on the safe side.  That'll need a synthesis run before I can test it -- which it does.

The remaining 5 lines: CNT1, CNT2, SP1, SP2 and /FLAG are a bit more of a pain to test, as they require more sophisticated programming of the CIA.  Also, for CNT and SP, these are the lines most likely to hit problems with the relatively slow update rate of the user port lines on the expansion port: The CIA can shift data at 250KHz or faster, but the ring bus on the expansion board is currently clocking only around 150KHz. So I'm just going to settle for a verification of the electrical connectivity of these lines, to back up the VHDL simulation based testing I did previously. 

The same is going to have to suffice for the C1565 connector, as I can't find the replica drive board I have here somewhere, and don't have time right now to reverse-engineer and make a new board.  Everything looks fine there.

In short, I have now tested all that I can test for the moment of the IO lines on expansion board, and am confident that the remaining lines work correctly at an electrical level, even if I can't confirm the VHDL to drive them is correct yet.

So now it's a bit of a home-run effort to square up a few miscellaneous things, in no particular order.

First, this revision of the board should have 3 channels on the analog video output. Again for now, I just want to be sure that they are on the correct pins, and are electrically working. Yes: Probing the appropriate pins of the video connector, I can see video signals. Again, for the moment I don't care if the video signals are correct, but rather whether the wiring is all correct, as this was wrong on the previous board revision. 

Next, I need to confirm that the floppy drive and cable can fit in with the board.  This was also a bit snug on the previous revision, so there is a bit more space on this revision to fit the cable in. That's what the funny cut-out below U6 and U7 is for:

Time to pull the floppy drive out of my R3 machine, and put it in this case, and see if it still all fits, which it does. The mess of temporary cables are the only thing preventing me closing the lid, so far as I can tell.  I'm going to order some female to female 12 pin IDC ribbon cables for the TE0790 extension lead. For the big mess down the front of the case, I'm going to design a little PCB that just has the headers on it for the PMODs, correctly spaced, so that it can just be connected, without any cable mess. Of course for that, I need to be confident of the exact spacing between the two boards. I guess I'll measure the current spacing and get a few made up that size, and if they don't fit, then I'll adjust them for the next set.  I'll order those together with the minimum order of the updated versions of the expansion board PCBs, so it won't be a separate lot of postage.

Otherwise, the last change to make on the board before I do send that off is to correct the position of the holes in the MEGA65 case.  This is a bit fiddly to get right. It's easy after you get a PCB to see that it's wrong, and roughly by how much. But getting any degree of precision is much harder, as is making sure you have made all the movements in the correct directions.  A good approach is to print a piece of paper with the hole positions, and then compare it to the case.  A challenge for me with this is that our printer likes to scale things without asking, so it's hard to get a true 1:1 printout.  After 5 test prints, I have converged on a scale factor of 1.0425:1 in KiCad's print dialog to get it pretty much exactly 1:1.  So now I can make changes, do a print, and then compare it to the case. What would be even better would be if I had access to a laser cutter, as I could cut a piece of paper to have holes in it where the screw holes should be to make it super easy to compare.  If necessary, I'll be able to use a power drill and 3mm bit to approximate this.

But first, let's get those holes in the right place, and also add the missing hole. I have pretty good measurements from the case now for their relative positions. One factor is getting the distance from the back of case to the first row of holes right, so that it positions well. I'm going to deem that hole H9 is in the correct location, and adjust everything from that.

I believe I have the holes right now, so time to attack a sheet of paper with a power drill, and see how well the holes actually line up.  By sandwiching the paper between a block of wood and a blank expansion board PCB, so that one of the 3mm holes in the PCB lined up with the black circle on the paper where a hole should be, I was able to quite cleanly and accurately drill out the holes in the print-out of the PCB, and then fit-test it:

Fit testing just consisted of making sure I could see into the screw boss holes in the case through the holes in the paper, like this:

Pleasingly all the holes were correct on the first attempt :)

I've also increased the thickness of the 5V tracks to 1mm everywhere, and branched the 3.3V and 5V rails as much as I could, to minimise voltage drop.  I am also tempted to see if I can possibly route the JTAG header to be closer to the MEGA65 board, so that a simple bridging PCB could be used instead of a cable for that, too. But I'm not sure if the PCB might not already be too congested.. and it turned out to be possible. This is how it looks now:


So in theory at least, the revised PCB is now ready for fabrication again. But as I mentioned, I want to also make a simple connector PCB that can join the PMODs on the MEGA65 to the connectors on the expansion board. 

But first, that PMOD bridging PCB. The PMOD connectors should be 77.5mm apart, and it just needs to connect all the pins 1:1, without any re-ordering. We can add a GND and 3.3V plane and make it a 4 layer PCB, so that we only need to route the data lines.  This is what the little board looks like:



The board is totally passive, as it just needs to connect the MEGA65 main board and the expansion board. It's only ~150x10mm, so it should also be quite cheap to make.  The notch on one of the lower corners is the corner that will be exposed in the trap-door slot near the SD card, so I removed the corner so you don't poke yourself on it when changing SD cards.

Well, I had hoped to get the PCBs ordered today, but its got late, and I've only just managed to finish the PCB designs, so that will have to wait until tomorrow, but I should just be able to export the gerber files and place the order with PCBway.

It's tomorrow, and among a bunch of errands I had to run, I decided to make a 3rd PCB that can be used in place of all the remaining cables to connect the expansion board to the MEGA65 main board, so that everything is neater and tidier:


The 3 connectors on the left connect to the appropriate headers on the MEGA65, and the other three to the expansion board.  It was quite tricky to get the connectors into exactly the right relative positions, as I don't have a super accurate layout of the MEGA65 board, and the relative positioning of the MEGA65 board to the expansion board also has a bit of slop in it. So I will just get some samples made, and then correct any position errors after. Hopefully there won't be any significant errors, as I did do the trick of printing the PCB out and laying it over to make sure the drill holes all line up -- as best I can without having actual connectors attached.

It was a bit of extra effort to design this up, and try to get the positions right, but I think it will be worth it, because the cables are all a pain to work with, while this should make it quite trivial to connect everything together.  In short, you won't have to have the inside of your MEGA65 look like this rat's nest -- which is good, because I can't actually close the case while all those cables are there!


So hopefully I will finally actually place the PCB order tomorrow, and then work out what components I need to be able to assemble. In particular, I don't think I have 34-pin female headers on hand.  But I'll have a poke around in the shed in the morning.

Saturday 13 January 2024

Working on the Expansion Board: Board Bus and Tape Port bring-up

It's been a while, but I have time and energy now to tackle some of the remaining tasks on the expansion board bring-up. 

To start, I have inserted most of the missing ICs, except for the couple that require me to remove the internal drive in my MEGA65, because the space behind the drive is quite tight. If I haven't already, I should certainly move those two ICs further towards the back of the machine.

 

The two with daughter boards with all the resistors are 74HC165s with current-limiting resistors to make them 5V tolerant, because the 74LS165s I had originally designed it for turned out to be Unobtainium due to Chipageddon.

You can see the two I haven't populated yet here, wedged behind and under the floppy cable. To make matters even more fun, one of those is also one of the 74HC165s, and so requires the daughter board, which probably won't fit there.  So when I go to test the tape drive port, I will likely have to remove the floppy drive.

That said, they are back in stock at Digikey now, so will order some for retro-fitting onto the board.  I have since changed the expansion board design to include the resistors, as they don't cause a problem for the LS version of the IC, but mean that users can fit either the HC or LS versions, without problem.

As for the IC positions, it looks like I have moved them further back on the latest revision. It will still be close, and putting them in sockets will be a bit sketchy, but they should fit just fine soldered in.  So no problem there.

Turning on the MEGA65 with all the ICs fitted has happened without incident: Nothing seems to be getting hot, and all the magic smoke is staying inside the chips.  So far so good.

Next step is to make a test bitstream that handles the shift registers to read and write the various bits.  As a reminder, the interface is just 4 pins for the main ports: Clock, Latch, Data Write and Data Read. When the Latch signal is high during a clock tick, the bits in the output serial buffers will appear on the appropriate pins, and the signal levels on the input pins will be read into the shift registers to be shifted out.  

This means that to do a complete sequence of updating pin values and reading the result, we clock out each of the bits for the output registers, enable latch for one clock tick, which also them captures the input port bits, and then we read those out.

Looking at the datasheet for the 74xx165s, it looks suspiciously like I have got the serial port chain backwards, with the pin that I am using to read from being the one that should be the input from the previous IC, and as a result, nothing will get read.  Fortunately, if this is the case, it will be fairly easy to fix, as I can just disconnect the pin at the PMOD connector, and run it over to the correct pin on the 74xx165 at the opposite end of the chain. To fix the PCB design, it also won't be too hard, as it just swaps pins 9 and 10 on the ICs. I've made and pushed this change in the PCB repo.  

Now I just have to switch the pins 9 and 10 on the prototype PCB I have made for those 3 ICs.  This is the same ICs that I have had to make the little daughter boards to add the resistors, so I should be able to modify those to switch the pins with a bit of luck.... Except that I have just discovered that I have completely messed up the pinout of those daughter boards. The resistors are not on the correct pins. So we can at least call this a "self-correcting stuff-up," in that through this other problem, I have identified this other problem.  However, it's only half self-correcting, as I still need a way to protect the 74HC165s from excess current due to over-voltage, as well as swap pins 9 and 10 around.

First stop will be to see exactly what I have done with the daughter boards, and whether I can do some sort of bodge to fix those.  If not, then I'll see whether it makes sense to design a new set of daughter boards to test, or whether its just easier to order a set of the corrected expansion board PCBs at a higher cost, but in the hope that I have now found and fixed all the remaining problems. I will need to get some of those fixed PCBs at some point anyway, for final testing, which is a vote in favour of that. But on the other hand, the risk is higher that I won't have found all of the problems, and will end up having to order even more PCBs after making further revisions.

Hopefully I haven't done something similar with messing up the cascaded shift register direction to the output chain of 74xx595's: Phew! Those look ok.The only other trick is that we have four output registers but only three read ones. But that doesn't really create any special problems.

Quite a few months have passed, since I last had time to work on this, during which time I did decide to get a new batch of boards with the known corrections made to them, which I'll start populating one of, with the parts I have on hand, and so that I know what parts I'm missing and will need to order.  I'm keen to get the order in before Christmas, so that I have the parts here while I am on holidays over the summer.


Let's start by checking the physical fit of the board, as some holes got moved between revB and revC to fix poor fit.  Generally speaking, it's much better, but there are still a few holes not quite in the right positions.

I've gone ahead and soldered on the resistors for the composite/component A/V connector, the connector itself, and the PMOD connectors. This should be sufficient to enable composite/component video.  I need to pop out the connector holes in the case of the MEGA65 R5 I am using to build this in, then I can screw it into place with one or two screws for now.

I'm continuing with the assembly, and keeping track of parts that I do and don't have.

I am short some 1K and 3K3 resistors, which will be easy to fix at Jaycar Electronics tomorrow.

I have exactly the 4 74LS125s I require.  

For the 74LVC165, we can substitute 74HC165s, as we have added 1K in-line current-limiting resistors. I have 4 of those, and only need 3, so that's good.  

For the 74LS595s, I don't have stock on hand.  So I've gone and ordered all the missing stuff and some spare ICs etc from digikey.  Allegedly they should arrive in only a couple of days, as it is via Fedex International Priority at no extra cost. I'm a little dubious this close to Christmas (16 Dec), but who knows! This means I should have them all by the time I go on leave.  I've got plenty of other stuff to do in the meantime around the house, so I'll just wait patiently for them to arrive, I guess.

And they did arrive before Christmas :) I've now soldered on the two ICs that have to be directly soldered, and installed all the resistors, except for the one related to the 6V line to the tape port, which I'll do when I also solder in the 6V DC regulator for the tape port. Here is how it looks right now:

Next step will be to create a module that handles this bus, and some automated tests using VUNIT for it, so that I can develop it as far as possible under simulation, before I incorporate it.  After that it will be tying the input and output bits to the appropriate ports of the MEGA65 machine module in the VHDL.  From memory, most of those are already exposed, so it shouldn't be too hard.  I'll then also need something to connect to the user port to make sure that I can read from and write to all of the appropriate lines.  

Okay, I have a first implementation of the 74LS595. Not sure if its correct, yet, but it is fairly simple:

  signal sr : unsigned(7 downto 0);
  signal q_int : unsigned(7 downto 0);
 
begin

  process (rclk, srclr_n, srck, g_n, ser) is
    if rising_edge(srclk) then
      -- Advance bits through shift register.
      sr(0) <= ser;
      sr(7 downto 1) <= sr(6 downto 0);

      -- Reset register contents
      if srclr_n = '0' then
        sr <= (others '0');
      end if;      
    end if;

    -- Allow for cascading
    if falling_edge(srclk) then
      q_h_dash <= sr(6);
    end if;

    if rising_edge(rclk) then
      q_int <= sr;
    end if;

    if g_n='0' then
      q <= q_int;
    else
      q <= (others => 'Z');
    end if;


 Now to do the 74LS165, which is the other way around: parallel in, serial out, which is just as simple:

  signal sr : unsigned(7 downto 0);
 
begin

  process (clk, q, ser, sh_ld_n, clk_inhibit) is
    if rising_edge(clk) and clk_inhibit='0' then
      -- Reset register contents
      if sh_ld_n = '0' then
        sr <= q;
      else
        -- Advance bits through shift register.
        sr(0) <= ser;
        sr(7 downto 1) <= sr(6 downto 0);
        q_h <= sr(7);
        q_h_n <= not sr(7);
      end if;      
    end if;

That just leaves the 74LS125, which is even simpler, as its just a non-inverting tri-state buffer:


  process (a,oe) is
    for i in 0 to 3 loop
      if oe(i)='1' then
        y(i) <= a(i);
      else
        y(i) <= 'Z';
      end if;
    end loop;
  end process;

So now I should be able to make a simulated version of the two serial rings on the board: One for reading pins, and the other for setting them.

I'm going through plumbing up the simulation identically to the schematic.  In the process I found I had one pin labelled incorrectly on U4, which resulted in a missed wire between pin 1 of U4 and pin 16 of U3. I'll have to put a blue wire on my prototype board to deal with that. Fortunately with all hole-through, that will be trivial.  What's even better, is that it will be easy to route it on the PCB for subsequent batches:

It's that white line. So let's route it...

 

Nice! I only had to slightly move one wire. Post a comment if you can spot the difference :)

Okay, back to building up the VHDL simulation of the board...

Next thing I've noticed: I'm using 74LS/HC595s, which are tri-state, not open-collector. I probably really intended to use open-collector outputs, so that we don't end up with the possibility of cross-driven lines.  The only thing is that to change to a 596, we would need pull-up resistors on all the outputs, which is a bit of a pain, as it would require a lot of re-routing in all likelihood. Oh, no, it's fine and a false-alarm.  There are only a couple of lines that this applies to, and they are lines that are, I believe, strictly output only: PA2 and PC2. Everything else has a 74LS125 tri-state buffer on it. 

Hmmm.. Maybe I could have reduced the part by using 74LS596s, and then I wouldn't have needed the 125s? That does seem rather likely, you know... Well, maybe if I get all excited and want to do an overhaul of the board, because that would be quite a lot of re-routing. 

Anyway, back to the plumbing in VHDL: It's normal to expose the output enables, outputs and inputs all to the top-level of the VHDL model, to avoid problems with synthesisers getting the wrong idea. So I probably don't need to hook up the 74LS125s in the VHDL.

With a bit of fiddling, I have the test harness all hooked up an running a dummy test. Now to write some actual tests.

Probably the best form of test is to implement the host-side of the controller that interfaces with the simulated side, and confirm that bit patterns on the host side show up on the simulated side within the appropriate number of clock cycles. That should be sufficient, as it really doesn't do anything more than that.  The expansion board in that regard is really quite unintelligent.

I'm making some good progress on this front, first testing that we can control output signals. The tests generally look like this:

      elsif run("TAPE_WRITE is correctly conveyed") then
        wait_for_ring_cycle;

        remember_current_signals;
        r_tape_write_o <= '0'; tape_write_o <= '0';
        wait_for_ring_cycle;
        compare_with_remembered_signals;
        
        remember_current_signals;
        r_tape_write_o <= '1'; tape_write_o <= '1';
        wait_for_ring_cycle;
        compare_with_remembered_signals;

The tests make use of three functions:

wait_for_ring_cycle allows enough time to go past, to make sure that the signals all get updated.

remember_current_signals remembers the current state of the signals.

compare_with_remembered_signals checks that the output signals still match the remembered signal states.

A test then consists of remembering the current signal state, changing some signal, and it's "remembered"  value to what we expect it will be, allowing the ring to update all the signals, and then checking that the new state matches what we expect, as stored in the (modified) remembered state.

Now to replicate that for all of the output signals we have, and if that passes, then we will have confidence that the output side is all working properly -- which I have done without great difficulty:

==== Summary ====================================================================================
pass lib.tb_exp_board_serial_rings.EXP_CLOCK ticks                            (0.5 seconds)
pass lib.tb_exp_board_serial_rings.EXP_LATCH is asserted                      (0.5 seconds)
pass lib.tb_exp_board_serial_rings.TAPE_WRITE is correctly conveyed           (0.5 seconds)
pass lib.tb_exp_board_serial_rings.DATA outputs are correctly conveyed        (0.5 seconds)
pass lib.tb_exp_board_serial_rings.DATA output enables are correctly conveyed (0.5 seconds)
pass lib.tb_exp_board_serial_rings.tape_6v_en is correctly conveyed           (0.5 seconds)
pass lib.tb_exp_board_serial_rings.c1565_serio_o is correctly conveyed        (0.5 seconds)
pass lib.tb_exp_board_serial_rings.c1565_serio_en_n is correctly conveyed     (0.6 seconds)
pass lib.tb_exp_board_serial_rings.c1565_clk_o is correctly conveyed          (0.6 seconds)
pass lib.tb_exp_board_serial_rings.c1565_ld_o is correctly conveyed           (0.6 seconds)
pass lib.tb_exp_board_serial_rings.c1565_rst_o is correctly conveyed          (0.7 seconds)
pass lib.tb_exp_board_serial_rings.user_pa2_o is correctly conveyed           (0.5 seconds)
pass lib.tb_exp_board_serial_rings.user_sp1_o is correctly conveyed           (0.5 seconds)
pass lib.tb_exp_board_serial_rings.user_cnt2_o is correctly conveyed          (0.5 seconds)
pass lib.tb_exp_board_serial_rings.user_sp2_o is correctly conveyed           (0.6 seconds)
pass lib.tb_exp_board_serial_rings.user_pc2_o is correctly conveyed           (0.6 seconds)
pass lib.tb_exp_board_serial_rings.user_cnt1_o is correctly conveyed          (0.5 seconds)
pass lib.tb_exp_board_serial_rings.user_atn_en_n is correctly conveyed        (0.5 seconds)
pass lib.tb_exp_board_serial_rings.user_reset_n_en_n is correctly conveyed    (0.5 seconds)
=================================================================================================
pass 19 of 19
=================================================================================================

So now onto the input ring :)

Setting up the plumbing, I noticed another errata: I had two pins to read the CNT1 pin, which doesn't matter. I have removed the duplicate.

What might be an issue, though, is that the 74LS165s shift when EXP_LATCH is high, and latch when EXP_LATCH is low.  This is backwards to the 74LS595s, and means I might need to effectively halve the sample rate, because I will need to leave EXP_LATCH for 24 ticks instead of 1.  But I need to read the datasheets more closely, as it might be that I can leave EXP_LATCH high most of the time, and then just bring it low for one cycle, if the 74LS595s are edge triggered, rather than level triggered.

The EXP_LATCH behaviour was fine, with the method I described above.  With that done, and all the signal plumbing in place, and the unit tests updated to handle it, the end result is that the expansion board controller is able to set and sense all bits for all ports. That is, it looks like the wiring of the rings is good, and I have a controller than can use them.  There is a remaining niggle about the timing of the serial shift register chips I am using, but I suspect it's just my imagination. The real test will come from populating the board and trying it out.

To make the trying it out much easier, what I should work on next is plumbing the expansion board controller into the MEGA65 core, so that I can easily do this.  To make that in turn easier, I might make some VHDL records for the various ports, so that the amount of plumbing I have to wire is one per port, rather than one per signal. This will be especially handy since I will need to add support for this expansion board to MEGA65 R2 through R6, so minimising the work required will be good, and reduce the chances of error.  I've now done this, and am ready to begin plumbing it into the MEGA65 architecture.

I had thought about burying the controller deeper into the VHDL, so that the changes per target would be very minor, but that would not work if we later made an FPGA board that had more lines available to control these ports with lower latency. 

I've plumbed it all in for the tape and user ports in the MEGA65 R5 target.  The C1565 port can wait for now, as can the other targets.

Hooking this up, I was reminded of something I had totally forgotten: The tape interface is not entirely on the CPU: The read line from the tape actually goes to the /FLAG pin of one of the CIAs.

I also found that the /PC line of the CIAs is output-only, so we can shave a few more parts.

Otherwise, the main thing I am contemplating is when the expansion board is not connected: Will the simple ring protocol make the MEGA65 think that various lines on the user port are being pulled low, for example, if they are not being actively driven.  But first, I need to get everything plumbed in.

I now have it all plumbed in, but at least one user port line is appearing to be pulled low, which blocks the C65 ROMs from booting.  I've seen this before, and forget the particular line involved.  To counter this, I have added logic that attempts to check for the presence of the expansion board, however it doesn't seem to be effective right now.  

More the point, I can't see the clock and latch signals on the PMOD. That was caused by an old clock line hanging around that I used by mistake, and was not really a clock. Resynthesising with that fix, and hopefully that will get both issues resolved: The clock will tick, and the expansion board detection will work to prevent lines being pulled low that shouldn't be.

The logic I am using to detect the board checks if the input ring is reading all zeros or all ones. If either is true, then it assumed the board is not present.  I'm going to add a couple of small changes to the PCB to ensure that this detection scheme will never ignore the board when it is really present: I'll tie one spare input high and another low, so that the input vector can never be all zeros or ones.

It doesn't need much change, just this:

The only change on the PCB layout is to connect pin 11 and pin 16 of this IC together, which I can quite easily bodge on the board I have here. So that's two little changes I need to make now:

1. pin 1 of U4 and pin 16 of U3
2. pin 11 and pin 16 of U7
3. pin 12 to pin 8 of U7

Anyway, back to finding out why the C65 ROM gets stuck, I'll start by comparing the status of the CIA connected to the user port with and without the expansion board controller wired in.  Hopefully there will be something obvious.

CIA2 registers with the expansion board controller connected:

:0FFD3D00:07FF3F00FD5BC5B000394500FF03C141

and without, with differences highlighted:

:0FFD3D00:07FF3F00A7FDBCFE06040000FF03C141

Actually, that bitstream also hung.  It's all a false alarm! I had a 1541 connected, but not powered on to the IEC port, and that causes this behaviour.

So it boots to BASIC just fine now :)

Time to put those bodge wires on, and populate the expansion board with the remaining ICs and start testing it!

Ok, bodge wires in place: I've just used convenient spare pins on the shift register, rather than following 

So the next step is to give it some power from a current-controlled power supply, and make sure that it doesn't want to suck lots of current and turn it into smoke. 

Current controlled power supplies are great for this kind of thing, because you can set the allowed current super low, and then slowly increase it until either it gets higher than you think it should, or the voltage reaches the correct level, at which point you know it is drawing all the current it wants. This lets you spot problems before you fry things -- like when I put one of the ICs in backwards just now ;)

Anyway, I have all the ICs fitted, and current checks are fine, so I should now be able to put it into my machine without great fear and trepidation. Which reminds me, one of the little easter eggs I included in this board design is the ability to move the TE0790 JTAG adapter so that it can poke out the tape port hole (and still leave enough room to connect a tape drive at the same time).  This means you can easily connect and disconnect the JTAG USB connector, without having to open the case, like this:

My only concern is whether the internal extension lead that it will require will be too long, and might cause trouble with the relatively high-speed JTAG lines. The serial monitor interface, however, should definitely be fine. We'll just have to suck it and see... and it does! Both the serial and JTAG functions still work, despite me having to use a rather longer cable than necessary.  It also just looks quite nice and neat and tidy from the outside:




Okay, so now I have everything connected, with all the temporary cables I have rigged up. For production, we would get some cables custom manufactured, that would be much easier to connect, and much neater and tidier as well. But for now, I'm just interested in functionality... and that no smoke comes out ;)

Well, no smoke has appeared, so that's good. In theory at least, I should now be able to control and sense the user port pins, as well as the tape button state (I haven't connected the tape motor control yet).

So let's try to see if we can read the TAPE_SENSE line. This line goes high when the tape PLAY button is pressed.  I'm not immediately seeing signs that it is being recognised.  Probing the board at R23, which is the 1K current-limiting resistor in-line with the TAPE_SENSE line, the output side goes from 2.4V when not pressed, to 3.66V when pressed.  I'm suspecting that the 2.4V when inactive is too high, resulting in it always reading high.

Yes, reading through the schematics for the datasette, the sense line is indeed expected to switch between ~2.4V and ~5V.  The 74HC165s I am using require the low voltage to be below 1.35V, so I'll need to do something about that. Probably the easiest approach is to put a couple of diodes in-line, so that the voltage drop across them gets it below 1.35V. Or better yet, a 3.3V zener diode so that I can just block the ~2.4V completely, but let the 5V through (still via the current limiting resistor).

Now the question is whether I have a zener diode handy, or if I need to go for a quick ride over to Jaycar Electronics to get one. More the point, whether I can find one in my stuff. I suspect I do have some. I think a Jaycar visit will be required...

In the meantime, I can test the tape motor control, through. And that works :) That's actually really encouraging, because it means that the output control ring is working, and that it is in the correct position in the ring and that I have it all correctly plumbed through. So when I put the zener diode in, I can also install the 6V regulator for the tape motor, which is the only active component I had left off for now.

Before I do that, I would like to test the user port pins as well. I thought I knew where I had a user port connector, but I can't find it right now. The data lines are easiest to test, but those are annoyingly on the underside of the connector. So it will take a little bit of gymnastics to be able to easily probe them. So I'll just finish fixing the tape port stuff first.

I've added the 3.3V Zener and also installed the DC-DC converter.  I had every expectation that this would fix the two problems I was seeing, but at best it has just changed them, which is quite frustrating.  The tape motor enable line is still being correctly driven, and I can probe it at the DC-DC converter. 

Well, the first half of the problem was I had the DC-DC converter turned around the wrong way. Fortunately I hadn't soldered it in, as it is a decent press-fit. Also it turns out this model of DC-DC converter, which is not the exact one I had originally planned to buy, does not work without a trim resistor fitted. I couldn't find any 220 Ohm resistors at Jaycar, only 240 Ohms, which will give a voltage of perhaps 5.5V instead of 6V. I don't know if that's enough for the tape motor to run or not.  I guess I'll give it a try, or do some resistor combining to get closer to the right value. By sheer good luck, putting a 3.3K Ohm resistor in parallel to a 240 Ohm resistor yields almost exactly 220 Ohms, and I have a bucket of 3.3K Ohm resistors. So I'll do that.

And it looks like I might have the Zener diode around the wrong way, too. Fortunately I bought 2 of those, if it proves a bit tricky to remove without destroying it. 

So I guess it's back to the soldering station to try to fix these things.

Ok, diode orientation switched, with no effect. Not yet sure what is going on there.

With the trim resistor fitted as 240 Ohm and a 3K3 Ohm in parallel, the output voltage is about 4.5V, instead of 6V. That's too low for the motor. Maybe my push-fit arrangement is adding too much resistance to the terminals of the DC-DC converter, and it is reading the trim resistor as somewhat higher. On the plus side, I can switch it on and off via the motor control bit of the CPU port, so that part of things is still fine.

Fiddling around with the DC-DC converter does yield odd effects, so I'm guessing that I do just need to bite the bullet and solder it in place.

Okay, with the DC-DC converter soldered in place, it is indeed switching between ~0V and 5.98V, with a nice smooth level.  So that's all good.  Except that the datasette motor doesn't run.  It's possible I fried it earlier, but I'm not convinced. I dug out a 2nd datasette, and that also does nothing. So I don't think its fried.

So how was the motor running before? Is it that 6V flat DC isn't sufficient voltage? Was it some ripple on it that was required?

I had a chat with Gideon to see how he does it on the Ultimate64 to see if there were any clues about the required voltage. He uses 6.3V, which I can't easily get with the DC converter here.  So then I had the idea I should have had a little while ago, to test the datasette motor with my voltage controlled power supply: It runs fine on 6.0V.

A bit more digging, and I discovered a silly error I made on the schematic: I didn't connect the GND pin of the datasette connector. Another bit of blue wire will fix that fairly easily... and that indeed gets the motor stopping and starting when it should.

It might also help this problem with the SENSE pin, since without a GND reference, that would have been all wonky, too. Nope, that's still not responding.  But I think the problem there now might be that I have fried the diode one way or another. So I might remove the diode, or at least break the circuit with it, and see what the sense line does now that we have GND connected.

Hmm.. The sense line now seems to be stuck on 0V.  Reading the C64 schematic, it looks like the SENSE line needs a pull-up resistor, as does the tape read line, so I should add those in.  In fact, I can just pull them up to 3.3V instead of 5V, and ditch the whole zener diode thing.  So where did I get the idea that tape sense floated to 2.4V? I'm sure I had read it somewhere, but looking at the schematic for the datasette, it is definitely just pulled to GND when a button is down.

With the pull-ups TAPE_SENSE does indeed switch between 0V and about 2.6V -- which is the voltage the 3.3V rail has sagged down to.  That makes me think that there might be excessive current draw on the 3.3V rail for some reason, so I had best investigate that. 

Investigating the 3.3V rail problem, it seems to have sagged even lower now, down to <1V. I'm wondering if I haven't shorted something previously, and now they are in thermal recovery (and hopefully not totally fried).

With the current controlled supply, the board is only drawing 20mA or less, so if there was a short-circuit, it was earlier. I might just leave everything off overnight, and see how it fares in the morning. It will be a pain if I've killed the 3.3V regulators for the PMODs on the R5 board here. I can at least in the interim power it from the current controlled supply, though, so it's not a total show-stopper.

The power rail for the PMODs is generated by U35, which is convieniently near them and on the top of the board, so I can try to figure out what is going on with it. It's also a part that could feasibly be replaced by hand, as it's only an 8-pin surface mount job with legs, rather than a little BGA. But it might have a thermal transfer pad underneath, which would be a pain.

Looking more at the part, which is an AP2196, they are actually designed to prevent short-circuits, and have thermal protection among other things.

Ah! On the R5 board, pull-downs were added to disable the 3.3V rail on the PMODs by default.  I'll have to fix that. But at least that's a nice simple explanation, and means that I probably haven't toasted anything... and that fixes that!

Now, the other thing I remembered for reading the TAPE_SENSE line, is that the data direction bit in location $00 has to be set to input to be able to read the line.  With that, I can now read the TAPE_SENSE line via the CPU port at $01.

So... I think it should all be set and ready to try to read something from a tape. I'll give that a go in the morning, because it's got late now.

It's tomorrow, and I have prepared a stock C64 ROM for the MEGA65, and put an old Zzap64! cover tape in the datasette, and tried to load it. And the result was partial success, as seen below:

On the positive side, it detected the PLAY button state, and seems to be reading something. On the negative side, I have the tape motor control enable inverted, so I had to keep enabling it remotely, which might well have contributed to the read errors. So I'm resynthesising with the tape motor control inverted, and will try it again in a few minutes. Fortunately synthesising on my new build box is faster than loading a game from tape ;)

Done -- and while it then refused to find or load anything on that datasette, I put the tape in the datasette in which I found it, and it finds the first game on the tape :)

It got all a bit confused there, because whatever it was reading from the other datasette seems to have messed up the memory pointers. 

But switching to the other deck, it starts loading, and as I type, I am watching the raster bars of the tape fast loader that is used on the tape:

Just the fact that it shows the correct name proves that the TAPE_READ line is working to a pretty good approximation -- the only question will be whether the timing accuracy is good enough, which I will be able to prove empirically when this finishes loading -- if the game runs, then it has to be fine. If it doesn't run, it will be less clear-cut, as it could be that the tape has rotted, or the game doesn't work on the MEGA65 or who knows what.

But all such fears can be ignored, because the game has loaded fine!

That's it for this post, I'll keep working on the user port and other remaining things in the next episode.