So, with a bit of experimentation I discovered that dropping R19 down to about 8000 ohm fixes the tape input. Not only are the tape signal high/low bars equally sized now , it also sounds right when it's loading instead of the weird "high frequency" noises in the previous video. Here's an updated schematic where I've just added a 47K resistor R61 in parallel with R19 to drop it to approximately the right value:
I've also been learning over the last week about the importance of clock domains, more specifically the scarceness of global clocks. It seems doing innocent things like this will introduce severe unpredictability:
if rising_edge(hsync) then ... something <= '1'; ... end if;Obviously, this introduces a D flip-flop clocked on hsync. However, what this actually seems to do is put this into a pool of things that might be promoted to a global clock or might be routed all over the place. At first, everything seems to work fine and then suddenly, changing a small bit of code somewhere causes resources to be placed slightly differently which causes a different selection of clocks to be promoted to be global clocks.
Around the time I started using the DCM to generate a 32MHz clock from the standard 16MHz clock, I'd forced most of important clocks to be globals using BUFG, but I hadn't realised how unpredictable all the innocent looking clocks like the one above would be. Something trivial like changing the RAM banking logic would cause glitches in the CRTC (e.g. bit 2 of the width register always being set) or even complete screen failure.
So, what's the solution? It actually turns out that if you stick to only ever using a small number of clocks (there's 8 global clock lines on all the Spartan 3 chips), then everything works completely as expected. Replacing the multitude of clocks actually turns out to be remarkably easy, e.g.
if rising_edge(clk32) then if hsync='1' and hsync'last_value='0' then ... something <= '1'; ... end if; end if;It turns out that you end up needing very few clocks. I've gone from having too many clocks that the assignments were random, to only 5 clocks in total: 32MHz, 32MHz @ 180°, 16MHz, 1MHz (for CRTC/PSG) and 4MHz (for Z80).
The best result of this is that this fixing these clock issues also fixed a critical problem with the sound emulation. I'd noticed some glitches before with a couple of songs, e.g. the fantastic Hyperdragon by Reed/Fairlight, but I hadn't ever managed to figure out what was wrong. What was happening was due to this clock issue the first sample in each envelope was actually played with the volume of the last sample in that envelope. This was due to audio clock no longer being a global clock, so the audio events had stopped being precisely synchronised and the envelope counter was being reset 1/16 envelope period after it was being used. So, BASIC which didn't use envelopes was fine and most songs sounded fine, but some of the drum effects sounded very odd in others. I'll upload a video of this soon, but my phone just ran out of battery as I was recording a demo for the blog...