Category:8086 Assembly: Difference between revisions

m
m (→‎Other Registers: formatting)
 
(6 intermediate revisions by the same user not shown)
Line 3:
==Architecture Overview==
===Segmented Memory===
The 8086 uses a segmented memory model, similar to the Super Nintendo Entertainment System. Unlike banked memory models used in the Commodore 64 and late NES games, segment addresses are held in <i>segment registers</i>. These segment registers are 16 bit and get left-shifted by 4 and added to the pointer register of interest to determine the memory address to look up. The 8086 has four in total, but only the Data Segment<code>DS</code> and Extra<code>ES</code> Segmentregisters can be used by the programmer. (The other two arework reserved forwith the stack pointer and instruction pointer, and are loaded for you.) On the 8086, you can only load segment registers with the value in a data register, or with the <code>POP</code> command. So first you must load a segment into a data register, THEN into a segment register.
 
<lang asm>;This is NOT valid code!
Line 12:
mov ax, @data ;I chose AX but I could have used BX, CX, or DX.
mov ds, ax ;load DS with the data segment.</lang>
 
It's important to remember the subtle distinction between a ''segment'' and a ''segment register.'' Your program might have labeled ''segments'' such as <code>.data</code> and <code>.code</code>, but in order to properly read from/write to these sections you'll need to load their memory locations into ''segment registers.''
 
===Data Registers===
Line 37 ⟶ 39:
 
===Other Registers===
When writing to or reading from consecutive sections of memory, it is helpful to apply an offset from a base value. The Base Pointer register <code>BP</code>, Source Index <code>SI</code>, and Destination Index <code>DI</code> can point to various regions of memory. Many commands that work with these registers can auto-increment or decrement them after each load or store. In addition, they can be optionally offset by a constant, the value stored in <code>BX</code>, or both at the same time. (<code>BX</code> cannot be added to <code>BP</code> in this manner, but it can be added to <code>DI</code>, and <code>SI</code>.) Unlike the data registers, <code>BP</code>,<code>DI</code>, and <code>SI</code> cannot be split in half and worked on separately. Only data registers allow you to work in 8-bit.
 
The syntax for offsetting an index register will vary depending on your assembler. Many assemblers will often accept multiple different ways of writing it, and it comes down to personal preference.
Line 100 ⟶ 102:
 
While both the 8086 and the 8087 can read the same code, data, and memory, they cannot read the contents of each other's registers. So for example, if you wanted to use the 8087 to perform a calculation and then output the result to the screen, you'd need to give the 8087 the command to do the math and store the result into RAM. Then, the 8086 would read that RAM and output the result to the screen. The 8087 also doesn't have the robust indexed addressing modes of the 8086 - so the 8086 will often do the job of looking up a value from a table, then dumping that value into a temporary "loading zone" at a known location where the 8087 can more easily read from. In order to make sure that the 8086 and 8087 stay in sync, the 8086 can <code>WAIT</code> for the 8087 to finish its current instruction before executing more instructions. This is incredibly useful in the event that the 8086 needs to use a calculation that the 8087 did - it might end up reading from the "loading zone" before the 8087 actually has put the result of the calculation in it! (Most assemblers will handle this for you.)
 
One other caveat to mention: On early IBM PCs and compatibles, the 8087 was not built into the CPU like it is now. Back then, it was sold separately. So if you're programming on original hardware and the floating point commands don't work, it might be because you don't have the coprocessor installed!
 
===Looping Constructs===
The 8086 has a lot more of these than most CPUs. The most obvious one is <code>LOOP</code>, which will subtract 1 from <code>CX</code>, then jump back to a specified label if <code>CX</code> is nonzero after the subtraction. If <code>CX</code> becomes zero after subtracting 1, then no jump will occur and the instruction pointer simply moves to the next instruction.
 
<lang asm>mov cx,0100h ;set the loop counter's starting value. This must be outside the loop, otherwise you'll loop forever!
 
foo:
;; your code that you want to loop goes here
loop foo </lang>
 
It's important not to alter <code>CX</code> inside the loop. It's easy to make a mistake like this if you're in a hurry:
 
<lang asm>foobar:
mov cx,1000h
;your code goes here
loop foobar</lang>
 
It may not be obvious at first but the above loop will never end. <code>CX</code> decrements to 0x0FFF with the <code>LOOP</code> instruction but the <code>mov cx,1000h</code> was mistakenly placed ''inside'' the loop, resetting the loop counter back to 0x1000, which means no progress is really being made. The correct way to do this is to set the starting loop counter '''outside''' the loop, like so:
 
<lang asm>mov cx,1000h
foobar:
;your code goes here
loop foobar</lang>
 
Sometimes you'll have to change <code>CX</code> during a loop, like if you want to do bit shifts with a shift amount other than 1, for example. There's a simple fix - use the stack to stash and retrieve the loop counter.
<lang asm>mov cx,0100h
 
foo:
push cx
mov cl,2
ror ax,cl ;starting with the 80186 you don't need CL for bit shifting.
pop cx
loop foo</lang>
 
By using <code>PUSH CX</code> and <code>POP CX</code>, you can temporarily use <code>CX</code> for something else, as long as you restore it before the <code>LOOP</code> instruction.
 
You can use other registers as loop counters as well, but not with the <code>LOOP</code> instruction - it's hardcoded to only work with <code>CX</code>. But you can do this:
<lang asm>mov dx,0100h
baz:
;your code goes here
dec dx
jnz baz
; A minor note for advanced programmers:
; If you're expecting the flags to be in a particular state based on the loop body, tough luck!
; They'll only reflect the decrement of DX from 1 to 0 at this point in the code.
; LOOP doesn't change the flags, which makes it more useful for loops meant to compare things.</lang>
 
 
Another frequently used looping construct is <code>REP</code>. <code>REP</code> can be combined with certain instructions to repeat that instruction until <code>CX</code> equals zero. However, unlike <code>LOOP</code>, which can be used to repeat a block of instructions, <code>REP</code> can only repeat one. It doesn't work on all instructions, only the "string" instructions which operate on a block of memory. Typically these include <code>MOVSB</code>, <code>LODSB</code>, <code>STOSB</code>, <code>CMPSB</code>, and <code>SCASB</code> (each has a variant that ends in W instead of B, for 16-bit data.) There are also <code>REPZ</code> and <code>REPNZ</code>, which stand for "Repeat if Zero" and "Repeat if Nonzero" respectively. These two only work properly with <code>CMPSB</code> and <code>SCASB</code>, as <code>MOVSB</code>, <code>LODSB</code>, <code>STOSB</code> do not affect the flags. (The CPU doesn't detect whether <code>CX</code> equals zero using the flags, as the flags never reflect this equality to zero like you would expect.)
 
==Citations==
1,489

edits