Zalt: Memory Management Unit

So everything is working so far, except that the Memory Management Unit (MMU) has not been tested yet. Before we dive right in, I will show the part of the schematic involved. Never mind the blue text, those are my scribbles on IO-address ranges etc.

Z80 MMU2

IC106 is the memory map RAM that maps the Z80’s A12-A15 to the Memory Addresses MA12-MA19. U101 select the operational memory map table – one of 256 – to use. The contents of the memory map table will be used to map A12-A15 to MA12-MA19 when it is selected. The contents of the memory map tables can be written and read back by setting U102 with the correct table address and driving U103 (the bi-directional buffer) to read or write the data. Note that all the necessary control signals are provided by the Logic board with the CPLD on it. As soon as the IO-address is detected to read or write the memory map table, U101’s output is disabled, and U102’s output is enabled – in normal operation U102’s output is always disabled and U101’s output is always enabled. This allows you to read and write a different memory map table than is currently used to (perhaps) run the program.

System Controller

Because the System Controller can access the entire address and data bus and control the important control lines also, I decided to have it exercise the MMU. So I wrote an additional command that allowed control of the MMU from the PC terminal application.

The Memory Manager command ‘mm’ has a couple of modes:

  • nul [table]
    Writes a null-table to the indicated memory map table. If no table is specified, all 256 tables are initialized to null tables. A null table has a 1:1 mapping between the incoming A12-A15 and the MA12-MA15 lines. MS16-MA19 are always zero. A null table only allows access to the first bank of 64kB of RAM memory.
  • sel <table>
    This writes the U101 with the specified value and selects the memory map table for operational use.
  • get <table> [index]
    This prints the value for the specified map-index of the specified memory map table. If no index is specified, all 16 map-values are printed on the PC terminal program. This uses U102 to select the table to read.
  • put <table> <index> <value>
    This writes the value in the map-index for the specified memory map table. This uses U102 to select the table to write.


After this was programmed into the System Controller it was time to start the test. I removed all chips from the CPU board and left only U101. This way I could test if the IO-address decoding for setting the operational memory map table was working. It was. I wrote the VHDL in the Decoder board’s CPLD earlier and this was the first indication that at least some of it was correct. I worked my way through the other MMU ICs and found a couple of small errors in the CPLD’s logic, that were easily fixed.

But there was something wrong when reading back the memory map table value. Both the WE and the OE of the memory map RAM were active at the same time. That is not good. I looked over the VHDL code a lot of times without seeing the problem.

Then I spotted that the WE and CE signals of the memory map RAM were labelled wrong on the connector in the CPU board schematic. That meant that the CPLD was sending the wrong signals to the wrong pins on the RAM. At moments like these, I love programmable logic. Simply swap the two pin definitions in the VHDL, reprogram the CPLD and all was well. Or was it…?

The 74F245 was getting quite hot when I stopped the System Controller on the code that engaged the correct IO-address and read the data. But why?

When I checked the data lines they were not a definite logical zero or logical one – they sort of floated around the 2V mark… That could only mean one thing: I had a data bus conflict. Something was driving the data bus at the same time as the memory map RAM was outputting its data and U103 was setting that onto the data bus.

The System Controller responds to a Z80 IO request (input and output). The System Controller detects a IO condition and responds. This is how a Z80 program can send characters out over to the PC and receive key strokes from the PC. That logic is disabled when the System Controller takes control of the Z80 bus(ses) – like when using the ‘mm’ command. The only thing that didn’t get disabled was setting the System Controller data bus to output when seeing an IO-input request. And the ‘mm’ command generates an IO-input situation to read the memory map table data back in. So the System Controller was driving the data bus while the 74-245 (U103) was trying to pass on the memory map table data.

Luckily it was an easy fix. The PSoC5 also has programmable logic and adding another AND-gate fixed the issue. Now it all worked.


Next is writing the humble beginnings for an actual Z80 program. I had been dabbling with some Z80 assembler to run some basic tests and have written some routines in assembler for the bios. The plan is to add these to the z88dk target I made earlier and be able to write a complete program. I wan’t to move over to programming in C (z88dk target) as soon as possible because that is just way faster and more productive. If and when I discover problems in performance, I will hand optimize the assembly for the critical parts only.

I have also been creating a system C API for the memory manager.  Here’s a sneak preview – subject to change.

// allocates max 64k of memory
handle_t Memory_Alloc16(uint16_t flags, uint16_t capacity, uint8_t pageIndex);

// allocates max 1MB of memory
handle_t Memory_Alloc24(uint16_t flags, uint32_t capacity, uint8_t pageIndex);

// releases onwership of memory (by handle)
void Memory_Free(handle_t memory);

// releases ownership of memory (any ptr in allocated block)
void Memory_FreePtr(void* memory);

// retrieves access to memory (by handle)
AccessFlags Memory_GetAccess(handle_t memory);

// retrieves access to memory (any ptr in allocated block)
AccessFlags Memory_GetAccessPtr(void* memory);

// locks memory into active region and returns pointer
void* Memory_Lock(handle_t memory, uint8_t pageIndex);

// releases lock on memory - keep ownership
void Memory_Unlock(handle_t memory);

// releases lock on memory - keep ownership (any ptr in allocated block) 
void Memory_UnlockPtr(void* memory);

So there is a good chance the next (couple of) post(s) will be about software primarily – simply because all the hardware’s pretty much done for now.

Published in: on July 1, 2016 at 5:33 pm  Leave a Comment