Page 2 of 5

Re: Simulating an 8080. Is there a better way to do this?

Posted: Fri Mar 26, 2021 9:00 pm
by BillG
IamRob wrote:
I did. The outcome is still the same as your code above, but without the Scratch variable.

The AND #$10 preserves the Aux Carry flag, which comes from the DEX TXA EOR above.
The AND #$3A also preserves the Aux Carry flag. If you punch the code in you will see you get exactly the same result as when using the scratch variable.

I had a typo in my previous post. It should have been #$3A, not #$3C.
Consider
Quote:
Command? u100l4
0100 3E10 mvi A,10
0102 3D dcr A
0103 3D dcr A
Command? r
A=00 BC=0000 DE=0000 HL=0000 SP=0000 PC=0100 szapc #=0000000000000000
0100 3E10 mvi A,10
Command? t
A=10 BC=0000 DE=0000 HL=0000 SP=0000 PC=0102 szapc #=0000000000000007
0102 3D dcr A
Command? t
A=0F BC=0000 DE=0000 HL=0000 SP=0000 PC=0103 szAPc #=000000000000000C
0103 3D dcr A
Command? t
A=0E BC=0000 DE=0000 HL=0000 SP=0000 PC=0104 szapc #=0000000000000011
0104 C30000 jmp 0000
I do not see how the aux carry flag is cleared in the second dcr unless it is ANDed out before ORing in the new aux carry.

Re: Simulating an 8080. Is there a better way to do this?

Posted: Sat Mar 27, 2021 4:07 pm
by BB8
if you have some bits in A that you want to merge with other bits from a byte in memory you can do:

Code: Select all

   eor Mem
   and #mask    ; mask corresponding to the bits you want from A; the others will be from Mem
   eor Mem
thus you can spare a STA

Code: Select all

   ldx regA
   txa         ; A= old value
   dex
   stx regA
   eor regA      ; oldval Eor newval

   eor regCC	      ; these lines insert the bits identified by maskAC
   and #maskAC	      ; into the value from regCC
   eor regCC

   and #maskAC^maskC  ; only AC and C bits; zero the others ($10 OR $2A)

   ora ValueFlags,X
   sta regCC
As for "dcr M"

Code: Select all

	clc
	lda RegL
	sta Ptr
	lda RegH
	adc #>Ram80
	sta Ptr+1
	ldy #0
	lda (Ptr),y
	tax		; X=oldvalue

	; clc		; you don't need it: actually if you had Carry=1 
			; it means trouble because you wrapped around memory
	sbc #0		; A=A-1
	sta (Ptr),y
	txa		; A=oldvalue
	eor (Ptr),y
	
	eor regCC
	and #maskAC
	eor regCC

	and #maskAC^maskC

	ora ValueFlags,x
	sta regCC

Re: Simulating an 8080. Is there a better way to do this?

Posted: Sat Mar 27, 2021 5:27 pm
by IamRob
BillG wrote:
IamRob wrote:
I did. The outcome is still the same as your code above, but without the Scratch variable.

The AND #$10 preserves the Aux Carry flag, which comes from the DEX TXA EOR above.
The AND #$3A also preserves the Aux Carry flag. If you punch the code in you will see you get exactly the same result as when using the scratch variable.

I had a typo in my previous post. It should have been #$3A, not #$3C.
I do not see how the aux carry flag is cleared in the second dcr unless it is ANDed out before ORing in the new aux carry.
I see what I missed. You have COMMON and MCOMMON labels being called from somewhere else. I would need to see the circumstances in which those two labels are being called to be of any help.

Re: Simulating an 8080. Is there a better way to do this?

Posted: Sun Mar 28, 2021 5:22 am
by BillG
BB8 wrote:
if you have some bits in A that you want to merge with other bits from a byte in memory you can do:

Code: Select all

   eor Mem
   and #mask    ; mask corresponding to the bits you want from A; the others will be from Mem
   eor Mem
The force is strong in that one...

I am trying to wrap my head around it.

So if the bit in the accumulator is 0, the first eor replaces it with the corresponding one from Mem. The and preserves it. The second eor clears it.

If the bit is 1, the first eor replaces it with the inverse of the corresponding one from Mem. The second eor leaves it set.

OK.

That would replace this

Code: Select all

 0004 29 10           [2] 00013          and    #f_A
 0006 85 02           [3] 00014          sta    Scratch
                          00015
 0008 A5 03           [3] 00016          lda    Reg_CC
 000A 29 2A           [2] 00017          and    #!(f_S|f_Z|f_A|f_P|f_C)
                          00018
 000C 05 02           [3] 00019          ora    Scratch
with this

Code: Select all

 000E 45 03           [3] 00023          eor    Reg_CC
 0010 29 10           [2] 00024          and    #f_A
 0012 45 03           [3] 00025          eor    Reg_CC
                          00026
 0014 29 11           [2] 00027          and    #f_A|f_C
for a savings of two bytes and three cycles.

And this

Code: Select all

 0016 B1 00         [5/6] 00031          lda    (Ptr),Y
 0018 AA              [2] 00032          tax
 0019 CA              [2] 00033          dex
 001A 8A              [2] 00034          txa
 001B 51 00         [5/6] 00035          eor    (Ptr),Y
 001D 29 10           [2] 00036          and    #f_A
 001F 85 02           [3] 00037          sta    Scratch
 0021 8A              [2] 00038          txa
 0022 91 00           [6] 00039          sta    (Ptr),Y
                          00040
 0024 A5 03           [3] 00041          lda    Reg_CC
 0026 29 2A           [2] 00042          and    #!(f_S|f_Z|f_A|f_P|f_C)
 0028 05 02           [3] 00043          ora    Scratch
with this

Code: Select all

 002A B1 00         [5/6] 00047          lda    (Ptr),Y
 002C AA              [2] 00048          tax
 002D E9 00           [2] 00049          sbc    #0
 002F 91 00           [6] 00050          sta    (Ptr),Y
 0031 8A              [2] 00051          txa
 0032 51 00         [5/6] 00052          eor    (Ptr),Y
                          00053
 0034 45 03           [3] 00054          eor    Reg_CC
 0036 29 10           [2] 00055          and    #f_A
 0038 45 03           [3] 00056          eor    Reg_CC
                          00057
 003A 29 11           [2] 00058          and    #f_A|f_C
for a savings of two bytes and five cycles.

Re: Simulating an 8080. Is there a better way to do this?

Posted: Sun Mar 28, 2021 5:36 am
by BillG
IamRob wrote:
You have COMMON and MCOMMON labels being called from somewhere else. I would need to see the circumstances in which those two labels are being called to be of any help.
There are fourteen varieties of dcr and inr register instructions.

One of the others is:

Code: Select all

                          02543 ;
                          02544 ; dcr B ; Decrement register B
                          02545 ;
 B17C                     02546 dcrB_:
 B17C A6 17           [3] 02547          ldx    Reg_B     ; Decrement the register
 B17E CA              [2] 02548          dex
                          02549
 B17F 8A              [2] 02550          txa              ; Make a copy of the result
 B180 45 17           [3] 02551          eor    Reg_B     ; Borrowed from upper nybble if its low bit changed
                          02552
 B182 86 17           [3] 02553          stx    Reg_B     ; Store the new value
                          02554
 B184 4C B16A         [3] 02555          jmp    dcrCommon
In addition to dcr M, there is the similar inr M; both end up jumping to dcrMCommon

Re: Simulating an 8080. Is there a better way to do this?

Posted: Sun Mar 28, 2021 5:48 am
by BillG
In the meantime, I worked on the daa instruction, decimal adjust accumulator. This is probably the most complicated instruction on the 8080.

The programming manual for the 6800 provides the most detailed explanation I have seen:
daa.png
This is the code:

Code: Select all

                          02374 ;
                          02375 ; daa ; Decimal adjust accumulator
                          02376 ;
                          02377 ; See page A-34 in the Motorola M6800 Programming Reference Manual
                          02378 ;
 B0C8                     02379 daa__:
 B0C8 A5 15           [3] 02380          lda    Reg_A
 B0CA A8              [2] 02381          tay              ; Make a copy of the high nybble
 B0CB 29 0F           [2] 02382          and    #$F       ; Isolate low nybble
 B0CD AA              [2] 02383          tax
                          02384
 B0CE E0 0A           [2] 02385          cpx    #9+1      ; Is low nybble above 9?
 B0D0 90 06 (B0D8)  [2/3] 02386          blo    daaLowInRange ; No
                          02387
 B0D2 C0 90           [2] 02388          cpy    #$90      ; Will this cause upper nybble to go over?
 B0D4 B0 10 (B0E6)  [2/3] 02389          bhs    daaAdjustUpper ; Yes
 B0D6 90 08 (B0E0)  [2/3] 02390          blo    daaNotAux
                          02391
 B0D8                     02392 daaLowInRange:
 B0D8 A5 14           [3] 02393          lda    Reg_CC
 B0DA 29 10           [2] 02394          and    #f_A      ; Is aux carry flag set?
 B0DC F0 02 (B0E0)  [2/3] 02395          beq    daaNotAux ; No
                          02396
 B0DE A2 0A           [2] 02397          ldx    #10       ; Lower nybble needs to be adjusted
                          02398
 B0E0                     02399 daaNotAux:
 B0E0 A5 14           [3] 02400          lda    Reg_CC
 B0E2 29 01           [2] 02401          and    #f_C      ; Is carry flag set?
 B0E4 F0 02 (B0E8)  [2/3] 02402          beq    daaProceed ; No
                          02403
 B0E6                     02404 daaAdjustUpper:
 B0E6 A0 A0           [2] 02405          ldy    #$A0      ; Upper nybble needs to be adjusted
                          02406
 B0E8                     02407 daaProceed:
 B0E8 A9 00           [2] 02408          lda    #0        ; Start with no adjustments
                          02409
 B0EA E0 0A           [2] 02410          cpx    #10       ; Does the lower nybble need adjustment?
 B0EC 90 02 (B0F0)  [2/3] 02411          blo    2f        ; No
                          02412
 B0EE 09 06           [2] 02413          ora    #$06
                          02414
 B0F0                     02415 2:
 B0F0 C0 A0           [2] 02416          cpy    #$A0      ; Does the upper nybble need adjustment?
 B0F2 90 02 (B0F6)  [2/3] 02417          blo    2f        ; No
                          02418
 B0F4 09 60           [2] 02419          ora    #$60
                          02420
 B0F6                     02421 2:
 B0F6 18              [2] 02422          clc              ; Do the adjustment(s)
 B0F7 65 15           [3] 02423          adc    Reg_A
 B0F9 85 15           [3] 02424          sta    Reg_A
 B0FB AA              [2] 02425          tax              ; Make a copy of the result to set flags
                          02426
 B0FC A5 14           [3] 02427          lda    Reg_CC
 B0FE 29 3B           [2] 02428          and    #!(f_S|f_Z|f_P)  ; Clear the flags we are updating
                          02429
 B100 C0 A0           [2] 02430          cpy    #$A0      ; The carry flag is set if upper nybble adjusted
 B102 90 02 (B106)  [2/3] 02431          blo    2f
                          02432
 B104 09 01           [2] 02433          ora    #f_C
                          02434
 B106                     02435 2:
 B106 1D AA9A       [4/5] 02436          ora    ValueFlags,X  ; Set the S, Z and P flags accordingly
 B109 85 14           [3] 02437          sta    Reg_CC
                          02438
 B10B 4C ADEE         [3] 02439          jmp    PCPlus1
I am sure it can be improved. The first thing to try is to build the adjustment byte from the carry and aux carry flags, then use the adc instruction in decimal mode to do the rest.

This is part of a program to test it:

Code: Select all

        mvi     A,'9'           ; Test 9
        lxi     D,Desc9
        call    ShowTest

        mvi     A,99h
        mvi     C,9
        add     C
        daa

        mvi     H,008h          ; Expect 08 value
        mvi     C,1             ; Expect carry set
        call    TestIt

        jmp     2f

Desc9   db      '99 + 09 -> 08 + carry$'
;
2:
        jmp     0

;
;
;
TestIt:
        mvi     B,0             ; Start with no error

        mov     L,A             ; Save result

        mvi     A,0             ; Get carry flag
        ral

        cmp     C               ; Test carry flag
        jz      PassC

        call    ShowBadC

        inr     B               ; Indicate error

PassC:
        mov     A,L             ; Test result
        cmp     H
        jz      PassV

        call    ShowBadVal

        inr     B               ; Indicate error

PassV:
        mov     A,B
        ora     A
        jnz     Fail

        call    ShowPass

        ret

Fail:
        jmp     0

;
;
;
ShowTest:
        sta     TestID          ; Implant test ID

        push    PSW
        push    B
        push    H

        push    D

        mvi     C,9
        lxi     D,Test
        call    BDOS

        mvi     C,9
        lxi     D,TestID
        call    BDOS

        pop     D

        mvi     C,9
        call    BDOS

        mvi     C,2
        mvi     E,0Dh
        call    BDOS
        mvi     C,2
        mvi     E,0Ah
        call    BDOS

        pop     H
        pop     B
        pop     PSW

        ret

;
;
;
ShowPass:
        push    PSW
        push    B
        push    H

        mvi     C,9
        lxi     D,Passed
        call    BDOS

        pop     H
        pop     B
        pop     PSW

        ret

;
;
;
ShowBadVal:
        push    PSW
        push    B
        push    H

        mvi     C,9
        lxi     D,BadVal
        call    BDOS

        pop     H
        pop     B
        pop     PSW

        ret

;
;
;
ShowBadC:
        push    PSW
        push    B
        push    H

        mvi     C,9
        lxi     D,BadC
        call    BDOS

        pop     H
        pop     B
        pop     PSW

        ret

;
; Strings
;
Test    db      0Dh,0Ah,'Test $'
TestID  db      '0: $'
Passed  db      'Passed.',0Dh,0Ah,'$'
BadVal  db      'Value wrong.',0Dh,0Ah,'$'
BadC    db      'Carry flag wrong.',0Dh,0Ah,'$'

Re: Simulating an 8080. Is there a better way to do this?

Posted: Sun Mar 28, 2021 10:45 am
by IamRob
I think this should work. It might be a touch slower but will save quite a bit of memory and also keeps all your registers together.

Code: Select all

f_C		equ $1
f_A		equ $10

Ptr		equ $12
Reg_CC	equ $14
Reg_A	equ $15
Reg_B	equ $17
Reg_H	equ $1B

; add more increment registers here

inc_A:	ldx	#Reg_A
		bne	incrCommon

inc_B:	ldx	#Reg_B

incrCommon:
		lda	$0,x
		inc	$0,x
		eor	$0,x
		tax
		jmp	Common

; add more decrement registers here

dcr_A:	ldx	#Reg_A
		bne	dcrCommon

dcr_B:	ldx	#Reg_B

dcrCommon:
		lda	$0,x
		dec	$0,x
		eor	$0,x
		tax

Common:	and	#$10		; Isolate and save aux carry flag
		sta	Scratch

dcrMCommon:
		lda	Reg_CC	; Clear the flags we are updating
		and	#$2A
		ora	Scratch	; Fold in the aux carry flag
		ora	ValueFlags,Y  ; (AA90) Set the S, Z and P flags accordingly
		sta	Reg_CC
		jmp	PCPlus1	; (ADE4)
Also this code snippet shows the very last line as a JMP to "dcrCommon" with an address of $B16A. But the above "dcrCommon" has an address of $ADF2. Are there 2 labels of dcrCommon?

Code: Select all

                          02543 ;
                          02544 ; dcr B ; Decrement register B
                          02545 ;
 B17C                     02546 dcrB_:
 B17C A6 17           [3] 02547          ldx    Reg_B     ; Decrement the register
 B17E CA              [2] 02548          dex
                          02549
 B17F 8A              [2] 02550          txa              ; Make a copy of the result
 B180 45 17           [3] 02551          eor    Reg_B     ; Borrowed from upper nybble if its low bit changed
                          02552
 B182 86 17           [3] 02553          stx    Reg_B     ; Store the new value
                          02554
 B184 4C B16A         [3] 02555          jmp    dcrCommon

Re: Simulating an 8080. Is there a better way to do this?

Posted: Sun Mar 28, 2021 11:23 am
by IamRob
And for the Memory Register

Code: Select all

; dcr M ; Decrement memory register
dcrM_:
		clc
		lda	$1A

Reg_L	 ; Get target address
		adc	#<RAM80	; (00 )
		sta	Ptr
		lda	Reg_H
		adc	#>RAM80	; (B0)
		sta	Ptr+1
Replace this:

Code: Select all

		ldy	#0
		lda	(Ptr),Y
		tax			; Decrement the register
		dex

		txa			; Make a copy of the result
		eor	(Ptr),Y	; Borrowed from upper nybble if its low bit changed
		and	#$10		; Isolate and save aux carry flag
		sta	Scratch

		txa			  ; Store the new value
		sta	(Ptr),Y
		jmp	dcrMCommon	; (AFD6)
with this:

Code: Select all

		ldy #0
		lda (Ptr),y
		sta temp
		tax
		dex
		txa
		sta (Ptr),y
	
		ldx #temp
		jmp dcrCommon
Note: by doing it this way you and if you can remove the entry point and label "dcrMCommon", then you can also use the shorter method I posted before of:

Code: Select all

Common:
		AND #$10
		ORA Reg_CC
		AND #$3A ; preserve the #$10 bit ( bit #4?)
		ORA ValueFlags,X
		STA Reg_CC

Re: Simulating an 8080. Is there a better way to do this?

Posted: Sun Mar 28, 2021 1:05 pm
by BillG
BB8 wrote:
As for "dcr M"

Code: Select all

	clc
	lda RegL
	sta Ptr
	lda RegH
	adc #>Ram80
	sta Ptr+1
	ldy #0
	lda (Ptr),y
I noticed you are assuming that Ram80 is page-aligned.

There is no reason it could not be if I put the memory at the low end of the address space and my code at the high end.

Then I can lay out my variables like this:

Code: Select all

Reg_H   rmb  1
PtrHL:
Reg_L   rmb  2
so that the code can just do this:

Code: Select all

	clc
	lda RegH
	adc #>Ram80
	sta PtrHL+1
	ldy #0
	lda (PtrHL),y

Re: Simulating an 8080. Is there a better way to do this?

Posted: Sun Mar 28, 2021 1:10 pm
by BillG
IamRob wrote:
I think this should work. It might be a touch slower but will save quite a bit of memory and also keeps all your registers together.
That is a good idea the reuse code by loading the address of a variable in register X. My priority is currently speed, so I'll file this one away for later in case I need to cut the size of the code down.
IamRob wrote:
Also this code snippet shows the very last line as a JMP to "dcrCommon" with an address of $B16A. But the above "dcrCommon" has an address of $ADF2. Are there 2 labels of dcrCommon?
The project is a work in progress and I am posting snapshots from different versions of the assembly listing.

Re: Simulating an 8080. Is there a better way to do this?

Posted: Sun Mar 28, 2021 1:12 pm
by BillG
I fixed a couple of bugs:

the dcr/inr code was clearing the carry flag when it should not and the daa code was not clearing it before ORing in the new value.

Re: Simulating an 8080. Is there a better way to do this?

Posted: Sun Mar 28, 2021 2:21 pm
by BB8
here an 8080 Manual.

At page 15:
DAA.jpg

Code: Select all

Daa:
'low nibble
	fAdd := false
	if AuxCarry = 1 
		fAdd := true
	elseif lownibble > 9 
		fAdd := true
	endif
	if fAdd  
		value := value + 6
		set Carry flag if overflowed the 8th bit   ; <-- this is my guess
	endif
	set AuxCarry accordingly
'high nibble
	fAdd := false
	if Carry == 1 
		fAdd := true
	elseif highnibble > 9
		fAdd := true
	endif
	if fAdd
		value := value + 60
		set Carry flag if overflowed
	end if
Set other flags accordingly to result
So: [not sure if correct]
- adjust +6 if lowernibble is > 9, or if AC was set
- adjust +60 if highernibble > 9, or if carry was set, or if carry gets set by lower nibble adjustment
- set AC to 1 if the adjustment set it, 0 otherwise
- set Carry to 1 if adjustment set it, leave unmodified otherwise
- set other flags based on the result

Re: Simulating an 8080. Is there a better way to do this?

Posted: Mon Mar 29, 2021 1:29 am
by BillG
BB8 wrote:
Thanks for reminding me of that.

Generally, I prefer the Intel 8080 Microcomputer Users Manual of 1975 as my reference on the 8080.
Next is either the Intel 8080-8085 Assembly Language Programming manual of 1977 or the Osborne book 8080a/8085 Assembly Language Programming.

Re: Simulating an 8080. Is there a better way to do this?

Posted: Mon Mar 29, 2021 11:30 pm
by BillG
It has been a fruitful day of hacking.

The emulator has implemented enough instructions so that MBASIC can run this program:

Code: Select all

100 print "Hello "+"world."
110 gosub 200
120 end
200 for i=10 to 1 step -1 : print i : next i : print "Liftoff...we have liftoff!"
210 return
A bit of history...

Three weeks ago, I set out to start implementing an emulator to run FLEX programs written for the 6800 on the 6502. Immediately, there were significant issues which required organized thought instead of merely hacking through them.

So the next day, I pivoted to an emulator to allow running 8080 programs written for the CP/M operating system.

* the 8080 is an easier processor to simulate. Many instructions do not change the status flags.
* the 8080 has input and output instructions. It is not necessary to emulate memory-mapped I/O so an emulator is simpler and likely faster.
* The CP/M operating system has a simpler API than FLEX.

The initial version runs in the 6800 FLEX environment. After a week of good process, work began on a version for the 6502.

The 6502 version has caught up with its 6800 sibling and now both progress together in lockstep.

Current efforts run completely in a bare emulator environment so that I do not have to boot FLEX to test and I do not have to continually move files onto a virtual disk. For faster turnaround, the 8080 program is built into the emulator image. There is no disk access provided to the simulated program at this time.

The test programs used so far include:

* The Space Voyage game paraphrased from the original 6800 code to the 8080 instruction set.
* DDT, the CP/M debugger.
* MBASIC, the CP/M version of Microsoft BASIC

Re: Simulating an 8080. Is there a better way to do this?

Posted: Tue Mar 30, 2021 8:58 am
by BigEd
Great progress!