MAXI000 build complete and thinking about next steps

The physical construction of MAXI000 is complete:

It looks good with the SIMM installed.

I first attached the joystick parts. Testing the 9 pin Atari joystick went exactly as expected, as did testing the buttons on the Game port joystick I have.

After writing a quick test routing for the SPI based MCP3002 (PDF) ADCs, I quickly encountered a problem with reading the stick position: nonsense values.

Looking at how a Game port joystick is actually wired would have been a good thing to do before coming up with the schematic for the MAXI000 joystick section. I was under the mistaken impressionthat the potentiometers in the stick are connected between each pole of the power supply, but in fact only one side is. To read the sticks position the X and Y axis are connected through to an RC timing circuit as shown in the following schematic:

Reading the position is normally done by timing how long a monostable takes to return to its previous value after being triggered.

One possible workaround to this mistake would be to attach resistors between the AX, AY, BX and BY pins and ground, of the same value as the maximum value on the position pots. This would have the effect of turning the position output into a voltage divider. One problem with this is it would limit the range of possible values at the ADC to between 0 and half of Vcc, or 5V. In summary I don’t intend to bother implementing this fix.

The DRAM controller is far more interesting, and in summary, it appears to work well: MAXI000 has 1MB of SRAM and 8MB of DRAM, in the form of the memory on a 72 pin SIMM.

Physical construction was straightforward, though the resistor arrays were fiddly. As it turned out I had an issue with a bit position across the whole SIMM, and had to reflow the solder on the resistor arrays, and for good measure I reflowed the pins on the SIMM socket as well.

I ended up asking for some help from Steve Moody, who is building his own 68000-based computer, which he calls Y Ddraig (Welsh for The Dragon). His computer includes slots for 30 pin SIMMs, with a DRAM controller implemented in VHDL, included in a XC95108 (PDF), coincidentally the same CPLD I’ve previous used on my first 6809 SBC. His code had several improvements to my own DRAM controller, which I’ve included here.

Here is the architecture:

architecture behavioral of simmcontroller is 
2, REFRESH3, REFRESH4);  -- Define the states 
       process (CLOCK) 
               variable REFRESH_COUNT : INTEGER range 0 to 504 := 0; 
               variable STATE : DRAM_STATE := IDLE; 
               variable NEEDS_REFRESH : STD_LOGIC := '0'; 
               if (RESET = '1') then 
                       WRITE <= '0'; 
                       MUXSEL <= '1'; 
                       RAS <= "0000"; 
                       CAS <= "0000"; 
                       DTACK <= '0'; 
                       REFRESH_COUNT := 0;
                       STATE := IDLE;
                       NEEDS_REFRESH := 0;
               elsif (CLOCK'Event and CLOCK = '1') then 
                       REFRESH_COUNT := REFRESH_COUNT + 1; 
                       if (REFRESH_COUNT = 504) then 
                               REFRESH_COUNT := 0; 
                               NEEDS_REFRESH := '1'; 
                       end if; 

                       case STATE is 
                               when IDLE => 
                                       WRITE <= '0'; 
                                       MUXSEL <= '1'; 
                                       RAS <= "0000"; 
                                       CAS <= "0000"; 
                                       DTACK <= '0'; 

                                       if (NEEDS_REFRESH = '1') then 
                                               NEEDS_REFRESH := '0'; 
                                               STATE := REFRESH1; 
                                       elsif (SIMM = '1' and AS = '1') then 
                                               STATE := MEMRW1; 
                                               STATE := IDLE; 
                                       end if; 

                               when MEMRW1 => 
                                       if    (ADDR = "00") then RAS <= "0001"; 
                                       elsif (ADDR = "01") then RAS <= "0010"; 
                                       elsif (ADDR = "10") then RAS <= "0100"; 
                                       else                     RAS <= "1000"; 
                                       end if; 
                                       STATE := MEMRW2; 

                               when MEMRW2 => 
                                       MUXSEL <= '0'; 
                                       WRITE <= not RnW; 
                                       STATE := MEMRW3; 

                               when MEMRW3 => 
                                       CAS <= UDS & LDS & UDS & LDS; 
                                       STATE := MEMRW4; 

                               when MEMRW4 => 
                                       DTACK <= '1'; 
                                       if (AS = '1') then 
                                               STATE := MEMRW4; 
                                               STATE := PRECHARGE; 
                                       end if; 

                               when PRECHARGE => 
                                       WRITE <= '0'; 
                                       MUXSEL <= '0'; 
                                       RAS <= "0000"; 
                                       CAS <= "0000"; 
                                       DTACK <= '0'; 
                                       STATE := IDLE; 

                               when REFRESH1 => 
                                       CAS <= "1111"; 
                                       STATE := REFRESH2; 

                               when REFRESH2 => 
                                       RAS <= "1111"; 
                                       STATE := REFRESH3; 

                               when REFRESH3 => 
                                       CAS <= "0000"; 
                                       STATE := REFRESH4; 

                               when REFRESH4 => 
                                       RAS <= "0000"; 
                                       STATE := PRECHARGE; 
                       end case; 
               end if; 
       end process; 
end architecture;

Note that as per the rest of the VHDL used by MAXI000, these signals are all active high. The FGPA’s external /DTACK line is copied from this controller whenever the SIMM memory is selected. /DTACK will thus be deasserted as soon as the processor selects the memory space; the processor will pause until the transfer is complete, which happens at a time set by this process.

The selection of the CAS and RAS pins are as follows. With 72 pin SIMMs, there are four of each:

  • CAS: One line per byte across the 32 bits of memory held on the SIMM. But because 16 bit wide accesses are used, CAS0 is wired with CAS2, and CAS1 is wired with CAS3, as per the datasheet recommendation.
  • RAS: Each line is a different “memory bank”, ie there are four banks. Address bits are used to select the bank. In this case the selection of bits is a little strange: the processor’s A1 line is used for one bit, and the A22 line is used for the other, which are then decoded into the 4 RAS lines.

A1 is essentially the odd and even word line, thus odd and even words will be stored on different banks. A22 will toggle on 4MB boundaries. These two bits combine to form the bank number, where each bank, with the Micron MT16D232-6 (PDF) 8MBytes part used, is 2MB in size (though it is interleaved across alternate words, as described).

Here’s a screenshot from the Logic Analyzer which should make the operation of the controller clearer. /RAS is /RAS0 and /CAS is /CAS0:

Note that the clock used by the controller is the main 32MHz clock; the processor is driven at half this speed which is the clock shown above. The first action is a longword read. /DTACK is asserted for two processor cycles per word. A RAS before CAS memory cycle is used, states MEMRW1 and MEMRW3 in the VHDL. The second /RAS pulse is not shown because the second word is stored in a different bank since A1 will have toggled, as described above.

To the right of the longword memory read is a CAS before RAS refresh cycle. The refresh cycle lasts 2 processor clock cycles, so if a memory cycle coincides with it the memory cycle will be delayed up to that amount due to the fact that the controller prioritises refresh cycles over memory cycles. /DTACK will be deasserted as soon as the processor selects the SIMM memory area, halting the processor so the memory cycle can finish after the refresh cycle.

So far I have only performed some rather superficial tests on the SIMM slot. I will, at some point, write some more exhaustive test routines then my existing checksum routine, which has been my main method for validating the DRAM controller. But I’m quietly confident it will turn out to be as reliable under heavy load as it does under my light testing.

One thing I have not explored is whether memory controller states (actual useful cycles and refresh cycles) can be trimmed away to reduce the wait states. I know there is certainly one optimisation which I might have a look at implementing: holding open a row so that the RAS pulse can be skipped if the row address is unchanged. This should be fairly easy to implement in the controller by caching the high bits of the address and then comparing it on subsequent memory cycles.

So this all leads onto: what’s next?

As usual there are many choices.

I could continue on with exercising the MAXI000 board hardware. At least the following things could be explored, and I’d learn a good deal from each of them:

  • Optimise the DRAM controller: as described above, and possibly look at trying out some other higher and lower capacity SIMMs.
  • A proper DMA controller for Alpha: this could allow multiple parallel transfers at different priorities, etc.
  • Page mapper, again for Alpha: this could be fascinating. The design could be extended over time with page tables held in FPGA memory being a starting point, eventually moving onto looking at page tables being retrieved from main memory with a local cache.
  • A flexible sound interface: DMA waveforms could be held in Beta’s local memory, with playback parameters (address, length, period, volume etc) set using MPU addressed registers.
  • Improved video: I could port the improvements made for my softcore back to Beta.
  • SPI master: there’s a few outstanding problems with the SPI logic. Basically it’s a hack at present.

On the software front, a whole load of possibilities exist. After playing with EmuTOS I’m less inclined to look at porting an existing OS. This means I would be writing my own, which is not at all a small project.

The base for this needs to be a proper understanding of the 68K from a systems programming point of view. Writing a simple task switching demo would be a useful first milestone to demonstrating this goal had been reached.

After having a serious go at writing an OS (MAXI09OS) in assembly for my 6809 based MAXI09 board, any future OS for any processor will have to be written in a relatively high level language, like C. The first job therefore in starting an OS project will be to elaborate on my previous efforts to compile and execute code for the 68000 written with a C compiler, and find out to add inline assembly, since this will be needed by any OS code.

Playing about with my MAXI000 board for some time has revealed a number of limitations.

The biggest and most obvious is the logic resources available in the FPGAs used, the EPF10K20 (PDF). This is especially noticeable in Beta.

With the basic 80×60 text mode, simplistic SPI master, very basic programmable timer, single PS/2 controller and hard coded interrupt routing, 66% of the logic resources are used. There is very little room for adding things like run-tine selectable multi-colour bitmap graphics modes and pretty much zero chance of being able explore features like hardware line drawing and sprites.

As well as logical resources the fact that only 5 bits of processor address are attached to the FPGA has lead to problems. As I saw with the EmuTOS port attempt, this means that an address pointer register must be used to access the locally attached video memory. Since the address space is greater then 64K words to write to an arbitrary address, 3 register writes are needed: the upper half of the address, the lower half, and the word itself. The current setup is fine for text mode, but no good at all for graphics, when no hardware acceleration is available. The 5 bits of address also limits the number of registers that can be exposed to the processor. If they could be fully implemented the features that I had in mind for Beta would need much more then 32 words of registers for their interface.

All this said, I don’t consider MAXI000 a failure. I’ve learned many, many things and achieved a lot in the process of getting this board up and running. Probably the thing I’m most pleased with is that I have abandoned my long standing aversion to SMT board construction. MAXI000 is a non trivial surface mount board, and I’m very pleased to have made the jump. I’m also pleased that I appear to have sussed out DRAMs, at long last, more then 7 years after building my first 8 bit computer on breadboard.

With the resource limits of the EPF10K20 (PDF) when applied to a graphics controller and the fact that I’ve spoiled myself playing with the more advanced FPGA on the DE2-115 board I used in my softcore processor experiments, I’m now seriously thinking about building a new 68K board using some bigger FPGAs for the graphics and possibly other functions.

Steve Moody’s Welsh Dragon board has been recently reworked to use expansion cards, and I think that idea could work very well for me too. It would allow me to worry about a video solution later, as a separate problem. A rough specification for such a “bare” board would be:

  1. 1M x 16 bit of flash, just as now
  2. No SRAM
  3. 72 pin SIMM slot
  4. Quad UART using SC16C654 (PDF) with the same connections as MAXI000
  5. Dual PS/2 as per MAXI000
  6. Glue logic and bus master
  7. RTC using the same DS3234 (PDF) as MAXI000
  8. 4 peripheral only expansion slots

The glue logic, an EPF10K20 FPGA, would be able to master the buses and will thus be attached to the full width data and address bus, allowing a DMA controller to be implemented, which I think is pretty important in such a computer.

Functionality wise, the following things would need to fit inside the EPF10K20:

  1. Address decoding
  2. DMA controller
  3. DRAM (SIMM) controller
  4. PS/2 controller
  5. SPI master
  6. Misc: buzzer, reset handler, etc

It may be I still have to supplement the main FPGA with a second smaller part (perhaps an EPF10K10) doing duty as a DRAM and peripheral controller, but it would be nice to avoid this scenario.

For a hypothetical graphics card, it seems obvious that even a video implementation on its own would not fit in an EPF10K20, not if I want to experiment with switchable graphics modes and hardware acceleration. This means I will have to look at larger FPGAs, and the complications involved in running mixed voltage level boards.

The bigger question is perhaps the choice of processor. It would be exciting to move up to the next one in the 68K series, the 68020 or perhaps even the 68030 with its inbuilt MMU. The main obstacle to this plan is pin requirements on the core FPGA: If I want to make it able to master the buses at the maximum data width, the main buses will consume many pins. Also, routing such a board, with wide 32 bits buses, would be a challenge.

Yet another question that arises is what kind of connector should be used on the expansion cards.

In parallel to working on the next 68K board, I want to begin planning for improvements to my 16 bit softcore processor, starting with looking at switching the design over to being 32 bit…


Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.