Monday, January 12, 2009


X08 banking specification

The X08 banking system is a new method of banking which nominally allows 32K of ROM and 8K of RAM. Because the cartridge must contain an operating system in addition to the user code, not all of the ROM and RAM will be available.

From a hardware standpoint, ROM is stored at addresses $00000000-$00007FFF and RAM is stored from $40000000-$400001FFF. As noted above, the first part of RAM and ROM will be used by the operating system. The emulator wouldn't have to worry about the OS RAM and ROM requirements, though it may be helpful to provide a 'trap' if a RAM address is written illegally. I'm not sure the best way to indicate what RAM areas should be considered legal and illegal, though.

Because the X08 controller is microprocessor based, it allows some features in addition to its rather generous RAM allocation. Most notably:
  1. It allows the 6502 address space to be subdivided into four 1K zones which can be moved on byte boundaries anywhere within RAM or ROM address space.
  2. It provides high-speed block copy operations; future versions of the specification may allow other special operatons as well. The cartridge will clock out NOPs while such an operation is in progress, and then clock out a JMP to the instruction following the instruction that triggered it; thus, while such an operation will take time, the user need not worry about waiting for it to complete.

The X08 banking specification is in some regards trickier than others, because the main controller is itself another microcontroller which can be in a number of different states at the start of each memory cycle. Much of this will usually be transparent to the person writing code for it, but to minimize the likelihood that code will work beautifully in emulation and then fail in weird and bizarre ways on actual hardware, an emulator should try to mimic these behaviors as closely as possible.

The following is a list of cartridge states. Note that at the end of each cycle, the cartridge may be outputting a byte of data or may be floating the bus. If the processor attempts a read while the bus is floating, the data bus contents should be randomized (on real hardware they may be unpredictable). If the processor attempts a write while the bus is being driven, that should probably cause a fatal error except in "inject" state. Note also that except when the processor is in busy state, any bus cycle which uses the same data as the previous one will be ignored.

Normal State
This is the most common state of the system. Each address is categorized into a 1K block, and then processed appropriately. A more detailed description appears below. This state may progress into many of the other states, based upon the address accessed.
Simple-Fetch State
If an access is not in cartridge space, it will be ignored. Otherwise the system will assume that the address is a normal read with the same memory-bank offset as the previous read. This has two main consequences:
  1. The first byte of the instruction which follows a bank switch will be fetched from the page that was selected before the bank switch occurred.
  2. If an instruction which triggers a bank-switch appears as the last byte of a bank, the first byte of the following instruction will be accessed from the byte following the end of the bank from which the instruction had been fetched. If the banks point to consecutive regions of memory, this will be as expected. If not, it may cause weird behavior.

In any case, the system will revert to Normal State following a Simple-Fetch access.
Simple-fetch-and-handle-write State
Because certain write operation trigger actions which cannot be undone by simply writing new data, the system will latch the address and data bus on each cycle until it sees a change in the address bus other than at bits 8 and 9. The system will then process a simple read, and then process the write. Note that indexed writes may safely cross page boundaries, but not bank boundaries. This state reverts to Normal State.
Enter-Busy-State State
The system will wait for a change in the address bus other than at bits 8-9, and then examine bit 12. If bit 12 is set, the system will drive $EA (NOP) onto the bus and queue up $4Cxxxx for injection state (where xxxx is the fetched address). The system will then perform a function based upon the previous access (said function will probably set busy_cycles, since the purpose of this state is to handle operations that will take multiple 6507 cycles to complete).
Busy State
The cartridge will keep floating the bus or driving it with a constant value until a specified number of cycles have elapsed. This state takes priority over all others, and it is recommended that an emulator use a "busy_cycles" counter; if it's non-zero, decrement the counter and perhaps check for bus contention (an attempted write while the cartridge is driving the bus) but otherwise do nothing. Busy State may progress to either Normal State or Injection State.
Injection State
Each time the address bus changes and the new address is in cartridge space, output a byte from the "injection queue". The injection queue allows up to four bytes to be loaded provided the last is not zero. The suggested implementation is to store all the bytes in a 32-bit unsigned integer. After each address change, if the address is in cartridge space, output the least significant byte and shift the integer 8 bits to the right. Once all data are shifted, the system will progress to Normal State.

As noted above, the system address space is divided into eight 1K blocks. The functions of each block are as follows:
$0000-$03FFSome key registers at $007x, $02xx, and $03xx, as described below.
$0400-$07FFOne page for each bank--Select mode for a bank based upon LSB of address
$0800-$0BFFOne page for each bank--Add LSB of address to bank pointer
$0C00-$0FFFOne page for each bank--Subtract LSB of address from bank pointer
$1000-$13FFUser Bank 0
$1000-$13FFuser Bank 1
$1000-$13FFUser Bank 2
$1000-$13FFUser Bank 3

Each of the three User Banks may be independently programmed to use any of the following modes:

RAM/ROM read
Read a specified address from RAM or ROM, in typical ordinary fashion
RAM write
Writes a specified address to RAM. Note that indexed writes are acceptable, even if they cross page boundaries within a bank. Reads will return arbitrary data, and a read which is followed by any address other than a page wrap will "accidentally" store arbitrary data. RAM writes will use the Simple-Fetch-and-Handle-Write state.
Tethered read
Like a normal read, except that the base address of bank 0 is added to the address for the current bank.
Tethered write
Like a normal write, except that the base address of bank 0 is added to the address for the current bank.
Set Function Pointer
Performs a RAM/ROM read, but also sets the 'Function Pointer' to the address accessed (in RAM or ROM).
Byte Copy
Storing a byte to a bank in Byte Copy mode will perform a block copy from the Function Pointer to the destination address. The number of bytes copied will be the value stored, plus one. This will use the Enter-Busy-State state; the system will be busy for approximately 2+(bytes/8) cycles. Reading a byte in a copy-mode bank may cause unpredictable behavior.
Word Copy
Word Copy mode is similar to Byte Copy mode, except that (1) the specified value is the number of 16-bit words to copy, minus one (instead of bytes), and (2) both source and destination addresses must be even. Words are copied at the same loop rate as bytes (so 2+(words/8) cycles).
Longword Copy
Longword Copy mode is similar to Byte Copy mode, except that (1) the specified value is the number of 32-bit longwords to copy, minus one (instead of bytes), and (2) both source and destination addresses must be multiples of four. Words are copied at the same loop rate as bytes (so 2+(longwords/8) cycles).
Indirect Read
A byte is read from RAM at the specified address, and the upper 7 bits are used to select an entry in a table of 128 16-bit addresses pointed to by the Function Pointer. The value at that address in RAM is then fetched and given to the 6507. If the original byte read was odd, the address in the table will be incremented. Code may be executed from a bank in this mode (indeed, that's the whole purpose!) provided that it does not perform any writes to the cartridge nor perform any bank switches.

Key bank-switch addresses:

Note that any access to a bank-switch address will cause the following cart-space address to be performed in Simple-Fetch state.

Tuesday, January 06, 2009


Silly poker odds trivia

A few interesting (though not terribly useful) observations about poker odds; I believe these are correct, though I might have missed something.

  1. A player holding pocket aces always has the best of it pre-flop when playing against three opponents, no matter what the opponents hold. Against four opponents, it's possible for the player not to have the best of it, but he can't be a very big dog (I think the worst-case is AcAd vs AhAs KcKd Tc9c 7d6d, with all the non-ace opponents holding the player's suits; that yields a pot equity of over 18.118%). If there are opponents who fold pre-flop, the cards they render dead may shift the odds. If there are six people in a hand to start, two of whom have pocket aces, and three of the players fold pre-flop, it's possible for the player who doesn't have pocket aces to be the only one to have over 33.3% pot equity.

  2. A player holding pocket aces could theoretically be almost totally dominated (less than 0.8% pot equity) when playing against nine opponents, but I believe there are only one arrangement of cards (given player's suits) which would push pot equity below 1%. If player holds AcAd, opponents hold AsAh and 6c6d-KcKd. The player has zero chance for an outright win, and less than 1.6% chance for a chop (most chops are 2-way). Changing any opponent's card adds many outs. For example, changing the 6's to 5's would allow 2345x (x > 6) as a winning out, with 126 suit combinations and 14 possibilities for x, in addition to adding some full-house outs. Without the change, the player's only chances for an out would be two-way chop with a four-of-a-kind 2-5 (remaining card anything) or else a hand with all cards in the range 2-5 (roughly 4,368 out of 201,376, though some of those double-count four-of-a-kinds or else lose outright), or a six-way chop with a straight flush (6-Q high in hearts or spades),

    It's interesting to note that even the opponent with the worst hand equity (the 7's) dominates the player by a factor of over 7.5 despite having six open overpairs; it's also interesting to note that the opponent with pocket aces has the best of it, with slightly over 10% hand equity.

  3. A player holding pocket kings against seven opponents could be drawing stone cold dead (e.g. against KK AA AA TT TT 55 55) but I don't know if there's any scenario with six opponents where the player wouldn't have any chance for even a chop. Note that the player would be drawing dead even if six of the opponents fold (any only the player with dominating aces stays in). The permutation of cards among those six players would be completely arbitrary.

  4. Against four opponents, pocket kings may have a hand equity well below 0.1%. Player holds KcKd; opponents hold AcAd, AhKh, AsKs, 8c7c. The player can win outright only with 9TJQx in clubs or diamonds (76/850,668 possible boards); the only other outs, for a five-way chop, are straight flushes (6-J high, in hearts, spades, or diamonds) or straights (8-J high, in suits that don't give opponents a flush).

Most of these observations should not be even remotely considered in actual play (it would be foolish to fold pocket aces on a ten-person table for fear that one might be totally dominated) but I find them all interesting from a mathematical perspective.

This page is powered by Blogger. Isn't yours?