Close

Address decoding

A project log for JJ65C02

Working on my own version of a 65C02-based SBC. Everything is open source and permissively licensed.

jim-jagielskiJim Jagielski 10/19/2023 at 15:410 Comments

As with most people, I started off with very simple address decoding, little more than "if A15 is 1, select ROM; if 0, select RAM". Breaking out space for I/O makes it a little more difficult, but depending on what address space you pick, even that can be easy (and by easy I mean "not requiring a lot of logic gates").

The problem is that this results in, at least in my opinion, a wasteful memory map. 32k of ROM seems way too much, and 32K RAM seems way too little. Carving out I/O space from ROM helps, but even then a simple implementation results in 16k or so just for I/O, which is extremely inefficient.

So I try to create a memory map that maximizes RAM, and minimizes ROM and especially I/O. My first real option was below:

MEMORY
{  ZP:   start = $0,    size = $100,  type = rw, define = yes;
  RAM:  start = $0200, size = $7e00, type = rw, define = yes, file = "%O.bin";
  IO:   start = $8000, size = $1000, type = rw, define = yes, fill = yes, fillval = $ea, file = %O;
  ROM:  start = $9000, size = $7000, type = ro, define = yes, fill = yes, fillval = $ea, file = %O;
}

SEGMENTS {
  ZEROPAGE:  load = ZP,  type = zp,  define = yes;
  SYSRAM:    load = RAM, type = rw,  define = yes, optional = yes, start = $0200;
  PROG:      load = RAM, type = rw,  define = yes, optional = yes, start = $0300;
  DATA:      load = ROM, type = rw,  define = yes, optional = yes, run = RAM;
  BSS:       load = RAM, type = bss, define = yes, optional = yes;
  HEAP:      load = RAM, type = bss, define = yes, optional = yes;
  CODE:      load = ROM, type = ro,  define = yes,  start = $a000;
  RODATA:    load = ROM, type = ro,  define = yes, optional = yes;
  RODATA_PA: load = ROM, type = ro,  define = yes, optional = yes, align=$0100;
  ISR:       load = ROM, type = ro,  start = $ffc0;
  VECTORS:   load = ROM, type = ro,  start = $fffa;
}

There are some problems with the above, namely that ROM is still a bit too big and you really can't access the higher addresses of RAM. The other problem is that the logic required to implement the above introduces some gate delays, even using faster AC chips, that limit overall system clock speed. It's obvious that improvements need to be made.

To handle both the complexity and the latency issues related to address decoding, I've moved to using a simple PLD, namely the Atmel ATF22V10C. I don't consider this "cheating" really, because I could easily use discrete chips if I wanted to, as long as I'm fine with clock speeds no faster than 4Mhz. But I like this solution for another reason: I can use CUPL to very easily design the logic.

Normally, you would describe the logic using standard boolean logic, but CUPL provides a much more clear and concise way. For example:

FIELD_ADDR = [A15..A00];
/* Address Decode Logic */
ROM       = ADDR:[B000..FFFF];  /* 20k */

IS_ROM = ROM;

 CUPL is smart enough to generate the underlying logic.

This makes it easy as well to carve out space for RAM bank switching as well, with some signal pins determining if we are in the banked address space as well as which bank we'll be using. The current PLD file for WinCUPL is as follows:

Name     JJ65C02 ;
PartNo   00 ;
Date     10/6/2023 ;
Revision 01 ;
Designer Engineer ;
Company  jimjag ;
Assembly None ;
Location  ;
Device   p22v10 ;


/* Pin Map
       --------
PHI   |1     24| Vcc
A15   |2     23| /RAM_CS
A14   |3     22| /ROM_CS
A13   |4     21| /IO_CS
A12   |5     20| BA13
BK0   |6     19| BA14
BK1   |7     18| --
--    |8     17| --
--    |9     16| --
--    |10    15| --
--    |11    14| --
Gnd   |12    13| --
       --------
*/

/* Inputs:  From CPU*/

Pin 1  =  PHI;   /* 65C02 CLK */
Pin 2  =  A15;   /* Address lines from CPU */
Pin 3  =  A14;
Pin 4  =  A13;
Pin 5  =  A12;
Pin 6  =  BK0;   /* Bit 0 of the VIA 3-bit Bank number */
Pin 7  =  BK1;   /* Bit 1 of the VIA 3-bit Bank number */

/* Outputs:  Chip Selects */
Pin 23 = RAM_CS;  /* to RAM /CS pin */
Pin 22 = ROM_CS;  /* to ROM /CS */
Pin 21 = IO_CS;   /* to IO (VIA and ACIA) /CS pin */
Pin 20 = BA13;    /* To RAM chip, A13 */
Pin 19 = BA14;    /* To RAM ship, A14 */


FIELD ADDR = [A15..A00];

/* Address Decode Logic */

RAM       = ADDR:[0000..9FFF];  /* 32k */
BANK      = ADDR:[8000..9FFF];  /*  8k */
IO        = ADDR:[A000..AFFF];  /*  4k */
ROM       = ADDR:[B000..FFFF];  /* 20k */

IS_ROM = ROM;
IS_IO = IO;

!ROM_CS  = IS_ROM;
!IO_CS   = IS_IO;
!RAM_CS  = PHI & !IS_ROM & !IS_IO;

BA13 = (!A15 & A13) # (A15 & BK0);
BA14 = (!A15 & A14) # (A15 & BK1);

The goal is that the BK* pins will be driven by pins on the VIA chip. Schematics are being worked on.

Discussions