Lo-tech XT-CF Board Technical Reference
Technical Reference manual for the lo-tech XT-CF Board.
CPLD Code Design
The CPLD code has been developed using Xilinx ISE version 13, using schematics, for the XC9572XL CPLD device. The starting point was the code generated by Dangerous Prototypes forum member Pietja, for use with the Dangerous Prototypes XT/IDE v2 board.
ROM_DECODER Module
The ROM decoder matches the PC address bus against the configuration of appropriate DIP switches, providing an asserted (low) signal output when a match is found, that signal being used to enabled the board ROM. It is a direct transcription of a 74521 or 74688 chip, output being disabled when AEN is asserted.
Once enabled, data is transferred from the ROM itself under the control of /MEMR and /MEMW and the low 14-bits of the PC address bus.
IDE_DECODER Module
The IDE decoder matches the PC address bus against two address locations:
- For port-based IO, A5..8 are matched to the configuration set by the DIP switches on the board (by default, base is 300h), A9 being high and A19 being low
- For memory-mapped IO, A12..19 (the upper 8 bits) are matched to the value stored in the memory-mapped IO register (a latch), output being asserted also only if A19 in the latch has been set to 1. A10 and A11 must also be low.
- In both cases, AEN suppresses output when asserted.
If either are matched, CS_IDE is asserted (low). Additionally, CS_IDE_MEM is asserted (low) if the memory-mapped IO address is matched.
Other outputs generate control information, for example to enable the IDE_MUX to provide the controller ID byte when a read to the controller ID port is made.
The decoder also provides mapping of PC address bus lines to the IDE interface address lines, DA0..2, according to the 'Chuck Mod' logic, whereby IDE DA0 is connected to PC address bus A3 (instead of A0, as might be expected):
PC Address Line | IDE Address Line |
---|---|
A0 | - |
A1 | DA1 |
A2 | DA2 |
A3 | DA0 |
This presentation leaves A0 free to be used to switch between low and high bytes of each 16-bit word, thereby enabling faster transfers via word instructions. Otherwise, for example if A3 were used to switch bytes, individual byte transfers and XOR instructions with a bit-mask would be needed, which reduces throughput (significantly).
Note that when CS_IDE is asserted based on memory-mapped IO address match, DA0..2 are held low; because of this the memory-mapped function can be used only for sector data transfers.
IDE_MUX Module
The IDE mux module provides the interface between the 8-bit PC bus, PCBD(7:0), and the 16-bit IDE bus, presented in two 8-bit components as IDE_DH(7:0) and IDE_DL(7:0). The mux works by storing a byte in a latch when required, then subsequently presenting that byte either to the PC data bus (for reads) or the IDE interface (for writes, concurrently with the data from the PC data bus to make up the required 16-bits).
The mux component also provides the logic to present the device ID byte (via port Base+0Fh) and a latch that can be loaded with the memory-mapped IO base address (also via port Base+0Fh).
The mux is enabled by IDE_DECODER module output CS_IDE, being asserted if either memory-mapped or port-mapped addresses match the configured ranges.
Since IO operations are addressed by A0..A9 and memory operations are addressed with A0..A19, use of the /MEMR and /MEMW signals (for memory-mapped IO) must be checked against the memory mapped base address specifically (given by CS_IDE_MEM). Without this check, a memory read matching the IO port range in A0..A9 could trigger some IDE function. The reverse test is not necessary for /IOR and /IOW, since A10..A19 will always be low during IO operation, hence the CS_IDE_MEM signal could not be asserted concurrently.
Port Based IO
The base port address (Base) is configured via the DIP switches. Since the logic uses A0..A4, 32 consecutive ports are used (Base+00h to Base+1Fh).
Base+ | Function |
---|---|
00h | Sector transfer (read low-byte) |
01h | Sector transfer (read high-byte) |
02h to 0Eh | IDE functions (note address line mapping) |
0Fh | Read controller ID byte or set memory-mapped IO base address |
10h | Sector transfer (write low-byte) |
11h | Sector transfer (write high-byte) |
12h to 1Fh | IDE control register access (note address line mapping) |
The controller uses A0 to determine if the low or high order byte is being transferred, and A4 to determine if the operation is read or write:
- For reads, A0 inverts IDE /CS0 and /CS1, such that the read command is driven on to the IDE bus only when accessing port Base+00h. When A0 is asserted, the two CS lines are switched, the three IDE lines DA0..2 remaining low, yielding a no-op as viewed by the IDE device. This allows the high order byte to be transferred from the latch to the PC data bus.
- For writes, the operation of the IDE /CS0 and /CS1 inversion based on A0 is itself inverted by A3 being asserted. That is, the IDE device sees the write to port Base+10h as a no-op, allowing the low order byte to be stored in the latch, and the write to port Base+11h as a valid write. The IDE word write will consist of IDE_DL(7:0), being presented from the latch, and IDE_DH(7:0), being presented from the byte on the PC data bus.
Controller specific functions:
- Reading from port Base+0Fh returns the controller ID byte, a fixed value coded into the CPLD logic:
- 03h = Port-based 'enhanced' operation (word-transfer logic)
- 04h = As type 03h, but also with memory-mapped CPLD logic
- The original board and the 'standard' CPLD code do not return a specific value from this port
- Memory-mapped IO is enabled by setting the high 8-bits of the required memory-mapped base address via writing to port Base+0Fh, and disabled by writing a value of 0 to the same port. The function is disabled at power-up, and can be enabled and disabled on-the-fly.
Memory Mapped IO
Memory mapped IO is a method of transferring data from a device as though it were system memory (so using A0..19 and /MEMR and /MEMW signals, instead of the /IOR and /IOW signals used for port-based IO). This technique offers a performance advantange on the 8088 CPU, since rep movsw can be used (25 cycles/word) instead of seperate load, store and loop instructions (30 to 48 cycles/word, depending on loop unrolling). (Note that 8-bit ISA bus timing is the same for both methods.)
IDE control and enquiry functions are always completed through the IO ports as detailed above, but sector data can be transferred by either port-based or memory-mapped IO (provided the controller ID port returns 04h).
To enable memory-mapped IO, the high 8-bits of the required memory-mapped base address are loaded through port Base+0Fh, noting that the MSB must be asserted. The card will then enable transfers through that base address in two ranges within a 4KB aligned 1KB block:
Base+ | Function |
---|---|
0000h to 01FFh | Read window |
0200h to 03FFh | Write window |
Only the transfer of sector data can be performed; IDE DA0..DA2 are held low during such IO. The transfer must be made in asceding sequence since words will be committed to (or read from) the IDE interface synchronously (the card buffer is only one byte, in the IDE_MUX module).
- As with port-based IO, the controller uses A0 to determine if the low or high byte is being transferred
- A9 is used to determine if the operation is read or write
- A1..A8 are not monitored, allowing the CPU to cycle though the 512 bytes in asceding order starting at zero, using rep movsw
For example, if the IO transfer window is to be D800:0000, send D8h to port Base+0Fh. Data can then be read from D800:0000-01FF, or written to D800:0200-03FF.
Once the transfer has been completed, memory-mapped IO function can be disabled by writing zero to port Base+0Fh. Note that port-based IO can be used regardless of whether memory-mapped IO is enabled or not.
Programming Examples
The following code examples illustrate block-mode transfers (i.e. of 1 to 128 sectors) for either port-mapped or memory-mapped controllers. For the sake of simplicity, the memory-mapped base address is hard-coded where required.
Block Read Example
; Block read routine, with memory-mapped IO if supported
; On-entry:
; DX : IDE controller base port (low 4-bits always zero)
; CX : sector count (max 128)
; ES:DI : target memory buffer address
;
; Corrupts: AX, CX, DX
or dl, 0x0f ; set DX to conntroller ID port (base+0Fh)
in al, dx ; get controller type
cmp al, 4 ; 4 = memory-mapped capable
jne .PortBasedRead ; use port-based IO if not type 4
mov al, 0xd8 ; set AL to high 8-bits of mem-mapped IO base...
out dx, al ; ... send high 8-bits of segment address to controller ID port ...
xor ah, ah ; ... and clear AH ...
xchg al, ah ; ... and swap AL/AH - AX now contains base address for reads
push ds ;
push si ; save DS:SI
mov ds, ax ; move segment address in AX to DS
mov ax, cx ; store total sector count in AX
xor cx, cx ; clear CX
.ReadTransferLoop: ; sector-by-sector transfer loop (256 words each iteration)
; note CX is zero at start of each iteration
mov si, cx ; clear SI; DS:SI now has mem-mapped IO base address
inc ch ; memory-mapped window is 512 bytes, CX now has 256 (words)
rep movsw ; do the transfer from [DS:SI] to [ES:DI], count in CX (256)
dec ax ; reduce number of sectors still to get
jnz .ReadTransferLoop ; repeat, if there's more to do (zero flag set by dec)
out dx, al ; we're done; disable memory-mapped IO (AL will be zero)
pop si
pop ds ; restore register values
ret
.PortBasedRead:
xchg cl, ch ; Sectors to words (SHL 8)
and dl, 0xf0 ; restore value in DX (the read IO port)
shr cx, 1 ;
shr cx, 1 ;
shr cx, 1 ; words to octa-words (16 bytes - shr 3)
.ReadNextOctaword: ; port-based IO code follows, unrolling improves throughput
in ax, dx ; Read 1st word
stosw ; Store 1st word to [ES:DI]
in ax, dx
stosw ; 2nd
in ax, dx
stosw ; 3rd
in ax, dx
stosw ; 4th
in ax, dx ;
stosw ; 5th
in ax, dx
stosw ; 6th
in ax, dx
stosw ; 7th
in ax, dx
stosw ; 8th
loop .ReadNextOctaword ; 17 clocks on 8088 hence unrolling
ret
Block Write Example
; Block write routine, with memory-mapped IO if supported
; On-entry:
; DX : IDE controller base port (low 4-bits always zero)
; CX : sector count (max 128)
; ES:SI : source memory buffer address
;
; Corrupts: AX, CX, DX
push ds ; save DS
mov ax, es ;
mov ds, ax ; move ES to DS via AX; DS:SI now pointer to source memory buffer
or dl, 0x0f ; set DX to conntroller ID port (base+0Fh)
in al, dx ; get controller type
cmp al, 4 ; 4 = memory-mapped capable
jne .PortBasedWrite ; use port-based IO if not type 4
mov al, 0xd8 ; set AL to high 8-bits of mem-mapped IO base...
out dx, al ; ,.. send high 8-bits of segment address to controller ID port ...
mov ah, al ; ... mov AL to AH ...
mov al, 0x20 ; ... and set AL to write offset. AX now contains write base address
push es ;
push di ; save ES:DI
mov es, ax ; move segment address in AX to ES
mov ax, cx ; store total word count in AX
xor cx, cx ; clear CX
.WriteTransferLoop: ; sector-by-sector transfer loop (256 words each iteration)
; note CX is zero at start of each iteration
mov di, cx ; clear DI - ES:DI now has mem-mapped IO write base address
inc ch ; memory-mapped window is 512 bytes, CX now has 256 (words)
rep movsw ; do the transfer from [DS:SI] to [ES:DI], count in CX (256)
dec ax ; reduce number of sectors still to write
jnz .WriteTransferLoop ; repeat, if there's more to do (zero flag set by dec)
out dx, al ; disable memory-mapped IO (AL will be 0)
pop di ;
pop es ;
pop ds ; restore register values
ret
.PortBasedWrite: ; Port-based IO routine follows
xchg cl, ch ; Sectors to words (SHL 8)
shr cx, 1 ;
shr cx, 1 ;
shr cx, 1 ; words to octa-words (16 bytes - shr 3)
xor dl, 0x1f ; restore value in DX to Base+10h
.WriteNextOctaword: ; port-based IO code follows, unrolling improves throughput
lodsw ; Load 1st word from [DS:SI]
out dx, ax ; Write 1st word
lodsw
out dx, ax ; 2nd
lodsw
out dx, ax ; 3rd
lodsw
out dx, ax ; 4th
lodsw ;
out dx, ax ; 5th
lodsw
out dx, ax ; 6th
lodsw
out dx, ax ; 7th
lodsw
out dx, ax ; 8th
loop .WriteNextOctaword ; 17 clocks on 8088, hence unrolling
pop ds ; restore DS to entry state
ret