Retro Z80: Deciding on Memory design

I am trying to decide how to organize the memory layout for the Z80 retro computer project I am doing. The Z80 only has 16 address lines which allows it to access 64k of linear address space. If you want more memory than that – and we do – you have to organize the memory in parallel banks and switch between them, also know as (memory) bank switching. There are many flavors of how to design bank switching and there are many factors that would favor one over the other. Lets look at some of them.

I write this mainly for myself because I find that writing things down forces you to organize your thoughts and reveals weak spots in your thinking.

Bank Switching

With bank switching you add multiple banks of memory (in parallel) for a specific address space and determine through a latch which one of the banks is active. There can be only one active bank at a time for one address space. It is good to realize that these memory banks do not have to reside in separate physical chips. So say you have a 128k RAM chip, that would allow you to implement 2 banks of 64k by controlling the A16 line of the chip. If you do have separate chips, you usually control them with the Chip-Enable (CE) line to make sure the data lines are in a high impedance state and no data is written into the chip when the chip is not active.

So bank switching involves some address decoding to control what (part of the) chip(s) is/are active when others are not.

Video Memory

Another aspect of managing memory is accessing video memory. Video memory is a dedicated memory address space (or bank) that will contain the graphics to display on the (VGA) screen. Dependent on the screen resolution and color depth a specific amount of memory is required. This adds up quickly.

Contention will occur between the processor wanting to write into the video memory to update the display and the VGA controller that continuously need to read the video memory to output it to the monitor. Usually the VGA controller takes precedence over the CPU access to avoid glitches in the image on the display.

This video memory contention complicates matters further and need to be thought through carefully. I see several solutions to this problem.

Dual-Ported Memory

The first obvious solution is to have dual-ported memory. A port is a complete address (for the RAM size of the chip) and data bus. This is a special type of memory that has two ports that can access the memory simultaneously. It has built-in logic to lock out access to the same memory cell by the two ports (semaphore). The granularity is at cell level so there is a high level op parallelism. This is a very easy solution to the problem with the only downside is that this type of memory is not cheap.

There is also two-ported memory that is also suitable. Two-ported memory has one dedicated read port and one dedicated write port. Dual-ported memory has at least one read/write port. Also note that FPGAs often have internal dual ported memory blocks.

Segmented Memory

Another solution would be to use separate physical RAM chips. This would allow simultaneous access to different chips for the two ports (CPU and VGA controller). These ports would require extra logic IC’s like latches. The smaller the size of the memory chips the more of them you need but also the more parallel the two processes can access the video memory. The granularity is determined by the chip memory size and has a low(er) level of parallelism. Because of this you still would need something to halt the CPU when a collision is eminent – there is still a chance that the CPU and VGA controller want to both access the same chip.

Double Buffering

There is also the option to use double buffered video memory. This is a technique that toggles two parallel video memory spaces (banks) between the CPU and the VGA controller. At any time the CPU has access its own video memory bank and the VGA controller has access to the other bank. When the CPU is done writing to the video memory it indicates the at the next appropriate moment the banks can be switched (swapped) presenting the VGA controller with the new video information.

One problem that occurs here is that the CPU’s video memory now contains an old snapshot of the video data. That means if the CPU partially updates the video memory – for instance only updates (bit-blit) one small area on the screen, the rest of the scene is old data from the previous display image. I do not have any ideas how to solve this yet – so leave a comment if you do.

Graphics Processor

An alternate way of doing video is to use a separate graphics processor. This relieves the CPU from the task of creating the graphics. It also opens the door to a different hardware solution where communication with the graphics processor is done through I/O and not shared memory. So there is no contention.

The program would communicate in drawing primitives to the graphics processor which would reduce the amount of data to be exchanged quite a bit.

I need to delve into sprites and this type of graphics processing in general a bit more before making any final decisions.

Software Implications

Getting the hardware to correctly bank switch memory blocks is relatively easy although there are a lot of flavors in varying levels of difficulty. Another question is how is the software going to deal with memory spaces ‘suddenly’ appearing and disappearing in the program? I could not find very much on this on the interweb – so again, if you know any resources, please leave them in a comment.

One obvious implication is the stack pointer (SP). What happens to the stack when you swap out the memory block it is currently located in? Or if you want to jump or call code that is loaded in another memory block? And how do you get back to the calling address (RET)?

This all requires very precise orchestration in the software to not crash the system. It stands to reason to facilitate managing memory block switches by some sort of built-in service routine that knows the details of how to manage this for the program that is running. Using a RST instruction is a very common way to provide service routines that perform this type of important and frequently used operations.

Note that these RST instructions require the lower memory page (high address byte is $00) to be initialized with some sensible code. That means that this lower area of all memory banks in this address space, is off-limits to a any program and needs to be initialized by a boot mechanism. For now I assume that this is all RAM.

Note that other addresses may need to be included in this safe area, such as the NMI Service Routine ($0066) or perhaps an ISR vector table for IM2 (depends on the value of the I register).

Idea: A system controller can perform the boot sequence and initialize all the RAM banks that overlap with this address space. It could then set a bit (latch) to disable writing to this part of the memory (of any bank) at a hardware level. This way that crucial address space cannot be corrupted by buggy programs.

All RST instructions push a return address onto the stack. This can be used to resume the program where the call was made. It also allows the RST functionality to intercept and redirect execution flow as well as retrieve extra parameters from the call address etc.

When execution is transferred from one memory bank to another, there always has to be a ‘common ground’ to make the switch from. It stands to reason to leave the lower memory space static to provide this basis.

Stack Pointer

The stack pointer and the contents of the stack itself are tied to the code that is currently executing, including all the calls and push instructions that may have been performed up until this moment.

So it seems logical to locate the stack in the same memory page as the code is executing.

Jumps and Calls

Jumps and calls are always within the 64k address space. This is not to complicate the instruction set. A separate way of calling out-of-context code is provided with RST service routine. A small structure can be supplied by the caller to indicate what code to execute on what memory page.

We could reuse the notion of a vector table (by index) we use for interrupts and will probably use for public BIOS functions.

Interruspts

The I register is initialized to 1 so a protected version of the ISR addresses is at the bottom of the memory (base page or all banks). A program (or OS) can override by setting I to another value but has to do all management – including bank switches.

BIOS ROM

BIOS contains the bare essentials for interfacing with the hardware (I/O). This includes bank switching but also keyboard, mouse, display, printer, network and storage (disk) access. It does also require a bit of RAM to maintain several structures for its services.

These service routines can be used by any program to help with memory bank/page switching.

Loading Programs

The size of the memory blocks also influences how programs are loaded. What if programs are larger than a memory block. What if they are larger than 64k?

We may have to design a program file layout that is relocatable and publishes entry and exit points. This clearly needs some more thoughts.

Memory Usage

Memory is typically used for several different types of data. One is programs. Another type is data. The system itself requires a reserved region of memory.

These different types of memory usage could be taken into account by the memory manager to enable/disable certain services for those blocks (performance optimization).

Multi-Threading

An OS could use memory banks to execute parallel tasks and use the memory manager to direct the programs to specific parts of the memory space and memory blocks.

Each process (program) can get its own base page. Not sure if that helps or just makes things worse. We’ll have to see.

 

Advertisements
Published in: on December 14, 2015 at 7:44 am  Leave a Comment  

The URI to TrackBack this entry is: https://jacobielectronix.wordpress.com/2015/12/14/retro-z80-deciding-on-memory-design/trackback/

RSS feed for comments on this post.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: