• Please review our updated Terms and Rules here

DIV/IDIV emulation

Mike Chambers

Veteran Member
Joined
Sep 2, 2006
Messages
2,621
i decided to have another look at the x86 emulator i've been working on today after a bit of a break, and i'm certain i've got my problem narrowed down to buggy DIV/IDIV emulation and i was wondering if anybody might be able to have a peek at this code here and see where my problem lies..

Code:
    Case 6: 'DIV
        If oper1 = 0 Then intcall86 0: Exit Sub 'division by zero
        If word Then
        		temp1 = (getreg16(dx) Shl 16) Or getreg16(ax)
        		temp2 = temp1 \ oper1
        		If (temp2 And &hFFFF0000) Then intcall86 0: Exit Sub 'divide overflow
        		putreg16 dx, getreg16(ax) Mod oper1
        		putreg16 ax, temp2
        Else
        		temp1 = getreg16(ax) \ oper1
        		temp2 = getreg16(ax) Mod oper1
        		If (temp1 And &hFF00) Then intcall86 0: Exit Sub
        		al = temp1
        		ah = temp2
        End If
    
    Case 7: 'IDIV
        If oper1 = 0 Then intcall86 0: Exit Sub
        If word Then
        		temp1 = (getreg16(dx) Shl 16) Or getreg16(ax)
        		tempoper = oper1
        		If temp1 >= &h8000& Then temp1 = -temp1
        		If tempoper >= &h8000& Then tempoper = -tempoper
        		temp2 = temp1 \ tempoper
        		If temp2 > &h7fff Then intcall86 0: Exit Sub
        		putreg16 dx, temp1 Mod oper1
        		putreg16 ax, temp2
        Else
        		temp1 = getreg16(ax)
        		tempoper = oper1
        		If temp1 >= &h80& Then temp1 = -temp1
        		If tempoper >= &h80& Then tempoper = -tempoper
        		temp2 = temp1 \ tempoper
        		If temp2 > &h7f Then intcall86 0: Exit Sub 'divide overflow
        		al = temp1 Mod oper1
        		ah = temp2
        End If

i know i can make big optimizations there, but i'm trying to determine the actual problem first. any ideas, guys? thanks.
 
does this look any more correct?

Code:
    Case 7: 'IDIV
        If oper1 = 0 Then intcall86 0: Exit Sub
        If word Then
        		s1 = (getreg16(dx) Shl 16) Or getreg16(ax)
        		s2 = oper1
        		If (s2 And &h8000&) Then s2 = (s2 Or &hffff0000&)
        		If s2 = 0 Then intcall86 0: Exit Sub
        		If ((s1 Xor s2) And &h80000000&) <> 0 Then sign = 1 Else sign = 0
        		If s1 >= &h80000000& Then s1 = (Not s1 + 1) And &hffffffff&
        		If s2 >= &h80000000& Then s2 = (Not s2 + 1) And &hffffffff&
        		
        		d1 = s1 \ s2
        		d2 = s1 Mod s2
        		If (d1 And &hffff0000&) Then intcall86 0: Exit Sub 'divide overflow
        		If sign = 1 Then
        			d1 = (Not d1 + 1) And &hffff&
        			d2 = (Not d2 + 1) And &hffff&
        		End If
        		
        		putreg16 ax, d1
        		putreg16 dx, d2
        Else
        		s1 = getreg16(ax)
        		s2 = oper1
        		If ((s2 And 255) And &h80&) Then s2 = (s2 And 255) Or &hff00& Else s2 = s2 And 255
        		If (s2 And &h8000&) Then s2 = (s2 Or &hffff0000&)
        		If s2 = 0 Then intcall86 0: Exit Sub
        		If ((s1 Xor s2) And &h8000&) <> 0 Then sign = 1 Else sign = 0
        		If s1 >= &h8000& Then s1 = (Not s1 + 1) And &hffff&
        		If s2 >= &h8000& Then s2 = (Not s2 + 1) And &hffff&
        		         		
        		d1 = s1 \ s2
        		d2 = s1 Mod s2
        		If (d1 And &hff00&) Then intcall86 0: Exit Sub 'divide overflow
        		If sign = 1 Then
        			d1 = (Not d1 + 1) And &hff&
        			d2 = (Not d2 + 1) And &hff&
        		End If
        		
        		putreg16 ax, (d2 Shl 8) Or d1
        End If

^ if that's correct then i still am not sure where my problem would be? who would like to join me in this project and see if we can't squash some bugs? ;)

i've been working on this emulator for some months now, and it feels like it's very close to having a good CPU core but i just cant quite seem to figure out what's left thats causing some issues.
 
This is the DIV/IDIV pseudocode taken from the Intel software developers manuals:
Code:
/////////////
/////DIV/////
/////////////

IF SRC = 0
	THEN #DE; (* Divide Error *)
FI;
IF OperandSize = 8 (* Word/Byte Operation *)
	THEN
		temp <- AX / SRC;
		IF temp > FFH
			THEN
				#DE; (* Divide error *)
			ELSE
				AL <- temp;
				AH <- AX MOD SRC;
		FI;
	ELSE IF OperandSize = 16 (* Doubbleword/word operation *)
		THEN
			temp <- DX:AX / SRC;
			IF temp > FFFFH
				THEN
					#DE; (* Divide error *)
				ELSE
					AX <- temp;
					DX <- DX:AX MOD SRC;
			FI;
	     FI;
FI;

//////////////
/////IDIV/////
//////////////

IF SRC = 0
	THEN #DE; (* Divide Error *)
FI;
IF OperandSize = 8 (* Word/Byte Operation *)
	THEN
		temp <- AX / SRC; (* Signed division *)
		IF (temp > 7FH) or (temp < 80H)
		(* If a positive result is greater than 7FH or a negative result is less than 80H *)
			THEN
				#DE; (* Divide error *)
			ELSE
				AL <- temp;
				AH <- AX SignedModulus SRC;
		FI;
	ELSE IF OperandSize = 16 (* Doubbleword/word operation *)
		THEN
			temp <- DX:AX / SRC; (* Signed division *)
			IF (temp > 7FFFH) or (temp < 8000H)
				THEN
					#DE; (* Divide error *)
				ELSE
					AX <- temp;
					DX <- DX:AX SignedModulus SRC;
			FI;
	     FI;
FI;
I hope this helps.
 
This might be a silly question -- but what the devil language is that??? The forward slash for what I'm assuming is a divide -- is that a integer or floating divide? uhh, That oddball logic flow is like someone took pascal, and then crapped all over it with the WORST of each from C and Basic...

Trying to make sense of that, FAILING MISERABLY... which is sad given I code x86 ASM regularly... Though I think your sign handling might be problematic -- I'd be using masks with XOR for handling that.... and you seem to be processing more variables than neccessary on the IDIV. Shouldn't whatever language you are using be preserving sign ANYWAYS assuming it allows for typecasting as signed/unsigned?
 
This is the DIV/IDIV pseudocode taken from the Intel software developers manuals:
Code:
/////////////
/////DIV/////
/////////////

IF SRC = 0
	THEN #DE; (* Divide Error *)
FI;
IF OperandSize = 8 (* Word/Byte Operation *)
	THEN
		temp <- AX / SRC;
		IF temp > FFH
			THEN
				#DE; (* Divide error *)
			ELSE
				AL <- temp;
				AH <- AX MOD SRC;
		FI;
	ELSE IF OperandSize = 16 (* Doubbleword/word operation *)
		THEN
			temp <- DX:AX / SRC;
			IF temp > FFFFH
				THEN
					#DE; (* Divide error *)
				ELSE
					AX <- temp;
					DX <- DX:AX MOD SRC;
			FI;
	     FI;
FI;

//////////////
/////IDIV/////
//////////////

IF SRC = 0
	THEN #DE; (* Divide Error *)
FI;
IF OperandSize = 8 (* Word/Byte Operation *)
	THEN
		temp <- AX / SRC; (* Signed division *)
		IF (temp > 7FH) or (temp < 80H)
		(* If a positive result is greater than 7FH or a negative result is less than 80H *)
			THEN
				#DE; (* Divide error *)
			ELSE
				AL <- temp;
				AH <- AX SignedModulus SRC;
		FI;
	ELSE IF OperandSize = 16 (* Doubbleword/word operation *)
		THEN
			temp <- DX:AX / SRC; (* Signed division *)
			IF (temp > 7FFFH) or (temp < 8000H)
				THEN
					#DE; (* Divide error *)
				ELSE
					AX <- temp;
					DX <- DX:AX SignedModulus SRC;
			FI;
	     FI;
FI;
I hope this helps.

thanks, per. in the end, i believe my code should be producing the same results as that pseudocode. the problems might be elsewhere.
 
This might be a silly question -- but what the devil language is that??? The forward slash for what I'm assuming is a divide -- is that a integer or floating divide? uhh, That oddball logic flow is like someone took pascal, and then crapped all over it with the WORST of each from C and Basic...

Trying to make sense of that, FAILING MISERABLY... which is sad given I code x86 ASM regularly... Though I think your sign handling might be problematic -- I'd be using masks with XOR for handling that.... and you seem to be processing more variables than neccessary on the IDIV. Shouldn't whatever language you are using be preserving sign ANYWAYS assuming it allows for typecasting as signed/unsigned?

i am doing it in FreeBASIC using its QB-compatibility syntax mode. unless you've never programmed in BASIC, that should be fairly easy code to follow although it would have helped if i commented it. and a \ is a back slash, not a forward slash. it means integer divide. i originally started writing this program in C, and i got to basically the same point i'm at now. i decided, for s&g's to rewrite it in BASIC and see what happens. and i am using a mask on the XOR stuff.

thanks for the input.

you can see from these screenshots of it running a BIOS ROM and then ROM BASIC, it's getting pretty well close to what should be working correctly. it completes POSTing and i can go start booting a disk image - but i can just never totally seem to coax DOS (or MINIX, ELKS, and others) into booting up. you can also see ROM BASIC doesn't quite look correct. look at the lower line.

fake86-bios.png


fake86-rombasic7.png


ROM BASIC in it also spits out weird garbage if i try to have it output hex, like PRINT HEX$(blah)
 
ROM BASIC in it also spits out weird garbage if i try to have it output hex, like PRINT HEX$(blah)

This is certainly your next clue. when you issue a print hex$(blah) does it execute your div code?
what other opcodes does that hit?

printing garbage usually means you're clobbering a pointer or perhaps returning data in a register that you shouldn't be. perhaps if your div was the same thing as a nop instruction, changing no data in registers whatsoever, does print hex$(blah) still print garbage?

I'm actually amazed that it gets through post, but then pukes in something as "simple" as basic and printing out a text line to the screen at the bottom.
either way, an emulator in basic. awesome.
 
This is certainly your next clue. when you issue a print hex$(blah) does it execute your div code?
what other opcodes does that hit?

printing garbage usually means you're clobbering a pointer or perhaps returning data in a register that you shouldn't be. perhaps if your div was the same thing as a nop instruction, changing no data in registers whatsoever, does print hex$(blah) still print garbage?

I'm actually amazed that it gets through post, but then pukes in something as "simple" as basic and printing out a text line to the screen at the bottom.
either way, an emulator in basic. awesome.

well its not GARBAGE, per se. it's trying to come up with hex output but its like theres some bad calculations converting the values to ASCII i think. i've looked at all my decimal ascii adjust related instructions and they appear good. i'll post them here in a minute. the code's on another machine.

heres some example of the hex output:
fake86-hexprint.png


and the line on the bottom isn't a single string it prints out. it does them one at a time and i think something is hapenning in between. also changing the div code to be the same as NOPs does nothing. i thought it might have been my division as i thought maybe it used it to generate hex from decimal. you know, like AND it with 15, do some addition and checks to convert to HEX ASCII, divide by 16, do it again.

side note, i'm impressed with the speed of FreeBASIC. this emulator runs fairly quick on any decent machine. decent meaning low-end P4 or better.
 
Uhm, just occurred to me... are you sure FB handles treating longints as booleans properly? I've seen a LOT of languages screw that up. You may want to swap to GT/LT compares just to test that's not what's messing with you... Why I don't trust loosly typecast languages and/or force typecasting where possible.

Oh, hey!!!

putreg16 dx, getreg16(ax) Mod oper1

That should be temp1, not getreg16(ax) shouldn't it? Preceeding DX can make the mod come up with different results if your operator isn't a power of two!

$0001:0000 mod 3 = 1... your code for unsigned DIV would return zero.

Well there's your problem...

Also... all your 'processing' variables are 32 bit integer/uinteger, right? Temp1,temp2, Oper1? If so all of your 'ands' are wrong! (I don't see you changing typecasting on the fly)

I'd give this a try:
Code:
	case 6: 'DIV
		if oper1=0 then intcall86 0: exit sub 'division by zero
		if word then
			temp1=(getreg16(dx) shl 16) or getreg16(ax)
			temp2=temp1\oper1
			if (temp2 and &hFFFF0000)>0 then intcall86 0: exit sub 'divide overflow
			putreg16 ax,temp2
			putreg16 dx,temp1 mod oper1
		else
			temp1=getreg16(ax)
			temp2=temp1\oper1
			if (temp2 and &hFFFFFF00)>0 then intcall86 0: exit sub 'divide overflow
			al=temp2
			ah=temp1 mod oper1
		end if

	case 7: 'IDIV
		if oper1=0 then intcall86 0: exit sub 'division by zero
		if word Then
			temp1=(getreg16(dx) Shl 16) or getreg16(ax)
			temp2=temp1\oper1
			if (temp2 and &h7FFF0000)>0 intcall86 0: exit sub 'divide overflow
			putreg16 ax,temp2
			putreg16 dx,temp1 mod oper1
		else
			temp1=getreg16(ax)
			temp2=temp1\oper1
			if (temp2 and &h7FFFFF00)>0 intcall86 0: exit sub 'divide overflow
			al=temp2
			ah=temp1 mod oper1
		end if

I also have to ask, how are you setting SF? Is that a side effect of your 'sign' variable? I'd have something like

SF=(temp2 and &h$80000000) on the two IDIV ops. (SF being a boolean... does FB have a boolean 'type'?)

You may have to have two sets of tempVars, one in integer and one in uinteger to get the correct handling of the integer divides... depends on how FB handles them behind the scenes... also given the size of those 'and' amounts I'd be setting those up as named constants -- likewise you might want to consider doing same with the case conditions so you have something more meaningful than a number to look at... but that depends on how/if FB does constant folding.

Another problem could arise from a lack of sign extension or forced sign extension... if your getreg(ax) returns an unsigned int, you won't have sign preservation operating on it in IDIV, if it returns a signed int, it will be processed incorrectly in DIV...You might want to implement a 'getreg16i' and 'get8i' to force a signed result type... unless you can have direct forced typing... Is it possible in FB to overlap a complex type over another? Reading the wiki I see it actually has pointers and complex types...

or is that why you had all those extra IF statements?!?

Oh, and thanks -- Looking at all this I'm half tempted to try and spin my own x86 emu in Pascal once I get my game creation library finished.
 
Last edited:
Uhm, just occurred to me... are you sure FB handles treating longints as booleans properly? I've seen a LOT of languages screw that up. You may want to swap to GT/LT compares just to test that's not what's messing with you... Why I don't trust loosly typecast languages and/or force typecasting where possible.

Oh, hey!!!

putreg16 dx, getreg16(ax) Mod oper1

That should be temp1, not getreg16(ax) shouldn't it? Preceeding DX can make the mod come up with different results if your operator isn't a power of two!

$0001:0000 mod 3 = 1... your code for unsigned DIV would return zero.

Well there's your problem...

Also... all your 'processing' variables are 32 bit integer/uinteger, right? Temp1,temp2, Oper1? If so all of your 'ands' are wrong! (I don't see you changing typecasting on the fly)

I'd give this a try:
Code:
	case 6: 'DIV
		if oper1=0 then intcall86 0: exit sub 'division by zero
		if word then
			temp1=(getreg16(dx) shl 16) or getreg16(ax)
			temp2=temp1\oper1
			if (temp2 and &hFFFF0000)>0 then intcall86 0: exit sub 'divide overflow
			putreg16 ax,temp2
			putreg16 dx,temp1 mod oper1
		else
			temp1=getreg16(ax)
			temp2=temp1\oper1
			if (temp2 and &hFFFFFF00)>0 then intcall86 0: exit sub 'divide overflow
			al=temp2
			ah=temp1 mod oper1
		end if

	case 7: 'IDIV
		if oper1=0 then intcall86 0: exit sub 'division by zero
		if word Then
			temp1=(getreg16(dx) Shl 16) or getreg16(ax)
			temp2=temp1\oper1
			if (temp2 and &h7FFF0000)>0 intcall86 0: exit sub 'divide overflow
			putreg16 ax,temp2
			putreg16 dx,temp1 mod oper1
		else
			temp1=getreg16(ax)
			temp2=temp1\oper1
			if (temp2 and &h7FFFFF00)>0 intcall86 0: exit sub 'divide overflow
			al=temp2
			ah=temp1 mod oper1
		end if

I also have to ask, how are you setting SF? Is that a side effect of your 'sign' variable? I'd have something like

SF=(temp2 and &h$80000000) on the two IDIV ops. (SF being a boolean... does FB have a boolean 'type'?)

You may have to have two sets of tempVars, one in integer and one in uinteger to get the correct handling of the integer divides... depends on how FB handles them behind the scenes... also given the size of those 'and' amounts I'd be setting those up as named constants -- likewise you might want to consider doing same with the case conditions so you have something more meaningful than a number to look at... but that depends on how/if FB does constant folding.

Another problem could arise from a lack of sign extension or forced sign extension... if your getreg(ax) returns an unsigned int, you won't have sign preservation operating on it in IDIV, if it returns a signed int, it will be processed incorrectly in DIV...You might want to implement a 'getreg16i' and 'get8i' to force a signed result type... unless you can have direct forced typing... Is it possible in FB to overlap a complex type over another? Reading the wiki I see it actually has pointers and complex types...

or is that why you had all those extra IF statements?!?

Oh, and thanks -- Looking at all this I'm half tempted to try and spin my own x86 emu in Pascal once I get my game creation library finished.

some good points, and tonight when i got more time i'm going to look closer into all that. i was just tinkering a bit with the program though, and was able to get this to happen.... :)

fake86-mspacman.png


i can play it as well. and yeah, you should start a x86 emulator this has been one hell of a learning experience for me.
 
Can you run this under QBASIC on an XT?
With less memory of course and taking a year to POST, but it would be interesting to see. :)

heh, actually.... there are some FB-isms that will have to be changed like bitshift functions, but with very minor changes it oughta work if i drop it down to support maybe 16 KB of RAM.

oh god that would run so awful on an 8088. eek! QBASIC would be evil. it would run INTERPRETED! would be way better to compile it in quickbasic to an EXE file. it would still be terrible.
 
btw deathshadow, i believe you're right on that one line that should actually be putreg16 dx, temp1 mod oper1

good call!

btw if you'd like to see ALL of my code, i'd be happy to post it up. you might cry though.
 
Last edited:
here's a video showing my emulator playing Evolution. weird graphical glitches, yet the game logic seems flawless. strange! wonder if it has anything to do with maybe waiting for screen refreshes via probing the CGA ports??

 
ooh, i fixed 2 stupid bugs in the ReadDisk routine. it was reading out of the disk images 1 byte off, and the variable it was keeping track of the memory destination in was an unsigned integer.... yeah, 16-bit variables to store memory addresses are not going to work out too well. now i get here booting DOS:

fake86-ohshit.png


but it still has a problem, here's from the end of the trace log:

Code:
Exec: 2E CS: @ 0:19ed
Exec: FF GRP5 Ev @ 0:19ee
 Mode:0
  Reg:5
   RM:6
   EA: 106
  AX = FE00      BX = 0000      CX = 0000      DX = A0BC
  CS = 0000      IP = 0000      SI = 88EC      DI = 8B18
  DS = 0A4F      ES = 9B0E      SS = 8ED4      SP = 04D8
  Flags: CF=0    ZF=0    SF=1    OF=0    IF=1    AF=0    DF=0    PF=0

Exec: 23 AND Gv Ev @ 0:0
 Mode:3
  Reg:7
   RM:7
  AX = FE00      BX = 0000      CX = 0000      DX = A0BC
  CS = 0000      IP = 0002      SI = 88EC      DI = 8B18
  DS = 0A4F      ES = 9B0E      SS = 8ED4      SP = 04D8
  Flags: CF=0    ZF=0    SF=1    OF=0    IF=1    AF=0    DF=0    PF=1

Exec: 0 ADD Eb Gb @ 0:2
 Mode:3
  Reg:6
   RM:0
  AX = FEA0      BX = 0000      CX = 0000      DX = A0BC
  CS = 0000      IP = 0004      SI = 88EC      DI = 8B18
  DS = 0A4F      ES = 9B0E      SS = 8ED4      SP = 04D8
  Flags: CF=0    ZF=0    SF=1    OF=0    IF=1    AF=0    DF=0    PF=1

Exec: F4 HLT @ 0:4

GRP5 /5 is JMP using address from a pointer. it's getting the effective address wrong somehow and jumping to 0:0. i'm working on it. this is good though, progress.
 
btw if you'd like to see ALL of my code, i'd be happy to post it up. you might cry though.
I'd love to see it -- as seasoned as I am at writing ASM, there's still a lot I don't grasp (like just exactly what does and doesn't go on inside ADC for example... or what the AF is even FOR)...

I did start playing with writing my own in Free pascal -- it's cute because Pascal makes handling the registers SO simple thanks to 'absolute'.

Code:
	tProcessor=packed object

		AH,AL,
		BH,BL,
		CH,CL,
		DH,DL:byte;

		CS,SS,DS,ES,
		SI,DI,SP,BP,IP,
		flags:word;

		AX:word absolute AH;
		BX:word absolute BH;
		CX:word absolute CH;
		DX:word absolute DH;

		memory:array[0..$FFFFF] of byte;

Let's me just overlap AX,BX,CX and DX directly over their 8 bit sub-registers... so I don't need any extra functions to set/get them. Also helps that it lets me typecast to signed or unsigned on the fly...
 
I'd love to see it -- as seasoned as I am at writing ASM, there's still a lot I don't grasp (like just exactly what does and doesn't go on inside ADC for example... or what the AF is even FOR)...

I did start playing with writing my own in Free pascal -- it's cute because Pascal makes handling the registers SO simple thanks to 'absolute'.

Code:
	tProcessor=packed object

		AH,AL,
		BH,BL,
		CH,CL,
		DH,DL:byte;

		CS,SS,DS,ES,
		SI,DI,SP,BP,IP,
		flags:word;

		AX:word absolute AH;
		BX:word absolute BH;
		CX:word absolute CH;
		DX:word absolute DH;

		memory:array[0..$FFFFF] of byte;

Let's me just overlap AX,BX,CX and DX directly over their 8 bit sub-registers... so I don't need any extra functions to set/get them. Also helps that it lets me typecast to signed or unsigned on the fly...

that IS handy. although i believe i could do something similar using pointers in FreeBASIC. there are many optimizations i need to make. even as is, it's rather fast though. i'm bout to close up my shop here but when i get home, i'll clean up my code a bit and post it. i might start a new thread. this thread's title has nothing do with it's current course..
 
Back
Top