MAXI09 is the name of my 6809 based microcomputer. I named it for my son, Maximillian, and of course the 6809.
Feature Set Rationale
Before describing the actual features of the computer, I think it would be useful to go over the reasoning behind the choices made:
- MAXI09 tries to be, I think, the dream mid 1980s 8 bit micro, with FPGAs used where ASICs would have been, and SRAMs instead of DRAMs for convenience
- 5V through-hole (or socketed PLCC) parts to ease construction
- Some current parts so I can gain some useful skills and knowledge, eg. AVRs and SPI ICs
- Power via barrel socket at 7-12V
- Potted switching regulator to avoid a massive heatsink
- 68B09 clocked at 8 Mhz (2 MHz machine cycle)
- 512KB RAM, mappable in 4KB pages
- 32KB EEPROM, mappable in 4KB pages, in system reprogrammable
- 6522 VIA, with attached parallel printer port and GPIO headers
- 2 9 pin digital joysticks on addressable latches
- 16C654 Quad UART
- Keyboard controller is an ATMega8515 with Amiga 600 keyboard attached
- Firmware for keyboard controller features configurable key repeat delays and debouncing
- V9958 video controller with 192KB video RAM
- OPL2 AKA YM3812 FM synth output on mono 3.5mm jack
- IDE interface with 5V pin for Compact Flashes
- JTAG header for config flash and two EPF10K10 FPGAs
- MuDdy FPGA
- DISCo FPGA
- Buffered expansion connector
The board measures 264mm by 194mm and is a 4 layer board with internal power planes.
Theory of Operation
The diagram below shows the major connections between the various ICs and external connections.
MuDdy (Memory management and DMA) is the heart of the computer. It acts as the core glue logic and provides address decoding and Read/Write generation. It is attached to nearly all the MPU pins and can assert /HALT to pause the processor and subsequently master the Address, Data and Control busses normally owned by the 6809. Currently a simple DMA Controller has been implemented which is capable of transferring arbitrary (up to) 64 KByte blocks with optional increment on source and destination, making the DMAC suitable for operating on memory or IO devices. No software has been written which makes use of this facility however.
Memory is arranged in physical and virtual (MPU) spaces. Physically the system memory space covers 20 bits for 1MB, though the 6809 is of course limited to 16 bits and 64KB. The low 12 bits, A0 to A11, are wired straight through, making a memory page 4KB. The high 4 bits, A12 to A15, form the Virtual Address. In essence the memory mapper, when it’s implemented, will be able to switch the 16 Virtual Pages to any of the 256 Physical Pages, in RAM, EEPROM of IO. For the present, the mappings are constant with the top four address bits mapped thusly:
- 0000-0111 RAM (32 KByte @ 0x0000-0x7fff)
- 1000-1011 IO (16 KByte @ 0x8000-0xbfff)
- 1100-1111 EEPROM (16 KByte @ 0xc000-0xffff)
The final function, at least for now, of MuDdy is as a holder of a 256 byte boot loader. This makes use of the RAM bits inside the EPF10K10 to hold 256 bytes of 6809 code whose main job is to facilitate reprogramming of the EEPROM by receiving data from Port 0 on the QUART. The system can therefore be recovered regardless of the contents of the EEPROM. The operation of the boot loader is discussed in the section about booting, detail below.
DISCo, short for Disk; Interrupts; SPI Controller, is mostly a peripheral controller but it is also responsible for key system control signals: reset and interrupts.
At board start-up, the FPGAs will receive their configuration from the EPC2 Config Flash. During this time the system /RESET line is pulled low by a pull down resistor. When FPGA config stage is complete, a few milliseconds after power up, /RESET will be driven high by DISCo. The MPU will then start executing code after jumping through its reset vector. It is also possible to generate a reset sequence by writing to the RESETTRIGGER register. In this way it is possible to generate a hardware reset through software.
Interrupts are managed by three priority encoders, one for each of the three Interrupt signals on the 6809: /NMI, /IRQ and /FIRQ. There are 8 interrupt sources on the board and they are encoded in the following priority order, highest to lowest:
- 8: MUDDY
- 7: VDC
- 6: IDE
- 5: UART
- 4: RTC
- 3: VIA
- 2: OPL
- 1: KBDREQ
- 0: No active interrupt
The masking of each of the sources onto each of the three MPU interrupt lines is done through set and clear registers. Thus it is possible to mask a source into a particular MPU interrupt signal without keeping track of what sources are already masked in. Three read-only registers present the number of the highest priority interrupt source active (or 0 if there are none). It is thus a trivial job for an ISR to translate this number into a code vector held in a table.
The SPI master within DISCo is basic, but adequate for the task and interfaces the four SPI ICs with the rest of the system in an efficient manner. Byte transfers are not currently interrupt driven, nor is there a status register. Instead transfers must be timed so they do not overlap.
The board contains four SPI ICs, and each is addressable via the SPISELECT register within DISCo:
- 0: MCP3002 ADC IC attached to joystick port 0
- 1: MCP3002 ADC IC attached to joystick port 1
- 2: DS1308 Real Time Clock
- 3: CAT25256 32KByte EEPROM
Other values, the nominal value being 0xFF, causes all SPI ICs to be deselected. DISCo decodes the value in SPISELECT to assert the correct Chip Select pin on the peripheral IC.
To shift a value out to an SPI IC, the IC needs to first be selected. Then the byte to shift out needs to be written to the SPIOUT register. Exactly 16 machine cycles (obtained, for example, by executing 8 nops) the received value will be available via the SPIIN register, if it required. Note that some peripheral ICs, like the MCP3002 ADC, require the IC to be deselected between each value reading.
The following is a list of registers, present in MuDdy and DISCo along with a description of their function. Note that registers are 8 bits wide unless stated. 16 bit registers are assumed to be accessed using 16 instructions, which is why they do not have split 8 bit symbolic names.
Registers that are written to may (generally) also be read.
This is the base address for all registers within MuDdy.
This is the base address for the DMA function within MuDdy. Note that the DMA controller is not yet finished. In particular, the address utilised by the controller are virtual (MPU) addresses, ie. 16 bits wide and not 20 bits wide.
The start address for the source address in a DMA transfer. This is 16 bit register, in Motorola (Big Endian by order.)
The start address for the destination address in a DMA transfer. Another 16 bit register.
The length of the transfer. As this is a 16 bit register up to 64 KByte can be transferred at once.
This is an 8 bit register and it has two purposes:
- To start a transfer
- To configure properties of the transfer
The byte is arrange as follows:
- 7-4: Unused
- 3: Write only, do not read from DMASRC
- 2: Negate the source before writing
- 1: Increment destination
- 0: Increment sourc
The purpose of the write only mode is to rapidly zero memory. Control of wether to increment or not allows the controller to be used with IO addresses, as both a source and/or a destination of the transfer. The negate function is, admittedly, not very useful and was mostly implemented for amusement.
This register is used by the boot-loader code to configure access to the highest page (256 byte) of memory, which is shared by both the physical EEPROM and the boot-loader. Two bits are used:
- bit 1: Expose real EEPROM at 0xff00. When this is a 0 (default) the pseudo ROM appears here, otherwise the real EEPROM does
- 0: Write protect the EEPROM. When this is a 0 (default) the EEPROM can’t be written too, otherwise it can
See the section on Booting for more information on the usage of this register.
This is the base address for all registers within DISCo.
Turns the LED, attached to DISCo, on and off. To be precise, if bit 0 of this register is a 1, the LED will be on. Otherwise it will be off.
If this 8 bit register is 0 the buzzer attached to DISCo will remain silent. Any other value will cause it to emit a tone at:
(2,000,000 / 16) / REGVALUE Hz
Writing a value with bit 0 set to 1 will cause a hardware reset to be generated.
This chooses which, if any, of the SPI Slave ICs on the board to be selected. The possible values are (these are decimal values, not bit positions):
- 0: The MCP3002 ADC attached to Joystick Port 0
- 1: The MCP3002 ADC attached to Joystick Port 1
- 2: The DS1305 Real Time Clock
- 3: The CAT25256 32 KByte EEPROM
- 255: None selected
255 for none selected is only the recommended value.
Write a value to send the byte out to the selected peripheral IC. A byte will take 16 machine-cycles to transfer.
After the 16 machine cycles a read-back byte may be retrieved by reading this register.
Other hardware documentation documentation is listed in the references section. The current IO register map, covering both the regular IO devices and the programmable logic is available through the hardware.inc file within the MAXI09OS repo. This is the authoritative source for all hardware addressing.
Though mostly a software-concern, booting does interact with the hardware, especially the MUDDYSTATE register in MuDdy. It is therefore useful to describe the booting process here. Note that serial communications are deliberately simple; no interrupts are used. The purpose of the boot loader is to facilitate in-system reprogramming of the main 32 KByte EEPROM (although currently only the low 16 KByte can be written to by the boot loader).
0. At startup MUDDYSTATE is 0x00: EEPROM is write protected and FPGA “ROM” is mapped at 0xff00
1. Initialise the UART registers: 9600 8n1, no interrupts
2. Copy the entire 256 byte loader program into RAM
3. Calculate the RAM address for step 5
4. Jump to to the RAM address for step 5
5. Send “\r\n***flash with f\r\n” to UART
6. Read from UART a byte with a 2 second timeout
7. If timeout or not “f”, go to step 9
8. if “f” go to step A
9. Write protect the EEPROM (bit 0 is 0) and map it at 0xff00 (bit 1 is 1) by writing 0x02 to MUDDYSTATE
10. Jump through the RESET vector at 0xfffe to start the real EEPROM
And the flashing subroutine. At this point MUDDYSTATE is still 0x00, and we are running in RAM.
A. Tell the remote end it can send data by sending “+++”
B. Write enable the EEPROM and map it at 0xff00 by writing 0x03 to MUDDYSTATE
C. Read 64 bytes into a scratch area of RAM
D. Copy the 64 bytes held in RAM into the EEPROM
E. Delay for around 10ms to match the AT28C256 page writing spec
F. Send a “#” character to the remote end to indicate that it can send the next 64 byte block (the sender will have paused waiting for it)
G. If we have not read in all 16 KB worth of 64 byte blocks, loop back to C
H. Send back the entire 16 KB EEPROM contents which we have just written. The sending routine checks it against what it sent, but the MAXI09 board will continue on to the next step regardless of any errors
I. Jump back to step 9, which will write protect the EEPROM and jump through the RESET vector stored there
Note that the booting code (a 256 byte page) consumes only one of the 2048 bits of RAM available in the EPF10K10.
All linked documents are in PDF format.
- 6809 MPU
- FLEX10K FPGA
- AT28C256 32 KByte EEPROM
- ASC4008 512 KByte SRAM
- 16C654 Quad UART
- 6522 VIA
- ATMega8515 8 bit MCU
- V9958 Video Display Controller
- MCP3002 Serial 10 bit ADC
- DS1305 Serial Real Time Clock
- CAT25256 Serial 32 KByte EEPROM
- YM3812 FM Synthesiser