• Please review our updated Terms and Rules here

Looking for Sample 8080 Assembly or Machine language listings for simple programs ...

wperko

Experienced Member
Joined
Jul 4, 2007
Messages
465
Hi,

I am looking for Sample 8080 Assembly or Machine language listings for a few simple programs ... I'm looking at how each 8080 instruction works, but it seems my brain is too old and feeble to write a program, so I need some starter help that I can experiment with and maybe learn.

An assembly program that starts at the typical CP/M programs address (I'm guessing 0100), then writes a "zero" into all the RAM from 0C00 to 0FFF.

Then another similar that will step starting from address 0C00 every time I hit the <ENTER> key, let me type in a byte that will be saved in that RAM address and then step to the next until it gets to address 0FFF.


.
 
Look around for the Scelbi 8080 "cookbook". It's usually available for sale and sometimes you can find a download.
It has a stupid name but don't hold that against it.

It will take you through the instruction set , common programming routines ,IO routines ,math ,table lookup ,search and ending with a floating point library.
It's a great starting point and will give you enough parts to build a program from scratch.

Well written.
 
If you go with the Cookbook, remember that the first edition used mnemonics for the 8080 that were invented by Scelbi, and different from the standard Intel mnemonics. This was changed in the second edition - so be sure to pick up the second edition Cookbook with the standard mnemonics.

smp
 
might be more useful.
You didn't mention the format of the number. I assume HEX since you show the address in HEX.
You need to get a character from ConIn and convert from a hex ascii into a binary number then write it to the address.
Dwight

Hi,

Thanks for the links guys ... but I think what I need are some small starter programs. I've been playing with an emulator and learning how individual commands work, but this doesn't teach me how to make a more complete program.

I think what I need is:

Some sample programs that ASM(DR).COM can compile ... I prefer Octal format, but Hex is okay too.

1. A program that can collect a character or a string from the console (88-2SIOJP Port 0 = 10h/11h) and send it to a specific RAM address 6000 octal/C00hex, then increment that RAM address for the next character. To exit the program I would type a "Ω" character and then CP/M 2.2 prompt would come back.

Perhaps with that assembly listing, I might be able to experiment with modifying the looping and input.

2. A program that can read a DATA.TXT file and send it to RAM address 6000 octal/C00hex.

3. A program that can collect a character or a string from the console (88-2SIOJP Port 0 = 10h/11h) and send it to a file on the floppy DSK.

I'm hoping to to eventually have one program that I can use to sketch patterns on a DAZZLER II S-100 board. Another program that can read a DATA.TXT file, decode the text and display it on the DAZZLER II screen. Maybe create another that sends the decoded output to the console or even 88-2SIOJP Port 1 = 12h/13h.

But first I need some smaller sample programs so I can identify what I need for subroutines to call as needed. Basically, it's easier to copy/paste from other programs and make small changes to have a new program. That's how I got started with MBASIC/BASIC-80 even though I can only write the most fundamental programs.


.
 
>> An assembly program that starts at the typical CP/M programs address (I'm guessing 0100), then writes a "zero" into all the RAM from 0C00 to 0FFF.

org 100h
lxi h,0c00h
x: mvi a,0
mov m,a
inx h
mov a,h
ani 10h
jnz x
hlt

The binary would be:

0100 21 00 0c
0103 3e 00
0105 77
0106 23
0107 7c
0108 e6 10
010a c2 03 01
010d 76

Please keep in mind that the average programmer introduces 1 bug in every 8 lines of code. I'm completely average, so there might be at least 1 bug in the above! *smile*

>> Then another similar that will step starting from address 0C00 every time I hit the <ENTER> key, let me type in a byte that will be saved in that RAM
>> address and then step to the next until it gets to address 0FFF.

The function you describe is most certainly common to every monitor program under the sun. If you can locate an 8080 monitor, and isolate that part of the code, you ought to be all set.

Roger
 
This almost sounds like some homework assignment, except what school uses 8080 anymore ;)

I've combined the questions into something that might at least be marginally useful, but as norwestrzh said you're probably looking for a monitor program.

Code:
; This example program does one of two things:

; - If a file named DATA.TXT exists, it is read into memory at LOAD$ADR

; - If not, it reads data from the serial port instead, until a specific
;   character is encountered, and saves it to the file

; Can be changed to read octal values too

LOAD$ADR        EQU 6000Q       ;where to load data
OMEGA           EQU '}'         ;end marker

SIO$STATUS      EQU 020Q        ;status port
SIO$DATA        EQU 021Q        ;data port

BDOS    EQU     5               ;jump address to BDOS entry point

        ORG     400Q
        JMP     START

FCB:
        DB      0               ;drive number (0=default)
        DB      'DATA    '      ;8 char name
        DB      'TXT'           ;3 char extension
        DB      0,0,0,0         ;zero init EX,S1,S2,RC
        DS      16              ;reserve space for block numbers
        DB      0               ;current record for sequential I/O

MSG$OLD DB      'DATA.TXT found, loading to memory',13,10,'$'
MSG$NEW DB      'DATA.TXT not found, enter new data',13,10,'$'
MSG$ERR DB      'Error creating file',13,10,'$'
MSG$DSK DB      'Disk full!',13,10,'$'

DMA$ADR DS      2               ;variable for storing pointer
END$ADR DS      2               ;end address of data read from serial port

;;;;;;;;
; Direct serial I/O routines (maybe use BIOS instead?)
;;;;;;;;

SIO$READ:
        ;input from serial port into register A
        IN      SIO$STATUS      ;read the status
        RAR                     ;shift "receive data" bit into carry
        JNC     SIO$READ        ;loop until bit is set
        IN      SIO$DATA        ;read the data
        RET

SIO$READ$ECHO:
        ;read with echo
        CALL    SIO$READ
        MOV     C,A
SIO$WRITE:
        ;output from register C to serial port
        IN      SIO$STATUS      ;read the status
        ANI     2               ;test "transmit empty" bit
        JZ      SIO$WRITE       ;loop until bit is set
        MOV     A,C             ;character to write into A
        OUT     SIO$DATA        ;write it
        RET

;;;;;;;;
; BDOS interface
;;;;;;;;

OPEN$FILE:
        ;DE => FCB
        ;reserved fields in FCB (EX,S1,S2) should be clear
        ;returns high bit of A set on error (not found)
        MVI     C,15
        JMP     BDOS

DEL$FILE:
        ;DE => FCB
        MVI     C,19
        JMP     BDOS

MAKE$FILE:
        ;DE => FCB
        ;CP/M doesn't check if file already exists, and will
        ;create another one with same name. Shouldn't happen
        ;in this program, but in the general case we need to
        ;explicitly delete an existing file to avoid this.
        PUSH    D
        CALL    DEL$FILE
        POP     D
        MVI     C,22
        JMP     BDOS

CLOSE$FILE:
        ;DE => FCB
        MVI     C,16
        JMP     BDOS

READ$SEQ:
        ;DE => FCB
        ;returns A nonzero if error (end of file)
        MVI     C,20
        JMP     BDOS

WRITE$SEQ:
        ;DE => FCB
        ;returns A nonzero if error
        MVI     C,21
        JMP     BDOS

NEXT$DMA:
        ;set address to DMA$ADR and increment it by 128
        LHLD    DMA$ADR         ;current address into HL
        MOV     D,H             ;copy to DE
        MOV     E,L
        LXI     B,128           ;128 bytes per record
        DAD     B               ;add to current address
        SHLD    DMA$ADR         ;and save it for next time
SET$DMA:
        ;DE => new buffer for read/write
        MVI     C,26
        JMP     BDOS

PRINT$STR:
        ;DE => string terminated with '$'
        MVI     C,9             ;function number
        JMP     BDOS

;;;;;;;;
; Input routines for raw-with-end-marker and octal
; both preserve DE, return character in A and zero flag set on end
;;;;;;;;

GET$CHAR        EQU SIO$READ$ECHO

TERM$READ:
        CALL    GET$CHAR
        CPI     OMEGA           ;set zero flag if end marker
        RET

OCT$READ:
        ;skips over space and control characters
        ;reads an octal number, returned in HL (16 bit) and A (8 bit)
        CALL    GET$CHAR        ;get first char
        CPI     041Q            ;control/whitespace?
        JC      OCT$READ        ;then loop until not
        LXI     H,0             ;init result
OCTL1:  SUI     '0'             ;convert ASCII to digit
        JC      OCTXX           ;below '0'? -> exit
        CPI     8
        JNC     OCTXX           ;above '7'? -> exit
        DAD     H               ;multiply HL by 8
        DAD     H
        DAD     H
        ORA     L               ;OR in the new digit
        MOV     L,A
        CALL    GET$CHAR        ;get next
        CPI     041Q            ;control/whitespace?
        JNC     OCTL1           ;if not then loop
        ;got valid number, ZF clear
        MOV     A,L             ;return byte result in both A and L
        RET

OCTXX:  ;invalid character - return with ZF set
        CMP     A               ;compare A with itself to set ZF
        RET

;;;;;;;;
; Main program
;;;;;;;;

START:
        LXI     H,LOAD$ADR      ;init address variable
        SHLD    DMA$ADR

        LXI     D,FCB
        CALL    OPEN$FILE
        ANA     A               ;check return status
        JM      NO$FILE         ;high bit set if file not found

; File opened, read it into memory

        LXI     D,MSG$OLD
        CALL    PRINT$STR

READ$LOOP:
        CALL    NEXT$DMA
        LXI     D,FCB
        CALL    READ$SEQ
        ANA     A               ;check return status
        JZ      READ$LOOP       ;loop until nonzero

        ;CP/M actually doesn't require closing a file unless the size
        ;was changed (so never when only reading), but it's cleaner
        ;to do it anyway. This also avoids problems when running on an
        ;emulator on some other OS that does keep track of open files.

CLOSE$EXIT:
        LXI     D,FCB
        JMP     CLOSE$FILE      ;will return to CP/M

; File not found, read from serial port and save to file

NO$FILE:
        LXI     D,MSG$NEW
        CALL    PRINT$STR

        LXI     D,LOAD$ADR      ;init pointer to memory
READ$INP:
        CALL    TERM$READ       ;get next character (raw)
        JZ      SAVE$FILE       ;end?
        STAX    D               ;store it
        INX     D               ;point to next byte
        JMP     READ$INP        ;and loop

SAVE$FILE:
        XCHG                    ;end pointer into HL
        SHLD    END$ADR         ;and save it for later

        LXI     D,FCB
        CALL    MAKE$FILE
        ANA     A               ;check return status
        LXI     D,MSG$ERR
        JM      PRINT$STR       ;if failed, print message and exit

SAVE$LOOP:
        LHLD    END$ADR
        XCHG                    ;DE=end address
        LHLD    DMA$ADR         ;HL=DMA address
        MOV     A,L             ;compare DMA$ADR - END$ADR
        SUB     E
        MOV     A,H
        SBB     D
        JNC     CLOSE$EXIT      ;if not below, we are done

        CALL    NEXT$DMA
        LXI     D,FCB
        CALL    WRITE$SEQ
        ANA     A               ;check return status
        JZ      SAVE$LOOP       ;loop until nonzero

        ;error writing to file
        LXI     D,MSG$DSK
        CALL    PRINT$STR
        JMP     CLOSE$EXIT

        END
 
Hi,

I'm 74 years old. I built an Altair 8800 computer in early 1975 ... started front panel switch flipping and learning machine language, but as a college student, I couldn't afford to keep the computer and sold it to pay for more college (that was a mistake I didn't learn I made for a few years.)

Now I want to create some programs for the DAZZLER II board in my Altair 8800c computer and since nobody else is interested in creating some fun useful programs I will have to try and do it myself which means I need to learn 8080 assembly programming. This is one of the books recommended to help me get started: http://www.bitsavers.org/pdf/osborne/books/CPM_Assembly_Language_Programming_1983.pdf and near the beginning was a little assembly listing ... but it didn't have any comments, so I had to start looking up each of the opcodes and looking into a couple other books I was able to create a working program with good comments any beginner can learn from:

; CHAR2CPM.ASM.txt

BDOS EQU 5 ;Symbol Declaration "BDOS" a Subroutine I/O Device Function Calls for CP/M to Perform Pre--Written Subroutine Tasks
WCONF EQU 2 ;Symbol Declaration WCONF to use the CP/M Write to Console Subroutine I/O Device Function Call
ORG 100h ;Starting Address of the Program in Memory for Programs Running in a CP/M Environment (TPA = Transient Program Area)
MVI C,WCONF ;Load the "C" register with the "2" Write to Console CP/M Subroutine
MVI E,'$' ;Load the "E" Register with a "$" Character to Send to the Console
CALL BDOS ;Engage CPM Subroutines
JMP 0 ;Vector Address 0000 to get to the CCP Entry Point (Console Command Processor) so CP/M can Re-Initialize to the CP/M Prompt
END ;Could be Pseudo Operation Code



As a beginner, I'm hoping more people can create some short simple assembly subroutines with good comments so I can learn and perhaps others can benefit from them too.

The idea is to learn how each subroutine works so that they can be slightly modified into a larger program. Kind of Assembly Program Building Blocks ...


.
 
>> An assembly program that starts at the typical CP/M programs address (I'm guessing 0100), then writes a "zero" into all the RAM from 0C00 to 0FFF.

org 100h
lxi h,0c00h
x: mvi a,0
mov m,a
inx h
mov a,h
ani 10h
jnz x
hlt

The binary would be:

0100 21 00 0c
0103 3e 00
0105 77
0106 23
0107 7c
0108 e6 10
010a c2 03 01
010d 76

Please keep in mind that the average programmer introduces 1 bug in every 8 lines of code. I'm completely average, so there might be at least 1 bug in the above! *smile*

>> Then another similar that will step starting from address 0C00 every time I hit the <ENTER> key, let me type in a byte that will be saved in that RAM
>> address and then step to the next until it gets to address 0FFF.

The function you describe is most certainly common to every monitor program under the sun. If you can locate an 8080 monitor, and isolate that part of the code, you ought to be all set.

Roger

Hi,

I'm trying to understand the logic of the subroutine ... To learn is to have good comments to the instructions ... but I am not getting the point of the "mov a,h and ani 10h" statements ...

; ZERORAM2.ASM.txt

ORG 100h ;Starting Program Address for Programs that Run Under CP/M

LXI H,0C00h ;Set Low Address (Address Range to work on 0C00h to 0FFFh)

X: MVI A,0 ;Set a ZERO into the Accumulator

MOV M,A ;Move the ZERO inside the Accumulator to Memory

INX H ;Increment the "H" register which is a Memory Address

MOV A,H ;Move Contents of "H" register into Accumulator


ANI 10H ;AND Immediate with Accumulator ??? Why?

JNZ X ;Jump to Start of LOOP and Do It Again

HLT ;END of Program



.
 
I am not a programmer and I assure you that I struggle with this as much as you do. I am a hardware person. So it might be like the blind leading the blind.

I think you want the process to stop when the address exceeds 0FFFh, the next address is 1000h. In this case 10h is in the H register and 00 is in the L register. So I think the program is trying to detect this state to jump out of the loop.

In this loop the H register can only have values 0Ch, 0Dh,0Eh,0Fh or 4 non zero values. Ideally the program needs to detect when the H register hits 10h, but how to do it ? In essence you are trying to detect a zero in the lower nibble of the H register to decide when to jump out of the loop.

In your program, the H register is tested with the code ANI 10h, But if you AND 10h with 10h, it won't give a zero result it will give 10h, but the JNZ instruction that follows, is looking for a zero result in the zero flag to jump out of the loop (I think).

So maybe the ANI 10h instruction should have been ANI 0Fh (known as a masking instruction), because AND'ing 0Fh with 10h in the H register would give a zero result, but AND'ing 0Fh with the other 4 possible values of the H register would not and the loop would keep going. Sorry if I got this wrong, somebody would surely correct it.

I also suppose that maybe if the 10h value was not an error, in that case it might have worked with SUI 10h instead of ANI 10h.

Also INX H means increment extended register (or register pair), so it increments the HL register pair not just the H register.
 
Last edited:
Hugo is right about how the code is detecting 1000h.

However, this code is very fragile. If you need to stop at any other value, it needs to be rewritten.
 
An assembly program that starts at the typical CP/M programs address (I'm guessing 0100), then writes a "zero" into all the RAM from 0C00 to 0FFF.
Code:
                          00001 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                          00002 ;
                          00003 ; Writes a "zero" into all the RAM from 0C00 to 0FFF.
                          00004 ;
                          00005 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                          00006
 0005                     00007 BDOS    equ     5                       ; CP/M function entry point
                          00008
 0100                     00009         org     100h                    ; It runs in the TPA
                          00010
 0100                     00011 Start:
 0100 21 0C00        [10] 00012         lxi     H,0C00h                 ; Point HL to 0C00h
 0103 11 0400        [10] 00013         lxi     D,0FFFh-0C00h+1         ; Load number of bytes
                          00014
 0106                     00015 Loop:
 0106 36 00          [10] 00016         mvi     M,0                     ; Store a zero at [HL]
                          00017
 0108 23              [5] 00018         inx     H                       ; Point HL to next location
                          00019
 0109 1B              [5] 00020         dcx     D                       ; Decrement count
                          00021
 010A 7A              [5] 00022         mov     A,D                     ; Has count reached 0?
 010B B3              [4] 00023         ora     E
 010C C2 0106        [10] 00024         jnz     Loop                    ; No
                          00025
 010F 0E 00           [7] 00026         mvi     C,0                     ; Exit to CP/M
 0111 CD 0005        [17] 00027         call    BDOS
                          00028
 0100                     00029         end     Start
 
Then another similar that will step starting from address 0C00 every time I hit the <ENTER> key, let me type in a byte that will be saved in that RAM address and then step to the next until it gets to address 0FFF.
Unfortunately, my code is more than 10000 characters...
 
Then another similar that will step starting from address 0C00 every time I hit the <ENTER> key, let me type in a byte that will be saved in that RAM address and then step to the next until it gets to address 0FFF.
Code:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Starting from address 0C00 every time I hit the <ENTER> key,
;   let me type in a byte that will be saved in that RAM address
;   and then step to the next until it gets to address 0FFF.
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

BDOS    equ    5            ; CP/M function entry point

CR    equ    0Dh            ; Carriage return in ASCII
LF    equ    0Ah            ; Line feed in ASCII

    org    100h            ; It runs in the TPA

Start:
    lxi    H,0C00h            ; Point HL to 0C00h
    lxi    D,0FFFh-0C00h+1        ; Load number of bytes

ByteLoop:
    call    Prompt            ; Prompt for a value

    call    GetHexByte        ; Get the value
    jc    ByteLoop        ; Try again if error

    mov    M,A            ; Store the value at [HL]

    inx    H            ; Point HL to next location

    dcx    D            ; Decrement count

    mov    A,D            ; Has count reached 0?
    ora    E
    jnz    ByteLoop        ; No

    call    PutCRLF

    mvi    C,0            ; Exit to CP/M
    call    BDOS

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Issue a prompt for a value in the form of <address>:
;
; Input:
;   register HL contains the address
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Prompt:
    call    PutCRLF            ; Start a new line

    call    PutHexAddress        ; Convert and output address

    mvi    A,':'            ; Output ': '
    call    PutChar
    mvi    A,' '
    call    PutChar

    ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Output the contents of register HL in hexadecimal
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PutHexAddress:
    mov    A,H            ; Get high byte of address

    call    PutHexByte        ; Convert and output it

    mov    A,L            ; Get low byte of address

; Fall through to PutHexByte

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Output the contents of register A in hexadecimal
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PutHexByte:
    push    PSW            ; Save value

    rrc                ; Isolate the upper nybble
    rrc
    rrc
    rrc
    call    PutHexNybble        ; Convert to ASCII and output

    pop    PSW            ; Recover value

; Fall through to PutHexNybble

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Output the low byte of register A in hexadecimal
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PutHexNybble:
    ani    0Fh            ; Isolate the lower nybble

    cpi    10            ; Below 10 is numeric
    jc    Numeric

    adi    'A'-10            ; Convert 10..15 to 'A'..'F'

    jmp    Common

Numeric:
    adi    '0'            ; Convert 0..9 to '0'..'9'

Common:

; Fall through to PutChar

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Output the character in register A to the console
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PutChar:
    push    PSW            ; Save all registers
    push    B
    push    D
    push    H

    mov    E,A            ; Put the character where CP/M wants
    mvi    C,2            ; Output it
    call    BDOS

    pop    H            ; Recover all registers
    pop    D
    pop    B
    pop    PSW

    ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Output carriage return and line feed to the console
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PutCRLF:
    mvi    A,CR            ; Output a carriage return
    call    PutChar
    mvi    A,LF            ; Output a line feed
    call    PutChar

    ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Input a byte in hexadecimal from the console to register A
;
; Output:
;   carry flag set if error
;   carry flag clear if valid value in register A
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
GetHexByte:
    mvi    B,'0'            ; Presume leading zero

    call    GetChar            ; Get input
    call    PutChar            ; Echo it

    cpi    0Dh            ; No value entered?
    jz    GotNoValue

    mov    C,A            ; Remember the value

    call    GetChar            ; Get more input
    call    PutChar            ; Echo it

    cpi    0Dh            ; Only one hexit entered?
    jz    DoConversion

    mov    B,C            ; Previous is now the leading hexit
    mov    C,A            ; This is the lower one

ExpectCRLoop:
    call    GetChar            ; Need to get a carriage return
    call    PutChar            ; Echo it

    cpi    0Dh            ; If CR, then try to convert it
    jz    DoConversion

    mvi    B,0            ; Bad input, spoil it

    jmp    ExpectCRLoop

DoConversion:
    mov    A,B            ; Get leading hexit

    call    ConvertHexNybble    ; Try converting to binary
    jc    GotBadValue

    rlc                ; Shift into upper nybble
    rlc
    rlc
    rlc

    mov    B,A            ; Save upper nybble

    mov    A,C            ; Get low hexit

    call    ConvertHexNybble    ; Try converting to binary
    jc    GotBadValue

    ora    B            ; Combine and clear carry flag

    ret

GotNoValue:
    push    D
    push    H

    lxi    D,NoValue

    jmp    ErrorMsg

GotBadValue:
    push    D
    push    H

    lxi    D,BadValue

ErrorMsg:
    mvi    C,9            ; Output string
    call    BDOS

    lxi    D,Entered
    mvi    C,9
    call    BDOS

    pop    H
    pop    D

    stc

    ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Convert a nybble in hexadecimal in register A
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ConvertHexNybble:
    cpi    '0'            ; Bad if < '0'
    jc    BadConvert

    cpi    '9'+1            ; Numeral if <= '9'
    jc    ConvertNumeral

    ani    0DFh            ; Fold lowercase to uppercase

    cpi    'A'            ; Bad if < 'A'
    jc    BadConvert

    cpi    'F'+1            ; Bad if > 'F'
    jnc    BadConvert

    sui    'A'-10            ; Convert 'A'..'F' to 10..15

    ret

ConvertNumeral:
    sui    '0'            ; Convert '0'..'9' to 0..9

    ret

BadConvert:
    stc                ; Set carry flag to return error

    ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Input a character from the console to register A
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
GetChar:
    push    B            ; Save all registers
    push    D
    push    H

    mvi    C,1            ; Input it
    call    BDOS

    pop    H            ; Recover all registers
    pop    D
    pop    B

    ret

BadValue:
    db    'Bad$'

NoValue:
    db    'No$'

Entered:
    db    ' value entered$'

    end    Start
 
Probably though wperko might like to start writing simple assembly language programs that are independent of CP/M its convenient function calls, so that they do not require it and are not dependent on CP/m to operate. That way he can load them into a ROM or RAM In his 8080 computer and run them anytime he likes without CP/M or disk drives being involved. I made a lot of simple programs like this for my SOL-20 to do things like block moves, memory testing, memory data manipulations etc, that he wants to do.

Though if he wants to send and retrieve named files to & from disk, that needs CP/M and its bdos function calls.

(But remember, this is advice from a hardware person, not a programmer).
 
Hugo is right about how the code is detecting 1000h.

However, this code is very fragile. If you need to stop at any other value, it needs to be rewritten.
But for his example he doesn't. I don't think he is trying to write flexible code, just enough for him to understand it and make it work for him, so the notion of "fragile code" doesn't help him.
 
This is why ever since I was a kid in '79, my favorite thing to do is disassemble code. I learned a lot from looking at TRS-80 Level II BASIC, there's a lot of good code in there. In a way, I learned assembly language directly from Bill Gates. If you're looking for simple programs, they're everywhere, you just need to know what to look for. Then you just sit down and try to figure out what the code is doing. The more code you read, the more you notice certain good patterns that are used a lot. And a good disassembler is like a good pair of reading glasses. Even a not so good disassembler is useful in that you get to understand when it doesn't work right.

Although I skipped the 8080 mnemonics from the start. I know them, but I prefer not to use them.

I think really what you need is something you can poke code into memory and see what it does. Some kind of emulator with a good debugger that lets you single step and see all the registers as you go along. It's a lot easier to understand what's going on when you can see everything at once.

Or just find an emulator for a single-board computer like the KIM-1, etc., or even CP/M, and go at it the same way you would in '78, only on a modern screen.
 
Probably though wperko might like to start writing simple assembly language programs that are independent of CP/M its convenient function calls, so that they do not require it and are not dependent on CP/m to operate. That way he can load them into a ROM or RAM In his 8080 computer and run them anytime he likes without CP/M or disk drives being involved. I made a lot of simple programs like this for my SOL-20 to do things like block moves, memory testing, memory data manipulations etc, that he wants to do.

Though if he wants to send and retrieve named files to & from disk, that needs CP/M and its bdos function calls.

(But remember, this is advice from a hardware person, not a programmer).
Hi,

Thanks Hugo, I was initially thinking that way so that the programs could run in any OS, but then somebody pointed out that most hobby Altair 8800s and IMSAI 8080s just ran CP/M anyway. So, for now since I work in a CP/M environment, I do want to learn assembly that uses CP/M BDOS calls first. I figure once I get comfortable with that, I should be able to figure out how to do other tasks sans CP/M.

My ultimate goal is to get three or four programs for the reproduction Cromemco DAZZLER II board, hopefully they continue to work with the original DAZZLER boards as well. Learning assembly is a by-product of playing with my Altair 8800c computer. I can barely write simple programs in MBASIC/BASIC-80 ...




My ultimate goal is to create a few fun programs for the DAZZLER and DAZZLER II boards. Most users are running their DAZZLERs from CP/M 2.2b … so when the programs exit (generally by typing a “Q”, the program would then restart CP/M and goto the CP/M prompt.

1. Simple artistic graphics display of growing squares on the screen starting at the center of the screen with a 4x4 dot square all the way up to the 64x64 dot square, then maybe triangles, then pentagons, then hexagons, then octagons ... and when a "Q" is typed, the program will Quit back to CP/M prompt.


2. An Etch-A-Sketch type program where the user can move a blinking cursor dot around the screen starting from either, the top left corner, bottom left corner. The user would use four keyboard keys to move the blinking cursor dot "I" = up, "K" = down, "L" = right, and "J" = left, select a color number and press the spacebar or return key to fix the color dot on the screen, then give the image a name and save it to the floppy drive letter of their choice (A:, B:, C:, or D:) and finally a "Q" to Quit back to CP/M prompt.


3. A game “Zombie Escape” … the idea that the game would be a first person, shooter game. The user would see simple black and white graphics 128x128 display on the screen similar to this image:

1723466549198.png


Using the same "JIKL" keys to turn left, go forward, go back, turn right ... IF the key is held down, the person is running, SPACEBAR = Shoot a Stun Gun at a Zombie that pops into view so you can either run past or turn and enter a new hallway (maybe it's a portal to a different room on the map) and finally one portal would be labeled "EXIT" to do just like "Q" ... Quit back to the CP/M prompt. IF scoring is added, it could be the number of rooms visited + stuns.


4. A Coding/De-Coding program that displays a message only on the DAZZLER and DAZZLER II screen. The user would have a text file that is the "KEY", type in a message and the program would start from the numbered position requested to substitute letters from the typed in message. IF the message is longer than the "KEY" file, then the program would go back to the first letter of the "KEY" text and continue until the message is fully coded.

This would be fun for little kids to type messages to their friends sharing the "KEY" file and messages. They would at first learn a little bit about coding and de-coding messages, but it would also get them to use some version of an old CP/M computer and maybe encourage them to get into the S-100 hobby.

I need to name a kid (preferably under 30 years old) in my will to give all my vintage computer stuff to so I know it's going to be played with and not just trashed or recycled parts.


.
 
Last edited:
Although I skipped the 8080 mnemonics from the start. I know them, but I prefer not to use them.

I prefer the 8080 mnemonics, because each of them corresponds to an actual machine code pattern. And for that reason I would especially recommend them for beginners: once you learn them, you know exactly what operations are available - Z80 adds some more of course, but it's very irregular!

For example, 8080 has "XCHG" to exchange the DE and HL register pairs, and "XTHL" to exchange HL with the top of stack. In Zilog's version of the assembly language, those are "EX DE,HL" and "EX HL,(SP)", and there is also "EX AF,AF'" to exchange the accumulator and flags with an alternate register. But these are the only forms allowed for that instruction, so it makes more sense to have each of them as separate mnemonics.

And then there's "EXX", which exchanges BC, DE, and HL each with their alternate registers - for consistency, it should be named something like "EX BCDEHL,BC'DE'HL'", but that would obviously be a bit excessive!

Also "JP (HL)" should be either "JP HL" or "LD PC,HL" (as in the 8080 mnemonic: "PCHL"). Since it loads the program counter with the value in HL, not with whatever is in memory at that address.

////////

Another tutorial example for learning 8080: you may have read that CP/M function 9 prints a string, but it has to be terminated with a '$' character, instead of a null byte as usual. Also you might want to print a string "inline" without having to load a pointer to it. So here's a way to do it, illustrating the use of some 8080 instructions:

Code:
PUT$STR:
        XTHL                    ;exchange HL with return address
        CALL    PUT$STR$HL      ;print it
        XTHL                    ;exchange HL (past end of string) with ret addr
        RET                     ;return there

PUT$STR$HL:
        MOV     E,M             ;load character into E
        INX     H               ;increment pointer to next character
        INR     E               ;test E for zero (first increment it)
        DCR     E               ;then decrement again - will set flag if zero
        RZ                      ;return if E is zero
        MVI     C,2             ;function number to print a character
        PUSH    H               ;preserve HL (may be destroyed by function call)
        CALL    5               ;call CP/M
        POP     H               ;restore HL
        JMP     PUT$STR$HL      ;and loop

MAIN:
        ;here's how to use it
        CALL    PUT$STR
        DB      'Hello world!',13,10,0
        RET
 
Last edited:
Back
Top