lgb.hu :: Commodore 65 related stuffs :: C65 DMA F018

Warning, work-in-progress, probably incorrect information! Also because of having so few available real C65s (I have none) and still being in differect development stage, there can be even more differences than just those two revisions I know (F018A and F018B). This page also tries to describe the behaviour of the DMA on the Mega-65, which is a third case (in its native DMA mode, but surely it can act as F018A and F018B too.

C65 DMA controller (F018), with some Mega65 information as well

F018 DMA controller uses an in-memory structure to control DMA operations, called "DMA list". The list consists of one or more "DMA entries". If CHAIN bit is set inside the entry, there is another entry in the list, if clear, it was the last (or the only one). There are at least two known incompatible versions of F018 DMA controller in real Commodore 65s; called F018A and F018B. Even the size of the DMA entries are different, F018A uses 11 bytes/entry, F018B uses 12 bytes/entry. In case of F018A subcommand byte is missing. Also, the interpretation of certain bits in other bytes can be different as well between F018A and F018B!

A Commodore 65 contains either F018A or F018B, it's nothing to do with this fact. On Mega-65 for compatibility, its DMA implementation can act as both of F018A and F018B, also it has an own mode too, called "enhanced mode DMA".

DMA list entry description

Because of the relative complexity of the differences and similarites, it's probably the best to show them in the form of a table. This table does not contain the enhanced DMA mode options for Mega65, for those, please read the chapter "Mega-65 enhanced mode DMA".

Byte summary Interpretation
bit7bit6bit5bit4bit3bit2bit1bit0 bit7bit6bit5bit4bit3bit2bit1bit0
F018A F018B
M65+enh.DMA only! In case of enhanced mode DMA on Mega-65, here is the variable length enhanced mode option list (see later). Enhanced mode DMA can use either F018A or F018B depends on the options.
Command MINTERMS INT CHAIN CMD ? ? DST_DEC SRC_DEC ??INT CHAIN CMD
DMA count low or COL DMA operation length (0 probably means 64K) as a single 16 bit counter
OR in MODulo (either SRC_MOD or DST_MOD is enabled) mode it probably forms two separated 8 bit counters COL/ROW
DMA count high or ROW
DMA source, low DMA source addr, bits 15-0
low byte also acts as the fill value with FILL cmd (when source is not used anyway!)
DMA source, mid
DMA source, hi + etc SRC_IO SRC_DEC SRC_MOD SRC_HOLD DMA source addr bits 19-16 SRC_IO SRC_BANK (bits 22-20) DMA source addr bits 19-16
DMA target, low DMA target addr, bits 15-0
DMA target, mid
DMA target, hi + etc DST_IO DST_DEC DST_MOD DST_HOLD DMA target addr bits 19-16 DST_IO DST_BANK (bits 22-20) DMA target addr bits 19-16
F018B-only subcommand !!MISSING HERE IN CASE OF F018A!! ??MAYBE MINTERMS DST_HOLD DST_MOD SRC_HOLD SRC_MOD
Modulo, low Function is not enterly known, but bytes must be here
My best guess: in MODulo mode, count COL bytes transfered, then this modulo 16 bits value is added to
the addr or source (what is in MODulo mode), and the whole process repeated ROW times
Modulo, high
List entry size information summary (excluding M65 enhanced mode option list, in case of M65+enh. DMA mode)
11 bytes / entry 12 bytes / entry
Here, if CHAIN bit was 1, a new entry follows with the same fields, etc (in case of M65+enhanced DMA, starting with the option list again)

Notes

Description of fields

Unknown factors of C65 / Mega-65 implementation / Xemu

It's higly unknown (should be tested on real C65s) what features are more planned and what actually worked. These (maybe not complete) list includes (as also noted above):

Mega-65 enhanced mode DMA

Mega-65 can play the role of an F018A and F018B too. This can be important, as some Commodore 65 models use ROMs depending on F018A behaviour, but later one on F018B already. Moreover, as Mega-65 has more memory and needs other fancy features as well, for greater flexibility it has its own DMA mode as well called "enhanced mode DMA". To know, how to trigger "classic" DMA (and select F018A/F018B), and enhanced DMA, please see the DMA register description chapter below.

However, it's important to tell, that enhanced mode DMA extends the structure of the DMA entry itself, I mean: on Mega-65 in enhanced DMA mode, there is always a "preamble" before the list itself (if the CHAIN bit set, even before the next one, so, before every entries the table shows above). It's called "enhanced DMA option list". This is a list of options to be applied in addition of the information of the table entry itself. During a chained operation, the next entry "inherits" the option setting from the previous, but the very first one opens with no option setting (even if there was a DMA session before some time with some option).

The enhanced mode option list - as the name suggests - is a (non-fixed sized) list. It can contains various options in the form of identifier bytes. Some options has a byte sized parameter as well, some of them has no parameters. The enhanced mode list is closed by byte 0. The minimalistic enhanced mode DMA list contains only a zero. But please note, that it's not recommanded at all, since Mega-65 should know, you want to use F018A or F018B behaviour and table construction after the option list. Without specifying it, the default is the global setting, which can be different if you try your program later with another ROM probably, so it's always highly recommended to specify the DMA list type! Surely however, in case of chained DMA, the chained option list can be just a zero byte, if there is no difference with used options, as those are inherited within a single chained DMA session at least.

Enhanced mode DMA options:

Skip rate allows you to do fractional steps, handy for example "texture zooming". The default settings are of course "1", which means 1 for integer part, and 0 for fractional part.

Megabyte settings select the higher 8 bits (A27-A20) for the address, what F018A misses (it can directly address only 1Mbyte). In case of F018B, it has some "BANK" part, but not enough for the whole Mega65 memory space (28 bit), thus in that case, the "high parts" are added. The default setting for these are of course zero.

Transparency is a mode, when a given value on writing (destination) is considered to be "transparent" (like being a "colour key" for example), and thus not written, if enabled. The default behaviour is disabled transparency of course. If you whish to use, you should also set the value up, and set it to "use". The "disable" is there, because you may want to enable it for only a list in a chained flow of lists during a chained DMA session.

My crazy idea: I would introduce enhanced DMA list option '1'. It would mean the really same as '0', end of list, but it also specifies that modulo word is NOT in the F018A or F018B table (for the next entry only, so if chained, the next enhanced option list can also use 0 or 1 to close the enhanced option list) so we can save two bytes, but still leaving the possibility open, that modulo will be implemented some day on M65, so we don't want to always ignore it ...

DMA commands

The CMD field of the DMA entry controls the DMA operation meant for the current DMA entry of the DMA list. The commands are:

Cmd # Description Acc
COPY0 this mode causes F018 to read byte from the source and write it to the target 2 (1R+1W)
MIX1 this mode (GUESSWORK!) read the source and the target(!) doing a logic operation between them according to the MINTERMS value, and writes the result to the target (that is: it "mixes") 3 (2R+1W)
SWAP2 reads both of source and target, and writes back exchanged the values 4 (2R+2W)
FILL3 fills the target with a single byte given by the source addr low byte (so then, source is not used like an addressing counter anymore, not even incremented/decremented of course) 1 (1W)

# = CMD value in the DMA list, Acc = number of Writes and Reads needed for the given command by iteration.

MIX command and MINTERMS (guesswork only!)

I have not so much idea about the minterms and the MIX command on F018, but according to the specification is the following:

DA
0 1
SA 0 result = !SA & !DA
if MINTERMS[0]
result = !SA & DA
if MINTERMS[1]
1 result = SA & !DA
if MINTERMS[2]
result = SA & DA
if MINTERMS[3]

What this wants to mean - I guess - that SA and DA are read bytes pointed by source and destination (target) counters, having two bytes, and for each bits, some logical operation is done before the result written back to the memory pointed by the target counter. The table shows the exact logic operation for each bits. The decision if a given logic operation between read data bytes are done or not is given by the MINTERMS parameter in the DMA list.

If more bits are '1' in MINTERMS, (guessing only!) the result will be the OR operation of the given enabled (by MINTERMS bits) statements can be seen in the table above. So, my guess is the following (highly unoptimized) pseudo-code:

loop for DMA length {
    BYTE_VAR sa := read_dma_data(source_counter);
    BYTE_VAR da := read_dma_data(target_counter);
    BYTE_VAR result := 0;
    if (minterms[0])
        result := result BITWISE_OR ((BITWISE_NOT sa) BITWISE_AND (BITWISE_NOT da));
    if (minterms[1])
        result := result BITWISE_OR ((BITWISE_NOT sa) BITWISE_AND (            da));
    if (minterms[2])
        result := result BITWISE_OR ((            sa) BITWISE_AND (BITWISE_NOT da));
    if (minterms[3])
        result := result BITWISE_OR ((            sa) BITWISE_AND (            da));
    write_dma_data(target_counter, result);
    // source and target addressing counter update according to hold, direction and skip rate (on Mega65)
    // probably even MODulo where it is supported
    maintain_dma_source_channel_addressing();
    maintain_dma_target_counter_addressing();
}
Actually the above can be done a more compact way, if we assume that minterms[0..3] contains bytes $FF if the given MINTERMS bit is set, and 0 if not. Then a single step (without conditional branching 'if'):
write_dma_data( target_counter,
    ((BITWISE_NOT sa) BITWISE_AND (BITWISE_NOT da) BITWISE_AND MINTERMS[0]) BITWISE_OR
    ((BITWISE_NOT sa) BITWISE_AND (            da) BITWISE_AND MINTERMS[1]) BITWISE_OR
    ((            sa) BITWISE_AND (BITWISE_NOT da) BITWISE_AND MINTERMS[2]) BITWISE_OR
    ((            sa) BITWISE_AND (            da) BITWISE_AND MINTERMS[3])
);

It's an interesting question if my guess is correct at all. And even if it is, how a real C65 would react. Also, there is no information this too much with the F018B ... This MINTERM would be nice to have anyway, since it allows to build many bitwise operation to be done quickly then by F018 on larger chunk of memories. I guess, it's even possible to mark source channel as HOLD, so a single byte can be used to apply logic operation for a chunk of memory with that. It's even more interesting what would happen if you combine MINTERMs using MIX command with MODulo mode set :) :)

Currently - AFAIK - Mega-65 does not support MIX. However if it does ever, it's interesting to think about combining MIX and transparency. Well, or even modulo :-)

DMA registers

Most information needed for DMA operations are fetched by F018 from the memory, in the form of DMA entires of the DMA list as we could see. However, there are some registers to set the memory address of the DMA list itself, for example. For sure, $D70X registers cannot be accessed in VIC-II I/O mode. And maybe, Mega-65 registers cannot be accessed outside of VIC-IV mode (FIXME?).

Register R/W Description Comments
Common (C65+M65) registers
$D700 -/W DMA list memory address low byte, writing this byte also starts the DMA list execution, so you should write this last! Selects the memory address of the DMA list itself.
Write-only registers, at least on C65 (M65?).
$D701 -/W DMA list memory address middle byte..
$D702 -/W DMA list memory address high byte
On Mega65, writing this register will cause to clear Mega65-specific register $D704 as well. Write only register (on C65 at least).
$D703 R/- Read-only register for DMA status. Quite useless alone, it would make sense only, if a DMA transfer can be really interrupted (see the description of bit INT in the DMA list) Read-only, but on M65, you can write it (see below).
Mega-65 only registers
$D703
(again!)
R/W bit#0: if set, F018B DMA mode is used, otherwise F018A
This register seems to be the same as the status (read-only) register on C65 though, but we write that register here. This register selects the DMA mode between F018A and F018B for "classic" DMA mode, and selects the default mode in "enhanced" DMA mode (there it can be overriden then with the options).
Logically, >$D703 registers can be accessed only (?) in M65 mode, but
what's about this register, write is disallowed in VIC-III mode?
$D704?/W"megabyte slice" of start of the DMA list, extension of $D700-$D702
$D705?/WThis register triggers an enhanced mode DMA. It works the same way as register $D700, it tells tells the low byte of the address of the DMA list (in case, it's the options part!), the difference that this triggers an enhanced mode DMA session, while register $D700 triggers a "classic" (C65) mode DMA session.
$D70E ?/W The same as $D700, however writing this register on Mega65 does not cause to start the DMA. this is primarily for internal use, not so much for the user

Counters

It is beleived, that every counters have only 16 bits, ie warps around within the specified 64K area. It's maybe true for all counters, ie the DMA list counter itself (controls the fetch of the DMA list/entries), source and target address counters. In case of Mega65, there are special registers to select upper bits for the addresses.

DMA list read counter is simply used by F018 to fetch the entries/bytes of the DMA list. It's always an increment by one counter of course. While source/target registers are special, as they can be incremeneted, decremented or hold. On C65, this can be set by the DMA list entry corresponding bit fields (SRC or DST_DEC and _HOLD). The same is true for Mega65, however you can use non-one byte steps as well, or even fractional steps, see the register description above. Still (I guess) the C65-std SRC_DEC and DST_DEC (and also HOLD) bits are important on Mega65 as well, to tell, you need to increment/decrement (or hold ...) the given counter with custom skip rate.

MODULO (guesswork only ... though starting from the C65 specification)

There is not so much actual information on this topic. However we have some place to guess here. According to the C65 specification, the DMA length (also called "count") is a 16 bits value, describing the length of the DMA operation for the given DMA entry. However, interestingly, low byte of this length paramter is also called "COL", and high byte as the "ROW". The DMA list entry also contains a seemingly simple 16 bit value of modulo.

From this information (for me at least) the logical operation seems to be this: if modulo is used for source or target (SRC_MOD or DST_MOD bit) the operation with source or/and target can be the following:

Process "COL" bytes normally, then add (or substract, based on the direction? It's not clear, as the modulo being 16 bit, using it as 2-complement value it will still work, using only addition!) the value of the modulo to the counter. Repeat the whole process "ROW" number of times. That is, length/count parameter in modulo mode used to count actually two things: it works like two separated 8 bit registers (this is also suspect, because of the COL/ROW notion mentioned in the specification). It makes sense, since this can be the reason to also name COL/ROW in the specification. Without modulo mode, these two bytes forms a single 16 bit wide register, nothing special, and modulo information at the end of the DMA list entry is simply not used at all.

What is interesting here, that source and target can be switched into modulo mode independently. But if modulo mode is activated, the length (count) parameters is handled differently with and without modulo mode. So what kind of meaning the only len/count parameter has, if we have different modulo mode (on for one, off for the other) is used? I guess, it's always the modulo mode interpretation is done, if one of the channel has modulo mode turned on! This simply means, that eg source can be a linear addressed memory area, while target is in MODulo mode. In this case, because either of source or target is in modulo mode, the COUNT/LENGTH parameter of the DMA list should be meant in COL/ROW counter mode. At least logically this means that. It's an interesting question what happens, if FILL DMA command is used, and only source is switched into MODulo mode. In this case (FILL), source is really not used at all. But according to the rule, that either source or target's MOD bit causes to use modulo mode for the count/length field, it seems it will be, however it makes not so much sense. So in nutshell, with modulo mode, the actual steps of the DMA operation is simply ROW * COL bytes.

Crazy FILL idea ...

FILL command uses source address lowest byte as the fill value to write the target. I was thinking on this. Normally with FILL mode, the source address of course not incremented since it's not even an address in this mode (thus like source HOLD would be set). However what happens, if we would allow to also increment the source address? Especially with M65 enhanced mode DMA - fractional step? Then it would allow to 'render' memory area with a 'gradient' where the FILLed value changes at the rate dictated by the the source skip rate registers! Now, that's a really interesting topic. In theory it can be used to runder even colour gradient, if palette is programmed that way, that it's really a linear transition between some colours/shades.