The 512's microprocessor is an Intel 80186, which runs at a clock speed of 10 MHz. The 80186 is object code compatible with the 8086 processor, though there are extensions in the 80186 instruction set which are not present in the 8086. There are two later processors in this series, the 80286 and 80386. Code produced specifically for them or compiled on them to use the extra facilities will not run in an 80186 machine.
The most notable omission from the 80186 chip, when compared with some of its contemporaries, is the absence of support for an 8087 maths co-processor. This is a hardware floating-point processor, which has its own range of separate instructions. When a machine fitted with an 8087 performs large calculations, the program can be written so that both processors can execute their own code, to a large extent, independently.
The only implication of the lack of an 8087 should be that a system would run slowly when performing large or complicated calculations. The presence or absence of an 8087 can be detected in software, so applications which can use the 8087 should check for its presence and self configure in it's absence to use overlaid software routines instead. A few calculation intensive applications do not verify the presence of the 8087 and cannot be run in the 512. These will crash as soon as an 8087 instruction is encountered.
Programs in this category the heavyweight statistical analysis and modelling routines or pure scientific applications. It must be assumed that the authors expected that no one would attempt to run such applications in a machine without an 8087 co-processor and did not feel the need to check for it.
The 80186 is a 16-bit processor, which means that each of its registers are 16 bits, or two bytes, wide. This has several direct implications for the capabilities of the system as a whole, governing factors as diverse as the total amount of memory that can be addressed, how so phisticated individual machine code instructions can be, and how comprehensively the processor can monitor events in the system by means of its range of status flags.
A further ramification is the maximum number of possible instructions in the instruction set. In the 6502, which can manipulate only two or three bytes at a time, the first byte always indicates the opcode, hence there could never be a repertoire of more than 256 instructions. The 86 series of processors has no such restrictions and more instructions have been added with each succeeding version, which is why code produced specially for the 80286 or 80386 processors is incompatible with the 80186.
There follows a brief introduction to the 80186 processor's architecture and an outline of some of its instruction set's capabilities. This is not a programming course, but 6502 assembly programmers will be able to contrast the 80186 with the 6502, so it may serve to whet some appetites.
The registers essentially fall into four categories, which are general purpose, segment, index (also called offset) and status.
In the first category are the registers, which are used for arithmetic and testing of values and results. The four general purpose registers are called A, B, C and D, but they can each be used both as a single two byte (word) register, or as two separate single byte registers. Depending on the current operation they have modified names in programs to indicate their current mode of use.
Note that there is no switch of mode. The portion of the register affected or used is controlled entirely by the form of the name used in each individual instruction in a program. This may vary from one instruction to the next.
When used as word registers, A, B, C and D are suffixed by x and are referred to as AX, BX, CX and DX. When coded into program instructions by these names, all sixteen bits in the register are involved in any operation.
Although there are two bytes in each of these registers, when used for memory address manipulation it is convenient to be able to manipulate the high address byte and the low address byte independently. When byte operations are performed a means is required to indicate which half of which register is to be used. The convention is to replace the x in each name by an H to refer to the high byte, or an L for the low byte. AX, BX, CX and DX therefore become AH and AL, BH and BL, CH and CL and DH and DL respectively.
This technique gives effective access to 8 single byte registers or four word registers, depending only on what they are called within each program instruction. Examine the following 80186 assembly instructions, noting that in 86 assembly code the target operand is always stated before the Source operand. That means that the second operand, the numeric value, is added to the first, which in each case here is a register:
is the same as:
since both of these increase the (low order byte) value of AX by one, but consider:
ADD AH, lh
which would be the equivalent of:
ADD Ax, l00h
In the example above, the second instruction would achieve the same result as the first, but would take more memory and time to do it. The h after a number is the 86 convention to indicate hexadecimal values and is directly equivalent to the '&' prefix used in native BBC notation, hence &FF and FFh represent precisely the same value. Some assemblers also accept a leading zero to indicate hex values so 0F would be taken to mean the same as Fh or 0Fh.
Most assemblers also accept decimal number literals, some requiring a suffix 'd', some assemblers that if neither a leading zero nor trailing 'h' is supplied the value is decimal. The last instruction above could equally have been written (in its 'long' version) as:
ADD AX, 256d
ADD AX, 256
Upper case has been used here to clarify instructions and register names, but most 86 assembly language programs are written mainly in lower case, though like DOS Plus commands, either will be accepted. Lower case is used because there is a generally recognised programming convention that constant values or external references, such as literal identifiers and library filenames declared at the start of a program are written in upper case, while memory addresses (i.e. labels) whether used as jump destinations in the code or as data storage, are in lower case.
The idea is that within the body of the code it is immediately obvious at a glance whether a name refers to an external area or a fixed constant value, or to the address of a local memory location, the contents of which are subject to change.
The second register category is segment registers. These are used to contain the number of the memory block that is the segment part of the RAM address for memory addressing operations (see Segmentation below). The four 16-bit segment registers are CS, DS, ES, and SS. These registers cannot be addressed as two separate bytes and so the following are the only recognised names for these registers. The segment register names mean:
CS Code segment DS Default segment SS Stack segment ES Extra segment
By convention three of these registers have agreed uses within programs, but the purpose of Cs is fixed by the processor. CS always points to the segment in which code is currently being executed. In fad all the segment registers are set to this value automatically when a .COM or .CMD program first loads (as is the general purpose register DX), though they may be altered within the program or during the initialisation process.
CS cannot be directly altered, but a program jump to a different segment (usually initially set up in ES) automatically causes the value in CS to change appropriately.
Depending on the particular assembler used, it may be necessary to make declarations at the start of source code about initial segment registers set-up. This can often be seen in 86 assembler source program as a series of 'ASSUME' statements, which may look like this:
ASSUME CS = CODE ASSUME DS = CS ASSUME SS = CS ASSUME ES = nothing
DS is under program control and usually initially defaults to the code segment. When used to indicate a different segment, DS can be pointed to a segment of memory where the program's data is stored, like the currently used part of a document in word processing. For most program operations involving addressing, if no other segment register is explicitly supplied the segment value in DS is used.
The stack segment is analogous to the stack page in the 6502, but it can be much larger. Programs in DOS have a local stack, that is, one unique to the program, and this may or may not be in the current code segment. This technique is employed to ensure, as far as possible, that no matter what happens within a program, nothing else in the machine should be affected. It is also necessary because, without local stacks, multiple simultaneous program operation, like foreground and background tasks, would be impossible.
The stack segment initially defaults to the code segment, but it can be located elsewhere if required. The maximum size of a program's stack is unlimited, but in practice is usually confined to the maximum address range of 16 bits (64k) and it can be anywhere within its available memory that a program chooses to put it. If required you can allocate a separate segment entirely for the stack. In this case the segment register is program controlled.
The extra segment register, ES, is conventionally used to point to any other area of memory not within a currently identified segment. For example, with Ds pointing to your current working data segment, you might set ES to point to another segment from which you wish to transfer data into the DS segment. At the same time CS points to the current code segment, while SS might point to a stack segment for temporarily stored data that you may need to retrieve later.
All segment registers are used in conjunction with a two-byte offset to address the memory location within the segment. This can be a hard coded literal value, or it may be a derived or calculated value contained in a memory location or one of the index registers. In the case of CS, the offset address is always indicated by the instruction pointer (See IP), the contents of which cannot he varied directly.
The two index registers, SI and DI are the third category of register. These are both 16 bit registers and can be used in conjunction with the segment registers. When used together, a segment register and a segment offset can define any address in any part of the memory. For this reason the index registers are sometimes also called offset registers.
The fourth register category is the pointer registers. These are also 16 bit register and are called:
SP Stack pointer BP Base pointer IP Instruction pointer
The stack pointer holds the address of the next free stack entry in the stack segment. The stack pointer must be initialised by the program to point to the top of the space reserved for the stack if the programmer wishes to specify a special stack area. On initial load the stack pointer is set to the address of the end of the code segment minus two bytes. Stack space is used from the top downward in the same manner as the 6502's stack. Note that it is up to the program itself to allocate stack space and to point SR to it (and SS too if necessary) before The stack is used. For example the instruction:
executed with SR set at 1000h would push the contents of registers AX, CX, DX, BX, SP, BP, SI and DI onto the stack and decrement SR by two for each, leaving it pointing to 0FF0h.
The base pointer is used as a pointer into memory, often as a sort of memo to an address in a local table, or as the starting value for an index register which will be varied. It is entirely under program control and is mainly intended to be used to address local variables stored on the stack. For this reason the default segment register for BP is SS, not Ds as it is for most other operations.
There is a third pointer register, the implicit instruction-pointer, IP. Arguably IP is not a register in the usual sense as, in conjunction with CS, if points to the address of the currently executing program instruction. Like Cs it cannot be changed directly, but only as a result of executing jumps or returns within the program code.
The last register, the status register, is also known as the flags register. It performs the same function as in other processors. Various bits are set or unset as a result of arithmetic operations, comparisons and moves. These status bits, or flags, can then be used to govern the actions of following conditional instructions.
There is one particularly interesting extra feature of the 86 status register over that of the 6502. One of the flags is the direction flag. As will be seen shortly, numerous automatic indexing and counting instructions exist and two instructions, 'STD' (SeT Direction) and 'CLD' (CLear Direction) set or clear this flag respectively. The setting of this flag dictates whether the automatic increment for certain instructions is positive or negative. The word 'advancing' is conventionally used to denote either of these in describing instruction operations.
In terms of operations which can be performed by or on all the registers mentioned so far (except CS and IP), there is a small number that can't. This is why the explanations above are qualified by words like 'usually', 'often' or conventionally'. There are few differences between the capabilities of any of the registers, with the exceptions that multiply, divide, input and output are restricted to register A only. It is the programmer's responsibility to use registers sensibly and to adhere to accepted good practice, because there are often several different ways to perform any given operation.
The contents of some registers, like CS and IP are significant to the processor and are excluded from some of the following operations, but within the bounds of common sense; there are almost no limits. If you wanted to add the contents of the data segment register to the contents of the base pointer you could do so, but whether such an operation is an appropriate technique is a separate question. Such is the nature of a 16-bit complex instruction set computer that there is a convenient machine code instruction for just about every eventuality.
When using the general-purpose registers or memory locations, there are effectively two versions of a great many instructions, allowing the same operations both at byte and at word level - such is the flexibility of 80186-assembly code. Here are just a few illustrations as there is insufficient space to explain the vast array of possible combinations of instruction formats. Each of the operations described below can be performed by a single machine code instruction.
You can add a literal value, a value held in memory, indexed or not, or a register's contents to another register. You can move a register's contents to or from another register or to or from memory, again indexed or not. It is even possible to add a register value or a literal value directly into memory! It is not, however, possible to add one area of memory directly to another area of memory without involving a processor register.
There are numerous compare instructions, some of which compare bytes, some words. Of these, some only set flags, while others will compare, set flags and also automatically advance the index registers in the currently set direction at the same time.
There is even an automatic loop instruction, giving a direct equivalent to BASIC's 'FOR...NEXT' construct. All the programmer needs to do is to set the loop counter, (CX) once, execute the code which is to be repeated, and then issue the instruction loop, with a label. Depending on the version of the instruction employed, various additional tests can be carried out by the Loop instruction making it conditional too. Implicit in all versions is the testing and decrementing of the loop counter. Regardless of the form of the instruction used, the loop ceases when the count reaches zero.
The 6502 operations of decrementing a counter, possibly performing an extra status test as well as a zero test and branching are all performed in 80186 assembler code by a single instruction.
To round off this brief insight into the 80186 instruction set's capabilities and as final food for thought for 6502 programmers, here are a few more facts about 80186 machine code. There is a REP (repeat) instruction, which repeats the following instruction a specified number of times or until a specified condition is met. There are 25 forms of the MOV (move) instruction, several of which can simultaneously advance the index registers. There are 31 conditional and 5 unconditional JUMP instructions and 5 different versions of the CALL instruction (the equivalent of the 6502's single JSR) and of course numerous versions of multiply and divide instructions.
The range of addressable memory in microcomputers is limited by the size of the registers concerned with memory addressing. In 86 series machines specific terms are used to refer to different sized blocks of memory. To fully appreciate the reasons a small amount of history is called for, together with a little simple arithmetic.
As usual the smallest base unit is a binary digit, or a bit. Four bits are known as a nibble, which is the smallest memory unit that can hold sixteen numeric values, or the complete range of unitary hexadecimal values (0h to Fh inclusive). Two nibbles form a byte, which is the smallest directly addressable single unit of memory. It is also the amount of memory needed to represent a single character.
In memory addressing a nibble can address 24 locations or 16 bytes. In both CP/M and 86 terminology a block of 16 bytes is known as a paragraph. This addressing convention was developed for the original 8 bit CP/M machines as a useful abbreviation, since any single register could hold a numeric value representing 16 times as much memory in paragraphs as it would be in bytes. In CP/M system calls, various operations required that quantities of memory be expressed in units of paragraphs. This is still the case in the 5l2 and all DOS Plus Systems when using many CP/M system calls (as opposed to DOS interrupts.)
Conventionally, one byte can address one nibble2 bytes, which is 256 or FFh locations and, as in the 6502, this amount of memory is called a page. Two bytes can address 216, 2562 or &FFFF locations, which is 65336 bytes, usually shortened to 64k. If an extra nibble (remember 8-bit CP/M) were combined with this addressing range, making it into paragraphs, any two byte register plus a nibble could address 16 times 64k, a million bytes (1 Mb). This is precisely what was done for the larger memory 16 bit processors first used for CP /M86 systems which succeeded 8 bit CP/M.
This concept, which was also directly inherited by DOS systems, forms the basis of all memory addressing in 86 based Systems. Because all the processor registers are two bytes wide, the largest single unit of memory which can be directly addressed by one register, but which can still be accessed byte by byte, is 64k. This is known as a segment.
All memory addressing is based on units of a segment, but the explicit use of the 'extra' nibble is optional (i.e. it can be defaulted). When specified, it precedes the sixteen-bit portion of the address. The standard adopted for writing the two parts of the address is to separate them by a colon (:). The segment is written before the colon; the byte address within the segment, or the segment offset (usually just called the offset) follows the colon. As all the processor's registers are two bytes wide, each part of the address is specified as four hexadecimal digits, the lowest byte of memory is therefore 0000:0000.
This addressing scheme has very significant implications for the convenience of the way programs can be written and memory locations can be expressed and manipulated. If anything less than a segment is addressed, only a single register is needed - if no explicit segment address is given, the value in DS is assumed (except in the case of BP, as noted). By only the simple addition of a nibble qualifier, up to a megabyte can be accessed in 64k blocks. Since the two parts of the address are conceptually separate, either can be varied without disturbing the other.
Bearing in mind that the contents of the general purpose registers can be altered as two separate bytes or as one two byte value, it is easy to see that, by altering each part of a register containing a memory address, RAM locations can he accessed a byte at a time, a paragraph at a time, a page at a time or a segment at a time.
When the processor translates addresses to an internal value for setting up the data bus to access a particular location, the two parts of the address, the segment and the offset, are combined to give a single 20 bit value. Remembering that the segment value is realty a nibble, representing 16 bytes, it can be seen that a segment value of &0001 is 16 bytes, &0010 is a page, or 256 bytes, &0100 is 4k, or 4096 bytes and a segment value of &1000 is a complete segment of 64k.
Obviously segment address values and segment offset values can overlap, depending on how they are expressed. This probably causes mere confusion to the new 86 programmer than almost any other aspect of the system. Examine the following segment and offset addresses (all are in hex and perfectly valid) Each pair gives the same address:
seg:offset seg:offset 0001:0000 0000:0010 000F:0000 0000:00F0 0010:0000 0000:0100 0F00:0000 0000:F000
It can be seen that the segment address simply represents a value sixteen times bigger than the offset. When working out a memory address from a segment:offset address statement, all that is required is to multiply the stated segment value by sixteen and add it to the offset to arrive at a complete absolute address.
In many tutorials it is pointed out that, as the segment address can point to any multiple of 16 bytes, any address can be expressed in 4K, or 4096 different ways (64k/16). For example:
0120:0000 011F:0010 011E:0020
all point to the same address. Such addresses though, even with simple examples like these, can be highly confusing and care should be exercised. Try adding these two addresses together if you are not convinced!
Ultimately it's a matter of personal choice, but when planning and designing large applications, perhaps using large tables, it is sensible to treat segment offsets as ranging from &0000 to &FFFF, and count segments in ones of Mk each, that is:
Segment zero is 0000:0000 to 0000:FFFF Segment one is 0000:0000 to 1000:FFFF Segment two is 2000:0000 to 2000:FFFF Segment ten is A000:0000 to A000:FFFF Segment fifteen is F000:0000 to F000:FFFF
and so on. You will notice that the last value given has a value of one less than a megabyte, the largest address possible in the 512. In real PCs, which do not directly address more than 640k, (ignoring extended memory systems, which use special mapping, much like sideways RAM does in the BBC Micro) the highest address possible is 9000:FFFF. The highest address in an unexpanded 512 is of course 8000:FFFF.
It leads to much more understandable addressing at the planning stage if you completely ignore the bottom three nibbles of the segment address. Always treat them as if they will be zero unless there are very special reasons for wanting to count in 4k page or paragraph sized chunks of memory in certain operations.
In practice, all DOS programs are relocatable, so in a live program DOS sets up the segment registers to point to the first available paragraph of the actual code segment. In this way, all offsets within the code segment of a live program will start from zero, unless you decide otherwise. No matter where the program loads (governed by what else is already running) all offset calculations therefore remain valid.
Going a step further, if you could work within a single segment addressing would be even easier, as you could, in effect, forget the segment address. This explains why many applications have a 64K limit on the size of memory-resident files. It is also the reason why .COM programs may not exceed 64k in size, and why some applications or languages include both a 'small' (64k maximum) and a large' system version.
Because of the 16 bit 80186 registers, it is convenient to work within whole segments if at all possible. On initial program load, all the segment registers are already set up to point to the code segment. If your program can confine all its code and its data within the code segment, you can ignore the segment addressing nibble altogether throughout the entire program.
The offset becomes the only relevant portion of addresses so far as memory accesses are concerned, and the resulting program will be both simpler and more efficient. In cases where 64k is not sufficient for all code and data, other techniques can be employed to minimise segment address calculations and this is why several segment registers are provided.
For example, while CS points to the executing instructions, DS can be set to point to the working data segment. ES can then be used to point to a segment containing various subroutines, each of these being accessed by a jump address contained in a known offset from the start of the extra segment. If such jump offsets are held in a lookup table at the Stan of the extra segment, assembler functions the equivalent of BBC BASIC's 'ON x GOSUB' are simplicity itself. Since there are four segment registers, it is perfectly feasible to write quite large systems, without the need to frequently alter segment registers.
The Master 512 is provided with 512k of RAM as standard, or eight segments As can be seen from the way segment addresses are calculated, there would seem to be no problem in addressing a megabyte. In terms of the processor this is quite true, but other factors such as the operating system or the hardware addressing chips may impose limits. In PCs this is precisely the situation. For reasons best known to themselves, the designers have, in most cases, imposed a limit of 640K on the directly addressable memory.
In the 512 there is no such arbitrary hardware memory limit. The Solidisk PC Plus was supplied with a modified version of DOS Plus which permitted a full megabyte to be fitted. Since some packages require more memory than an unexpanded 512 can provide, a memory expansion project has been included later in the book and one is also available from Essential Software.
You should be aware that, while no 512 expansion board will give access to a full megabyte, it can provide more directly usable memory than in any PC- Because of this fact, there are few packages that will take advantage of extra memory over 640k. The main benefits of a larger memory are that a reasonable sized ramdisc or other memory resident utilities can be run, while still leaving the same net amount of free RAM as in MS-DOS systems.