STP
and WAI
) that the simulator's 65C02 mode, which is the Rockwell version of the 'C02, doesn't implement.
I should note that I assemble programs with the simulator's 65C02 mode enabled because that mode includes all
instructions shared by the 'C02 and '816, other than the aforementioned STP
and WAI
.
As
luck would have it, the assembler internally represents all numeric
values as 32 bit integers, so it is possible to work with the 24 bit
addressing that is used by some '816 instructions.
The assembler also understands C-like notation for shifting, masking
and performing Boolean logic, which is handy for isolating the least
and most significant words of a 32 bit value.
All of this proved to useful in writing '816 instruction macros and will
be useful for future software development, such as when I get to
developing a filesystem to work with POC—filesystems typically use 32 or 64 bit arithmetic.@
to represent bit-wise operands, rather than %
as defined by the MOS Technology standard, often causing me to
inadvertently create syntax errors by reflexively typing %
where @
is required (@
usually
represents octal values in MOS-compliant assemblers), the S-record output
format
is incomplete—there is no S0
header record, and my '816 instruction macros had to be assigned
odd names to avoid colliding with genuine 65C02 instructions, while
retaining a modicum of similarity to the "real" mnemonic. The
result is that, for example, LDA
(1,S),Y
in '816 assembly language ends up being coded as LDASI
1
, where the SI
tacked on to LDA
indicates stack indirect indexed addressing. Similar oddities are
in place to allow the coding of 16 bit immediate mode instructions,
such as LDA
#$1234
, which ends up being LDAW
$1234
, the W
signifying a "word" or 16 bit load. Yet another weird one is JSX
<addr>
, which stands in for the '816's very useful JSR
(<addr>,X)
instruction, which behaves much like the ON ... GOSUB
construct of BASIC.
.SET
pseudo-op, a useful function in defining stack frames. The entire set of
macros and logical
operators, as
well as a link to the Kowalski simulator, may be found on the Downloads page.od
is particularly useful for this purpose—it's how I determined that the
Kowalski S-record output was missing the S0 header record. As earlier mentioned, the design of
POC accounts for the UNIX file transferring capabilities by including
an auxiliary serial port that can be linked to the server. A
simple shell script reads the S-record object file,
prepends an optional S0 record from command line arguments, and sends
the file to POC a record at a time, using a protocol I devised to keep
the two systems in sync during the transfer process. POC, in turn,
error-checks the input and if it is satisfactory, converts it to binary
format and pokes it into memory for later execution. Trying to do
all
this with Windows is an incredibly convoluted
process. As most seasoned computer professionals know, in Windows the easy stuff is easy and geeky stuff like
sending object code to another system through a serial data link is fraught with difficulty.pocrom.65s
, lists all the .INCLUDE
files used to build the code, which also serves as a table of contents
for anyone interested in studying the various modules. Near the
beginning of pocrom.65s
, as well as at the beginning
of the listing file, is a fairly detailed revision table that describes every significant
change in the code.
From that you can glean an idea about how development progressed.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 aBRK
orCOP
instruction.H
– Search memory for pattern, accepts multiple byte values or ASCII strings.J
– Run code as a subroutine—stops on anRTS
instruction, as well as onBRK
andCOP
.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 enteringa
(assemble code) followed by the assembly address and then a 65C816 instruction. For example:
Upon 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 2000 rep #%00110000
The assembler is able to assemble a 16-bit immediate-mode instruction when syntactically correct. Continuing the example:A 002000 C2 30 REP #$30
.A 002002
would result in:A 002000 C2 30 REP #$30
.A 002002 lda #1234As 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 atA 002000 C2 30 REP #$30
A 002002 A9 34 12 LDA #$1234
.A 002005
$8F210E
:
resulting in:A 002000 C2 30 REP #$30
A 002002 A9 34 12 LDA #$1234
.A 002005 jsl 8f210e
Assembly is terminated by pressing [CR] without typing any input at the next prompt.A 002000 C2 30 REP #$30
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:
The period (.) is both the monitor's prompt and the code disassembly prefix..d 2000 2005
. 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:
In the case of the%
– Binary
@
– Octal
+
– Decimal
$
– Hexadecimal
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
.
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:
The supported numeric range is.$7fff
$7FFF
+32767
@00077777
%111111111111111
$00000000
to$FFFFFFFF
, that is, 32 bits.
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
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.!
so the monitor can tell that it is to access the SCSI subsystem. For example:will read!r <ID> <LUN> <LBA> <N> <ADDR>
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 unitLUN
of SCSI deviceID
, starting at logical block addressLBA
, 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 noAre 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:
Read from left to right are: the 8-bit program bank (.r
PB PC nvmxdizc .C .X .Y SP DP DB IRQV
; 00 C09B 00110010 3100 0057 00C1 CDFF 0000 1C E180
PB
), 16-bit program counter (PC
; combiningPB
withPC
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
.
As them
andx
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 (call 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:
would result in:;04 21C3 %00000000 1234 5678 9abc
Note that values have to be entered in the correct order and that omitted values (i.e.,PB PC nvmxdizc .C .X .Y SP DP DB IRQV
; 04 21C3 00000000 1234 5678 9ABC CDFF 0000 1C E180
SP
,DP
andDB
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 theg
orj
command is issued, the MPU registers will be loaded from the shadow registers and execution will commence at$0421C3
.