Monday, June 29, 2020

Wire, Tri, and Wor

Earlier today I cleaned up the VFD driver and the keypad remapping code so it was consistent with the rest of this project's code, and updated the top module for my P170-DH rebuild. Behavioral simulations ran fine, and synthesis and implementation gave only the expected warnings, so I decided to load it into the hardware and see how it ran.

At first I was pretty happy. The VFD displayed a single zero in the right-most digit, with the decimal point illuminated to just the right of the zero. The VFD driver reads the WR register, which is stored in RAM 0 register 1, via a dual-port interface to the RAM. Since an i4002 RAM clears itself to zeros while the reset input is asserted, this is what it should display. Unfortunately, that was the last part that worked as expected.

I have a logic analyzer interface built into the board, and I can connect internal signals to this interface by using assign statements in the top module. I'd configured this to monitor the keyboard scanning and printer output, and it was pretty apparent that the CPU was not scanning the keyboard. This confused me because I'd observed this working in simulation, and the CPU ran my MCS-4 Digital Clock demo not long ago.

To look more closely at basic functionality I changed my debug interface to show the ROM storage address, the ROM data being output, and the 4-bit data bus that connects the i4004 CPU to the five i4001 ROM and two i4002 RAM chip emulations. The first thing I noticed was a lot of "noise" and unexpected addresses appearing on the ROM address bus.

Because I wanted separate module instantiations for each emulated i4001 ROM chip, while still allowing them to share a single Block RAM containing the entire ROM contents, the i4001 modules share a 12-bit ROM address bus, and receive ROM output on a shared 8-bit ROM data bus. The active i4001 drives the ROM address bus while the inactive i4001s drive Z (high impedance) bits. The synthesis tools understand this and convert it to the appropriate FPGA logic. Yet this did not seem to be happening, as I was seeing addresses on the ROM address bus none of the i4001s should have been driving. And I didn't see valid ROM contents coming out.

I'd always wondered what ends up on a supposedly tri-state bus inside an FPGA when all of the drivers are high-Z. I'd assumed the bits would end up sane, like all zeros, but apparently not. The ROM address  bus is unidirectional with multiple drivers rather than bidirectional, and after some research and experimentation I converted it to a "Wired Or" configuration. This is done by changing the i4001 ports from "inout wire" to "output wor" and changing the inactive output value from Z to 0. This seemed to quiet the bus, as there was now a known value on the bus (zeros) when none of the i4001 modules were driving it. With this in place I saw 0xF0 on the ROM data bus, which is the correct first byte of the Busicom program. Why this was needed I don't know, because my clock demo used two i4001 ROM chips to store its program and there's video of that running in hardware.

I did see the basic MCS-4 bus control signals: my clock generator is putting out the expected two-phase clock signals; the i4004 CPU is asserting the sync and cmrom signals at the appropriate times; the data bus is changing state during the X1/X2/X3 subcycles, though not to the expected values. What I was not seeing was the ROM data getting onto the data bus during the M1 and M2 subcycles; instead I saw only zeros. Nor was the instruction address getting incremented from cycle to cycle; it, too, was always zero. Maybe the CPU was being held in reset even after the power-on clear (POC) signal was de-asserted?

i4004 POC FF
In a real i4004 CPU, the external poc_pad pin is connected to the "set" input of a Set/Reset flip-flop, and the "reset" input is connected to the A12 subcycle signal. The "set" input has priority: when the poc_pad input is asserted the internal poc signal is immediately asserted, but when the poc_pad input is de-asserted the internal poc signal remains active until the beginning of the next CPU instruction cycle.

The Spartan-6 doesn't have a FF primitive with both asynchronous Set and Reset inputs (you can have one or the other, but not both), and the synthesis tools complained mightily about the one I constructed out of combinational logic. The problem is that combinational logic in an FPGA is done with a look-up table and not NAND or NOR gates, so you can't predict how it will behave in all circumstances. There may be glitches or incorrect outputs as the inputs change.

To address this I recoded the reset synchronization logic as follows:


1
2
3
4
    always @(posedge a12 or posedge poc_pad) begin
        if (poc_pad)    poc <= 1'b1;
        else            poc <= 1'b0;
    end

This synthesizes to a single FDP flip-flop. It lacks multistable protection, but at this point I just don't care. After several confusing problems trying to get the poc signal routed out to the debug header I finally was able to verify a clean, properly timed, internal reset.

Yet it still doesn't run.

The screen capture below shows the logic analyzer output displaying one CPU cycle, plus a bit more on either end:

  • Cursor A (red) marks the start of the A1 subcycle.
  • Cursor B (blue) marks the start of the M1 subcycle. 
  • Cursor C (yellow) marks the start of the X1 subcycle.
  • Cursor D (violet) marks the start of the end of the X3 subcycle and the beginning of the next instruction cycle.

i4004 Bus Timing
During the M1 subcycle, the i4001 should drive the high four bits of the rom_data bus (value 0xF) onto the system data bus. Clearly that's not happening. To try to determine whether the i4001 module had properly decoded the instruction fetch cycle I tried to display its "extbusdrive" signal from the i4001 module instantiating ROM 0. This signal is the one that gates internal signals onto the external data bus. All I got was a constant zero. Looking into the ISE logs I found this warning:
Xst:653 - Signal <extbusdrive> is used but never assigned. This sourceless signal will be automatically connected to value GND.
Well, yes, that explains the zero output. But what happened to this signal? Admittedly this is a hierarchical access rather than a module port, but it's standard Verilog and I've done this before to access internal signals. Apparently there is a constraint I can add to my UCF to force a signal to be preserved, but this will also change the logic.

By this time I'd blown the entire afternoon and part of the evening, and I decided to walk away for a while and make dinner. In the middle of cooking it occurred to me that while I've seen this implementation of the i4004 CPU and peripherals run on a real FPGA, it was a Spartan-3E and not a Spartan-6. I've run some tests of the Spartan-6 -- the M-32TL printer driver, the VFD driver, and the keypad remapper -- but to my best recollection I've never run the i4004 CPU on it.

My guess is that I'm now looking at a rather lengthy debugging effort involving a lot of trial and error and maybe some post-synthesis simulation. I'll have to go back to tests that validate individual modules and figure out how to run them in the FPGA rather than simulation. That will eventually lead me to the problem, but it's a slow process.

No comments:

Post a Comment