; Z-19.ASM Z-19 emulator for the SSM VB3 video card ; ; Version 1.0 Released 82.4.18 ; Copyleft (L) Scott W. Layson. All rights reversed. ; This code is in the public domain. ; ; This code performs approximate (but usually adequate) emulation ; of the Heath/Zenith Z-19 terminal on the SSM VB3 video card. It ; supports most of the Z-19 features required for normal text editing. ; About the only useful thing missing is the special 25th line (49th?). ; ; Things to note about this code: ; -- The indentation in this file looks so strange because I edit with ; five-column instead of eight-column tabs. ; -- Sections of this code, especially the relatively low-level routines, ; are not well commented. Sorry 'bout that. ; -- The code uses Intel mnemonics, but there are a couple of Z-80 ; block moves inserted with `db's. If you don't have a Z-80, you'll ; have to replace these with 8080 loops that do the same thing. ; -- This file requires MAC for assembly as it stands, though it might ; not be very hard to make it ASM-compatible. ; -- It assumes that the video controller has already been initialized ; (to 48 lines, but you can change this in the block of code below). ; -- There's no keyboard I/O code in this file. ; -- I have to turn the lower 48K of my main memory off, because my ; ExpandoRam II doesn't respond to PHANTOM* on write. You will ; presumably have to change or remove this code; it's all right ; near the beginning. ; -- This code uses a fair amount of stack space; I don't know exactly ; how much. Because of this and because I'm turning most of main ; memory off, I use a private stack at the top of memory. I recommend ; you do likewise if possible. ; -- I normally use this code in "roll mode" rather than "scroll mode": ; after the last line on the screen is printed, the cursor is placed ; on the top line, which is then cleared. The screen is never scrolled. ; I find this more comfortable on a P39 monitor, where scrolling leaves ; "trails". The Z-19, of course, supports only "scroll mode". The ; defaults for this and other modes are set in the `db's at the end ; of the file. ; -- The behavior of this code when it gets an insert- or delete- line ; command is a little weird. Instead of doing the insertion or ; deletion immediately, it counts the number of successive insertions ; or deletions and only executes them when it gets some other kind ; of command. Sometimes this means that when you give an insert-line ; or delete-line command to your editor, nothing happens until you ; type another character. The insert-n-lines and delete-n-lines ; commands, on the other hand, are executed immediately. ; -- Consider: when you put a terminal into inverse video mode and give ; it a clear command of some sort (clear-to-end-of-line, home-and-clear- ; screen), should it clear to inverse or normal spaces? The Z-19 clears ; to normal spaces; this code, like most other "smart" terminals these ; days, clears to inverse spaces. Likewise for the other video ; attributes it supports. ; -- The "graphics" mode accesses the user-definable font ROM. Obviously, ; for emulation of Z-19 graphics mode, you must burn a ROM with that ; character set. ; ; Here is a list of control characters and escape sequences the code ; currently supports. A `*' marks sequences the Z-19 doesn't have. ; ; BS ; TAB ; CR ; LF ; Esc @ Enter insert-char mode ; Esc A Cursor up ; Esc B Cursor down ; Esc C Cursor forward ; Esc D Cursor backward ; Esc E Home and clear ; Esc F Enter graphics mode ; Esc G Exit graphics mode ; Esc H Home ; Esc J Clear to end of screen ; Esc K Clear to end of line ; Esc L Insert line ; Esc M Delete line ; Esc N Delete char ; Esc O Exit insert-char mode ; Esc Y <r> <c> Cursor pos ; *Esc l <n> Insert n lines ; *Esc m <n> Delete n lines ; Esc p Enter inverse video ; Esc q Exit inverse video ; Esc x/y 5 Turn cursor off/on ; *Esc x/y * Enter/exit scroll mode ; *Esc x/y A Enter/exit inverse video ; *Esc x/y B Enter/exit hide-char mode ; *Esc x/y C Enter/exit underline mode ; *Esc x/y D Enter/exit blink mode ; *Esc x/y E Enter/exit strike-thru mode ; *Esc x/y F Enter/exit dim mode ; *Esc x/y @ Set/clear all video modes ; Esc v Wrap mode on ; Esc w Wrap mode off ; ; And now the code itself. vnormal equ 3 ; standard character attribute code vinverse equ 4 ; inverse video bit bs equ 08h tab equ 09h lf equ 0ah cr equ 0dh esc equ 1Bh vkbstat equ 0e0h ; video keyboard status port vkbdata equ 0e1h ; video keyboard data port vc equ 0d0h ; video controller registers: vhcount equ vc + 0 ; char times per scan vhsync equ vc + 1 ; interlace(1), hsp(4), hbp(3) vchars equ vc + 2 ; 0(1), scans/char(4), chars/line(3) vlines equ vc + 3 ; lines/frame vscans equ vc + 4 ; scans/frame vvsync equ vc + 5 ; vertical scan delay vscrol equ vc + 6 ; scroll register vcolin equ vc + 9 ; cursor column in vcolout equ vc + 12 ; cursor column out vrowin equ vc + 8 ; cursor row in vrowout equ vc + 13 ; cursor row out vncols equ 80 ; number of cols vnrows equ 48 ; number of rows vvideo equ 2000h ; address of video memory voffset equ 1000h ; offset to attributes ramcard equ 0FFh ; ExpandoRam bank-switch port ramoff equ 1 ; to turn main memory off ramon equ 0 ; to turn memory back on org 0F810h ; Main entry point. The character is in C. h19 push h ; save registers push d push b lxi h, 0 dad sp ; get stack pointer lxi sp, 0 ; move stack to high memory push h ; save old SP mvi a, ramoff ; turn main memory off out ramcard out vkbstat ; enable VB3 call curoff ; turn cursor off call process ; process char call curon ; cursor back on out vkbdata ; disable VB3 mvi a, ramon ; turn main memory back on out ramcard pop h ; recover old SP sphl ; and put the stack back where it was pop b ; restore registers pop d pop h ret ; and done! process lda escmode ; are we in an escape sequence? ora a jnz escseq ; yes: go interpret this char mov a, c cpi esc ; test for esc before checking insdelcnt jz doesc ; in case we're getting another ins or del lda insdelcnt ; any saved line insertions or deletions to do? ora a cnz doinsdel ; yes: do them first mov a, c ; check for the known control chars cpi cr ; (others are displayed) jz docr cpi lf jz dolf cpi bs jz dobs cpi tab jz dotab ; we have a displayable character. display lda insmode ; are we in insert-char mode? ora a cnz inschar ; yes: move rest of line over first lda attrib ; get current attribute byte mov b, a ; set up for putchar call putchar call right ; move cursor right rnz ; done if no wrap lda wrapp ; are we in wrap mode? ora a rz ; no: return jmp nextline ; wrap occurred ; write the char in c, attribute in b, to the video memory. putchar push h call addr ; get memory address of char mov m, c ; store char lxi d, voffset dad d ; address of attribute mov m, b ; store attribute pop h ret nextline mvi l, 0 ; move to col. 0 ; move the cursor down one line. Clear the new line ; if the cursor was on the last logical line. Scroll the screen ; if in scroll mode. dolf lda scrollp ; are we in scroll mode? ora a jnz dolfscr ; yes: go do the right thing dolfnscr lda lastlrow ; do lf in non-scroll mode: cmp h ; are we moving off the last logical row? push psw ; save the answer to that question call down ; move down jnz dolf3 mvi h, 0 ; wrap to top of screen dolf3 pop psw ; are we moving off the last row? rnz ; no: done call nextlrow ; increment last-logical-row jmp dolf2 dolfscr call down ; do lf in scroll mode rnz ; not moving off bottom: done call nextlrow ; increment last-logical-row out vscrol ; scroll screen sta lastprow ; set last-physical-row dolf2 push h mvi l, 0 call cleol ; clear the new line pop h ret nextlrow lda lastlrow ; increment last-logical-row inr a cpi vnrows ; modulo vnrows jnz nextl1 xra a nextl1 sta lastlrow ; store result ret docr mvi l, 0 ; CR: set col to 0 ret dobs jmp left ; BS: move cursor left dotab mov a, l ; TAB: move to current col... ani 0F8h ; ... modulo 8 ... adi 8 ; ... plus 8 cpi vncols jz right ; except near edge of screen mov l, a ret ; turn the cursor off. Returns logical cursor address in HL. curoff lhld curaddr ; get logical cursor address mvi a, 0FFh out vcolout ; move cursor off screen ret ; turn the cursor on. Called with logical cursor address in HL. curon shld curaddr ; save logical cursor address lda curoffp ora a rnz ; if cursor turned off, leave it off screen mov a, l ; set column out vcolout lda lastprow ; set row relative to last physical row inr a add h cpi vnrows jc curon1 sui vnrows curon1 out vrowout ret ; move the cursor left, if possible. Returns Z iff at left edge. left mov a, l ; get col dcr l ora a rnz ; R(not at left edge) mov l, a ; force col. 0 ret ; move the cursor right, if possible. Returns Z iff at right edge. right inr l mvi a, vncols cmp l rnz ; R(not at right edge) mvi l, vncols - 1 ; can't just dcr, cuz it clears Z! ret ; move the cursor down, if possible. Returns Z iff at bottom. down inr h mvi a, vnrows cmp h rnz ; R(not at bottom) mvi h, vnrows - 1 ; can't just dcr, cuz it clears Z! ret ; move the cursor up, if possible. Returns Z iff at top. up mov a, h dcr h ora a rnz mov h, a ret ; clear to end of line. cleol mov d, h ; set de to end of line mvi e, vncols - 1 mvi c, ' ' ; space character in c lda attrib ; current attribute in b mov b, a jmp fills ; and do it! ; home and clear. hcl call home jmp cleow ; cursor home. home lxi h, 0 ret ; clear to end of window (screen). cleow lda lastprow sta lastlrow ; set lastlrow to bottom of screen mvi d, vnrows - 1 ; set de to end of screen mvi e, vncols - 1 mvi c, ' ' ; space char in c lda attrib ; current attribute in b mov b, a jmp fills ; and do it! ; insert a character at the cursor. inschar push b push h mvi a, vncols - 1 ; how many chars to move? sub l jz insch1 ; none: skip mov c, a mvi b, 0 ; # chars in bc push b ; and save it mvi l, vncols - 1 ; set hl to end of line call addr ; get starting address of move push h mov d, h ; dest in de mov e, l dcx h ; source in hl ; lddr ; and move! db 0EDh, 0B8h ; Z80 instruction pop h ; address pop b ; byte count lxi d, voffset ; now do attributes dad d mov d, h ; just like before mov e, l dcx h ; lddr db 0EDh, 0B8h ; another Z80 instruction insch1 pop h pop b ret ; delete a character at the cursor. delchar push b push h mvi a, vncols - 1 ; get # of chars to move sub l jz delch1 ; none: skip mov c, a mvi b, 0 ; # chars in bc push b call addr ; get starting address push h mov d, h ; de = dest mov e, l inx h ; hl = source ; ldir ; and move! db 0EDh, 0B0h ; Z80 instruction pop h ; address pop b ; byte count lxi d, voffset ; now do attributes dad d mov d, h ; just like before mov e, l inx h ; ldir ; and move! db 0EDh, 0B0h ; Z80 instruction delch1 pop h ; get current row, col back push h mvi l, vncols - 1 ; set up to clear last char in line mvi c, ' ' lda attrib mov b, a call putchar ; do it pop h pop b ret ; delete the line containing the cursor. delline mvi c, 1 ; and fall through ; delete <n> lines, starting with the one containing the cursor. ; <n> is in C. delnlines push h mov a, h add c ; other end of region to be deleted cpi vnrows jm deln1 mvi a, vnrows deln1 mov l, a ; h = first dest, l = first source mvi a, vnrows sub l mov b, a ; b = no. of lines to move call moveblk mvi a, vnrows sub c ; c = no. of lines to clear mov h, a ; h = first row to clear call clrblk pop h mvi l, 0 ; move to beginning of line ret ; insert a line where the cursor is. insline mvi c, 1 ; and fall through ; insert <n> lines before the line containing the cursor. ; <n> is in C. insnlines push h mov a, h mov l, h add c ; other end of region to be inserted cpi vnrows jm insn1 mvi a, vnrows insn1 mov h, a ; h = first dest, l = first source mvi a, vnrows sub h mov b, a ; b = no. of lines to move call moveblk mov h, l ; h = first row to clear call clrblk ; c = no. of lines to clear pop h mvi l, 0 ; move to beginning of line ret ; move a block of B lines from row L to row H. moveblk push h push d push b mov a, l cmp h jm movbrev movbfwd mov d, h mov h, l movbfwd1 mov a, b ora a jz movbret dcr b call movelin inr h inr d jmp movbfwd1 movbrev mov a, h add b mov d, a ; d = h + b mov a, l add b mov h, a ; h = l + b movbrev1 mov a, b ora a jz movbret dcr b dcr h dcr d call movelin jmp movbrev1 movbret pop b pop d pop h ret ; clear C lines starting at H. clrblk push h push d push b mvi l, 0 clrblk1 mov a, c ora a jz clrblk2 push b call cleol pop b inr h dcr c jmp clrblk1 clrblk2 pop b pop d pop h ret ; move a line from row H to row D. movelin push h push d push b mvi l, 0 mov e, l lxi b, voffset call addr push h dad b xchg call addr push h dad b xchg lxi b, vncols ; ldir db 0EDh, 0B0h ; Z80 instruction pop d pop h lxi b, vncols ; ldir db 0EDh, 0B0h ; Z80 instruction pop b pop d pop h ret ; get physical address from logical address. addr push b lda lastprow inr a add h cpi vnrows jc addr1 sui vnrows addr1 mov c, a mvi b, 0 mov a, l ; save col lxi h, rowtab dad b dad b mov c, a ; get col back mov a, m ; look row up in table inx h mov h, m mov l, a dad b ; add col pop b ret rowtab j set vncols i set 0 rept vnrows dw vvideo + j * i i set i + 1 endm ; fill the screen with the data in C, the attribute in B ; from x, y location HL through DE. fills push h call addr xchg call addr mov a, h cmp d jnz fills1a mov a, l cmp e fills1a xchg jnc fills1 ; J(area to fill doesn't wrap) push d push b lxi d, vvideo + vnrows * vncols - 1 call fills2 pop b pop d lxi h, vvideo fills1 call fills2 pop h ret fills2 push b push h push d call fill ; fill in the data pop h pop d lxi b, voffset dad b xchg dad b pop b mov c, b call fill ; fill in the attributes ret fill mov m, c ; put down first copy mov a, e sub l mov c, a mov a, d sbb h mov b, a ; bc = de - hl = no. bytes to move ora c rz ; R(nothing to do -- only one byte to fill) mov d, h ; hl = source mov e, l inx d ; de = dest ; ldir db 0EDh, 0B0h ; Z80 instruction ret ; turn on escape mode. doesc mvi a, stesc sta escmode ret ; ; These are the various escape-states we can be in. They indicate ; what part of an escape sequence we've seen already. stesc equ 1 ; Esc stcprow equ 2 ; Esc Y stcpcol equ 3 ; Esc Y <row> stsetmode equ 4 ; Esc x stclrmode equ 5 ; Esc y stinsn equ 6 ; Esc l stdeln equ 7 ; Esc m ; We get here if we're in the middle of an escape sequence. ; escmode is in A. escseq push psw xra a sta escmode ; clear escape mode here, for convenience pop psw cpi stcprow ; row byte? jz docprow cpi stcpcol ; col byte? jz docpcol cpi stsetmode ; set-mode byte? jz dosetmode cpi stclrmode ; clear-mode byte? jz doclrmode cpi stinsn ; insert-n-lines byte? jz doinsn cpi stdeln ; delete-n-lines byte? jz dodeln mov a, c cpi 'L' ; insert (1) line? jz doinslin cpi 'M' ; delete (1) line? jz dodellin lda insdelcnt ; for anything else: do any saved ora a ; insertions/deletions first cnz doinsdel mov a, c cpi 'Y' ; cursor pos? jz docp cpi 'K' ; clear to end of line? jz docleol cpi 'E' ; home and clear screen? jz dohcl cpi 'H' ; home? jz dohome cpi 'J' ; clear to end of screen? jz docleow cpi 'A' ; cursor up? jz doup cpi 'B' ; cursor down? jz dodown cpi 'C' ; cursor right? jz doright cpi 'D' ; cursor left? jz doleft cpi '@' ; set char-insert mode? jz inschon cpi 'O' ; clear char-insert mode? jz inschoff cpi 'N' ; delete char? jz dodelchar cpi 'l' ; insert n lines? jz doinsnlins cpi 'm' ; delete n lines? jz dodelnlins cpi 'p' ; set inverse video? jz doinvon cpi 'q' ; clear inverse video? jz doinvoff cpi 'x' ; set mode? jz setmode cpi 'y' ; clear mode? jz clrmode cpi 'v' ; set wrap mode? jz dowrapon cpi 'w' ; clear wrap mode? jz dowrapoff cpi 'F' ; set graphics mode? jz dografon cpi 'G' ; clear graphics mode? jz dografoff escfail push b ; not any recognized command. display mvi c, esc ; sequence literally so user can see what call display ; happened pop b call display ret docp mvi a, stcprow ; cursor pos command: set esc mode sta escmode ret docprow mov a, c ; get row byte sui 32 ; subtract bias mov h, a mvi a, stcpcol ; set new esc mode sta escmode lda curoffp inr a ; cursor off during CP sta curoffp ret docpcol mov a, c ; get col byte sui 32 ; subtract bias mov l, a lda curoffp dcr a ; cursor back on (unless it was already off) sta curoffp ret docleol equ cleol dohcl equ hcl dohome equ home docleow equ cleow doup equ up dodown equ down doright equ right doleft equ left inschon mvi a, 1 ; set insert-char mode sta insmode ret inschoff xra a ; clear insert-char mode sta insmode ret dodelchar equ delchar doinslin lda insdelcnt ; accumulate insertions ora a push psw cm doinsdel ; do any saved deletions first pop psw inr a ; then increment insdelcnt sta insdelcnt ret dodellin lda insdelcnt ; accumulate deletions dcr a push psw cp doinsdel ; do any saved insertions first pop psw sta insdelcnt ret doinsnlins mvi a, stinsn ; set escmode to expect no. of insertions sta escmode ret dodelnlins mvi a, stdeln ; set escmode to expect no. of deletions sta escmode ret doinsn equ insnlines dodeln equ delnlines doinsdel lda insdelcnt ; do saved insertions/deletions ora a ; any to do? rz ; no: done push b jm doinsdel1 ; pos: insertions; neg: deletions mov c, a ; insertion was saved call insnlines ; do it jmp doinsdel2 doinsdel1 cma inr a mov c, a ; deletion was saved call delnlines ; do it doinsdel2 pop b xra a sta insdelcnt ; clear saved count ret doinvon lda attrib ; inverse video on ori vinverse sta attrib ret doinvoff lda attrib ; inverse video off cma ori vinverse cma sta attrib ret setmode mvi a, stsetmode ; set escmode to expect mode to set sta escmode ret clrmode mvi a, stclrmode ; set escmode to expect mode to clear sta escmode ret ; come here with a mode to set in C dosetmode mov a, c cpi '5' jz setcuroff ; turn cursor off cpi '@' jnc setattr ; set display attributes cpi '*' jz setscrol ; set scroll mode push b mvi c, esc ; display unimplemented sequence, so call display ; user can see what happened mvi c, 'x' call display pop b call display ret setcuroff mvi a, 1 ; turn cursor off sta curoffp ret setattr call attrbit ; get bit for this attribute lda attrib ; or it into current attribute byte ora e sta attrib ret setscrol lda scrollp ; set scroll (Z-19 normal) mode ora a rnz ; already on mvi a, 1 sta scrollp mvi h, vnrows - 1 lda lastlrow ; get last logical row out vscrol ; make it last physical row sta lastprow ret ; come here with a mode to clear in C doclrmode mov a, c cpi '5' jz clrcuroff ; turn cursor back on cpi '@' jnc clrattr ; clear an attribute cpi '*' jz clrscrol ; turn roll mode back on push b mvi c, esc ; display unimplemented sequence call display ; so the user can see what happened mvi c, 'y' call display pop b call display ret clrcuroff xra a ; turn cursor back on sta curoffp ret clrattr call attrbit ; get bit for attribute lda attrib cma ora e ; clear it in current attr. byte cma sta attrib ret clrscrol lda scrollp ; set "roll" mode: no scrolling ora a rz ; scroll mode already off xra a sta scrollp lda lastlrow mov h, a mvi a, vnrows - 1 ; return screen to 0-origin out vscrol sta lastprow ret ; given command char in A, leaves attribute bit set in E attrbit sbi '@' jz attrbit1 mov e, a mvi a, 2 ; start with 2 attrbit2 rlc ; rotate left dcr e ; the right number of times jnz attrbit2 mov e, a ret attrbit1 mvi e, 0FCh ; '@': all attributes ret dowrapon mvi a, 1 ; turn wrap mode (end-of-line wrapping) on sta wrapp ret dowrapoff xra a ; turn wrap mode (end-of-line wrapping) off sta wrapp ret dografon lda attrib ; turn graphics mode on (enable ROM) ani 0FDh sta attrib ret dografoff lda attrib ; turn graphics mode off (disable ROM) ori 2 sta attrib ret ; ; Data section. If you want to change the default modes (e.g., to ; wrapping and scrolling), this is the place to do it. (Just change ; wrapp and scrollp.) ; curaddr dw 0 ; logical address of cursor lastprow db vnrows - 1 ; physical last row lastlrow db vnrows - 1 ; logical last row attrib db 3 ; character attribute byte escmode db 0 ; escape-sequence mode wrapp db 0 ; wrap at end of line? insmode db 0 ; insert-character mode scrollp db 0 ; scroll mode curoffp db 0 ; cursor-off mode insdelcnt db 0 ; insert/delete line count ; End of Z-19.ASM -- Z-19 Emulator for SSM VB3