iProgram
iProgram

Reputation: 6547

What is causing text to be displayed in a random place?

I am trying to write some code which displays some text at a given position on the screen.

When doing some research, I found this page that shows the formula position = (y_position * characters_per_line) + x_position;.

Here is the snipped of code that calculates and sets the position:

set_cursor_pos:
  push ax
  push bx

  mov al, [ypos]
  mov bl, 80
  mul bl
  add ax, [xpos]
  mov bl, 2
  mul bl
  mov di, ax

  pop bx
  pop ax

  ret

This works until ypos = 3 and xpos = 15 After this, it seems to wrap around to the beginning. Hear are some examples:

y=2, x = 30:

enter image description here

y=0, x = 60:

enter image description here

y=3, x=15:

enter image description here

y=4, x=0:

enter image description here




As you can see, my algorithm works until y=3, x-15. After that, it wraps around.

Is this because there is not enough memory? Do I need to enable the A20 line? Is it another problem? If so, please can you explain what and why.

Finally, here is all. of my code:

org 0x7c00


mov ax, 0xB800
mov es, ax
xor di, di

cli

mov ah, 0Fh
mov si, msg
call set_cursor_pos
call print

hlt

print:
  cli
  lodsb
  stosw
  cmp al, 0
  jne print
  ret


set_cursor_pos:
  push ax
  push bx

  mov al, [ypos]
  mov bl, 80
  mul bl
  add ax, [xpos]
  mov bl, 2
  mul bl
  mov di, ax

  pop bx
  pop ax

  ret

msg db 'Hello, World', 0

xpos db 0
ypos db 4

times 510-($-$$) db 0
dw 0xaa55

Upvotes: 1

Views: 85

Answers (1)

Peter Cordes
Peter Cordes

Reputation: 364170

Look at your operand-sizes. xpos is only 1 byte, but you're reading 2 bytes with add ax, [xpos]. Also, mul bl does ax = al * bl, so you're throwing away the high half of the mul-by-80 result.

i.e. set_cursor_pos returns with

di = (( (80*ypos) & 0xff) + (xpos + (ypos<<8)) ) & 0xFF) * 2

From your previous questions, you're targeting a 386-compatible, so you could write it with

movzx  di, byte [ypos]
imul   di, di, 80
movzx  ax, byte [xpos]
add    di, ax

add    di, di       ; di *= 2.  equivalent to shl di, 1  but more efficient.

(80 = 16 * 5, so you can also avoid imul and use one lea di, [edi + edi*4] / shl di, 4. Or whatever 8086-compatible trick for multiply by a number with so few set bits.)

There is zero point in using mul to multiply by 2, unless you were going to use mul bx and use the full 32-bit result in dx:ax. But even then, for 2 you should just use add di,di / setc al because the carry-out can only be 1 bit.

If xpos and ypos were 16-bit, you could use them as memory operands:

imul   di, [ypos], 80
add    di, [xpos]
add    di, di       ; di *= 2

Or of course you could keep them in registers in the first place.

Upvotes: 2

Related Questions