What is itIn 1988 I built an interface that enabled me to attach 8-bit PC-cards to my C64. The idea was to connect a MFM-hard disk drive but this failed as I had to write my own driver for it. A year later I build a 16-bits version. But I lost this card during a move :(
In 1999 I built a $10 IDE-interface. I got many positive reactions but decided not develop it further due to the fact that a lot of C64 software simply could not cooperate with such a device.
In April 2001 I had some serious requests for both interfaces. The requesters for the IDE interface were pointed to the PC-card interface and agreed that this would satisfy their needs as well. What is my personal interest in this? In 1990 network- and VGA-cards were expensive, nowadays they aren't. In fact most of the ISA-bus cards I own, I got for free :)
DMADo we need DMA? One thing is sure: to be able to use 1.44 MB drives you'll need it. In 1989 I had no interest in DMA. Now the goal is to include DMA using two 8237's. Why the 8237? For two simple reasons:
* I have a dozen of them laying around
* you can find enough documentation about it everywhere
SpeedMy goal was to be able to let it run at 2 MHz so it could be used by a C128 in 2 MHz mode. Problem: the C128 always accesses the I/O-area, $D000-$DFFF, in 1 MHz-mode.
To be honest, not until writing Beta version 1 I haven't thought about using the free memory ranges available in the C128-mode. I'm not familiar with the behaviour of the bus when in C128 mode so I first have to do some research on that before offering a C128-version of this interface.
Until then the design will be focussed on use with a C64 or a C128 in C64-mode.
CMD's SuperCPUI simply don't know if the SCPU can be used with this card. Both need the expansion port but I don't see any problem when using a expander. What I don't know is how the SCPU behaves when accessing an address. In some cases it accesses the C64, in others it doesn't. If you map the PC-card to a range the SCPU doesn't access, it simply won't be seen at all.
65816I want use the 65816's ability to address up to 16 MB of course. This will be covered with the article PC-card Extra.
SchematicYou'll find it here.
Used codes with ICsAll used ICs can be of the 74LS, 74ALS or 74F type. I refer to a IC with its own code plus the place where to find it in the schematic.
Example 1: 32, U3C-A5d. This is a 74LS32, identified by U3, third gate, found in quadrant A5, bottom right.
Example 2: 573, U14-D2b. This is a 74ALS573, identified by U14, found in quadrant D2, top right.
The position inside a quadrant is not always given for various reasons.
Theory and hardwareIf you are unfamiliar with the so called ISA-bus of the PC, please read The ISA-bus first.
The ISA-bus has 24 address lines that makes it capable of addressing up to 16 MB of memory and (theoretically) up to 64 KB of I/O. The C64 has 16 address lines and therefore is only able to address up to 64 KB. But a lot of this 64 KB already is occupied by memory, ROM and I/O. The only free space left to use for our own purposes we can find from $DE00 to $DFFF: a meagre 512 bytes. This leaves us no alternative then to use pageswapping.
What is pageswapping? You can compare pageswapping with having a C64 with a lot of floppies. Although you have loads of data and programs to your disposal, you can only access it by one floppy a time. If the data you need resides on another floppy then the one you currently are using, you have to exchange it. In our case we are able to connect up to 16+ MB of RAM, ROM and I/O to our C64 through the ISA-bus but we are only able to access 256 bytes a time. Only 256 bytes, what about the other 256?
A window of 256 bytes supplies 8 address lines. This means we need to supply the other 16 ourselves. Therefore we have to add at least some I/O-registers. Beside these, we need some registers for purposes that will be explained later. To be able to DMA, we need two 8237's that also have a number of internal registers of their own. In other words: we simply need to an address range for all these registers. FYI: 64 bytes should cover the whole range.
I had some discussions about which other area(s) could be used beside the original ones. Some examples:
* demirroring the VIC-II video chip will free up to 3 pages: $D1xx..$D3xx
* demirroring the SID audio chip will free up to 3 pages: $D5xx..$D7xx. But only $D700 can be used to remain compatible with the C128.
* demirroring one or both CIA's which frees up to 2 * 240 bytes for the I/O part.
Personally I do NOT prefer to use one of the original free pages as using them disables you to use the REU and/or other cartridges. The second SID and some other projects I know of use $D7xx. The SCPU uses some of the demirrored VIC-pages. So not much choice left :(
I personally favour one of the freed VIC-pages for the window and a part of the CIA-page for the I/O.
FYI: from this moment on I will use "I/O" and "WINDOW" when referring to these areas.
Whatever your personal choice is, I supplied a simple hardware solution to choose the wanted areas: two jumpers enable you to connect I/O to IO1 and WINDOW to IO2 of the expansion port. You want your own area's? No problem, just remove the jumpers and connect a two-pin header and two wires to the freed pins. The wires must now be connected to the active (L) outputs representing the area's you want to use.
What about the software? The above solution means that many combinations of addresses are possible. A solution is to provide the addresses as parameter and to use indirect addressing ie. LDA ($xx),Y. But this will slow down the performance in several ways. IMHO there are other solutions:
* the programmer of a tool or driver also provides a small program that accepts two addresses as input which will change all addresses of the main program where needed.
* provide the sources and compile them with the wanted addresses.
Another remark about the size of the window: suggestions have been made to downsize it to 128 bytes. Other suggestions have been made to use two demirrored pages of the VIC-II. IMHO this is not such a good idea. Although both have their advantages at first glance, I personally found out with my old design that these solutions both created problems seen from a software point of view:
* an other number then 8 causes problems with shifting
* 128 bytes hinder you to exploit the full potential of indexed addressing
* with 512 bytes: accessing the second page roughly costs the same amount of instructions as doing a pageswap. No gain at the end.
Decoding the I/O-area.I assume that used area is 256 bytes wide. A 139, U29b-A6c, enables you to choose a 64 bytes window by means of a jumper. Using a demirrored CIA-area means that the line already represents a 64 bytes wide area. In that case you must think of setting the jumper according this area! Another idea is to connect the generated signal directly to a pin leading to the enable-input of U29a-A6c.
The second half of the 139, U29a, takes care of dividing the 64 bytes in four parts of 16 bytes: two are used for the 8237's, one for the rest of the I/O-registers and one is unused.
A 154, 4-to-16 decoder, U44-A6a, combines three address lines and the R/W-line to 16 negative outputs for addressing all needed buffers and latches:
0w: writing control register U9-B6c
0r: reading control register U10-B6d
1w: writing address lines A8..15 register U24-C7c
1r: reading address lines A8..15 register U25-C8c
2w: writing address lines write A16..23 register U28-D7c
2r: reading address lines write A16..23 register U27-D8c
3w: writing address lines read A16..23 register U31-C7a
3r: reading address lines read A16..23 register U30-C8a
4r: reading IRQ buffer 1 U49-C4
5r: reading IRQ buffer 2 U48-C4
7w: writing D8..15 -> ISA-bus register U12-D2c
7r: reading D8..15 -> ISA-bus register U13-D2d
4w, 5w, 6r, 6w not used.
To make sure the timing is according PHI2, one of the enable-inputs is connected to the inverted PHI2-signal.
DMAThe 16-bit ISA-bus has seven DREQ/DACK lines and that's why we'll need two 8237's. Like a PC we'll use the second 8237 for 16-bit transfers only.
During a DMA-transfer, output HLDA of the first 8237, U2-B4c, is (H). This signal is inverted by U21a-B6a, a 7406 OC-inverter. This signal is fed to the DMA-input of the C64.
The 8237's run on a 4 MHz clock derived from the DOT-clock. This done using a 393, U36b-C4c. If nothing is done, the read- and write-signals outputted by the 8237 use this speed as well (remember that the 8237 was meant for the 4.77 MHz 8088 in the first IBM-PC's). This means we need to synchronise the 8237 with PHI2 using the Ready-input.
To make sure that the 8237 covers the whole high part of PHI2, we need a mechanism that detects both edges of PHI2. The VIC-II sometimes needs to disable the CPU and signals this by negating BA. To make sure a DMA-transfer doesn't mess up this process, we first AND PHI2 with BA (U37b-B7) and use the result for activating the Ready-input.
First the mechanism has to be activated by either MEMR or MEMW during a DMA-transfer. An AND-gate, U26c-D5d, combines these two signals. The output of U26c is fed to a NOR-gate (U22d-C1) which output is fed to the READY-inputs of the 8237's.
The output of U26c is also fed to the data-input of a 74 flip-flop, U40a-D1. This 74 is clocked by the output of U37b, (PHI2 * BA). In this way U40a detects the rising edge of PHI2 but only then when the VIC-II doesn't access the bus. The negative Q-output is fed to the data-input of the second half of this 74, U40b.
This 74 is clocked by the inverted PHI2. In this way U40b detects the falling edge of PHI2.
The Q-output of U40b is fed to the above NOR-gate, U22d, as well. At the falling edge of PHI2, the output of U22d becomes (L) allowing the 8237's to go on. The moment the 8237 ends the MEMW- or MEMR-action, the output of U22d becomes (H), clearing both the flip-flops through the inverter U32F-D1.
During a memory-to-memory transfer the 8237 first reads and then writes the byte using an internal temporary buffer. The problem will be reading from the C64. The moment PHI2 goes (L) and releases the Ready-input, this will not be the moment the 8237 stores the info as well. So the data has to be buffered in some way. We'll see we need this buffer as well when transferring data from/to the ISA-bus. The solution is to place the 8237's BEHIND these buffers. This also means that the buffers have to be steered as if addressed by a WINDOW-access.
The 8237 is able to transfer data in blocks of maximal 64 KB. This means it must be able to manipulate 16 address lines. It is capable of driving the first eight, A0..7, itself. For driving the other eight, A8..15, it needs the support of an extra 8-bit latch: the 74ALS573. As said we'll use two 8237's like in a PC: one will take care of 8-bit transfers, the other of 16-bit transfers. The last is archived by connecting the (generated) address lines A0..14 of the 8237 to A1..15 of the ISA-bus. A0 must be kept (L) during a 16-bit transfer. As U1-A4a, the 16-bit 8237, already provides A8, the used 573 ,U7-A3c, only has to provide A9..15. The 8th bit is used to generate a (L) A0 during a 16-bit transfer.
This solution means that the second 8237 is only capable of transferring 32K words. I have the schematics of the IBM AT that I used as reference. But one page is missing: the one covering DMA. So I have no idea how IBM solved this. The 573 is enabled by output AEN of the 8237. But this signal has to be inverted first. We use a NAND-gate, U6b-A3c. Negating one of the inputs of the NAND-gate tri-states the 573. One is AEN of course. The other input comes from a AND-gate, U37c-B3c, which ANDs the PHI2 and the signal coming from the MASTER-input of the ISA-bus as input. Negating this last line means a card wants to control the bus. The use of PHI2 means that the address is only valid during the positive half of PHI2. This is necessary for the refresh of DRAM's and the fact that the VIC-II controls the C64 during the lower half of PHI2.
U5-B3c is the 573 A8..15 latch towards the ISA-bus for the 8-bit 8237. Another NAND-gate, U6c-B3c, provides the same function as U6b.
The ability to DMA with the C64 created an instant problem: how do we tell the system that the source or destiny is the C64 or the ISA-bus? And what about the moments that the C64 is or is not involved in a DMA transfer?
The solution to the first question is using two bits of a register to tell whether the source or destiny is the C64 or an ISA-card.
The solution to the second question is that when the C64 is not involved in a DMA transfer, we present the C64 a safe situation like reading an address that has no further influence to the system in any way. In this case I had $FFFF in mind.
These register bits are combined with the MEMx-lines using two NOR- and one OR-gate (U22c, U22b, U3d - B6a/b). The result is used to disable the MEMx-lines towards the ISA-bus (see later) and to serve as one of the inputs for U46c.
The goal is to be able to DMA from/to the C64 as well. A 245, U18-A8, is used to buffer the address lines A0..7 in both directions. The DIR-input is fed by the signal coming from U21a, the one also going to the DMA-input of the expansion port of the C64.
The 245 is enabled by NAND-ing (U46c-B7) (PHI2 * BA), MASTER and the signal coming from U3d telling the C64 is involved (see later).
U17-A8-c, a 573, latches the address lines A8..15 towards the C64 during a DMA transfer. Its clock input is fed by ASTB. Its enable input is fed by U6c as well but an OR-gate, U3b-A7c, first ORs this signal with the one coming from U46c.
During a DMA transfer the 8237 has to take care of the R/W-line of the C64. For this the output of U3d is NANDed (U6d-B7a) with the output of U37b, (PHI2 * BA). The result is ORred (u52a-B5c) with the output of U21a which output on its turn is used to enable a 125, U43a-A6b, which buffers MEMR.
The moment the C64 is not involved in a DMA transfer, the data bus, address bus and the R/W-line towards the C64 are disabled. Seventeen 33K resistors pull the address lines and R/W-line (H) (not drawn). This causes the Kernal-ROM or underlying RAM to output the data stored at address $FFFF which is completely harmless (AFAIK).
The address busThe C64 can generate the address lines A0..7 by itself but the other 16 have to be generated by I/O. As already mentioned, a 245 bi-directional buffer (U18-A8) takes care of A0..7.
For generating the address lines A8..23 for the ISA-bus, we'll use a pair of 573's for every eight bits. Why a pair? I need an 8-bit latch to output the data. But I also need some means to be able to read what was written in the past. For this purpose a 541 could do as well but I choose the 573 so I can piggyback it on top of the first 573; it saves some valuable space and wiring.
The C64 and 8237 are only capable of addressing 64 KB. So I needed a mechanism that enabled me to transfer data between separate 64K-blocks. My solution is to use two pairs of 573's for A16..23: one pair is only activated during a read-action (U27-D8c, U28-D7c), the other during a write-action (U30-C8a, U31-C7a).
First we have to find out whether a memory read- or write-operation is going on using U26c. This signal is used to enable a 139, 2-to-4 demultiplexer, U45a-D7d. The other inputs for this 139 are MASTER and MEMR. When MASTER is (L), neither one of the 573's are allowed to be enabled.
Both the needed outputs of the 139 are ANDed (U26a-D7d, U26b-C7b) with the read-signal for its 573-counterpart. This last construction enables reading the contents of U28 and U31.
U24-C8: C64 -> ISA-bus A8..15 Clock: I/O - write "1w" through 04 inverter, U42d-C7d Output enable: only when PHI2=(H), MASTER=(H) and no DMA : NAND U46a-C7d U25: read ISA-bus A8..15 Clock: (H) Output enable: I/O - read "1r" U31: C64 -> ISA-bus A16..23 Clock: I/O - write "2w" through 04 inverter, U42a-C7d Output enable: MEMR = (L) and MASTER=(H), or I/O-read of U30 U30: read ISA-bus A16..23 Clock: (H) Output enable: I/O - read "2r" U28: C64 -> ISA-bus A16..23 Clock: I/O - write Output enable: MEMW = (L) and MASTER=(H), or I/O-read of UCx17 U27: read ISA-bus A8..15 Clock: (H) Output enable: I/O - read "3r"
The data bus (1)To make sure the C64 can drive the data bus outside the C64, a 245, U4, will buffer it.
Enable during: * I/O * WINDOW * DMA, when the C64 is involved Direction: * R/W from C64 * MEMW during a DMA-transferTo enable the 245, an 3-input AND gate, U20a-B7c, combines the output of U6d with the enable signals for I/O and WINDOW. Its output is fed to the enable-input of the 245.
The C64's R/W line is either driven by the CPU or the 8237. Transferring data from the C64 towards the ISA-bus is a write-operation for the 6510, but a read-operation for the 8237. The technical solution is an EXOR-gate, 74LS86 U19a-A7a, that combines the R/W-signal coming from the C64 or 8237 and the signal towards the DMA-input of the expansion port. This last signal tells the EXOR-gate to invert the original R/W-signal when a DMA-transfer is going on.
The data bus (2): 8-bit transfer registersThis sounds easy: the C64 is a 8-bit system so it must be able to handle 8-bit transfers. But unfortunately it isn't that simple. Let's have a look how a 16-bit card handles 8-bit operations:
SHBE A0 Transfer 0 0 word 0 1 byte on D8..15 1 0 byte on D0..7 1 1 will never occurThe above means that we need a kind of T-junction which enables us to read/write from/to D0..7 or D8..15 depending on address line A0 and what type of card it is.
The SHBE-signal is a signal generated by the CPU. In that case you would say that a 80286+ cannot exchange data with an 8-bit only card. Indeed, it cannot. That a PC can is made possible by a 245 buffer that leads the data read from D0..7 to D8..15 or vica versa.
How does a PC know that a card can handle 16-bit transfers? The card signals this by negating MEMCS16 or IOCS16 according the type of operation. I have thought about providing some means to detect the fact that a card used one of these lines. But IMHO the Eyeball MK 1 should be sufficient enough :)
Remark: I have read some info that mentioned that SBHE also can be generated by a card. For the moment I don't know of any card capable of doing so.
The data bus (2): 16-bit transfer registersThe trouble I ran into with my initial the design was caused by real 16-bit transfers. First I found out that some transfers MUST be 16 bits wide.
Example: an 16-bit IDE-interface only accepts 16-bit I/O transfers. In case of the IDE-hard disk drive there is an additional problem. Data can only be handled sectorwise: it has to be received/sent in portions of 256 words. And this data has to be read/written in a row from/to the SAME address. This means that after reading this address the next read will make the hard disk drive output the next WORD. A C64 can only perform 8-bit transfers so we must provide a temporary 16-bit latch for storing the word.
Then there is another problem with 16-bit transfers. Let's have a look at the following program:
.eq WINDOW = $DF00 START ldx #$00 lda WINDOW,x sta $2000,x inx bne STARTWhen A0=0 the WORD is read from the ISA-bus and stored in the buffer, the Accu is filled with the lower-byte. When A0=1, the only thing that should happen is that the Accu is filled with the contents of the register that stored the upper-byte.
In the above example register X is incremented. But I can imagine examples where it is preferred to decrement X during the loop. In that case the word must be read when A0 = 1.
This means that we need some means to tell the system that A) we want a 16-bit transfer and B) in what direction the addresscount goes.
To worsen the problem, writing is done in the opposite way: when incrementing X, the actual write has to be done when A0 = 1, when decrementing X, the actual write has to be done when A0 = 0.
Some experiments of my own made me draw the following conclusions:
* all memory related cards (videocards, the buffers of networkcards) are able to perform 8-bit operations
* the only 16-bit-only operations I know of are I/O operations
* 16-bit I/O operations can happen on odd addresses as well (??? no proof)
The last conclusion means that reading an odd 16-bit I/O address will give you an other register and not, as with reading on odd 16-bit memory address, the upperbyte of the even address. The only solution I have to read this upper-byte is to reserve a C64-address just for reading the contents of the buffer that stored the data bits D8..15 during the initial read of the word. As the above is also valid for writing, another C64-address must be reserved for writing the buffer towards D8..15 of the ISA-bus.
Two pairs of 573's, U12..15-D2, latch the data. How are they clocked or enabled?
+----------------------------------------- DMA (0 = no) | +--------------------------------------- Source (0 = C64) | | +------------------------------------- Destination (0 = C64) | | | | | | +----------------------------------- 8/16 bit (0 = 8-bit) | | | | +--------------------------------- ReaD/Write (0 = write) | | | | | +------------------------------- IO/MEM (0 = MEM) | | | | | | +----------------------------- A0 C64 | | | | | | | | | | | | | | +------------------------- 573 D0..7 to ISA Clock U15 | | | | | | | | +----------------------- 573 D0..7 to ISA Output En. U15 | | | | | | | | | +--------------------- 573 D0..7 to C64 Clock U14 | | | | | | | | | | +------------------- 573 D0..7 to C64 Output En. U14 | | | | | | | | | | | +----------------- 573 D8..15 to ISA Clock U12 | | | | | | | | | | | | +--------------- 573 D8..15 to ISA Output En. U12 | | | | | | | | | | | | | +------------- 573 D8..15 to C64 Clock U13 | | | | | | | | | | | | | | +----------- 573 D8..15 to C64 Output En. U13 | | | | | | | | | | | | | | | +--------- MEMR / IORD | | | | | | | | | | | | | | | | +------- MEMW / IOWR | | | | | | | | | | | | | | | | | +----- SBHE | | | | | | | | | | | | | | | | | | +--- A0 ISA 0 1 2 3 4 5 6 0 1 2 3 4 5 6 7 8 9 A B --------------+------------------------ 0 - - 0 0 - 0 | 1 0 - 1 - - - 1 1 0 - 0 0 - - 0 0 - 1 | 1 0 - 1 - - - 1 1 0 - 1 0 - - 0 1 - 0 | - 1 1 0 - 1 - 1 0 1 - 0 0 - - 0 1 - 1 | - 1 1 0 - 1 - 1 0 1 - 1 0 - - 1 0 0 0 | 1 0 - 1 - - - 1 1 0 1 0 0 - - 1 0 0 1 | - - - 1 1 0 - 1 1 0 0 1 0 - - 1 0 1 0 | 1 0 - 1 0 0 - 1 1 0 - 0 0 - - 1 0 1 1 | 1 0 - 1 0 0 - 1 1 0 - 1 0 - - 1 1 0 0 | - 1 1 0 - 1 - 1 0 1 - 0 0 - - 1 1 0 1 | - 1 - 1 - 1 1 0 0 1 0 1 0 - - 1 1 1 0 | - 1 1 0 - 1 1 1 0 1 - 0 0 - - 1 1 1 1 | - 1 1 0 - 1 1 1 0 1 - 1 1 0 - 0 - - 0 | 1 0 - 1 - - - 1 * * 1 0 * = depends on 8237 1 0 - 0 - - 1 | 1 0 - 1 - - - 1 * * 0 1 1 0 - 1 - - 0 | 1 0 - 1 - - - 1 * * 0 0 1 0 - 1 - - 1 | - - - 1 1 0 - 1 * * 0 1 1 - 0 0 - - 0 | - 1 1 0 - 1 - 1 * * 1 0 1 - 0 0 - - 1 | - 1 1 0 - 1 - 1 * * 0 1 1 - 0 1 - - 0 | - 1 1 0 - 1 - 1 * * 0 0 1 - 0 1 - - 1 | - 1 - 1 - 1 1 0 * * 0 1 1 1 1 - - - - | - 1 - 1 - 1 - 1 * * * -All the above equations are only valid for a valid read- or write-operation. For U12..15 this means they have to be clocked when PHI2 = (H) and one of the MEMx- or IOxx-lines = (L). This is realized by U39b-C4c and U20c-C3c. PHI2 is needed because the timing of the MEM/IO lines aren't guaranteed when generated by a 8237.
In case of U12 we want to avoid that the latch is clocked during a 16-bit I/O operation. On the other hand it need to be clocked during a specific C64 write. We NAND "7w" and the output of U26c, (MEMR * MEMW), using U41b-D7, and AND it with PHI2 using U51d-D5.
U15 and U12 need to be enabled during:
- (No DMA) and (C64 write) U22a-C6b
- (DMA) and (Source = C64) U53a-C6b
under the condition MASTER=(H).
An OR-gate, U54A-C5b combines outputs and the result is NANDed (U41c-C4a) with the signal coming from MASTER.
U14, D0..7 to C64, is enabled during: - (No DMA), (8-bit card) and (C64 read) U20b-D6d
- (No DMA), (16-bit card), (C64 read) and (I/O card) U33a-D6
- (No DMA), (16-bit card), (C64 read), (Memory card) and (A0=0) U23b-D6a
- (DMA), (8-bit card) and (Destination = C64) U33b-D6b
- (DMA), (16-bit card), (Destination = C64) and (A0=0) U55a-D5b
The last line means I am not able to DMA with 16-bit IO-cards (for the moment, see later).
All the above outputs are NORred using U47b-D5b.
U13, D8..15 to C64, is enabled during: - (No DMA), (16-bit card), (C64 write), (Memory card) and (A0=1) U47a-D6d
- (DMA), (16-bit card), (Destination = C64) and (A0=1) U23a-D5b
* a C64 I/O-read "7r"
The outputs of U47a and U23a are ORred (U53b-C5b) and its output is ANDed with "7r" using U56a-C5b.
Enabling U12..15 will cost us a lot of gates. A logical thought is using a PAL or a GAL, but unfortunately I cannot program them :( However, an other idea is using an EPROM as the Enable signals aren't that time critical. In the worst case, that is if the idea won't work, I can replace the EPROM with a little add-on card.
SBHE must be (L) during a 16-bit DMA transfer or the opposite of address line A0. This realized by NORring the AEN-signal of U1 with A0 using U53c-A2b. A 125 (U43c-B1d) takes care of tristating it.
16-bit I/O and DMAI first thought that it was impossible to DMA from/to IDE but then I got the following idea: use the 8-bit 8237 and disable all certain features when address line A0=1:
* disable the IOxx-lines towards the ISA-bus
* A0=0 towards the ISA-bus during a read or write
* disable the clock input of U13 when reading its contents
* take care of what operation happens during a read or write.
This means we need an extra bit in the Control register to tell the circuit there is (or isn't) a 16-bit DMA transfer going on through the 8-bit 8237 (U2). This bit is fed (through the OR-gate U3c-C1d) to U39a-B3a for disabling the IOxx-lines (see later) and to U20c for disabling the clock inputs of U13 and U14.
U19b-C1d EXORs address line A0 and IOWR:
A0 IOWR | result -----------+--------- 0 0 | 0 0 1 | 1 1 0 | 1 1 1 | 0The output is fed to U3c, the OR-gate mentioned above, which causes that the bit only is effective during a I/O-read or -write when A0=1.
U37a-B1b ANDs the bit with A0 to guarantee A0=0 during the transfer. U43b makes sure that A0 can be tristated if needed.
Remark: the above means we "borrow" the odd address for our own purposes. This means that the above mechanism cannot be used for DMA transfers with an odd I/O-address. (Don't know any, to be honest. Soundcard, SCSI ???)
The Read- and write-signalsThe PC is capable of addressing memory and I/O on a very low level: the CPU has separate read- and write-lines to do that. The 6502-compatible CPU's don't have this feature. Therefore I/O in a system with such a CPU is called "Memory mapped I/O".
As the C64 doesn't have this feature, we have to create it. My original solution was using one bit but Pasi came with the idea to use two: one for writing, the other for reading. This enables one to read from I/O and to write to memory (or the other way around) without the need to toggle the bit between every action.
The 6510 uses the PHI2 signal to tell attached ICs when the data- and address bus is valid. The PC uses the read- and write-signals mentioned above to perform the same trick.
This means we have to transfer PHI2, R/W and the two bits into MEMW, MEMR, IOWR and IORD. My idea is to use a 74LS156 (U16-A4c), a 3-to-8 demultiplexer with OC-outputs.
R/W IOR IOW | function ---------------+---------- 0 0 0 | IOWR 0 0 1 | MEMW 0 1 0 | IOWR 0 1 1 | MEMW 1 0 0 | IORD 1 0 1 | IORD 1 1 0 | MEMR 1 1 1 | MEMRThe OC-outputs enables us to "AND" the outputs with the same functions without using extra gates. Disable the 156 and it behaves like it was tristated which enable us to perform DMA-transfers as the 8237 must be able to control these lines as well. Pull-up resistors make sure of valid levels in all other situations (not drawn).
The C64 must be able to address the 8237's. Being Intel-ICs they need IORD and IOWR as input lines. This means that the 156 must perform its function during an access of one of the 8237's as well. To make sure that IORD or IOWR are activated independent of the state of the I/O-bits, they are ANDed with the two CS-lines, U37d-A5a, U51a-A5a and U51b-A5c.
I already mentioned the fact that a DMA transfer can be made from/to the C64. In that case the MEMx-lines towards the ISA-bus must be disabled to prevent faulty behaviour of attached memory-devices. This is realized by two OR-gates, U42a-A2 and U42b-A2, which OR the MEMx-signals with the output of U16.
The ISA-bus lines SMEMR and SMEMW are only activated if the address is under $100000. The reason for this is to prevent that an old 8-bit card, which only can decode 20 address lines, writes/reads data when in reality an address above $0FFFFF is outputted. This can be realized by decoding A20..A23 using three OR-gates, U54b..d-A2. The output of the last one is combined with the outputs of U42a and U42b using again two OR-gates, U42c-A2 and U42d. The result is fed to SMEMx.
The six generated signals are fed to a 541 buffer, U57-A2a, for several reasons:
* to make sure that the signals are strong enough to drive all the busses
* adapt the timing for DRAM's
* it enables us to disable these signals towards the ISA-bus when:
* MASTER becomes (L)
* the C64 needs to access the 8237's
* reading/writing the D8..15 latch after a 16-bit I/O-access
This is made possible by enabling the 541 through a 4-input NAND-gate, U39a-C4. This NAND-gate is fed by the output of U37d (8237 access), the output of U3c (16-bit DMA), MASTER and the RAS-signal (U8b, see REFRESH).
Remark: the R/W-line has to drive a lot of inputs. IMHO it cannot hurt to buffer it with some left over gates: see bottom.
The Control RegisterThis register supplies some bits for special purposes:
* bit 0: WINDOW: I/O- or memory-transfer, read (I/O = 0) * bit 1: WINDOW: I/O- or memory-transfer, write (I/O = 0) * bit 2: DMA-source (C64 = 0) * bit 3: DMA-destination (C64 = 0) * bit 4: 8/16 bits DMA transfer (8 bit = 1) * bit 5..7: future useAgain I use a pair of 573's, U9-B6 and U10-B6.
U9: C64 -> store Clock: I/O - write "0w" Output enable: (L) U10: read stored value Clock: (H) Output enable: I/O - read "0r"
The REFRESHThis line is used to signal an ISA-card that there is a valid refresh address on the bus. The IBM-AT uses a 74LS590 to do the job. Unfortunately the 590 is difficult to get. But some 393-counters plus buffers will do as well.
AFAIK the PC only refreshes A0..A7 which is enough for the 41256 (256K*1) or equivalents. But this is not good enough for the 44256 (256K*4) and bigger. IMHO it won't hurt the bus to expand the addressrange up to A11 which even allows us to refresh 16 MB modules! Three 393's, UA35a, UA35b and UA36a, all in C2, provide A0..11. These lines are buffered through two 541's, U34 and U38, which are enabled by PHI2.
When do we have to execute a refresh? In a PC the CPU or a DMA-transfer is halted for a moment to allow a refresh-cycle. During any transfer where the C64 is involved, the CPU or 8237 cannot do anything the lower half of PHI2. So my idea is to use this lower halve of PHI2 as the moment to activate the refresh.
How to refresh the DRAM's? The keyword is timing. The 4164-12 64K*1 DRAM needs 120 ns RAS-time, read: access time. But before (or after) it needs an extra 90 ns recovery time. We can archive this by delaying the read/write-signals for the ISA bus by 125 ns. Why 125 ns? This is exactly one complete Dotclock-cycle. In 2 MHz-mode this will leave us with another 125 ns for the RAS.
But this also applies to an ordinary read- or writecycle. An extra condition is that the read- or writecycle ends at the falling edge of PHI2. This simply means that the recovery time has to be situated at any edge of PHI2.
Hardware solution: The Dotclock is fed to an 393 4-bit counter, U36b-C4c, to generate a 4 MHz signal. This clock is fed to two 74's, D-flip-flops (U8b-C4d, U8a-C3c). PHI2 is fed to the Data- and Clear-input of the first 74, U8b. An inverted PHI2 is fed to the same inputs of the second 74, U8a.
The idea is that the 4 MHz signal clocks the state of (the inverted) PHI2 into the 74. The 74 is reset when needed by (the inverted) PHI2. Both signals will look like this:
__ __ __ __ __ __ __ __ __ __ Dotclock: | | | | | | | | | | | | | | | | | | | | __| |__| |__| |__| |__| |__| |__| |__| |__| |__| |__ _____ _____ _____ _____ _____ __ 4 MHz: | | | | | | | | | | |_____| |_____| |_____| |_____| |_____| _____ _______________________ PHI2: | | | |_______________________| |________ ___________ _____________________________ Output /Q: | | | (REFRESH) |_________________| |__ _____________________________ ________ Output /Q: | | | (ACCESS) _____| |_________________|Remark:
* REFRESH is the negative output of the 74, U8a, fed by the inverted PHI2
* ACCESS is the negative output of the 74, U8b, fed by PHI2
In case CLK = 2 MHz, the result is:
__ __ __ __ __ __ __ __ __ __ Dotclock: | | | | | | | | | | | | | | | | | | | | __| |__| |__| |__| |__| |__| |__| |__| |__| |__| |__ _____ _____ _____ _____ _____ __ 4 MHz: | | | | | | | | | | |_____| |_____| |_____| |_____| |_____| _____ ___________ ___________ PHI2: | | | | | |___________| |___________| |________ ___________ _________________ _________________ Output /Q: | | | | | (REFRESH) |_____| |_____| |__ _________________ _________________ ________ Output /Q: | | | | | (ACCESS) _____| |_____| |_____|The signal coming from output /Q named ACCESS is used in the first place to steer any memory-transfer where the ISA-bus is involved: this signal ensures us of enough recovery time after a refresh. This is realized by using this signal to steer U39a, the 541 that buffers the ISA-bus read- and write-lines.
It is essential that the generated 4 MHz-signal and PHI2 run in phase as seen in the above figures. This can be realized by giving the 393 a reset pulse the moment PHI2 becomes low. R3, C1 and U32c-C5 take care of this.
Remark: older PC-cards can be equipped with 150 ns DRAM's. When in 1 MHz, you probably won't experience problems. But in the 2 MHz mode only have 125 ns accesstime and that is not enough! So be warned.
The InterruptsThe ISA-bus outputs 11 active (H) IRQ-signals. The C64 can only handle one active (L) signal. The transformation is done by two 5-input NOR-gates, U50a-D3c and U50b-D4d, an inverter U32f-D3c and a 3-input NAND-gate, U46b-D4c. We also feed the IRQ's to two 541's, U48-D4d and U49-C4b, so the C64 can check which IRQ actually triggered the circus.
To make sure that unused lines aren't treated as an interrupts, we'll pull the inputs low using 3K3 resistors.
Remark for myself: what about using 74HCT541's? Maybe I then can increase the value of the resistors.
This register, U49, reads the state of the first eight available IRQ's: 3, 4, 5, 6, 7, 9, 10 and 11.
Output enable: I/O - read "4r"
This register, U48, reads the state of the last three available IRQ's: 12, 14 and 15. The other 5 bits are reserved for future use.
Output enable: I/O - read "5r"
Some friends suggested to use 8259's, Intels Programmable Interrupt Controller, but I couldn't see any real advantages. IMHO I even needed more hard- and software then with the above simple design.
The other linesIOCHCK
The input IOCHCK is connected to the NMI-input.
This signal tells the CPU to stop. The 6510 has an input for this purpose but unfortunately this input is not provided at the expansion port. Except some cards I built myself, I don't know any commercial card using this line.
The 6510 has no equivalent at all for this input. In fact it is running at "zero wait states" all the time. So we simply do nothing with this input.
I have no idea which cards use this input. For the moment I use PHI2. If needed one can use the DOT-clock.
This output tells the card when the address is valid. Therefore we'll feed this output with PHI2.
This signal is, AFAIK, only used by the CGA-card. We'll feed this output with a signal from a 14.318 MHz crystal-module.
This line will be fed by the C64's RESET-signal through an inverter, U11b-B8a.
A (H) during an IORD or IOWR signals that not an I/O-operation but a DMA-transfer is going on. So when no DMA is in progress, this line remains (L). It is connected to the HLDA-output of the master-8237.
To be connected to the 8237's.
Signal to gain complete control of the bus during a DMA, a card can pull this line (L). Used to disable all bussignals. For the moment I don't know of any card capable of using this line.
Used ICs, codes etc.
code: part: type: function: place: U1 8237 DMA A4a U2 8237 DMS B4a U3 abcd 32 4*2-OR A5d, A7c, C1c, B6a U4 245 bi-directional buffer B7c U5 573 8-bits latch B3c U6 abcd 00 4*2-NAND A6a, A6a, B3a, B7a U7 573 8-bits latch A3c U8 ab 74 D-flip-flop C3c, C4d U9 573 8-bits latch B6c U10 573 8-bits latch B6d U11 bcdef 04 6*inverter B8b, B8b, C8c, C7, C7b U12 573 8-bits latch D2c U13 573 8-bits latch D2d U14 573 8-bits latch D2b U15 573 8-bits latch D2a U16 156 3-to-8 demultiplexer, OC A4d U17 573 8-bits latch A8 U18 245 bi-directional buffer A8 U19 ab 86 4*2-EXOR A7a, C1c U20 abc 11 3*3-AND B7c, D6d, C4a U21 ab 06 6*OC-inverter B6a, D5 U22 abcd 02 4*2-NOR C6b, B6b, B6b, D5d U23 ab 260 2*5-NOR C6b, D7b U24 573 8-bits latch C7c U25 573 8-bits latch C8d U26 abcd 08 4*2-AND D7d, C7b, D5d, C1 U27 573 8-bits latch D8d U28 573 8-bits latch D7c U29 ab 139 2-to-4 demultiplexer A6c, A6c U30 573 8-bits latch C8b U31 573 8-bits latch C7a U32 abcdef 04 6*inverter C7d, C6d, C5d, D3d, A2b, D1b U33 ab 27 3*3-NOR D6d, D6b U34 541 8-bits buffer C2b U35 ab 393 4-bit binary counter C2a, C2a U36 ab 393 4-bit binary counter C2a, C4d U37 abcd 08 4*2-AND B1a, B7a, A5a, B3d U38 541 8-bits buffer C2b U39 ab 20 2*4-NAND B3a, C4d U40 ab 74 D-flip-flop D1, D1 U41 abc 00 4*2-NAND C5d, D7d, D4c U42 abcd 32 4*2-OR A2c, A2c, A2d, A2d U43 abc 125 4*buffer A5a, B1a, B1c U44 154 4-to-16 demultiplexer A6a U45 a 139 2-to-4 demultiplexer D7d U46 abc 10 3*3-NAND C7d, D4d, B7 U47 ab 260 2*5-NOR C6b, D5b U48 541 8-bits buffer D4d U49 541 8-bits buffer C4b U50 ab 260 2*5-NOR D4d, D3d U51 abcd 08 4*2-AND A5a, A5c, A5c, D5b U52 a 32 4*2-OR B5c U53 abc 02 4*2-NOR C6d, C5b, A2b U55 a 260 2*5-NOR D6b U54 abcd 32 4*2-OR C5b, A2a, A2c, A2b U56 a 08 4*2-AND D5d U57 541 8-bits buffer A2a Not used: 00 - 1 02 - 1 04 - 1 06 - 4 08 - 3 27 - 1 32 - 3 86 - 2 125 - 1 139 - 1 260 - 1
Practical remarksAs you can see, only one AND-gate is used of U56. It seems very logical to replace this AND- gate by a NAND-gate and an inverter which are left over.
U52a, an OR-gate, can be replaced by a 02 or 260 (NOR-gates) plus an EXOR-gate.
One EXOR gate can be used to buffer the R/W-line.
You can email me here.