Version |
Origin Date |
Features of Note |
0.1 |
2010/08/01 | A) Coded to run entirely in the 65C816's native mode. B) Fully functioning machine language monitor. |
0.2 |
2011/07/06 |
A) Converted TIA-232 I/O from polled to interrupt-driven. B) More stringent RAM testing, especially stack and zero page. C) Many small but important refinements, including a substantial code shrink. |
0.4 |
2011/10/01 |
A) Added SCSI subsystem functions (coincided with construction of a SCSI host adapter). B) Modified reset to enumerate the SCSI bus & construct a device table in RAM. |
0.5 |
2012/04/28 |
A) Enabled the BIOS to load a boot block from a SCSI disk. B) Added an alarm function analogous to alarm() in Linux or UNIX. |
0.7 |
2012/06/20 |
A) Completely redesigned the part of the monitor that translates 65C816 mnemonics to opcodes & back. |
0.8 |
2012/08/11 |
A) SCSI driver primitive rewritten to use IRQs and the controller's DMA handshake functions. |
1.0 |
2012/09/07 |
A) First "production" version. |
BIOS
The BIOS proper consists of TIA-232 I/O primitives, SCSI subsystem driver API, SCSI bus I/O primitives, and interrupt service routines (ISR). The hardware IRQ ISR is the most complex, as all I/O is interrupt-driven. The IRQ ISR also processes the 32 bit uptime counter, a 16 bit programmable delay feature and an alarm feature, all of which are slaved to the 100 Hz jiffy IRQ generated by the watchdog timer. All useful BIOS functions are accessible through a jump table that starts at $00E000. Also part of the BIOS is a set of vectors located at $000100 that allow a program to "wedge" into the various interrupt handlers and modify or replace that part of the ISR. A SCSI primitive indirect vector is located here, as well as two user-definable vectors at $00010C and $00010E.
POST
POST is an acronym for Power-On-Self-Test, which is something that virtually all modern computers execute at start-up. POC's POST is modeled on contemporary practice and includes the following major steps:
- First-stage memory testing. Immediately after power-on or reset, interrupts are disabled and the MPU is switched to native mode operation. A destructive test of page zero RAM is performed, followed by a similar test on page one RAM, as that is the default stack used by the MPU following a hard reset. If a memory fault is discovered the MPU is halted, as processing cannot continue without a functioning page zero and stack area. As no console I/O has been established it is not possible to inform the user of the problem.
- First-stage hardware initialization. The real-time clock is initialized so the watchdog timer will generate jiffy IRQs, interrupts are enabled and the DUART is initialized to establish TIA-232 communication. TIA-232 buffer indices are initialized, thus making the TIA-232 subsystem ready for bi-directional I/O.
- POST screen display. An initialization sequence is sent to the console terminal, which clears the screen. A banner display follows, producing the first visible sign of system activity.
- Second-stage memory testing. All RAM from $000200 to $00CFFF is non-destructively tested, the progress of which is displayed on the console as an ascending "bytes found" count. If a defective memory location is discovered, testing is discontinued, an attempt is made to display the faulty location's address and the system is halted.
- Second-stage hardware initialization. The BIOS checks for the presence of the SCSI host adapter and if found, initializes it and then executes a SCSI bus hardware reset. A delay period follows to allow all SCSI devices to recover from the reset.
- SCSI enumeration. The SCSI bus is probed to detect the presence of devices and an enumeration table is built in RAM at $000110. During enumeration some information about each discovered device is displayed on the console.
- Initial system load (ISL). If a bootable device is detected at SCSI ID $00 an attempt is made to load and start an operating system. A successful load will result in control being given to the operating system. As is customary practice, the ISL starts with the BIOS reading physical block $00 on the disk, examining it for signs that a valid boot block is present and if found, executing the boot code. It is then up to the boot code to continue ISL or abort if it can't.
- Monitor console. If ISL is unsuccessful control will be given to the machine language monitor. This is also the default action if the SCSI host adapter is not present, fails to respond to initialization or generates an error during initialization.
Machine Language Monitor
The machine language monitor is the default operating environment if ISL fails or no SCSI hardware is present. The monitor includes functions to examine and change memory and MPU registers, assemble, disassemble, load and execute code, and execute some basic low-level SCSI commands. The assembler and disassembler support the entire W65C816S instruction set and all addressing modes. Implemented monitor commands include:Monitor commands are not case-sensitve.
- A – Assemble code.
- C – Compare memory areas.
- D – Disassemble code.
- F – Fill memory range with byte value.
- G – Run code—stops on a BRK or COP instruction.
- H – Search memory for pattern, accepts multiple byte values or ASCII strings.
- J – Run code as a subroutine—stops on an RTS instruction, as well as on BRK and COP.
- L – Load Motorola S-record code through the auxiliary TIA-232 port.
- M – Display memory contents.
- R – Display MPU registers.
- T – Copy memory range.
- Z – Clear the console screen.
- > – Modify memory, accepts multiple byte values or ASCII strings.
- ; – Modify MPU registers.
- ! – SCSI extensions.
- Radix conversion (described below).
Central to the monitor are the assembler and disassembler, which are essential to debugging and patching code. The assembler is WDC syntax-compliant and is started by entering a (assemble code) followed by the assembly address and then a 65C816 instruction. For example:
a 2000 rep #%00110000Upon assembly of the statement, the monitor will immediately replace the typed input with a disassembly of the instruction and prompt for the next instruction. In the case of the above, the user would see the following upon pressing [CR] (the Return or Enter key):A 002000 C2 30 REP #$30The assembler is able to assemble a 16-bit immediate-mode instruction when syntactically correct. Continuing the example:
.A 002002
A 002000 C2 30 REP #$30would result in:
.A 002002 lda #1234A 002000 C2 30 REP #$30As the 65C816 can address 16 MB of memory, an instruction with a 24 bit address is permissible. For example, here is a "long" (interbank) call to a subroutine at $8F210E:
A 002002 A9 34 12 LDA #$1234
.A 002005
A 002000 C2 30 REP #$30resulting in:
A 002002 A9 34 12 LDA #$1234
.A 002005 jsl 8f210e
A 002000 C2 30 REP #$30Assembly is terminated by pressing [CR] without typing any input at the next prompt.
A 002002 A9 34 12 LDA #$1234
A 002005 22 0E 21 8F JSL $8F210E
.A 002009
Disassembly of the above code would be displayed as follows:
.d 2000 2005The period (.) is both the monitor's prompt and the code disassembly prefix.
. 002000 C2 30 REP #$30
. 002002 A9 34 12 LDA #$1234
. 002005 22 0E 21 8F JSL $8F210E
You may have noticed that excepting the first entered instruction, operands were entered as hexadecimal numbers without telling the assembler that they were to be interpreted as such. Hexadecimal is the default number base unless a radix prepends the operand. Radices are as follows:
% – BinaryIn the case of the REP instruction, bit-wise notation is especially convenient, since the instruction clears selected MPU status register bits. So the #%00110000 operand is more convenient and mnemonic than #$30.
@ – Octal
+ – Decimal
$ – Hexadecimal
In general, all numeric values may be entered in any radix. Also, the radix conversion feature allows one to enter a numeric value prefixed with a radix symbol as a monitor command and get a conversion to all radices. For example, entering $7fff at the monitor prompt will result in the following display:
.$7fffThe supported numeric range is $00000000 to $FFFFFFFF, that is, 32 bits.
$7FFF
+32767
@00077777
%111111111111111
The SCSI extensions permit the issuing of low-level commands to logged SCSI devices or to the SCSI subsystem itself:A SCSI command is always prefixed with ! so the monitor can tell that it is to access the SCSI subsystem. For example:
- B – Recalibrate device (disk) or rewind medium (tape).
- F – Format or erase medium.
- I – Reset and re-enumerate SCSI subsystem, used to recover from errors.
- R – Read data from device.
- S – Check device status (request sense).
- W – Write data to device.
!r <ID> <LUN> <LBA> <N> <ADDR>will read N blocks or bytes of data (the SCSI driver figures out if it's blocks or bytes by examining the enumerated device type) from logical unit LUN of SCSI device ID, starting at logical block address LBA, and deposit the data into RAM starting at address <ADDR>. Without the !, the monitor would think that the MPU registers are to be displayed, but would flag an error because the R (register display) command takes no parameters. Once the transfer has completed monitor commands can be used to examine block images, etc.
I should hasten to add that this is a primitive and unforgiving interface, as the monitor performs no error checking (beyond basic syntactical checks) or other hand-holding—it is assumed that you are not a naive user. Doing something stupid can, and usually will, crash the system or overwrite something important (for example, the RTC registers, which has happened a few times due to clumsy typing), resulting in various and sundry weird events. In particular, there is no Are you sure (Y/N)? question when the !f <ID> <LUN> command is used on a disk or tape—formatting is a hardware-level operation on the medium that blows away all data. The monitor will indicate an error only if the SCSI subsystem returns one, such as trying to access an LBA that is outside of the target device's addressable range.
As the '816 has more registers than its eight bit cousins, the register dump command offers more information. Here's an example:
.rRead from left to right are: the 8-bit program bank (PB), 16-bit program counter (PC; combining PB with PC produces the 24 bit effective execution address, $00C09B in this case), 8-bit status register—displayed in convenient bit-wise format, 16-bit accumulator (.C, which is really two registers, .A and .B), 16-bit X index register (.X), 16-bit Y index register (.Y), 16-bit stack pointer (SP), 16-bit direct (zero) page starting address (DP), 8-bit data bank (DB) and 16-bit IRQ service routine vector address (IRQV), the latter which is read from the IRQ handler's indirect jump vector at $000106.
PB PC NVmxDIZC .C .X .Y SP DP DB IRQV
; 00 C09B 00110010 3100 0057 00C1 CDFF 0000 1C E180
As the m and x status register bits are set in the above example, all registers will be set to eight bits the next time a g (run code) or j (run subroutine) instruction is issued to the monitor. .C continues to display a 16 bit value because the most significant byte (MSB, $31 in this case) is retained in the hidden B-accumulator. Switching the index registers to 8 bits forces their MSBs to $00, something that a programmer cannot afford to forget.
The registers may be changed by issuing the ; (change registers) command to the monitor, followed by the appropriate values. For example:
;04 21C3 %00000000 1234 5678 9abcwould result in:
PB PC NVmxDIZC .C .X .Y SP DP DB IRQVNote that values have to be entered in the correct order and that omitted values (i.e., SP, DP and DB in the above example) leave the corresponding registers unchanged. The entered values are actually written to "shadow registers" in RAM, not the MPU hardware registers. If the g or j command is issued, the MPU registers will be loaded from the shadow registers and execution will commence at $0421C3.
; 04 21C3 00000000 1234 5678 9ABC CDFF 0000 1C E180