The Annotated VT100 Firmware: The Disassembly

The raw source is in the Git repository at Codeberg.

Although my I usually attempt to produce web pages with ‘mobile first’ in mind, I can’t see any reasonable way to make an assembler listing fit. I can’t imagine why you’d be attempting to view 5000 lines of assembler on a phone anyway; it’s akin to appraising a house through the letter box.

The source code itself is (presumably) copyrighted by DEC and its successor companies. My annotations are licensed under a Creative Commons Attribution 4.0 International License.

     1                  ;
     2                  ;  DZ80 V3.4.1 8080 Disassembly of VT100.bin
     3                  ;  Initial disassembly: 2021-12-03 08:56
     4                  ;  Last updated:        2022-08-09 11:00
     5                  ;
     6                  ;  Comments by Paul Flo Williams <paul@frixxon.co.uk<,
     7                  ;  released under Creative Commons Attribution 4.0 International licence.
     8                  ;
     9                  ; Documentation random things:
    10                  ; 1. Sequences not mentioned in User Guide:
    11                  ;    a) DECHCP and its VT52 equivalent ESC ];
    12                  ;    b) DECGON/DECGOFF,
    13                  ;    c) SS2 and SS3.
    14                  ;    DECHCP and DECGON are dependent on GPO anyway, but SS2/SS3 work for unexpanded VT100.
    15                  ;    DECHCP is mentioned in TM p.A-21
    16                  ;
    17                  ; Comments on notation in comments:
    18                  ; A <- x means "A becomes/is set to x", which frees up "=" to be the straightforward mathematical "is equal to"
    19                  ; HL <- x means HL becomes x, mostly implying that HL is now a pointer to something at location x.
    20                  ;
    21                  ; References to TM here are all to EK-VT100-TM-003, "VT100 Series Video Terminal Technical Manual"
    22                  ;
    23                  ;       TM Table 4-2-3, p.4-18, explains the RST interrupt handling.
    24                  ;       
    25                  ;       Table 4-2-3  Interrupt Addresses
    26                  ;
    27                  ;       00H     Power-up (Not hardware driven)
    28                  ;       08H     Keyboard
    29                  ;       10H     Receiver
    30                  ;       18H     Receiver and keyboard
    31                  ;       20H     Vertical frequency
    32                  ;       28H     Vertical frequency and keyboard
    33                  ;       30H     Vertical frequency and receiver
    34                  ;       38H     Vertical frequency, receiver and keyboard
    35                  ;
    36      00 00                       org     00h
    37 0000 F3          start:          di
    38 0001 31 4E 20                    lxi     sp,stack_top
    39 0004 C3 3B 00                    jmp     post
    40                  ;
    41      00 08       restart1:       org     08h
    42 0008 CD FD 00                    call    keyboard_int
    43 000B FB                          ei
    44 000C C9                          ret
    45                  ;
    46      00 10       restart2:       org     10h
    47 0010 CD CC 03                    call    receiver_int
    48 0013 FB                          ei
    49 0014 C9                          ret
    50                  ;
    51      00 18       restart3:       org     18h
    52 0018 CD CC 03                    call    receiver_int
    53 001B CD FD 00                    call    keyboard_int
    54 001E FB                          ei
    55 001F C9                          ret
    56                  ;
    57      00 20       restart4:       org     20h
    58 0020 CD CF 04                    call    vertical_int
    59 0023 C9                          ret
    60                  ;
    61      00 28       restart5:       org     28h
    62 0028 CD CF 04                    call    vertical_int
    63 002B C9                          ret
    64                  ;
    65      00 30                       org     30h
    66 0030 CD CC 03    restart6:       call    receiver_int
    67 0033 CD CF 04                    call    vertical_int
    68 0036 FB                          ei
    69 0037 C9                          ret
    70                  ;
    71      00 38       restart7:       org     38h
    72 0038 C3 30 00                    jmp     restart6
    73                  ;
    74                  ; post
    75                  ; self_test
    76
    77                  ; The only difference between "proper" POST and the Self Test invoked by DECTST is making sure that POST
    78                  ; doesn't repeat indefinitely. As register E is a mask of requested tests and repeat indication, POST sets
    79                  ; the mask just as if DECTST had been invoked with "just self-test, no repeats."
    80                  ;
    81                  ; TM §4.2.8, "Power-Up and Self-Test", p. 4-19, describes POST.
    82                  ;
    83 003B 1E 01       post:           mvi     e,1             ; Pretend this is DECTST and we requested POST without repeats
    84                  ;
    85                  ; Self-test starts by checksumming ROMs, which is a rotate-and-XOR operation across all four 2K ROMs
    86                  ; individually. The checksum is expected to produce a zero result, which is ensured by including a
    87                  ; byte somewhere in each ROM that can mix-in to make zero. Checksum bytes are marked in this listing
    88                  ; where they have been identified.
    89                  ;
    90 003D F3          self_test:      di
    91 003E 3E 0F                       mvi     a,0fh
    92 0040 D3 62                       out     iow_nvr_latch   ; "standby" to NVR latch (see port definition for details)
    93 0042 2F                          cma                     ; A <- 0xf0
    94 0043 D3 42                       out     iow_brightness  ; set mid brightness
    95 0045 AF                          xra     a
    96 0046 57                          mov     d,a
    97 0047 6F                          mov     l,a
    98 0048 67                          mov     h,a             ; HL <- 0, starting address of ROMs
    99                                                          ; Checksumming ROMS 1 to 4
   100 0049 3C          next_rom:       inr     a
   101 004A 47                          mov     b,a
   102 004B D3 82                       out     iow_keyboard    ; Place ROM number on LEDs
   103 004D 0E 08                       mvi     c,8             ; Each ROM is 8 x 256 = 2K bytes
   104 004F 07          next_byte:      rlc                     ; checksum is rotate and XOR
   105 0050 AE                          xra     m
   106 0051 2C                          inr     l
   107 0052 C2 4F 00                    jnz     next_byte
   108 0055 24                          inr     h
   109 0056 0D                          dcr     c
   110 0057 C2 4F 00                    jnz     next_byte
   111 005A B7                          ora     a               ; A spare byte in each ROM is programmmed to ensure the correct
   112 005B C2 5B 00    hang_rom:       jnz     hang_rom        ; checksum is always zero. Failed? Hang with ROM number on keyboard.
   113 005E 78                          mov     a,b
   114 005F FE 04                       cpi     4
   115 0061 C2 49 00                    jnz     next_rom        ; Loop for all four ROMs
   116                  ;
   117 0064 3C                          inr     a
   118 0065 D3 82                       out     iow_keyboard    ; RAM test next; failure leaves "5" on the LEDs
   119 0067 0E AA                       mvi     c,0aah          ; C is test pattern
   120 0069 06 2C                       mvi     b,HIGH ram_top+1
   121 006B DB 42                       in      ior_flags
   122 006D E6 02                       ani     iob_flags_avo   ; Test for presence of AVO
   123 006F C2 74 00                    jnz     next_pattern    ; jump if absent
   124 0072 06 40                       mvi     b,HIGH avo_ram_top+1
   125 0074 60          next_pattern:   mov     h,b             ; Start at top of RAM (L zero either from ROM test, or from last loop) 
   126 0075 2B                          dcx     h
   127 0076 36 00       zero_ram:       mvi     m,0
   128 0078 2B                          dcx     h
   129 0079 7C                          mov     a,h
   130 007A FE 1F                       cpi     HIGH ram_start - 1
   131 007C C2 76 00                    jnz     zero_ram
   132                                                          ; Now work back up through RAM, applying pattern
   133 007F 23                          inx     h
   134 0080 7E          patt_loop:      mov     a,m             ; A <- current contents, which should be zero
   135 0081 B7                          ora     a
   136 0082 CA 90 00                    jz      zero_good
   137 0085 E6 0F                       ani     0fh             ; A <- low 4 bits of memory value
   138 0087 7C                          mov     a,h             ; 
   139 0088 C2 A1 00                    jnz     ram_fail        ; if low 4 bits are not zero, we've failed
   140 008B FE 30                       cpi     HIGH avo_ram_start
   141 008D DA A1 00                    jc      ram_fail        ; Don't fail RAM if we're looking at AVO, because its RAM is 4 bits wide
   142 0090 71          zero_good:      mov     m,c             ; Now place pattern
   143 0091 7E                          mov     a,m             ; and read back
   144 0092 A9                          xra     c               ; should be identical (i.e. XOR to zero)
   145 0093 CA A8 00                    jz      patt_good
   146 0096 E6 0F                       ani     0fh             ; Again, AVO RAM is only 4 bits wide
   147 0098 7C                          mov     a,h
   148 0099 C2 A1 00                    jnz     ram_fail
   149 009C FE 30                       cpi     HIGH avo_ram_start
   150 009E D2 A8 00                    jnc     patt_good       ; so don't fail RAM for 4 high bit being wrong, in AVO space
   151 00A1 16 01       ram_fail:       mvi     d,1             ; D <- accumulates test failures
   152 00A3 FE 2C                       cpi     HIGH ram_top+1
   153 00A5 DA A5 00    hang_ram:       jc      hang_ram        ; Hang for baseboard RAM, not AVO
   154 00A8 AF          patt_good:      xra     a
   155 00A9 23                          inx     h
   156 00AA B5                          ora     l
   157 00AB C2 80 00                    jnz     patt_loop
   158 00AE B4                          ora     h
   159 00AF B8                          cmp     b
   160 00B0 C2 80 00                    jnz     patt_loop
   161 00B3 79                          mov     a,c             ; A <- pattern 
   162 00B4 07                          rlc                     ; 1st time: 0xaa -< 0x55 set carry; 2nd time: 0x55 -< 0xaa, reset carry
   163 00B5 4F                          mov     c,a             ; C <- new pattern
   164 00B6 DA 74 00                    jc      next_pattern
   165 00B9 D5                          push    d
   166 00BA CD A4 02                    call    clear_scratch
   167 00BD CD EB 02                    call    init_terminal
   168 00C0 CD 5B 17                    call    recall_nvr
   169 00C3 D1                          pop     d
   170 00C4 CA CB 00                    jz      recall_ok
   171 00C7 7A                          mov     a,d
   172 00C8 F6 02                       ori     2               ; add "nvr failed" to test results
   173 00CA 57                          mov     d,a
   174 00CB 3E 2F       recall_ok:      mvi     a,2fh           ; c111 d1 = "standby" (inv)
   175 00CD 32 C9 21                    sta     vint_nvr
   176 00D0 D3 62                       out     iow_nvr_latch
   177                                                          ; Keyboard test
   178 00D2 01 FF 0F                    lxi     b,0fffh         ; good long loop
   179 00D5 FB                          ei                      ; We rely on keyboard_int going off to detect end-of-scan
   180 00D6 3E 08       beep_loop:      mvi     a,8             ; If we entered POST from DECTST, and we requested repeating tests,
   181 00D8 A3                          ana     e       
   182 00D9 3E 7F                       mvi     a,7fh           ; clear the "SPKR. CLICK" bit in the byte we send to the keyboard,
   183 00DB C2 E0 00                    jnz     skip_click
   184 00DE 3E FF                       mvi     a,0ffh          ; If we're not repeating test, go wild, beep-boy!
   185 00E0 D3 82       skip_click:     out     iow_keyboard
   186 00E2 0B                          dcx     b
   187 00E3 78                          mov     a,b
   188 00E4 B1                          ora     c
   189 00E5 C2 D6 00                    jnz     beep_loop
   190 00E8 D3 82                       out     iow_keyboard    ; quieten keyboard again
   191 00EA 3A 68 20                    lda     key_flags       ; Check key_flags that have been maintained by keyboard_int
   192 00ED B7                          ora     a
   193 00EE FA F5 00                    jm      seen_eos        ; We should have seen an end of scan after that loop
   194 00F1 7A                          mov     a,d
   195 00F2 F6 04                       ori     4               ; add "keyboard failed" to test results
   196 00F4 57                          mov     d,a
   197 00F5 D5          seen_eos:       push    d
   198 00F6 CD A2 03                    call    init_devices
   199 00F9 D1                          pop     d
   200 00FA C3 75 08                    jmp     continue_tests
   201                  ;
   202                  ; The keyboard interrupt detects modifier keys and "end of scan" and places them into a
   203                  ; flags location, and places other keys into what the Technical Manual calls the SILO,
   204                  ; here called key_silo.
   205                  ;
   206                  ; The TM describes the SETUP key going into key_flags but it doesn't; its key code of 7bh
   207                  ; falls just under the cut-off established here and it goes into the SILO.
   208                  ;
   209                  ; Interpretation of keys happens in see process_keys.
   210                  ;
   211 00FD F5          keyboard_int:   push    psw
   212 00FE DB 82                       in      ior_keyboard
   213 0100 E5                          push    h
   214 0101 C5                          push    b
   215 0102 47                          mov     b,a             ; B <- key row/column
   216 0103 D6 7C                       sui     7ch             ; 07ch and up are the modifier keys: CTRL, SHIFT and CAPS LOCK
   217 0105 FA 1A 01                    jm      into_key_buf    ; normal keys will go into SILO
   218 0108 67                          mov     h,a             ; H <- key code - 7ch
   219 0109 24                          inr     h               ; H <- 1 = CTRL, 2 = SHIFT, 3 = CAPS, 4 = EOS
   220 010A 3E 10                       mvi     a,10h           ; Not at all sure why A could not have been initialised to
   221 010C 0F                          rrc                     ; 08h at this point, and we could lose the RRC instruction.
   222 010D 07          shift_key:      rlc
   223 010E 25                          dcr     h
   224 010F C2 0D 01                    jnz     shift_key
   225 0112 21 68 20                    lxi     h,key_flags
   226 0115 B6                          ora     m
   227 0116 77                          mov     m,a
   228 0117 C3 2D 01                    jmp     kexit           ; Done for this key, exit interrupt
   229                  ;
   230 011A 21 68 20    into_key_buf:   lxi     h,key_flags
   231 011D 3E 07                       mvi     a,7
   232 011F A6                          ana     m
   233 0120 FE 04                       cpi     4               ; Number of keys waiting to be processed?
   234 0122 F2 2D 01                    jp      kexit           ; No room if already got 4
   235 0125 34                          inr     m               ; Increment number of keys waiting
   236 0126 21 6A 20                    lxi     h,key_silo
   237 0129 CD DE 13                    call    add_a_to_hl
   238 012C 70                          mov     m,b             ; Add this key to queue
   239 012D C1          kexit:          pop     b
   240 012E E1                          pop     h
   241 012F F1                          pop     psw
   242 0130 C9                          ret
   243                  ;
   244                  ; proc_func_key
   245                  ;
   246                  ;       Having placed keyboard scan codes through the switch array, we now have a key code that identifies
   247                  ;       a non-ASCII, or "function" key.
   248                  ;
   249                  ;       BREAK is 081h, NO SCROLL is 082h (although it has already been acted upon, and doesn't get here)
   250                  ;       Numeric keypad keys all have their ASCII codes with the high bit set, i.e. KP COMMA is 0ach, and
   251                  ;       the digits 0 to 9 have codes 0b0h to 0b9h.
   252                  ;       Arrow keys are codes 0c1h to 0c4h (bit 7 plus 41h to 44h, i.e. 'A' to 'D')
   253                  ;       PF1 to PF4 are 0d0h to 0d3h (bit 7 plus 50h to 053h, i.e 'p' to 's')
   254
   255 0131 E6 7F       proc_func_key:  ani     7fh             ; All keys entering here at the bit 7 set, so remove it
   256 0133 4F                          mov     c,a             ; C <- adjusted key code
   257 0134 3A 7B 20                    lda     in_setup
   258 0137 B7                          ora     a
   259 0138 C2 FF 01                    jnz     setup_cursor
   260 013B 79                          mov     a,c
   261 013C FE 01                       cpi     1               ; BREAK was 081h, adjusted to 01h
   262 013E CA C3 01                    jz      break_pressed
   263 0141 3A A7 21                    lda     setup_b2
   264 0144 E6 20                       ani     sb2_ansi
   265 0146 C2 75 01                    jnz     ansi_keys       ; jump to ANSI mode processing
   266 0149 3A 78 21                    lda     keypad_mode
   267 014C B7                          ora     a
   268 014D C2 5F 01                    jnz     vt52_app_mode
   269 0150 79                          mov     a,c             ; A <- adjusted key code
   270 0151 FE 41                       cpi     'A'             ; If it isn't a cursor key, it's a number from the numeric
   271 0153 FA 5B 01                    jm      send_c          ; keypad and they are sent just as '0' to '9' on VT52
   272 0156 3E 1B                       mvi     a,C0_ESC        ; otherwise it's a cursor key and they have leading ESC
   273 0158 CD 18 0F                    call    send_key_byte
   274 015B 79          send_c:         mov     a,c
   275 015C C3 0D 08                    jmp     send_key_end
   276                  ;
   277 015F 3E 1B       vt52_app_mode:  mvi     a,C0_ESC
   278 0161 CD 18 0F                    call    send_key_byte
   279 0164 79                          mov     a,c             ; A <- adjusted key code
   280 0165 FE 41                       cpi     'A'             ; Is it an arrow key?
   281 0167 F2 5B 01                    jp      send_c          ; we've sent ESC already, so just send 'A' to 'D' now
   282 016A 3E 3F                       mvi     a,'?'           ; else numeric keypad keys are KP0 -< ESC ? p
   283 016C CD 18 0F    send_plus_col4: call    send_key_byte
   284 016F 79                          mov     a,c             ; A <- adjusted key code
   285 0170 C6 40                       adi     40h             ; '0' -< 'p'
   286 0172 C3 0D 08                    jmp     send_key_end
   287                  ;
   288 0175 3A 78 21    ansi_keys:      lda     keypad_mode
   289 0178 B7                          ora     a
   290 0179 C2 9E 01                    jnz     ansi_app_mode
   291 017C 79                          mov     a,c             ; A <- adjusted key code
   292 017D FE 41                       cpi     'A'             ; If it isn't a cursor key (and here we're in numeric keypad mode),
   293 017F FA 5B 01                    jm      send_c          ; send the plain digit
   294 0182 FE 50                       cpi     'P'             ; PF1 to PF4?
   295 0184 FA 9E 01                    jm      ansi_app_mode   ; No, deal with arrow keys
   296 0187 3E 1B       esc_o_last:     mvi     a,C0_ESC        ; PF1 to PF4 are the same in numeric and application modes,
   297 0189 CD 18 0F                    call    send_key_byte   ; sending ESC O P to ESC O S
   298 018C 3E 4F                       mvi     a,'O'
   299 018E CD 18 0F    send_ac:        call    send_key_byte
   300 0191 C3 5B 01                    jmp     send_c
   301                  ;
   302 0194 3E 1B       esc_brack_last: mvi     a,C0_ESC
   303 0196 CD 18 0F                    call    send_key_byte
   304 0199 3E 5B                       mvi     a,'['
   305 019B C3 8E 01                    jmp     send_ac
   306                  ;
   307 019E 3A BC 21    ansi_app_mode:  lda     mode_ckm        ; Responses may also depend on DECCKM (cursor key mode)
   308 01A1 B7                          ora     a
   309 01A2 CA B5 01                    jz      decckm_reset    ; CKM is reset
   310 01A5 79                          mov     a,c             ; A <- adjusted key code
   311 01A6 FE 41                       cpi     'A'             ; Cursor keys?
   312 01A8 F2 87 01                    jp      esc_o_last      ; Yes, in ANSI + CKM mode, these are ESC O A to ESC O D
   313 01AB 3E 1B       ansi_digits:    mvi     a,C0_ESC
   314 01AD CD 18 0F                    call    send_key_byte
   315 01B0 3E 4F                       mvi     a,'O'
   316 01B2 C3 6C 01                    jmp     send_plus_col4
   317                  ;
   318 01B5 79          decckm_reset:   mov     a,c             ; A <- adjusted key code
   319 01B6 FE 41                       cpi     'A'             ; Is this a digit key?
   320 01B8 FA AB 01                    jm      ansi_digits     ; ANSI-mode digits are same with CKM set or reset
   321 01BB FE 50                       cpi     'P'             ; PF1 to PF4?
   322 01BD FA 94 01                    jm      esc_brack_last  ; ANSI-mode arrows are ESC [ A to ESC [ D
   323 01C0 C3 87 01                    jmp     esc_o_last      ; ANSI + CKM reset PF1 to PF4 are ESC O P to ESC O S
   324                  ;
   325                  ;
   326                  ;
   327                  ;
   328 01C3 21 15 08    break_pressed:  lxi     h,pk_noclick    ; Return to a "tidy keyboard processing" point
   329 01C6 E5                          push    h
   330 01C7 3A A5 21                    lda     local_mode      ; BREAK has no function in local mode
   331 01CA B7                          ora     a
   332 01CB C0                          rnz
   333 01CC CD 53 08                    call    make_keyclick
   334 01CF 01 0E 02                    lxi     b,020eh         ; B <- "not DTR" for PUSART; C <- 14 vert. frames =~ 0.2333 s
   335 01D2 21 68 20                    lxi     h,key_flags
   336 01D5 7E                          mov     a,m
   337 01D6 E6 10                       ani     key_flag_ctrl   ; CTRL + BREAK sends answerback message
   338 01D8 C2 00 09                    jnz     c0_answerback
   339 01DB 7E                          mov     a,m             ; A <- key flags
   340 01DC E6 20                       ani     key_flag_shift  ; SHIFT + BREAK is a longer break
   341 01DE CA E4 01                    jz      not_shift       ; but if we're not doing that, keep counter short
   342 01E1 01 D2 00                    lxi     b,00d2h         ; Long break C <- 210 vertical frames =~ 3.5 s
   343 01E4 3E 25       not_shift:      mvi     a,25h           ; A <- base command: "not RTS, rx enable, tx enable"
   344 01E6 F6 08                       ori     8               ; A <- mix in "send break character"
   345 01E8 B0                          ora     b               ; A <- possibly mix in disconnect (dropping DTR) for SHIFT+BREAK
   346 01E9 D3 01                       out     iow_pusart_cmd  ; send PUSART command
   347 01EB 3A 7F 20                    lda     frame_count     ; get current vertical frame count
   348 01EE 81                          add     c               ; add number we want
   349 01EF 4F                          mov     c,a             ; C <- end number of frames
   350 01F0 C5          wait_break:     push    b
   351 01F1 CD 93 14                    call    update_kbd
   352 01F4 C1                          pop     b
   353 01F5 3A 7F 20                    lda     frame_count
   354 01F8 B9                          cmp     c               ; are we there yet?
   355 01F9 C2 F0 01                    jnz     wait_break
   356 01FC C3 94 03                    jmp     ready_comms
   357                  ;
   358 01FF 21 12 08    setup_cursor:   lxi     h,pk_click      ; Push a return address that will tidy up keyboard
   359 0202 E5                          push    h
   360 0203 AF                          xra     a
   361 0204 32 30 21                    sta     csi_params
   362 0207 32 B8 21                    sta     csi_private
   363 020A 21 A3 21                    lxi     h,brightness
   364 020D 79                          mov     a,c
   365 020E D6 41                       sui     'A'
   366 0210 47                          mov     b,a             ; B <- adjusted cursor key
   367 0211 7E                          mov     a,m             ; Get current brightness
   368 0212 CA 22 02                    jz      brighter        ; up arrow - screen brighter
   369 0215 05                          dcr     b
   370 0216 CA 26 02                    jz      dim_screen      ; down arrow - screen more dim
   371 0219 05                          dcr     b
   372 021A CA 15 18                    jz      cuf_action      ; right arrow - normal cursor movement
   373 021D 05                          dcr     b
   374 021E CA 1E 18                    jz      cub_action      ; left arrow - normal cursor movement
   375 0221 C9                          ret
   376                  ;
   377 0222 3D          brighter:       dcr     a
   378 0223 F8                          rm                      ; limit at 0
   379 0224 77                          mov     m,a             ; write new brightness
   380 0225 C9                          ret
   381                  ;
   382 0226 3C          dim_screen:     inr     a
   383 0227 FE 20                       cpi     20h             ; limit at 1fh
   384 0229 C8                          rz
   385 022A 77                          mov     m,a             ; write new brightness
   386 022B C9                          ret
   387                  ;
   388 022C 3A A7 21    no_scroll_key:  lda     setup_b2
   389 022F 2F                          cma                     ; complement sense of switches
   390 0230 E6 10                       ani     10h             ; A <- 10h if autoxon is OFF
   391 0232 21 A5 21                    lxi     h,local_mode
   392 0235 B6                          ora     m
   393 0236 C2 41 08                    jnz     clear_keyboard  ; Can't send XOFF if autoxon is OFF or we're in local mode
   394 0239 3A BF 21                    lda     why_xoff
   395 023C 01 11 02                    lxi     b,2<<8|C0_XON
   396 023F A0                          ana     b               ; NZ if we had previously sent XOFF, in which case
   397 0240 C2 45 02                    jnz     send_xany       ; we'll send an XON
   398 0243 0E 13                       mvi     c,C0_XOFF
   399 0245 79          send_xany:      mov     a,c
   400 0246 D6 11                       sui     C0_XON          ; If we've sent XON, A = 0, XOFF -< A = 2
   401 0248 32 C4 21                    sta     noscroll
   402 024B CD 7E 0F                    call    send_xonoff
   403 024E C3 12 08                    jmp     pk_click
   404                  ;
   405 0251 F3          after_tests:    di
   406 0252 31 4E 20                    lxi     sp,stack_top
   407 0255 21 00 20                    lxi     h,ram_start     ; Just done tests, so this is the where failures are recorded
   408 0258 E5                          push    h
   409 0259 7E                          mov     a,m             ; A <- test results
   410 025A 1F                          rar                     ; Set carry if AVO failed tests
   411 025B DA 65 02                    jc      no_avo
   412 025E DB 42                       in      ior_flags
   413 0260 E6 02                       ani     iob_flags_avo
   414 0262 CA 69 02                    jz      have_avo        ; AVO is an "absence" flag
   415 0265 3C          no_avo:         inr     a
   416 0266 32 C8 21                    sta     avo_missing
   417 0269 CD A2 03    have_avo:       call    init_devices
   418 026C 3A 50 20                    lda     screen_cols
   419 026F 3D                          dcr     a
   420 0270 32 53 21                    sta     right_margin
   421 0273 E1                          pop     h
   422 0274 7E                          mov     a,m             ; A <- test results
   423 0275 36 7F                       mvi     m,7fh           ; Write line terminator over test results, at start of screen layout
   424 0277 B7                          ora     a               ; Did any fail?
   425 0278 CA 98 02                    jz      done_results    ; no, don't bother displaying
   426 027B F2 85 02                    jp      no_cycle_bit    ; jump if we aren't cycling through results
   427 027E 21 CB 21                    lxi     h,test_field    ; To show that tests are being repeated and failing, the entire screen
   428 0281 36 0A                       mvi     m,0ah           ; is toggled between normal and reverse field, so initialise this.
   429 0283 E6 7F                       ani     7fh             ; Remove cycle bit when storing results
   430 0285 32 BD 21    no_cycle_bit:   sta     test_results
   431 0288 C6 30                       adi     '0'             ; convert to printable range
   432 028A 2A F6 20                    lhld    cursor_address
   433 028D 77                          mov     m,a             ; Display results at cursor position
   434 028E FE 34                       cpi     '4'             ; was it just modem test that failed?
   435 0290 CA 98 02                    jz      done_results    ; OK, don't go into local mode just for that
   436 0293 3E 20                       mvi     a,20h           ; 
   437 0295 32 A5 21                    sta     local_mode      ; any other failures, go into local
   438 0298 CD F2 0B    done_results:   call    set_charsets
   439 029B 01 11 00                    lxi     b,C0_XON
   440 029E CD 7E 0F                    call    send_xonoff
   441 02A1 C3 AE 03                    jmp     idle_loop
   442                  ;
   443 02A4 21 4E 20    clear_scratch:  lxi     h,stack_top
   444 02A7 11 B2 0F                    lxi     d,avo_ram_start - stack_top
   445 02AA 06 00                       mvi     b,0
   446 02AC CD 83 10                    call    memset          ; Clear all of scratch RAM above stack
   447 02AF 2F                          cma                     ; A <- 0ffh (because memset always zeroes A)
   448 02B0 32 04 21                    sta     saved_rend      ; saved character rendering
   449 02B3 21 04 20                    lxi     h,line1_dma
   450 02B6 22 52 20                    shld    UNREAD_X2052
   451 02B9 21 D0 22                    lxi     h,main_video    ; (home address)
   452 02BC 22 F6 20                    shld    cursor_address
   453 02BF C9                          ret
   454                  ;
   455                  ; init_video_ram
   456                  ;
   457 02C0 CD 15 0A    init_video_ram: call    to_ground       ; initialise character processing routine
   458 02C3 21 00 20                    lxi     h,ram_start     ; HL <- start of screen RAM
   459 02C6 11 D9 02                    lxi     d,screen_layout
   460 02C9 06 12                       mvi     b,12h
   461 02CB CD 8B 03                    call    memcopy         ; initial video RAM layout
   462 02CE 21 00 30                    lxi     h,avo_ram_start
   463 02D1 11 00 10                    lxi     d,avo_ram_top - avo_ram_start + 1
   464 02D4 06 FF                       mvi     b,0ffh
   465 02D6 C3 83 10                    jmp     memset          ; attribute RAM blank (default rendition)
   466                  ;
   467                  ; This is the initial configuration of screen RAM. TM §4.6 explains the
   468                  ; screen refresh and three termination bytes on each line.
   469                  ; This layout almost matches TM Figure 4-7-3, p.4-89, except that that figure seems to have
   470                  ; a typo (repeated 7F 70 06 line, when second should be 7F 70 0C) - other figures in that
   471                  ; chapter get this right.
   472                  ;
   473 02D9 7F 70 03    screen_layout:  db      07fh,70h,03h
   474 02DC 7F F2 D0                    db      07fh,0f2h,0d0h
   475 02DF 7F 70 06                    db      07fh,70h,06h
   476 02E2 7F 70 0C                    db      07fh,70h,0ch
   477 02E5 7F 70 0F                    db      07fh,70h,0fh
   478 02E8 7F 70 03                    db      07fh,70h,03h
   479                  ;
   480                  ; init_terminal
   481                  ;       Initialise a bunch of terminal settings: timers, scroll, cursor and receiver buffer
   482                  ;
   483 02EB 21 12 02    init_terminal:  lxi     h,0212h         ; long, visible, cursor timer 
   484 02EE 22 2D 21                    shld    cursor_timer
   485 02F1 3E 35                       mvi     a,35h
   486 02F3 32 2C 21                    sta     blink_timer
   487 02F6 3E 01                       mvi     a,1
   488 02F8 32 5B 20                    sta     scroll_dir
   489 02FB 32 76 21                    sta     tparm_solicited ; By default reports cannot be sent unsolicited
   490 02FE 21 FF 07                    lxi     h,07ffh         ; Never used
   491 0301 22 49 21                    shld    UNREAD_X2149    ; Never used
   492 0304 3E 02                       mvi     a,2
   493 0306 32 73 20                    sta     key_rpt_pause
   494 0309 3E F7                       mvi     a,0f7h          ; All attributes off, normal character set
   495 030B 32 FA 20                    sta     char_rend
   496 030E DB 42                       in      ior_flags
   497 0310 E6 04                       ani     iob_flags_gpo
   498 0312 3E 01                       mvi     a,1
   499 0314 C2 1A 03                    jnz     no_gpo          ; AVO and GPO flags are both "absence" flags
   500 0317 32 79 20                    sta     gpo_flags
   501 031A 3E FF       no_gpo:         mvi     a,0ffh
   502 031C 32 0E 21                    sta     saved_curs_row  ; invalidate cursor row
   503 031F 32 BA 21                    sta     cursor_visible
   504 0322 26 80                       mvi     h,80h
   505 0324 6C                          mov     l,h
   506 0325 22 C0 20                    shld    rx_head         ; Initialise rx_head and rx_tail of receive buffer
   507 0328 C9                          ret
   508                  ;
   509 0329 3E 40       reset_pusart:   mvi     a,40h           ; Reset PUSART so we can write a mode byte again
   510 032B D3 01                       out     iow_pusart_cmd
   511 032D 3A 58 21                    lda     tx_rx_speed
   512 0330 D3 02                       out     iow_baud_rate
   513 0332 3A A4 21                    lda     pusart_mode
   514 0335 D3 01                       out     iow_pusart_cmd
   515 0337 CD 94 03                    call    ready_comms
   516 033A 3E 10                       mvi     a,10h
   517 033C 32 C9 21                    sta     vint_nvr
   518 033F D3 62                       out     iow_nvr_latch
   519 0341 C9                          ret
   520                  ;
   521                  ; update_dc011
   522                  ;
   523                  ;       This is the only routine that writes to I/O port 0c2h, which drives DC011, the circuit
   524                  ;       that provides most of the timing signals to the Video Processor. Because setting
   525                  ;       columns or refresh rate always sets or resets interlaced mode, we may need to write
   526                  ;       the column mode twice.
   527                  ;
   528                  ;       See TM §4.6.2, p. 4-55
   529                  ;
   530 0342 3A A2 21    update_dc011:   lda     columns_132
   531 0345 B7                          ora     a
   532 0346 CA 4B 03                    jz      is80
   533 0349 3E 10                       mvi     a,10h
   534 034B 47          is80:           mov     b,a             ; B <- 0 for 80 columns, 10h for 132 columns
   535 034C D3 C2                       out     iow_dc011       ; this write also sets interlaced mode
   536 034E 3A 7C 20                    lda     refresh_rate
   537 0351 D3 C2                       out     iow_dc011       ; also sets non-interlaced mode
   538 0353 FE 20                       cpi     refresh_60Hz
   539 0355 21 70 09                    lxi     h,0970h         ; 50Hz fill, per TM Figure 4-7-3, p. 4-89
   540 0358 C2 5E 03                    jnz     fill_req
   541 035B 21 70 03                    lxi     h,0370h         ; 60Hz fill
   542 035E 22 01 20    fill_req:       shld    line0_dma
   543 0361 3A A8 21                    lda     setup_b3
   544 0364 E6 10                       ani     sb3_interlace   
   545 0366 C8                          rz
   546 0367 78                          mov     a,b             ; A <- 0 for 80 columns, 10h for 132 columns
   547 0368 D3 C2                       out     iow_dc011       ; write columns again, so we get interlaced mode
   548 036A C9                          ret
   549                  ;
   550                  ; update_dc012
   551                  ;       DC012 is the video controller chip, and here we update the field colour (reverse/normal field) and
   552                  ;       whether the basic attribute (important in the absence of the AVO) is underline or reverse video.
   553                  ;
   554 036B 3A A6 21    update_dc012:   lda     setup_b1
   555 036E E6 20                       ani     sb1_lightback
   556 0370 C2 75 03                    jnz     is_light
   557 0373 3E 01                       mvi     a,1
   558 0375 F6 0A       is_light:       ori     0ah             ; A <- 0ah/0bh = "set reverse field on/off", respectively
   559 0377 D3 A2                       out     iow_dc012
   560 0379 3A 5B 21                    lda     basic_rev_video ; 0 = underline, 1 = reverse video
   561 037C F6 0C                       ori     0ch             ; "set basic attribute" command
   562 037E D3 A2                       out     iow_dc012
   563 0380 C9                          ret
   564                  ;
   565 0381 3A A2 21    clear_display:  lda     columns_132
   566 0384 B7                          ora     a
   567 0385 CA 63 0B                    jz      init_80col
   568 0388 C3 77 0B                    jmp     init_132col
   569                  ;
   570                  ; memcopy - copy B bytes from (DE) to (HL)
   571 038B 1A          memcopy:        ldax    d
   572 038C 77                          mov     m,a
   573 038D 23                          inx     h
   574 038E 13                          inx     d
   575 038F 05                          dcr     b
   576 0390 C2 8B 03                    jnz     memcopy
   577 0393 C9                          ret
   578                  ;
   579 0394 3A A5 21    ready_comms:    lda     local_mode
   580 0397 B7                          ora     a
   581 0398 3E 01                       mvi     a,1             ; "RTS"
   582 039A C2 9F 03                    jnz     skip_dtr        ; don't set DTR if we're in local mode
   583 039D 3E 05                       mvi     a,5             ; "RTS" and "DTR"
   584 039F C3 7B 1F    skip_dtr:       jmp     modem_signals   ;
   585                  ;
   586                  ; Initialise display, PUSART, and the video devices DC011 and DC012
   587                  ;
   588 03A2 CD 81 03    init_devices:   call    clear_display
   589 03A5 CD DB 1D                    call    program_pusart
   590 03A8 CD 6B 03                    call    update_dc012
   591 03AB C3 42 03                    jmp     update_dc011
   592                  ;
   593 03AE CD 88 14    idle_loop:      call    keyboard_tick
   594 03B1 CD 87 05                    call    receiver_tick
   595 03B4 21 77 21                    lxi     h,pending_setup
   596 03B7 7E                          mov     a,m
   597 03B8 B7                          ora     a
   598 03B9 36 00                       mvi     m,0
   599 03BB C4 20 1A                    cnz     in_out_setup
   600 03BE 3A A5 21                    lda     local_mode
   601 03C1 B7                          ora     a
   602 03C2 CA AE 03                    jz      idle_loop
   603 03C5 AF                          xra     a
   604 03C6 32 44 21                    sta     keyboard_locked
   605 03C9 C3 AE 03                    jmp     idle_loop
   606                  ;
   607 03CC F5          receiver_int:   push    psw
   608 03CD C5                          push    b
   609 03CE E5                          push    h
   610 03CF DB 00                       in      ior_pusart_data
   611 03D1 E6 7F                       ani     7fh
   612 03D3 CA 3A 04                    jz      exit_rx_int     ; quick exit for received NULs
   613 03D6 4F                          mov     c,a             ; C <- character received
   614 03D7 3A A5 21                    lda     local_mode
   615 03DA B7                          ora     a
   616 03DB C2 3A 04                    jnz     exit_rx_int     ; if we're in local mode, reject received characters too
   617 03DE DB 01                       in      ior_pusart_cmd  ; read PUSART status
   618 03E0 E6 38                       ani     38h             ; do we have framing, overrun or parity errors?
   619 03E2 CA ED 03                    jz      no_rx_errors    ; no, phew
   620 03E5 0E 1A                       mvi     c,C0_SUB        ; Rx errors are treated as SUB, shown as checkboard on screen
   621 03E7 3E 27                       mvi     a,27h           ; PUSART command enables RTS*, DTR*, transmit and receive
   622 03E9 F6 10                       ori     10h             ; mix-in "error reset"
   623 03EB D3 01                       out     iow_pusart_cmd
   624 03ED 79          no_rx_errors:   mov     a,c             ; A <- received character (or SUB)
   625 03EE FE 7F                       cpi     7fh             ; Is this DEL?
   626 03F0 CA 37 04                    jz      pass_on
   627 03F3 3A A7 21                    lda     setup_b2
   628 03F6 E6 10                       ani     sb2_autoxon     ; test XON
   629 03F8 79                          mov     a,c             ; A <- received character
   630 03F9 CA 09 04                    jz      no_autoxon      ; jump if we don't do auto XON/XOFF
   631 03FC 21 C2 21                    lxi     h,received_xoff
   632 03FF FE 11                       cpi     C0_XON          ; have we received XON?
   633 0401 CA 3E 04                    jz      rx_xon
   634 0404 FE 13                       cpi     C0_XOFF         ; have we received XOFF?
   635 0406 CA 44 04                    jz      rx_xoff
   636 0409 21 C0 20    no_autoxon:     lxi     h,rx_head
   637 040C 4E                          mov     c,m             ; Get pointer
   638 040D 44                          mov     b,h             ; as 16-bits
   639 040E 02                          stax    b               ; Place in buffer
   640 040F 79                          mov     a,c             ; A <- rx_head pointer
   641 0410 3C                          inr     a               ; move along
   642 0411 E6 BF                       ani     0bfh            ; And wrapping, so location after 20bf is 2080
   643 0413 77                          mov     m,a             ; Write rx_head back
   644 0414 47                          mov     b,a             ; B <- rx_head
   645 0415 3A C1 20                    lda     rx_tail         ; A <- rx_tail
   646 0418 90                          sub     b               ; A <- rx_tail - rx_head (= space in buffer)
   647 0419 C2 27 04                    jnz     not_caught
   648 041C 71                          mov     m,c             ; Put old head back, so we lose newest character
   649 041D 78                          mov     a,b             ; A <- new rx_head
   650 041E 3C                          inr     a               ; move along
   651 041F E6 BF                       ani     0bfh            ; and wrap
   652 0421 6F                          mov     l,a             ; make a pointer
   653 0422 36 1A                       mvi     m,C0_SUB        ; place SUB in buffer to mark lost character
   654 0424 C3 31 04                    jmp     do_xoff
   655                  ;
   656 0427 F2 2C 04    not_caught:     jp      nowrap          ; If rx_head < rx_tail
   657 042A C6 40                       adi     40h             ; space is "over the wrap"
   658 042C FE 20       nowrap:         cpi     32              ; Have we got half a buffer full?
   659 042E C2 37 04                    jnz     pass_on
   660 0431 01 13 01    do_xoff:        lxi     b,1<<8|C0_XOFF  ; Half buffer full, so sent XOFF
   661 0434 CD 7E 0F                    call    send_xonoff
   662 0437 CD 47 0E    pass_on:        call    try_tx2
   663                  exit_rx_int:
   664 043A E1                          pop     h
   665 043B C1                          pop     b
   666 043C F1                          pop     psw
   667 043D C9                          ret
   668                  ;
   669 043E 3E FE       rx_xon:         mvi     a,0feh          ; Clear bit 0 of received_xoff
   670 0440 A6                          ana     m
   671 0441 C3 47 04                    jmp     track_xon_xoff
   672                  ;
   673 0444 3E 01       rx_xoff:        mvi     a,1             ; Set bit 0 of received_xoff 
   674 0446 B6                          ora     m
   675 0447 77          track_xon_xoff: mov     m,a             ; Update received_xoff
   676 0448 C3 37 04                    jmp     pass_on
   677                  ;
   678                  ;
   679                  ; Map from an unshifted key to the symbol it has when shifted. This table has no terminator,
   680                  ; so it is exhaustive, i.e. every code other than the letter keys that passes through here
   681                  ; must be found somewhere. Taking the table as ending at the byte of key_scan_map, every
   682                  ; ASCII code from 20h to 7fh is here, either as the unshifted code or the shifted one.
   683                  ;
   684 044B 30 29       key_shift_map:  db      '0', ')'
   685 044D 31 21                       db      '1', '!'
   686 044F 32 40                       db      '2', '@'
   687 0451 33 23                       db      '3', '#'
   688 0453 34 24                       db      '4', '$'
   689 0455 35 25                       db      '5', '%'
   690 0457 36 5E                       db      '6', '^'
   691 0459 37 26                       db      '7', '&'
   692 045B 38 2A                       db      '8', '*'
   693 045D 39 28                       db      '9', '('
   694 045F 2D 5F                       db      '-', '_'
   695 0461 3D 2B                       db      '=', '+'
   696 0463 60 7E                       db      '`', '~'
   697 0465 5B 7B                       db      '[', '{'
   698 0467 5D 7D                       db      ']', '}'
   699 0469 3B 3A                       db      03bh, ':'       ; 03bh is semicolon, which asm8080 hates in quotes :-)
   700 046B 2F 3F                       db      '/', '?'
   701 046D 27 22                       db      027h, 022h      ; 027h is single quote, 022h is double quote
   702 046F 2C 3C                       db      ',', '<'
   703 0471 2E 3E                       db      '.', '<'
   704 0473 5C 7C                       db      '\', '|'
   705 0475 20 20                       db      20h,20h
   706 0477 7F 7F                       db      7fh,7fh
   707                  ;
   708                  ; Key map. Keys are received from the keyboard in a column/row form, shown in TM Figure 4-4-4,
   709                  ; "Keyboard Switch Array". This table is arranged in natural order, by columns as
   710                  ; labelled in that figure, except there are just 11 rows for each column (most of the remaining
   711                  ; key codes being either absent or corresponding to key modifier functions like SHIFT, which
   712                  ; were weeded out by the keyboard interrupt.
   713                  ;
   714                  ; Because the first three keys of column 0 (rows 0 to 2) are absent from the array (i.e. these
   715                  ; scan codes are never sent by the keyboard), this table is overlapped by three bytes with
   716                  ; key_shift_map, hence the equate below. So, at first glance, each column should have 11 rows,
   717                  ; but column 0 appears to have 8.
   718                  ;
   719                  ; Numeric keypad entries have their "normal" ASCII code with the top bit set, so that KP9 is
   720                  ; '9'|80h, i.e. 0b9h. However, asm8080 won't let me write '9'|80h in a DB directive without
   721                  ; parenthesizing, so I'm just leaving hex values here. Whinge over.
   722                  ;
   723                  ; For stability, because of its big reversed "L" shape, the RETURN key has two switches,
   724                  ; with scan codes 04h and 64h. This table converts 04h to 0 and 64h to 13 (i.e. C0_CR) so
   725                  ; that it only counts once.
   726                  ;
   727      04 76       key_scan_map    equ     $-3
   728                                  ; Column 0 - this starts with row 3 (see comments above).
   729 0479 7F 00 70 6F                 db        07fh,    0,  'p',  'o',  'y',  't',  'w',  'q'
            79 74 77 71 
            
   730                                  ; Column 1 - c3 is arrow right
   731 0481 C3 00 00 00 key_scan_col1:  db       0c3h,    0,    0,    0,  ']',  '[',  'i',  'u',  'r',  'e',  '1'
            5D 5B 69 75 
            72 65 31 
   732                                  ; Column 2 - c4 is arrow left, c2 is arrow down, 81 is break
   733 048C C4 00 C2 81 key_scan_col2:  db       0c4h,    0, 0c2h,  81h,  '`',  '-',  '9',  '7',  '4',  '3',C0_ESC
            60 2D 39 37 
            34 33 1B 
   734                                  ; Column 3 - c1 is arrow up, d2 is PF3, d0 is PF1, backspace and
   735                                  ; tab have their natural codes
   736 0497 C1 D2 D0 08 key_scan_col3:  db       0c1h, 0d2h, 0d0h,C0_BS,  '=',  '0',  '8',  '6',  '5',  '2',C0_HT
            3D 30 38 36 
            35 32 09 
   737                                  ; Column 4 - b7 is num7, d3 is PF4, d1 is PF2
   738 04A2 B7 D3 D1 B0 key_scan_col4:  db       0b7h, 0d3h, 0d1h, 0b0h,C0_LF,  '\',  'l',  'k',  'g',  'f',  'a'
            0A 5C 6C 6B 
            67 66 61 
   739                                  ; Column 5 - bx is numeric keypad,
   740 04AD B8 8D B2 B1 key_scan_col5:  db       0b8h, 08dh, 0b2h, 0b1h,    0,  27h,  3bh,  'j',  'h',  'd',  's'
            00 27 3B 6A 
            68 64 73 
   741                                  ; Column 6
   742 04B8 AE AC B5 B4 key_scan_col6:  db       0aeh, 0ach, 0b5h, 0b4h,C0_CR,  '.',  ',',  'n',  'b',  'x', 082h
            0D 2E 2C 6E 
            62 78 82 
   743                                  ; Column 7
   744 04C3 B9 B3 B6 AD key_scan_col7:  db       0b9h, 0b3h, 0b6h, 0adh,    0,  '/',  'm',  20h,  'v',  'c',  'z'
            00 2F 6D 20 
            76 63 7A 
   745
   746                  ; This is the checksum byte for ROM 1 (0000-07FF)
   747 04CE FF                          db      0ffh            ; CHECKSUM
   748                  ;
   749 04CF F5          vertical_int:   push    psw
   750 04D0 E5                          push    h
   751 04D1 D5                          push    d
   752 04D2 CD 4E 10                    call    shuffle         ; finalise shuffle, if ready
   753 04D5 C5                          push    b
   754 04D6 3E 09                       mvi     a,9             ; "clear vertical frequency interrupt"
   755 04D8 D3 A2                       out     iow_dc012
   756 04DA FB                          ei
   757 04DB 3A 65 20                    lda     smooth_scroll
   758 04DE B7                          ora     a
   759 04DF C2 0B 05                    jnz     update_scroll
   760 04E2 21 51 20                    lxi     h,scroll_pending
   761 04E5 B6                          ora     m
   762 04E6 CA 3E 05                    jz      check_bell      ; (not been asked to scroll, so skip to next job)
   763 04E9 3E 01                       mvi     a,1             ; Now start a smooth scroll, so mark it as in progress
   764 04EB 32 65 20                    sta     smooth_scroll
   765 04EE B6                          ora     m               ; reading scroll_pending (direction) setting flags too
   766 04EF 36 00                       mvi     m,0             ; scroll_pending <- 0
   767 04F1 3E 01                       mvi     a,1             ; scrolling up if
   768 04F3 32 5B 20                    sta     scroll_dir
   769 04F6 3A 56 21                    lda     bottom_margin
   770 04F9 F2 05 05                    jp      connectx        ; scroll direction is positive
   771 04FC 3E 99                       mvi     a,99h           ; else we're scrolling down (BCD 99h = -1)
   772 04FE 32 5B 20                    sta     scroll_dir
   773 0501 3A 55 21                    lda     top_margin
   774 0504 3D                          dcr     a
   775 0505 CD 2F 12    connectx:       call    connect_extra   ; 
   776 0508 32 7A 20                    sta     shuffle_ready   ; A is not zero here
   777 050B 01 5B 20    update_scroll:  lxi     b,scroll_dir
   778 050E 0A                          ldax    b               ; A <- scroll direction (-1 or 1)
   779 050F 21 5A 20                    lxi     h,scroll_scan
   780 0512 86                          add     m               ; A <- scan line ± 1
   781 0513 27                          daa                     ; keep arithmetic decimal, as there are 10 scan lines
   782 0514 E6 0F                       ani     0fh             ; A <- scan line (single BCD digit)
   783 0516 77                          mov     m,a             ; update scroll_scan
   784 0517 57                          mov     d,a             ; D <- scroll latch
   785 0518 E6 03                       ani     3               ; Update scroll latch in DC012, two low bits first
   786 051A D3 A2                       out     iow_dc012
   787 051C 7A                          mov     a,d             ; A <- scroll latch
   788 051D 1F                          rar                     ; 
   789 051E A7                          ana     a               ;
   790 051F 1F                          rar                     ; shift right two bits
   791 0520 F6 04                       ori     4               ; mark this as "high bits of scroll latch"
   792 0522 D3 A2                       out     iow_dc012       ; Update scroll latch in DC012, two high bits
   793 0524 7A                          mov     a,d             ; A <- scroll latch
   794 0525 B7                          ora     a               ; done?
   795 0526 C2 3E 05                    jnz     check_bell      ; still scrolling, so skip the termination stuff
   796 0529 32 65 20                    sta     smooth_scroll   ; now we've finished that scroll
   797 052C 0A                          ldax    b               ; A <- scroll direction
   798 052D B7                          ora     a
   799 052E 3A 56 21                    lda     bottom_margin
   800 0531 FA 38 05                    jm      do_shuf         ; A is -1 if screen is scrolling down
   801 0534 3A 55 21                    lda     top_margin      ; no, we're going up
   802 0537 3D                          dcr     a
   803 0538 CD CE 11    do_shuf:        call    calc_shuf1
   804 053B 32 7A 20                    sta     shuffle_ready   ; A is not zero here
   805 053E 21 78 20    check_bell:     lxi     h,bell_duration
   806 0541 7E                          mov     a,m
   807 0542 B7                          ora     a
   808 0543 CA 4F 05                    jz      nobell
   809 0546 35                          dcr     m               ; decrease remaining duration
   810 0547 E6 04                       ani     4               ; and flip speaker click bit every 8 cycles
   811 0549 0F                          rrc                     ; bit 2 -< bit 1
   812 054A 0F                          rrc                     ; bit 1 -< bit 0
   813 054B 0F                          rrc                     ; bit 0 -< bit 7 (speaker click)
   814 054C 32 46 21                    sta     kbd_online_mask
   815 054F 21 2C 21    nobell:         lxi     h,blink_timer
   816 0552 7E                          mov     a,m
   817 0553 3D                          dcr     a
   818 0554 C2 69 05                    jnz     no_blink
   819 0557 36 35                       mvi     m,35h           ; reset blink timer if it's expired, then do blink
   820 0559 3E 08                       mvi     a,8
   821 055B D3 A2                       out     iow_dc012       ; DC012 <- "toggle blink flip flop"
   822 055D 21 CB 21                    lxi     h,test_field
   823 0560 7E                          mov     a,m             ; If tests are repeatedly failing, the screen toggles between
   824 0561 B7                          ora     a               ; normal and reverse field at the blink rate
   825 0562 CA 6A 05                    jz      out_ports
   826 0565 D3 A2                       out     iow_dc012
   827 0567 EE 01                       xri     1
   828 0569 77          no_blink:       mov     m,a             ; update blink_timer or test_field, as necessary
   829 056A 3E 40       out_ports:      mvi     a,iow_kbd_scan  ; Keyboard scan is started by vertical refresh
   830 056C 32 48 21                    sta     kbd_scan_mask   ; When this mask is sent to the keyboard, it gets cleared
   831 056F 21 7F 20                    lxi     h,frame_count   ; increment frame count, used for timing purposes
   832 0572 34                          inr     m
   833 0573 3A A3 21                    lda     brightness
   834 0576 D3 42                       out     iow_brightness
   835 0578 3A 58 21                    lda     tx_rx_speed
   836 057B D3 02                       out     iow_baud_rate
   837 057D 3A C9 21                    lda     vint_nvr
   838 0580 D3 62                       out     iow_nvr_latch
   839 0582 C1                          pop     b
   840 0583 D1                          pop     d
   841 0584 E1                          pop     h
   842 0585 F1                          pop     psw
   843 0586 C9                          ret
   844                  ;
   845 0587 3A A5 21    receiver_tick:  lda     local_mode      ; Do nothing in local mode
   846 058A 21 7B 20                    lxi     h,in_setup      ; or if we are in SET-UP
   847 058D B6                          ora     m
   848 058E C0                          rnz
   849 058F CD 75 06                    call    test_rx_q
   850 0592 C8                          rz                      ; No characters waiting
   851 0593 47          reflect_char:   mov     b,a             ; B <- received char
   852 0594 3A 7B 20                    lda     in_setup
   853 0597 B7                          ora     a
   854 0598 78                          mov     a,b             ; A <- received char
   855 0599 C2 A9 05                    jnz     no_exec 
   856 059C 3A 79 20                    lda     gpo_flags
   857 059F 07                          rlc                     ; Test if we are currently passing characters to GPO
   858 05A0 78                          mov     a,b             ; A <- received char
   859 05A1 DA A9 05                    jc      no_exec         ; and if we are, don't interpret control characters
   860 05A4 FE 20                       cpi     20h
   861 05A6 DA B2 08                    jc      exec_c0
   862 05A9 2A 40 21    no_exec:        lhld    char_action
   863 05AC E9                          pchl
   864                  ;
   865                  ; Once DECGON has been received, all characters are passed through to the graphics
   866                  ; port until DECGOFF, ESC 2, is detected. This requires two states, an escape detector,
   867                  ; installed first, and a "2" detector. If detect ESC and then the next character isn't
   868                  ; "2", these routines will pass through the ESC and other character, and then head back
   869                  ; to escape detector.
   870                  ;
   871 05AD FE 1B       gfx_det_esc:    cpi     C0_ESC
   872 05AF C2 CC 05                    jnz     gfx_send_char
   873 05B2 21 B8 05                    lxi     h,gfx_det_final
   874 05B5 C3 18 0A                    jmp     install_action
   875                  ;
   876                  ; This state detects the final character of DECGOFF: '2'. Any other character will
   877                  ; need to be forwarded to the graphics port, after catching up by sending the preceding
   878                  ; ESC.
   879                  ;
   880                  ; Detecting DECGOFF will transition to ground state.
   881                  ;
   882 05B8 FE 32       gfx_det_final:  cpi     '2'
   883 05BA C2 C5 05                    jnz     gfx_send_esc
   884 05BD 3E 01                       mvi     a,1             ; mark as present (bit 0) but not using (bit 7)
   885 05BF 32 79 20                    sta     gpo_flags
   886 05C2 C3 15 0A                    jmp     to_ground
   887                  ;
   888 05C5 47          gfx_send_esc:   mov     b,a             ; Save the character that we weren't looking for
   889 05C6 3E 1B                       mvi     a,C0_ESC        ; while we send through the ESC we previously detected
   890 05C8 CD D2 05                    call    gfx_tx_char
   891 05CB 78                          mov     a,b
   892 05CC CD D2 05    gfx_send_char:  call    gfx_tx_char
   893 05CF C3 62 0C                    jmp     gfx_set_state   ; back to detecting ESC
   894                  ;
   895 05D2 4F          gfx_tx_char:    mov     c,a
   896 05D3 C5          wait_gpo_rdy:   push    b
   897 05D4 CD 88 14                    call    keyboard_tick
   898 05D7 C1                          pop     b
   899 05D8 DB 42                       in      ior_flags       ; TM §6.4.1 "When data is passed, the GRAPHICS FLAG goes
   900 05DA E6 04                       ani     iob_flags_gpo   ; high and stays high until the data is stored in [RAM]"
   901 05DC C2 D3 05                    jnz     wait_gpo_rdy
   902 05DF 79                          mov     a,c
   903 05E0 D3 E2                       out     iow_graphics
   904 05E2 C9                          ret
   905                  ;
   906                  ; This early entry point (fall through) for print_char is only called from one place, when
   907                  ; we insert a control character in an answerback string, and at that point this point is
   908                  ; called with A = 1, in order to represent the control character onscreen as a diamond shape,
   909                  ; which is ROM glyph 1. It would be important at that point to ensure that the mappings
   910                  ; didn't disrupt the display, and calling single_shift would do that for ASCII range, but
   911                  ; codes 00h to 01fh pass straight through print_char anyway and produce Special Graphics codes,
   912                  ; without any mappings getting in the way.  In fact, that is exactly what happens when CAN
   913                  ; and SUB are used to cancel an escape/control sequence and a checkboard character is printed;
   914                  ; the print_char entry point is used. Am I missing something?
   915                  ;
   916 05E3 CD 46 0C    print_nomap:    call    single_shift
   917                                  ; fall through
   918                  ;
   919                  ; There is a bug with character set mapping at the line marked [* BUG *] below.
   920                  ;
   921                  ; To find out which character should be printed, the number in gl_invocation (normally 0 or 1),
   922                  ; is added to the address g0_charset, which is 20fdh, and the encoding designated into that set
   923                  ; will be used to print a character. If a single shift is in effect, the number in gl_invocation
   924                  ; will have been increased by 2, so G0 invoked into GL will have become G2 invoked into GL for
   925                  ; a single character. If SHIFT OUT (^N) was already in effect, such that G1 was invoked into GL,
   926                  ; and then a single shift is applied, G3 will be invoked into GL.
   927                  ;
   928                  ; At this point, the address of g0_charset (20fdh), in HL, has 3 added to it to find the mapping,
   929                  ; except that the addition only affects the L register, making HL now point to 2000h, when it
   930                  ; should be pointing to 2100h.
   931                  ;
   932                  ; Because 2000h always contains the value 7fh, as the start of the video DMA stream, this bug
   933                  ; will make it appear as if the United Kingdom character set had been invoked, regardless of the
   934                  ; SET-UP condition.
   935                  ;
   936                  ; To demonstrate this, the following three sequences should produce a string of three '#' (hash)
   937                  ; characters, but the third one will produce two '#' and a '£' (pound).
   938                  ;
   939                  ;
   940                  ;       a) SI # SO # SI #       (designations being G0, G1, G0)
   941                  ;       b) SI # ESC N # #       (     "         "   G0, G2, G0)
   942                  ;       c) SI # SO # ESC N #    (     "         "   G0, G1, G3)
   943                  ;
   944 05E6 F5          print_char:     push    psw
   945 05E7 FE 7F                       cpi     7fh             ; Discard DEL
   946 05E9 CA 73 06                    jz      delete_exit
   947 05EC E5                          push    h
   948 05ED D5                          push    d
   949 05EE C5                          push    b
   950 05EF 4F                          mov     c,a             ; C <- character to be printed
   951 05F0 21 FC 20                    lxi     h,gl_invocation ; Now perform character set mapping
   952 05F3 56                          mov     d,m             ; D <- character set (0 = G0, 1 = G1) invoked
   953 05F4 23                          inx     h               ; HL <- g0_charset
   954 05F5 7A                          mov     a,d             ; A <-  charset
   955 05F6 85                          add     l               ; advance to invoked charset
   956 05F7 6F                          mov     l,a             ; HL <- mapped charset [* BUG *]
   957 05F8 7A                          mov     a,d             ; A <- charset
   958 05F9 D6 02                       sui     2               ; Speculatively remove single shift
   959 05FB F2 FF 05                    jp      charset_range   ; detect whether this worked
   960 05FE 7A                          mov     a,d             ; A <- charset, which was fine anyway
   961 05FF 32 FC 20    charset_range:  sta     gl_invocation   ; Unshifted, ready for next character
   962 0602 56                          mov     d,m             ; D <- character map (see charset_list)
   963 0603 3A FA 20                    lda     char_rend
   964 0606 B2                          ora     d
   965 0607 47                          mov     b,a             ; B <- mixed map + rendition
   966 0608 7A                          mov     a,d             ; A <- character map
   967 0609 07                          rlc                     ; Set carry flag for Special Graphics or Alternate ROM Graphics
   968 060A 79                          mov     a,c             ; A <- character
   969 060B D2 16 06                    jnc     normal_mapping  ; jump for "normal" sets
   970 060E D6 5F                       sui     5fh             ; Special graphics are encoded from 05fh, but are in ROM from glyph 0
   971 0610 FE 20                       cpi     20h             ; ... but there are only 20h of them, so may unmap for rest of range
   972 0612 D2 16 06                    jnc     normal_mapping
   973 0615 4F                          mov     c,a             ; Preserve our adjusted mapping for graphics only
   974 0616 7A          normal_mapping: mov     a,d             ; A <- character map
   975 0617 E6 40                       ani     40h             ; Only United Kingdom will be non-zero here
   976 0619 79                          mov     a,c             ; A <- character
   977 061A CA 24 06                    jz      not_uk_enc
   978 061D FE 23                       cpi     23h             ; Only difference between UK and ASCII is '#', 023h
   979 061F C2 24 06                    jnz     not_uk_enc      ; So leave everything else untouched
   980 0622 0E 1E                       mvi     c,1eh           ; '#' becomes '£', at ROM glyph 01eh (TM Figure 4-6-18, p.4-78)
   981 0624 3A FB 20    not_uk_enc:     lda     char_rvid       ; A <- 0 = normal video, 80h = reverse video
   982 0627 B1                          ora     c
   983 0628 4F                          mov     c,a             ; C <- ROM glyph + normal/reverse video bit
   984 0629 3A 42 21                    lda     pending_wrap
   985 062C B7                          ora     a
   986 062D CA 41 06                    jz      skip_wrap
   987 0630 21 F8 20                    lxi     h,curs_col
   988 0633 3A 53 21                    lda     right_margin    ; test for cursor at right-hand edge
   989 0636 BE                          cmp     m
   990 0637 C2 41 06                    jnz     skip_wrap
   991 063A 36 00                       mvi     m,0             ; wrap to next line
   992 063C C5                          push    b
   993 063D CD 55 09                    call    index_down      ; and move cursor down
   994 0640 C1                          pop     b
   995 0641 2A F6 20    skip_wrap:      lhld    cursor_address  ; HL <- cursor address in screen RAM
   996 0644 71                          mov     m,c             ; display it
   997 0645 7C                          mov     a,h             ; Now adjust address to point at attributes
   998 0646 C6 10                       adi     10h
   999 0648 67                          mov     h,a
  1000 0649 70                          mov     m,b             ; Apply rendition (and mapping)
  1001 064A 78                          mov     a,b
  1002 064B 32 F5 20                    sta     rend_und_curs   ; Record this rendition and mapping
  1003 064E 21 F4 20                    lxi     h,char_und_curs
  1004 0651 71                          mov     m,c             ; Store character under cursor
  1005 0652 21 F8 20                    lxi     h,curs_col
  1006 0655 3A 53 21                    lda     right_margin
  1007 0658 BE                          cmp     m
  1008 0659 C2 68 06                    jnz     no_wrap_needed
  1009 065C 79                          mov     a,c             ; A <- glyph + normal/reverse
  1010 065D 32 F4 20                    sta     char_und_curs
  1011 0660 3A A8 21                    lda     setup_b3
  1012 0663 E6 40                       ani     sb3_autowrap
  1013 0665 C3 6D 06                    jmp     may_need_wrap
  1014                  ;
  1015 0668 34          no_wrap_needed: inr     m               ; move cursor right
  1016 0669 CD 36 16                    call    move_updates
  1017 066C AF                          xra     a
  1018 066D 32 42 21    may_need_wrap:  sta     pending_wrap
  1019 0670 C1                          pop     b
  1020 0671 D1                          pop     d
  1021 0672 E1                          pop     h
  1022 0673 F1          delete_exit:    pop     psw
  1023 0674 C9                          ret
  1024                  ;
  1025                  ; test_rx_q
  1026                  ;       See if there are any received characters waiting to be processed,
  1027                  ;       provided the user hasn't pressed NO SCROLL.
  1028                  ;       Returns NZ if there is a character available; character in A.
  1029                  ;       Returns Z if no scroll in operation or no characters waiting.
  1030                  ;
  1031 0675 3A C4 21    test_rx_q:      lda     noscroll
  1032 0678 B7                          ora     a
  1033 0679 C2 85 06                    jnz     no_test_q
  1034 067C 21 C0 20                    lxi     h,rx_head
  1035 067F 7E                          mov     a,m             ; A <- rx_head  
  1036 0680 23                          inx     h               ; HL <- rx_tail
  1037 0681 96                          sub     m               ; A <- rx_head - rx_tail
  1038 0682 C2 87 06                    jnz     rx_not_empty    ; jump if characters received
  1039 0685 AF          no_test_q:      xra     a
  1040 0686 C9                          ret
  1041                  ;
  1042 0687 6E          rx_not_empty:   mov     l,m             ; L <- rx_tail
  1043 0688 26 20                       mvi     h,20h           ; Make HL pointer to character
  1044 068A 56                          mov     d,m             ; D <- first character in buffer
  1045 068B 21 C1 20                    lxi     h,rx_tail
  1046 068E 7E                          mov     a,m
  1047 068F 3C                          inr     a               ; advance rx_tail pointer
  1048 0690 E6 BF                       ani     0bfh            ; with wrap 
  1049 0692 F6 80                       ori     80h
  1050 0694 77                          mov     m,a             ; write new rx_tail 
  1051 0695 2B                          dcx     h               ; HL <- rx_head
  1052 0696 96                          sub     m               ; A <- tail - head
  1053 0697 F2 9C 06                    jp      nowrap2
  1054 069A C6 40                       adi     40h             ; space is "over the wrap"
  1055 069C FE 30       nowrap2:        cpi     30h             ; Is the buffer three-quarters empty?
  1056 069E C2 A7 06                    jnz     not_empty_yet
  1057 06A1 01 11 01                    lxi     b,1<<8|C0_XON
  1058 06A4 CD 7E 0F                    call    send_xonoff
  1059 06A7 7A          not_empty_yet:  mov     a,d             ; A <- first character in buffer
  1060 06A8 B7                          ora     a
  1061 06A9 C9                          ret
  1062                  ;
  1063 06AA 3A 68 20    process_keys:   lda     key_flags
  1064 06AD 5F                          mov     e,a
  1065 06AE E6 80                       ani     key_flag_eos    ; Don't bother processing anything until end of scan,
  1066 06B0 C8                          rz                      ; so exit immediately
  1067 06B1 21 41 08                    lxi     h,clear_keyboard
  1068 06B4 E5                          push    h               ; always exit by clearing down keyboard flags and silo
  1069 06B5 7B                          mov     a,e             ; A <- key_flags
  1070 06B6 E6 07                       ani     7               ; isolate count
  1071 06B8 FE 04                       cpi     4
  1072 06BA FA C2 06                    jm      process_silo    ; if fewer than 4 keys waiting, process them
  1073 06BD AF                          xra     a               ; else we've overflowed
  1074 06BE 32 67 20                    sta     new_key_scan    ; there are no new keys
  1075 06C1 C9                          ret                     ; throw away flags and silo on way out
  1076                  ;
  1077 06C2 57          process_silo:   mov     d,a             ; D <- number of keys in silo
  1078 06C3 0E 00                       mvi     c,0             ; C <- 0, number of keys in silo matching history
  1079 06C5 06 04                       mvi     b,4             ; loop through (up to) 4 history entries
  1080 06C7 21 6E 20                    lxi     h,key_history
  1081 06CA 7E          next_history:   mov     a,m             ; grab next key from history
  1082 06CB B7                          ora     a
  1083 06CC CA E8 06                    jz      zero_history    ; if there wasn't a key here, skip search
  1084 06CF E5                          push    h
  1085 06D0 C5                          push    b
  1086                                  ; Having grabbed a key from the key history, we try to find it in the silo. Finding
  1087                                  ; it still down confirms it as something other than a bounce, and we'll leave it for
  1088                                  ; processing. If we don't find it, we'll zero out the entry in the history.
  1089                                  ; This loop employs a neat trick of distinguishing between a silo match and
  1090                                  ; exhausting the the search. If we find a match, we'll jump out of the comparison
  1091                                  ; loop with Z flag set. The normal way of running a count would also leave us
  1092                                  ; looping from a number down to zero though, meaning the Z flag would be set in
  1093                                  ; either case. So here, the loop is run from one count less and B is decremented
  1094                                  ; and loops while it is still positive. So, exhausting the silo leaves us with
  1095                                  ; Z flag *unset*.
  1096 06D1 06 03                       mvi     b,3
  1097 06D3 21 6A 20                    lxi     h,key_silo
  1098 06D6 BE          compare_silo:   cmp     m               ; Does old key exist in silo?
  1099 06D7 CA DF 06                    jz      silo_match      ; yes, process
  1100 06DA 23                          inx     h               ; next location in silo
  1101 06DB 05                          dcr     b
  1102 06DC F2 D6 06                    jp      compare_silo
  1103 06DF C1          silo_match:     pop     b
  1104 06E0 E1                          pop     h
  1105 06E1 CA E7 06                    jz      found_in_silo   ; If we found key in silo (result of cmp), add to history
  1106 06E4 36 00                       mvi     m,0             ; zero out the key from history
  1107 06E6 0D                          dcr     c               ; Reduce the number of matching keys, to overcome next instruction
  1108 06E7 0C          found_in_silo:  inr     c               ; Increase the number of matching keys
  1109 06E8 23          zero_history:   inx     h               ; next history entry
  1110 06E9 05                          dcr     b
  1111 06EA C2 CA 06                    jnz     next_history
  1112                  ;
  1113 06ED 7B                          mov     a,e             ; A <- key flags
  1114 06EE E6 08                       ani     8               ; According to TM, this bit was intended for SETUP, but this
  1115 06F0 C0                          rnz                     ; must be out of date; can't see a way of setting this bit now.
  1116 06F1 B2                          ora     d               ; Include the number of keys in silo
  1117 06F2 C2 FB 06                    jnz     keys_avail
  1118 06F5 3E E1                       mvi     a,-31           ; When there are no keys down, reset the key repeat timer
  1119 06F7 32 72 20                    sta     key_rpt_timer
  1120 06FA C9                          ret
  1121                  ;
  1122 06FB 21 47 07    keys_avail:     lxi     h,check_history ; next stage
  1123 06FE E5                          push    h
  1124 06FF 3A 72 20                    lda     key_rpt_timer
  1125 0702 3C                          inr     a
  1126 0703 CA 09 07                    jz      rpt_expired
  1127 0706 32 72 20                    sta     key_rpt_timer
  1128 0709 3A A6 21    rpt_expired:    lda     setup_b1
  1129 070C E6 40                       ani     sb1_autorep     ; If we're not auto-repeating, timer expiry is irrelevant
  1130 070E C8                          rz
  1131 070F 7B                          mov     a,e             ; A <- key flags
  1132 0710 E6 10                       ani     10h             ; CTRL pressed?
  1133 0712 C0                          rnz                     ; A key with control cannot repeat (TM §4.4.9.5)
  1134 0713 3A 50 21                    lda     latest_key_scan
  1135 0716 21 5F 08                    lxi     h,nonrepeat     ; Five keys are not allowed to auto-repeat
  1136 0719 06 05                       mvi     b,5
  1137 071B BE          chk_nonrep:     cmp     m               ; If this is one of them, reject it (return early)
  1138 071C C8                          rz
  1139 071D 23                          inx     h
  1140 071E 05                          dcr     b
  1141 071F C2 1B 07                    jnz     chk_nonrep
  1142 0722 21 73 20                    lxi     h,key_rpt_pause ; Pause before repeating begins (about half a second)
  1143 0725 35                          dcr     m
  1144 0726 C0                          rnz
  1145 0727 36 02                       mvi     m,2             ; Two counts per repeat (30 per second)
  1146 0729 3A 72 20                    lda     key_rpt_timer
  1147 072C FE FF                       cpi     0ffh
  1148 072E C0                          rnz
  1149 072F 79                          mov     a,c             ; A <- number of matching keys (in history)
  1150 0730 FE 01                       cpi     1               ; Only one key can repeat at a time
  1151 0732 C0                          rnz
  1152 0733 06 04                       mvi     b,4             ; The key could be in any of the history slots
  1153 0735 21 6E 20                    lxi     h,key_history
  1154 0738 7E          check_rpt_slot: mov     a,m
  1155 0739 B7                          ora     a
  1156 073A C2 43 07                    jnz     got_repeater
  1157 073D 23                          inx     h
  1158 073E 05                          dcr     b
  1159 073F C2 38 07                    jnz     check_rpt_slot
  1160 0742 C9                          ret                     ; We should have found one, but failed!
  1161                  ;
  1162 0743 E1          got_repeater:   pop     h               ; Pop the return address we stacked earlier
  1163 0744 C3 76 07                    jmp     set_latest
  1164                  ;
  1165 0747 79          check_history:  mov     a,c             ; A <- number of matching keys in history
  1166 0748 FE 04                       cpi     4               ; Still got false keys down?
  1167 074A F0                          rp                      ; if so, exit 
  1168 074B 01 6A 20                    lxi     b,key_silo
  1169 074E 21 6E 20    comp_silo_hist: lxi     h,key_history
  1170 0751 7E          try_next_hist:  mov     a,m             ; A <- key from history
  1171 0752 B7                          ora     a
  1172 0753 CA 5B 07                    jz      next_hist_key
  1173 0756 0A                          ldax    b               ; Is this key in silo same as this history key?
  1174 0757 BE                          cmp     m
  1175 0758 CA 66 07                    jz      next_silo_key   ; OK, found it
  1176 075B 23          next_hist_key:  inx     h               ; try next key in history
  1177 075C 7D                          mov     a,l             ; 
  1178 075D FE 72                       cpi     LOW key_history + 4
  1179 075F C2 51 07                    jnz     try_next_hist   ; try again, unless at end of history buffer
  1180 0762 0A                          ldax    b               ; A <- key from silo
  1181 0763 C3 6C 07                    jmp     found_new_key
  1182                  ;
  1183 0766 03          next_silo_key:  inx     b               ; point to next silo entry
  1184 0767 15                          dcr     d               ; decrease number of keys in silo
  1185 0768 C2 4E 07                    jnz     comp_silo_hist
  1186 076B C9                          ret
  1187                  ;
  1188 076C 47          found_new_key:  mov     b,a             ; B <- scan code
  1189 076D 3A 67 20                    lda     new_key_scan
  1190 0770 B8                          cmp     b
  1191 0771 78                          mov     a,b             ; A <- scan code
  1192 0772 32 67 20                    sta     new_key_scan
  1193 0775 C0                          rnz
  1194 0776 E1          set_latest:     pop     h
  1195 0777 32 50 21                    sta     latest_key_scan
  1196 077A FE 7B                       cpi     7bh             ; is SETUP pressed?
  1197 077C CA 1A 1A                    jz      setup_pressed   ; yes, mark as pending
  1198 077F FE 6A                       cpi     6ah             ; is NO SCROLL pressed?
  1199 0781 CA 2C 02                    jz      no_scroll_key
  1200 0784 43                          mov     b,e             ; B <- key flags
  1201 0785 5F                          mov     e,a             ; E <- scan code
  1202 0786 3A 44 21                    lda     keyboard_locked ; Don't process while keyboard locked - clear SILO
  1203 0789 B7                          ora     a
  1204 078A C2 41 08                    jnz     clear_keyboard
  1205                  ;
  1206                  ; Each column of the key switch array contains 11 entries, so need to take a column
  1207                  ; number in the top four bits of E, divide by 16, and then multiply by 11.
  1208                  ;
  1209 078D 7B                          mov     a,e             ; A <- scan code
  1210 078E E6 F0                       ani     0f0h            ; A <- 16 * column (because shifted)
  1211 0790 0F                          rrc
  1212 0791 0F                          rrc
  1213 0792 57                          mov     d,a             ; D <- 4 * column
  1214 0793 0F                          rrc
  1215 0794 0F                          rrc                     ; A <- column
  1216 0795 82                          add     d               ; A <- 5 * column
  1217 0796 57                          mov     d,a             ; D <- 5 * column
  1218 0797 7B                          mov     a,e             ; A <- scan code = 16 * column + row
  1219 0798 92                          sub     d               ; A <- 11 * column + row
  1220 0799 21 76 04                    lxi     h,key_scan_map
  1221 079C 5F                          mov     e,a
  1222 079D 16 00                       mvi     d,0             ; DE <- A (offset)
  1223 079F 19                          dad     d               ; HL <- offset into array for this scan code
  1224 07A0 4E                          mov     c,m             ; C <- key code
  1225 07A1 79                          mov     a,c             ; A <- key code
  1226 07A2 B7                          ora     a               ;
  1227 07A3 FA 31 01                    jm      proc_func_key   ; jump to deal with function keys
  1228 07A6 FE 20                       cpi     20h
  1229 07A8 DA FF 07                    jc      done_modifiers  ; unmapped, ESC, BS, TAB, LF, RETURN
  1230 07AB 78                          mov     a,b             ; A <- key flags
  1231 07AC E6 70                       ani     70h             ; any modifiers pressed?
  1232 07AE CA FF 07                    jz      done_modifiers  ; no, simple path
  1233                                  ; Given that some modifiers have been pressed, work out a candidate shift code
  1234 07B1 79                          mov     a,c             ; A <- key code
  1235 07B2 FE 7B                       cpi     7bh             ; candidates for table shift
  1236 07B4 D2 C2 07                    jnc     use_shift_map
  1237 07B7 FE 61                       cpi     61h             ; also shift these by table
  1238 07B9 DA C2 07                    jc      use_shift_map
  1239                                  ; Otherwise, we are just left with lowercase letter range (61h to 07ah),
  1240                                  ; and they are shifted to uppercase by a simple logical and.
  1241 07BC E6 DF                       ani     0dfh            ; make uppercase from lowercase
  1242 07BE 4F                          mov     c,a             ; C <- shifted key code
  1243 07BF C3 D7 07                    jmp     exam_key_mods
  1244                  ;
  1245 07C2 78          use_shift_map:  mov     a,b             ; A <- key flags
  1246 07C3 E6 30                       ani     30h             ; are CTRL or SHIFT pressed?
  1247 07C5 CA D7 07                    jz      exam_key_mods   ; no, skip shift table
  1248 07C8 21 4B 04                    lxi     h,key_shift_map ; HL <- start of shift table
  1249 07CB 7E          next_shift:     mov     a,m             ; get unshifted code from table
  1250 07CC B9                          cmp     c               ; compare with ours
  1251 07CD CA D5 07                    jz      pick_shift      ; match, go and pick up shift code
  1252 07D0 23                          inx     h               ; go past shifted code
  1253 07D1 23                          inx     h               ; to next key
  1254 07D2 C3 CB 07                    jmp     next_shift      ; round again
  1255                  ;
  1256 07D5 23          pick_shift:     inx     h               ; advance to shift code
  1257 07D6 4E                          mov     c,m             ; C <- shifted key code
  1258 07D7 78          exam_key_mods:  mov     a,b             ; A <- key flags
  1259 07D8 E6 10                       ani     10h             ; mask with CTRL
  1260 07DA CA FF 07                    jz      done_modifiers  ; not CTRL, straight out, we've got final code
  1261 07DD 79                          mov     a,c             ; A <- shifted key code
  1262 07DE FE 41                       cpi     'A'
  1263 07E0 DA E8 07                    jc      below_alpha
  1264 07E3 FE 5B                       cpi     '['             ; (the real '[' has already been shifted to '{')
  1265 07E5 DA FC 07                    jc      apply_ctrl      ; apply to 'A' to 'Z'
  1266 07E8 FE 3F       below_alpha:    cpi     '?'
  1267 07EA CA FC 07                    jz      apply_ctrl      ; '?' 3fh =< 1fh (US)
  1268 07ED FE 20                       cpi     20h             ;
  1269 07EF CA FC 07                    jz      apply_ctrl      ; SP 20h =< 00h (NUL)
  1270 07F2 FE 7B                       cpi     7bh
  1271 07F4 DA 41 08                    jc      clear_keyboard
  1272 07F7 FE 7F                       cpi     7fh
  1273 07F9 D2 41 08                    jnc     clear_keyboard
  1274                                                          ; Codes 07bh to 07eh drop through here, allowing us to
  1275                                                          ; produce control codes from 1bh (ESC) to 1eh (RS)
  1276 07FC E6 9F       apply_ctrl:     ani     9fh             ; e.g. 'A' 01000001 & 10011111 -< 00000001
  1277 07FE 4F                          mov     c,a             ; C <- final code
  1278 07FF B1          done_modifiers: ora     c
  1279 0800 F5                          push    psw
  1280 0801 3A A7 21                    lda     setup_b2
  1281 0804 E6 80                       ani     sb2_marginbell
  1282 0806 CA 0C 08                    jz      mbell_disabled
  1283 0809 32 54 21                    sta     margin_bell     ; Typing a key could potentially trigger a margin bell
  1284 080C F1          mbell_disabled: pop     psw
  1285 080D F6 80       send_key_end:   ori     80h             ; Set high bit to mark as last character we're sending
  1286 080F CD 18 0F                    call    send_key_byte
  1287                  ;
  1288 0812 CD 53 08    pk_click:       call    make_keyclick
  1289                  ;
  1290                  ; Now we've identified the latest key, place it in a zeroed location in the key history,
  1291                  ; if it isn't already there.
  1292                  ;
  1293 0815 3A 50 21    pk_noclick:     lda     latest_key_scan
  1294 0818 16 04                       mvi     d,4
  1295 081A 21 6E 20                    lxi     h,key_history
  1296 081D BE          next_hist:      cmp     m
  1297 081E CA 41 08                    jz      clear_keyboard
  1298 0821 23                          inx     h
  1299 0822 15                          dcr     d
  1300 0823 C2 1D 08                    jnz     next_hist
  1301 0826 16 04                       mvi     d,4
  1302 0828 21 6E 20                    lxi     h,key_history
  1303 082B 7E          next_hist2:     mov     a,m
  1304 082C B7                          ora     a
  1305 082D CA 38 08                    jz      got_hist_place
  1306 0830 23                          inx     h
  1307 0831 15                          dcr     d
  1308 0832 C2 2B 08                    jnz     next_hist2
  1309 0835 C3 41 08                    jmp     clear_keyboard
  1310                  ;
  1311 0838 3A 50 21    got_hist_place: lda     latest_key_scan
  1312 083B 77                          mov     m,a             ; place key in history
  1313 083C 3E E1                       mvi     a,-31           ; reset key repeat timer
  1314 083E 32 72 20                    sta     key_rpt_timer
  1315                  ;
  1316                  ; clear_keyboard
  1317                  ;       At the end of keyboard processing, clear key_flags and the keyboard silo.
  1318                  ;
  1319 0841 AF          clear_keyboard: xra     a       
  1320 0842 21 68 20                    lxi     h,key_flags
  1321 0845 56                          mov     d,m             ; D <- key_flags
  1322 0846 77                          mov     m,a             ; zero key_flags
  1323 0847 23                          inx     h               ; HL <- last_key_flags
  1324 0848 72                          mov     m,d             ; last_key_flags <- key_flags
  1325 0849 23                          inx     h               ; HL <- key_silo
  1326 084A 16 04                       mvi     d,4             ; 4 locations to clear
  1327 084C 77          clear_silo:     mov     m,a             ; zero silo location
  1328 084D 23                          inx     h               ; next location
  1329 084E 15                          dcr     d
  1330 084F C2 4C 08                    jnz     clear_silo
  1331 0852 C9                          ret
  1332                  ;
  1333 0853 3A A7 21    make_keyclick:  lda     setup_b2
  1334 0856 E6 40                       ani     sb2_keyclick
  1335 0858 C8                          rz                      ; exit if keyclick is not enabled
  1336 0859 3E 80                       mvi     a,iow_kbd_click
  1337 085B 32 47 21                    sta     kbd_click_mask  ; When this mask is sent to the keyboard, it gets cleared
  1338 085E C9                          ret
  1339                  ;
  1340                  ; Table of keys that are not allowed to auto-repeat (TM §4.4.9.5)
  1341                  ; These are: SETUP, ESC, NO SCROLL, TAB, RETURN
  1342                  ;
  1343 085F 7B 2A 6A 3A nonrepeat:      db      7bh, 2ah, 6ah, 3ah, 64h
            64 
  1344
  1345                  ;       Entry point for DECTST, which has form ESC [ 2 ; Ps y
  1346 0864 3A 30 21    tst_action:     lda     csi_p1          ; The first parameter to DECTST
  1347 0867 D6 02                       sui     2               ; must be the value 2
  1348 0869 C0                          rnz                     ; else quit early!
  1349 086A 57                          mov     d,a             ; D <- 0
  1350 086B 3A 31 21                    lda     csi_p2          ; Now grab the test(s) we'd like to perform
  1351 086E 5F                          mov     e,a             ; E <- test values (each bit set is a test)
  1352                  rep_tests:
  1353 086F 3E 01                       mvi     a,1             ; Test mask 0x01 - power up self test
  1354 0871 A3                          ana     e               ; if POST is requested,
  1355 0872 C2 3D 00                    jnz     self_test       ; do it. (POST knows to return here.)
  1356 0875 7A          continue_tests: mov     a,d             ; A <- cumulative test results
  1357 0876 B7                          ora     a
  1358 0877 C4 A7 08                    cnz     note_failure
  1359 087A D5                          push    d               ; preserve current results on stack ...
  1360 087B CD BE 17                    call    print_wait      ; "Wait", we could be here a while
  1361 087E D1                          pop     d               ; ... and restore
  1362 087F 3E 02                       mvi     a,2             ; Test mask 0x02 - data loop back test
  1363 0881 A3                          ana     e
  1364 0882 C4 11 1F                    cnz     data_loop_test
  1365 0885 3E 08                       mvi     a,8             ; A <- display mask for failing tests
  1366 0887 DC A7 08                    cc      note_failure
  1367 088A 3E 04                       mvi     a,4             ; Test mask 0x04 - modem control test
  1368 088C A3                          ana     e
  1369 088D C4 62 1F                    cnz     modem_test
  1370 0890 3E 10                       mvi     a,10h           ; A <- display mask for failing tests
  1371 0892 DC A7 08                    cc      note_failure
  1372 0895 7A                          mov     a,d             ; Have we failed any tests?
  1373 0896 B7                          ora     a
  1374 0897 C2 A0 08                    jnz     skip_repeat     ; if so, don't go round again
  1375 089A 3E 08                       mvi     a,8             ; Test mask 0x08
  1376 089C A3                          ana     e               ; means repeat selected tests indefinitely until
  1377 089D C2 6F 08                    jnz     rep_tests       ; failure or power off
  1378                  skip_repeat:
  1379 08A0 7A                          mov     a,d             ; Get test results
  1380 08A1 32 00 20                    sta     ram_start       ; squirrel away (see after_tests)
  1381 08A4 C3 51 02                    jmp     after_tests     
  1382                  ;
  1383                  ;       Add bits to D register as tests fail. Current mask to add to failure is in A register.
  1384                  ;       This corresponds directly to the characters displayed on screen for test failure,
  1385                  ;       except bit 7. The displayed characters are shown in TM Table 5-6.
  1386                  ;       Pulling this into ASCII range is done in see after_tests.
  1387                  ;
  1388                  ;              7     6     5     4     3     2     1     0
  1389                  ;           +-----+-----+-----+-----+-----+-----+-----+-----+
  1390                  ;       D:  |cycle|     |     |modem| data| kbd | RAM | AVO |
  1391                  ;           +-----+-----+-----+-----+-----+-----+-----+-----+
  1392                  ;
  1393 08A7 B2          note_failure:   ora     d               ; A <- new failure mask | previous failures
  1394 08A8 57                          mov     d,a             ; D <- new failure tally
  1395 08A9 3E 08                       mvi     a,8             ; 8 is a request to cycle tests
  1396 08AB A3                          ana     e               ; E is tests requested
  1397 08AC C8                          rz
  1398 08AD 3E 80                       mvi     a,80h           ; Mark "cycle tests" in failure mask
  1399 08AF B2                          ora     d
  1400 08B0 57                          mov     d,a
  1401 08B1 C9                          ret
  1402                  ;
  1403 08B2 FE 1B       exec_c0:        cpi     C0_ESC
  1404 08B4 CA 9E 09                    jz      c0_escape       ; start extended processing
  1405 08B7 FE 10                       cpi     C0_DLE          ; Are we in column 0 or column 1?
  1406 08B9 DA D4 08                    jc      process_col0    ; Column 0 controls go through a jump table
  1407 08BC 5F                          mov     e,a             ; E <- character
  1408 08BD D6 18                       sui     18h
  1409 08BF E6 FD                       ani     0fdh            ; Z if A was 018h (CAN) or 01ah (SUB)
  1410 08C1 C0                          rnz                     ; No? No action for anything else in this column
  1411 08C2 7B                          mov     a,e             ; A <- character
  1412 08C3 FE 1A                       cpi     C0_SUB
  1413 08C5 CA CB 08                    jz      cancel_seq
  1414 08C8 FE 18                       cpi     C0_CAN
  1415 08CA C0                          rnz
  1416 08CB CD 15 0A    cancel_seq:     call    to_ground
  1417 08CE 3E 02                       mvi     a,2             ; ROM glyph 2 is checkerboard (error indicator)
  1418 08D0 C3 E6 05                    jmp     print_char
  1419                  ;
  1420 08D3 C9                          ret                     ; UNREACHABLE
  1421                  ;
  1422                  ; It happens that the only C0 controls that the VT100 "processes" are those from column 0,
  1423                  ; i.e. with codes 00h to 0fh, and they are looked up here. Codes from column 1 are either
  1424                  ; dealt with earlier for communication control (XON and XOFF), invoke extended processing
  1425                  ; (ESC) or just cancel that extended processing (CAN and SUB).
  1426                  ;
  1427 08D4 D6 05       process_col0:   sui     5               ; Codes 00h to 04h are ignored (actually, NUL never got here)
  1428 08D6 F8                          rm
  1429 08D7 21 E5 08                    lxi     h,c0_actions
  1430 08DA 87                          add     a               ; Addresses are two bytes long, so double up our code,
  1431 08DB 5F                          mov     e,a             ; use DE to hold a 16-bit offset
  1432 08DC 16 00                       mvi     d,0
  1433 08DE 19                          dad     d               ; and add to the table base address
  1434 08DF 5E                          mov     e,m             ; extract action routine
  1435 08E0 23                          inx     h               ; address
  1436 08E1 56                          mov     d,m             ; DE <- action routine
  1437 08E2 EB                          xchg                    ; HL <-< DE (can't jump to DE)
  1438 08E3 AF                          xra     a               ; convenience clear - used for SHIFT IN/OUT
  1439 08E4 E9                          pchl                    ; Jump to action routine, so its return will continue
  1440                                                          ; other processing
  1441                  ; JUMP TABLE
  1442                  c0_actions:
  1443 08E5 00 09                       dw      c0_answerback   ; 05 ENQ (answerback)
  1444 08E7 97 09                       dw      do_nothing_ack  ; 06 ACK (acknowledge) - does nothing
  1445 08E9 38 09                       dw      c0_bell         ; 07 BEL (bell)
  1446 08EB 41 09                       dw      c0_backspace    ; 08 BS  (backspace)
  1447 08ED F9 0D                       dw      c0_horiz_tab    ; 09 HT  (horizontal tab)
  1448 08EF 55 09                       dw      index_down      ; 0a LF  (line feed)
  1449 08F1 55 09                       dw      index_down      ; 0b VT  (vertical tab)
  1450 08F3 55 09                       dw      index_down      ; 0c FF  (form feed)
  1451 08F5 4B 09                       dw      c0_return       ; 0d CR  (carriage return)
  1452 08F7 FB 08                       dw      c0_shift_out    ; 0e SO  (shift out)
  1453 08F9 FC 08                       dw      c0_shift_in     ; 0f SI  (shift in)
  1454
  1455 08FB 3C          c0_shift_out:   inr     a
  1456 08FC 32 FC 20    c0_shift_in:    sta     gl_invocation
  1457 08FF C9                          ret
  1458                  ;
  1459 0900 3A A5 21    c0_answerback:  lda     local_mode      ; Can't send answerback in local mode
  1460 0903 B7                          ora     a
  1461 0904 C0                          rnz
  1462 0905 2A 7B 21                    lhld    aback_buffer
  1463 0908 7C                          mov     a,h             ; If the first two characters of the answerback buffer
  1464 0909 BD                          cmp     l               ; are the same, they are two delimiters and the message
  1465 090A C8                          rz                      ; is empty, so exit
  1466 090B 21 72 21                    lxi     h,pending_report
  1467 090E 3E 10                       mvi     a,pend_aback
  1468 0910 B6                          ora     m
  1469 0911 77                          mov     m,a
  1470 0912 C9                          ret
  1471                  ;
  1472                  ; aback_report
  1473                  ;       Produce the answerback report by dumping the buffer from aback_buffer, raw, to the serial stream.
  1474                  ;       There can be up to 20 characters in this buffer, but may be fewer, as determined by the first
  1475                  ;       byte, the delimiter.
  1476                  ;
  1477 0913 21 72 21    aback_report:   lxi     h,pending_report
  1478 0916 7E                          mov     a,m
  1479 0917 E6 EF                       ani     ~pend_aback
  1480 0919 77                          mov     m,a
  1481 091A 21 7B 21                    lxi     h,aback_buffer
  1482 091D 46                          mov     b,m             ; B <- delimiter
  1483 091E 23                          inx     h               ; next character
  1484 091F 0E 14                       mvi     c,20            ; we're going to send 20 characters at most
  1485 0921 11 5C 21                    lxi     d,report_buffer
  1486 0924 7E          more_aback:     mov     a,m             ; grab next character
  1487 0925 B8                          cmp     b               ; if it is the delimiter, exit
  1488 0926 CA 30 09                    jz      aback_repend
  1489 0929 12                          stax    d               ; add character to report
  1490 092A 13                          inx     d               ; next report location
  1491 092B 23                          inx     h               ; next answerback location
  1492 092C 0D                          dcr     c
  1493 092D C2 24 09                    jnz     more_aback
  1494 0930 1B          aback_repend:   dcx     d               ; go back one character
  1495 0931 1A                          ldax    d               ; grab the last character
  1496 0932 F6 80                       ori     80h             ; mark it as the end of string, by setting bit 7
  1497 0934 12                          stax    d               ; put it back
  1498 0935 C3 F3 0E                    jmp     send_report
  1499                  ;
  1500 0938 21 78 20    c0_bell:        lxi     h,bell_duration
  1501 093B 7E                          mov     a,m
  1502 093C C6 08                       adi     8
  1503 093E D8                          rc
  1504 093F 77                          mov     m,a
  1505 0940 C9                          ret
  1506                  ;
  1507 0941 21 F8 20    c0_backspace:   lxi     h,curs_col
  1508 0944 7E                          mov     a,m
  1509 0945 B7                          ora     a
  1510 0946 C8                          rz                      ; Can't backspace beyond the start of the line
  1511 0947 35                          dcr     m               ; Move cursor one column left
  1512 0948 C3 36 16                    jmp     move_updates
  1513                  ;
  1514                  ; c0_return - move the cursor to the start of the line, the CR action
  1515 094B AF          c0_return:      xra     a
  1516 094C 32 F8 20                    sta     curs_col        ; Column zero
  1517 094F C3 36 16                    jmp     move_updates
  1518                  ;
  1519 0952 CD 4B 09    nextline:       call    c0_return
  1520                  ;
  1521                  ; C0 codes LF, VT and FF, as well as escape sequence IND, are all processed the same way
  1522 0955 3A A8 21    index_down:     lda     setup_b3        ; Check newline (LNM) mode, to see if moving down also
  1523 0958 E6 20                       ani     sb3_newline     ; implies moving to the start of the (next) line.
  1524 095A C4 4B 09                    cnz     c0_return
  1525 095D 21 F9 20                    lxi     h,curs_row
  1526 0960 56                          mov     d,m
  1527 0961 3A 56 21                    lda     bottom_margin
  1528 0964 BA                          cmp     d
  1529 0965 CA 73 09                    jz      at_margin_b
  1530 0968 CD BF 11                    call    last_row        ; B <- last row number
  1531 096B 78                          mov     a,b
  1532 096C BA                          cmp     d
  1533 096D C8                          rz
  1534 096E 14                          inr     d               ; Not at bottom of screen, so just increment row number
  1535 096F 72                          mov     m,d
  1536 0970 C3 36 16                    jmp     move_updates
  1537                  ;
  1538 0973 3A A6 21    at_margin_b:    lda     setup_b1
  1539 0976 E6 80                       ani     sb1_smooth
  1540 0978 CA 98 09                    jz      jump_scroll_up  
  1541 097B 0E 01                       mvi     c,1
  1542 097D CD 8E 10                    call    wait_scroll
  1543 0980 3E FF                       mvi     a,0ffh
  1544 0982 32 C3 21                    sta     row_clearing
  1545 0985 34                          inr     m               ; scroll_pending <- 1
  1546 0986 CD E6 0F                    call    move_lines_up
  1547 0989 3A C3 21    wait_clear1:    lda     row_clearing
  1548 098C B7                          ora     a
  1549 098D C2 89 09                    jnz     wait_clear1
  1550 0990 32 F4 20                    sta     char_und_curs   ; blank under cursor (00h)
  1551 0993 3D                          dcr     a
  1552 0994 32 F5 20                    sta     rend_und_curs   ; and default rendition (0ffh)
  1553                  ;
  1554                  ; This return instruction is used as a handy jump point for the C0 ACK control,
  1555                  ; which the terminal ignores.
  1556 0997 C9          do_nothing_ack: ret
  1557                  ;
  1558 0998 CD 1A 10    jump_scroll_up: call    start_jump_up
  1559 099B C3 12 10                    jmp     inv_saved_row
  1560                  ;
  1561                  ; The routine that kicks off all escape and control sequence processing in the terminal.
  1562                  ;
  1563 099E AF          c0_escape:      xra     a
  1564 099F 32 7D 20                    sta     inter_chars
  1565 09A2 32 B8 21                    sta     csi_private
  1566 09A5 21 AB 09                    lxi     h,recog_esc
  1567 09A8 C3 18 0A                    jmp     install_action
  1568                  ;
  1569 09AB FE 30       recog_esc:      cpi     '0'             ; all characters <= '0' are finals
  1570 09AD D2 BD 09                    jnc     esc_final
  1571 09B0 21 7D 20                    lxi     h,inter_chars   ; so this is an intermediate character that we will
  1572 09B3 4F                          mov     c,a             ; store for later
  1573 09B4 7E                          mov     a,m
  1574 09B5 B7                          ora     a               ; check that we haven't already had an intermediate
  1575 09B6 CA BB 09                    jz      store_inter
  1576 09B9 0E FF                       mvi     c,0ffh          ; invalidate the intermediate if so
  1577 09BB 71          store_inter:    mov     m,c             ; store valid or invalid intermediate
  1578 09BC C9                          ret
  1579                  ;
  1580 09BD 32 7E 20    esc_final:      sta     final_char
  1581 09C0 21 15 0A                    lxi     h,to_ground     ; Setup a return address for after the escape sequence's action
  1582 09C3 E5                          push    h
  1583 09C4 3A 7D 20                    lda     inter_chars     ; Is there an intermediate character? (Or one we invalidated
  1584 09C7 B7                          ora     a               ; earlier by storing 0ffh?)
  1585 09C8 C2 DC 09                    jnz     esc_inter
  1586 09CB 3A A7 21                    lda     setup_b2        ; Given that there are no intermediates, we are now dealing
  1587 09CE E6 20                       ani     sb2_ansi        ; with a simple ESC Final sequence, which we will now lookup
  1588 09D0 21 50 0A                    lxi     h,ansiesct      ; in either the ANSI (VT100) table, or VT52 table.
  1589 09D3 C2 D9 09                    jnz     do_esc_find
  1590 09D6 21 1C 0A                    lxi     h,vt52esct
  1591 09D9 C3 F7 09    do_esc_find:    jmp     find_action
  1592                  ;
  1593 09DC 47          esc_inter:      mov     b,a             ; B <- intermediate
  1594 09DD 3A A7 21                    lda     setup_b2        ; Check if we're in ANSI mode
  1595 09E0 E6 20                       ani     sb2_ansi        ; and if not, exit immediately because
  1596 09E2 78                          mov     a,b             ; there are no VT52 escape sequences with
  1597 09E3 C8                          rz                      ; intermediate characters
  1598 09E4 FE 28                       cpi     '('             ; SCS sequence - designate to G0
  1599 09E6 11 FD 20                    lxi     d,g0_charset    ; DE <- location of G0 mapping, for when we've looked up charset
  1600 09E9 CA 32 0C                    jz      scs_g0
  1601 09EC FE 29                       cpi     ')'             ; SCS sequence - designate to G1
  1602 09EE CA 31 0C                    jz      scs_g1
  1603 09F1 FE 23                       cpi     '#'             ; ESC # Final are the presentation sequences (DECDWL, etc.)
  1604 09F3 21 7B 0A                    lxi     h,esc_hash_table
  1605 09F6 C0                          rnz                     ; If the intermediate was something else, or invalid (0ffh), do nothing
  1606                                  ; drop through to find an action for the final character of the ESC # sequence
  1607
  1608                  ; find_action is used to search a table for a final character and extract the address of the
  1609                  ; appropriate action routine. A secondary entry point,
  1610                  ; find_action_a is used to select an action when a Ps parameter value is already in register A
  1611                  ;(see sgr_action for an example.)
  1612                  ; find_action will jump to the found action routine, which means a normal return from that routine
  1613                  ; will return to find_action's caller. In some cases we see someone jumping to this routine, having
  1614                  ; stowed another return address on the stack in advance.
  1615                  ; As well as calling an action routine, record the found entry in found_action, which allows the
  1616                  ; caller to try several tables in sequence, if needed (i.e. CSI execution.)
  1617                  ; This routine preserves B, as it is used to store the selective parameter to be
  1618                  ; passed to the action routine.
  1619                  ;
  1620 09F7 11 B9 21    find_action:    lxi     d,found_action  ; we'll record success here
  1621 09FA AF                          xra     a
  1622 09FB 12                          stax    d               ; but initally say "nothing found"
  1623 09FC 3A 7E 20                    lda     final_char
  1624 09FF 4F          find_action_a:  mov     c,a
  1625 0A00 AF          next_action:    xra     a
  1626 0A01 86                          add     m               ; A <- next comparison value
  1627 0A02 C8                          rz                      ; return if reached end of table
  1628 0A03 23                          inx     h               ; increment to action routine low byte
  1629 0A04 B9                          cmp     c               ; is this the value we want?
  1630 0A05 CA 0D 0A                    jz      exec_action     ; found value/character we were looking for
  1631 0A08 23                          inx     h               ; else increment over the action routine address
  1632 0A09 23                          inx     h               ; to the next comparison value
  1633 0A0A C3 00 0A                    jmp     next_action     ; and try again
  1634                  ;
  1635 0A0D 12          exec_action:    stax    d               ; record entry that matched
  1636 0A0E 7E                          mov     a,m             ; A <- low byte of action address
  1637 0A0F 23                          inx     h               ; hop to high byte
  1638 0A10 66                          mov     h,m             ; H <- high byte of action address
  1639 0A11 6F                          mov     l,a             ; L <- low byte of action address
  1640 0A12 AF                          xra     a
  1641 0A13 E9                          pchl                    ; jump to action routine
  1642                  ;
  1643                  ; pop_to_ground is only jumped to if initialisation for DECGON fails, in see gfx_init. We now pop the
  1644                  ; original return address before returning to ground state. Because gfx_init was reached through either
  1645                  ; the ANSI or VT52 escape jump tables, and the stack had already been primed with the address to_ground,
  1646                  ; gfx_init could have just returned instead of jumping to this point, which pops the stack and then
  1647                  ; falls through to the original destination anyway. _test_ t/decgon-init.txt
  1648                  ;
  1649 0A14 E1          pop_to_ground:  pop     h               ; throw away a return address if DECGON fails
  1650                  ;
  1651                  ; This address gets stored on the stack before find_action is jumped to, for escape sequences
  1652 0A15 21 E6 05    to_ground:      lxi     h,print_char
  1653 0A18 22 40 21    install_action: shld    char_action
  1654 0A1B C9                          ret
  1655                  ;
  1656                  ;       VT52 escape sequence action table (0a1c - 0a4f)
  1657                  ;       Each entry is three bytes: character after ESC has been
  1658                  ;       recognised, and then a word of an action routine.
  1659                  ; JUMP TABLE
  1660                  vt52esct:                               ; Cursor movement
  1661 0A1C 41                          db      'A'
  1662 0A1D 9B 0A                       dw      vt52curmove
  1663 0A1F 42                          db      'B'
  1664 0A20 9B 0A                       dw      vt52curmove
  1665 0A22 43                          db      'C'
  1666 0A23 9B 0A                       dw      vt52curmove
  1667 0A25 44                          db      'D'
  1668 0A26 9B 0A                       dw      vt52curmove
  1669
  1670                                                          ; "Graphics" mode - selects special character set
  1671 0A28 46                          db      'F'
  1672 0A29 0A 0C                       dw      vt52gfxenter
  1673 0A2B 47                          db      'G'
  1674 0A2C 0F 0C                       dw      vt52gfxexit
  1675
  1676 0A2E 48                          db      'H'
  1677 0A2F 96 0A                       dw      vt52curhome
  1678
  1679 0A31 49                          db      'I'
  1680 0A32 A3 0B                       dw      reverse_index
  1681
  1682                                  ; Erase to end of screen
  1683 0A34 4A                          db      'J'
  1684 0A35 9B 0A                       dw      vt52curmove
  1685
  1686 0A37 4B                          db      'K'
  1687 0A38 9B 0A                       dw      vt52curmove
  1688
  1689 0A3A 59                          db      'Y'
  1690 0A3B 11 0B                       dw      vt52_det_cup
  1691
  1692 0A3D 5A                          db      'Z'
  1693 0A3E C6 0C                       dw      identify
  1694
  1695 0A40 3D                          db      '='
  1696 0A41 DF 0B                       dw      app_keypad
  1697
  1698 0A43 3E                          db      '<'
  1699 0A44 E5 0B                       dw      num_keypad
  1700
  1701 0A46 31                          db      '1'             ; DECGON - graphics mode
  1702 0A47 57 0C                       dw      gfx_init
  1703
  1704 0A49 3C                          db      '<'
  1705 0A4A EB 0B                       dw      ansi_mode
  1706
  1707 0A4C 5D                          db      ']'
  1708 0A4D 60 0D                       dw      print_screen
  1709
  1710 0A4F 00                          db      0               ; end of table
  1711                  ;
  1712                  ; Same as above but for ANSI (VT100) mode
  1713                  ; JUMP TABLE
  1714 0A50 63          ansiesct:       db      'c'             ; RIS - reset to initial state
  1715 0A51 00 00                       dw      start
  1716
  1717 0A53 45                          db      'E'             ; NEL - next line
  1718 0A54 52 09                       dw      nextline
  1719
  1720 0A56 4D                          db      'M'             ; RI - reverse index
  1721 0A57 A3 0B                       dw      reverse_index
  1722
  1723 0A59 31                          db      '1'             ; DECGON - graphics mode
  1724 0A5A 57 0C                       dw      gfx_init
  1725
  1726 0A5C 5B                          db      '['             ; CSI - control sequence introducer
  1727 0A5D 8E 0A                       dw      recog_csi
  1728
  1729 0A5F 48                          db      'H'             ; Set tab at current column
  1730 0A60 DF 0D                       dw      set_tab_here
  1731
  1732 0A62 44                          db      'D'             ; IND - index (move down without changing column)
  1733 0A63 55 09                       dw      index_down
  1734
  1735 0A65 37                          db      '7'             ; DECSC - save cursor and attributes
  1736 0A66 68 0C                       dw      save_cursor
  1737
  1738 0A68 38                          db      '8'
  1739 0A69 71 0C                       dw      restore_cursor  ; DECRC - restore cursor and attributes
  1740
  1741 0A6B 3D                          db      '='
  1742 0A6C DF 0B                       dw      app_keypad
  1743
  1744 0A6E 3E                          db      '<'
  1745 0A6F E5 0B                       dw      num_keypad
  1746
  1747 0A71 5A                          db      'Z'
  1748 0A72 C6 0C                       dw      identify
  1749
  1750 0A74 4E                          db      'N'             ; SS2 - mentioned in TM p.D-3, but not UG
  1751 0A75 46 0C                       dw      single_shift
  1752
  1753 0A77 4F                          db      'O'             ; SS3 - mentioned in TM p.D-3 but not UG
  1754 0A78 46 0C                       dw      single_shift
  1755
  1756 0A7A 00                          db      0               ; end of table
  1757                  ;
  1758                  ; JUMP TABLE
  1759                  esc_hash_table: ; Triple byte entries again, with final character followed by address of routine
  1760 0A7B 33                          db      '3'             ; DECDHL - double height line, top half
  1761 0A7C A8 12                       dw      dhl_top_action
  1762
  1763 0A7E 34                          db      '4'             ; DECDHL - double height line, bottom half
  1764 0A7F AC 12                       dw      dhl_bot_action
  1765
  1766 0A81 35                          db      '5'             ; DECSWL - single-width line
  1767 0A82 4A 13                       dw      line_attr_swl
  1768
  1769 0A84 36                          db      '6'             ; DECDWL - double-width line
  1770 0A85 A4 12                       dw      line_attr_dwl
  1771
  1772 0A87 37                          db      '7'             ; DECHCP - hard copy
  1773 0A88 60 0D                       dw      print_screen
  1774
  1775 0A8A 38                          db      '8'             ; DECALN - screen alignment display
  1776 0A8B A7 0D                       dw      align_pattern
  1777
  1778 0A8D 00                          db      0               ; end of table
  1779                  ;
  1780                  ; Received ESC [ (CSI), so set up processing for next character
  1781 0A8E 21 C2 16    recog_csi:      lxi     h,start_csi
  1782 0A91 22 40 21                    shld    char_action
  1783 0A94 E1                          pop     h
  1784 0A95 C9                          ret
  1785                  ;
  1786 0A96 3E 48       vt52curhome:    mvi     a,'H'           ; setup to pretend this is ANSI sequence
  1787 0A98 32 7E 20                    sta     final_char
  1788 0A9B 21 00 00    vt52curmove:    lxi     h,0
  1789 0A9E 22 30 21                    shld    csi_params
  1790 0AA1 E1                          pop     h
  1791                  ;
  1792 0AA2 21 15 0A    execute_seq:    lxi     h,to_ground     ; we are always going to transition to ground state next
  1793 0AA5 E5                          push    h               ; pushing so we can just return from this routine
  1794 0AA6 3A 7D 20                    lda     inter_chars     ; Did we get any intermediate characters?
  1795 0AA9 B7                          ora     a               ; If so, quit early, as no VT100 sequences allow them
  1796 0AAA C0                          rnz
  1797 0AAB 21 D9 0A                    lxi     h,fixed_param_t ; search the table of fixed parameter sequences first
  1798 0AAE CD F7 09                    call    find_action
  1799 0AB1 3A B9 21                    lda     found_action    ; and if any of those matched (and just got executed),
  1800 0AB4 B7                          ora     a               ; we can stop looking
  1801 0AB5 C0                          rnz
  1802 0AB6 21 30 21                    lxi     h,csi_params    ; HL <- start of parameter list. We'll iterate through this
  1803 0AB9 3A 4B 21                    lda     num_params
  1804 0ABC B7                          ora     a               ; if there weren't any parameters, we'll treat the sequence
  1805 0ABD 5F                          mov     e,a             ; as if there was a single, defaulted, parameter
  1806 0ABE C2 C2 0A                    jnz     loop_params
  1807 0AC1 1C                          inr     e               ; pretend we have one parameter
  1808 0AC2 7E          loop_params:    mov     a,m             ; retrieve this parameter
  1809 0AC3 E5                          push    h               ; preserve param list pointer
  1810 0AC4 D5                          push    d               ; preserve param count
  1811 0AC5 21 F5 0A                    lxi     h,sel_param_t   ; search the selective parameters table
  1812 0AC8 47                          mov     b,a             ; B <- selective parameter for action routine
  1813 0AC9 CD F7 09                    call    find_action
  1814 0ACC D1                          pop     d               ; restore param count
  1815 0ACD E1                          pop     h               ; and list
  1816 0ACE 3A B9 21                    lda     found_action    ; If we couldn't find an action at all, no point iterating ...
  1817 0AD1 B7                          ora     a
  1818 0AD2 C8                          rz                      ; ... so quit early
  1819 0AD3 23                          inx     h               ; point to next parameter
  1820 0AD4 1D                          dcr     e
  1821 0AD5 C2 C2 0A                    jnz     loop_params     ; and go round again
  1822 0AD8 C9                          ret
  1823                  ;
  1824                  ; Table of sequences that have a fixed number of parameters, whether defaulted or not.
  1825                  ; All other sequences have "Ps" selective parameters, in the next table.
  1826                  ; JUMP TABLE
  1827 0AD9 44          fixed_param_t:  db      'D'             ; CUB - cursor left n columns
  1828 0ADA 1E 18                       dw      cub_action
  1829 0ADC 42                          db      'B'             ; CUD - cursor down n rows
  1830 0ADD 07 18                       dw      cud_action
  1831 0ADF 43                          db      'C'             ; CUF - cursor forward n columns
  1832 0AE0 15 18                       dw      cuf_action
  1833 0AE2 48                          db      'H'             ; CUP - cursor position
  1834 0AE3 53 18                       dw      curpos_action
  1835 0AE5 41                          db      'A'             ; CUU - cursor up n rows
  1836 0AE6 FE 17                       dw      cuu_action
  1837 0AE8 72                          db      'r'             ; DECSTBM - set top and bottom margins
  1838 0AE9 A6 15                       dw      stbm_action
  1839 0AEB 66                          db      'f'             ; HVP - horizontal and vertical position
  1840 0AEC 53 18                       dw      curpos_action   ; (same routine as CUP) 
  1841 0AEE 78                          db      'x'             ; DECREQTPARM - request terminal parameters
  1842 0AEF DF 12                       dw      tparm_action
  1843 0AF1 79                          db      'y'             ; DECTST - invoke confidence test
  1844 0AF2 64 08                       dw      tst_action
  1845 0AF4 00                          db      0               ; end of table
  1846
  1847                  ; Table of sequences with selective parameters. There action routines will be called
  1848                  ; multiple times, with a single parameter each time. This way, conflicts between parameters
  1849                  ; get resolved naturally, by having later parameters override earlier ones.
  1850                  ; JUMP TABLE
  1851 0AF5 63          sel_param_t:    db      'c'             ; DA - device attributes
  1852 0AF6 C1 0C                       dw      da_action
  1853 0AF8 71                          db      'q'             ; DECLL - load LEDs
  1854 0AF9 16 0C                       dw      decll_action
  1855 0AFB 6E                          db      'n'             ; DSR - device status report
  1856 0AFC 03 0D                       dw      dsr_action
  1857 0AFE 4A                          db      'J'             ; ED - erase in display
  1858 0AFF 41 15                       dw      ed_action
  1859 0B01 4B                          db      'K'             ; EL - erase in line
  1860 0B02 FC 14                       dw      el_action
  1861 0B04 6C                          db      'l'             ; RM - reset mode
  1862 0B05 ED 13                       dw      rm_action
  1863 0B07 6D                          db      'm'             ; SGR - select graphic rendition
  1864 0B08 7F 0C                       dw      sgr_action
  1865 0B0A 68                          db      'h'             ; SM - set mode
  1866 0B0B F2 13                       dw      sm_action
  1867 0B0D 67                          db      'g'             ; TBC - tabulation clear
  1868 0B0E CF 0D                       dw      tbc_action
  1869 0B10 00                                  db      0               ; end of table
  1870                  ;
  1871                  ; In VT52 mode, recognised ESC Y, which now needs two more characters, row and column.
  1872                  ;
  1873 0B11 21 19 0B    vt52_det_cup:   lxi     h,vt52_get_coord
  1874 0B14 22 40 21                    shld    char_action
  1875 0B17 E1                          pop     h
  1876 0B18 C9                          ret
  1877                  ;
  1878                  ; vt52_get_coord
  1879                  ;       Two characters after ESC Y should be row and column. This state gathers them both, using a flag
  1880                  ;       to record whether we've seen the row already.
  1881                  ;
  1882                  ; NOTE: This routine shows that the VT100 will execute C0 controls when coordinates are expected
  1883                  ;       for this sequence, making ESC Y BEL SP SP identical in effect to ESC Y SP SP BEL.
  1884                  ;       Would be interesting to check this against later terminals' behaviour and VSRM.
  1885                  ;
  1886                  ;       There is a "sub-state" flag here, called got_vt52_row. It is not initialised when ESC Y is
  1887                  ;       recognised and is only cleared when a column has been received, which means that not only will
  1888                  ;       single-character C0 controls be executed in the middle of a sequence, but that a second ESC Y
  1889                  ;       received after the row character will not stop the very next printable character being
  1890                  ;       interpreted as a column coordinate and terminating the sequence.
  1891                  ;
  1892                  ;       For example:
  1893                  ;               a) ESC Y ESC Y 0 0 executes as ESC Y 0 0
  1894                  ;               b) ESC Y 0 ESC Y 0 also executes as ESC Y 0 0
  1895                  ;
  1896 0B19 47          vt52_get_coord: mov     b,a
  1897 0B1A FE 20                       cpi     20h             ; Do we have a C0 control? Pointless test as these are processed
  1898 0B1C DA B2 08                    jc      exec_c0         ; before other characters are passed to action routines, like this.
  1899 0B1F FE 1B                       cpi     C0_ESC          ; Similarly, ESC cannot reach here.
  1900 0B21 21 51 21                    lxi     h,got_vt52_row  ; HL <- flag location, kind of sub-state
  1901 0B24 C2 2E 0B                    jnz     store_coord     ; this character wasn't ESC, so treat as coordinate
  1902 0B27 21 AB 09                    lxi     h,recog_esc     ; UNREACHABLE
  1903 0B2A 22 40 21                    shld    char_action     ; UNREACHABLE
  1904 0B2D C9                          ret                     ; UNREACHABLE
  1905                  ;
  1906 0B2E 7E          store_coord:    mov     a,m             ; Check whether we've had a coord already
  1907 0B2F B7                          ora     a
  1908 0B30 C2 42 0B                    jnz     vt52_move       ; if we have, perform the move
  1909 0B33 36 01                       mvi     m,1             ; got_vt52_row <- 1 "had a row, now need column"
  1910 0B35 78                          mov     a,b             ; grab raw character
  1911 0B36 D6 20                       sui     20h             ; turn it into a zero-based row number
  1912 0B38 32 52 21                    sta     vt52_row_coord  ; keep it for later
  1913 0B3B 21 19 0B                    lxi     h,vt52_get_coord
  1914 0B3E 22 40 21                    shld    char_action     ; keep the same action, really
  1915 0B41 C9                          ret
  1916                  ;
  1917 0B42 36 00       vt52_move:      mvi     m,0             ; got_vt52_row <- 0 -- clear the "had a row" flag
  1918 0B44 78                          mov     a,b             ; grab raw character
  1919 0B45 D6 20                       sui     20h             ; turn it into a zero-based column number
  1920 0B47 FE 50                       cpi     50h             ; which can only take us up to column 80!
  1921 0B49 D2 4F 0B                    jnc     skip_col_upd    ; Could end up only moving the cursor row, if the column is invalid
  1922 0B4C 32 F8 20                    sta     curs_col        ; in range, so update
  1923 0B4F CD BF 11    skip_col_upd:   call    last_row        ; B <- last row number
  1924 0B52 3A 52 21                    lda     vt52_row_coord
  1925 0B55 04                          inr     b
  1926 0B56 B8                          cmp     b
  1927 0B57 D2 5D 0B                    jnc     skip_row_upd
  1928 0B5A 32 F9 20                    sta     curs_row
  1929 0B5D CD 15 0A    skip_row_upd:   call    to_ground
  1930 0B60 C3 36 16                    jmp     move_updates
  1931                  ;
  1932                  ; init_80col
  1933                  ;       Create an 80 column display, which involves clearing the 132-column flag and setting up the
  1934                  ;       screen RAM with terminators at the righ places. Margins, cursor position and DC011 updates
  1935                  ;       are shared with the 132-column initialisation, below.
  1936                  ;
  1937 0B63 AF          init_80col:     xra     a
  1938 0B64 32 A2 21                    sta     columns_132
  1939 0B67 CD AD 10                    call    init_screen
  1940 0B6A 0E 50                       mvi     c,80
  1941 0B6C CD F9 10                    call    make_screen
  1942 0B6F CD 4C 11                    call    make_line_t
  1943 0B72 0E 50                       mvi     c,80
  1944 0B74 C3 89 0B                    jmp     col_common
  1945                  ;
  1946                  ; init_132col
  1947                  ;       Create a 132 column display, in the same manner as described above.
  1948                  ;
  1949 0B77 3E 01       init_132col:    mvi     a,1
  1950 0B79 32 A2 21                    sta     columns_132
  1951 0B7C CD AD 10                    call    init_screen
  1952 0B7F 0E 84                       mvi     c,132
  1953 0B81 CD F9 10                    call    make_screen
  1954 0B84 CD 4C 11                    call    make_line_t
  1955 0B87 0E 84                       mvi     c,132
  1956 0B89 AF          col_common:     xra     a
  1957 0B8A 32 55 21                    sta     top_margin      ; Reset top margin again (make_line_t just did this)
  1958 0B8D CD BF 11                    call    last_row        ; B <- last row number
  1959 0B90 78                          mov     a,b
  1960 0B91 32 56 21                    sta     bottom_margin   ; Deja margin
  1961 0B94 79                          mov     a,c             ; A <- screen columns
  1962 0B95 32 50 20                    sta     screen_cols
  1963 0B98 CD 42 03                    call    update_dc011
  1964 0B9B CD 48 18                    call    cursor_home
  1965 0B9E 3E 01                       mvi     a,1
  1966 0BA0 C3 AA 0F                    jmp     wait_n_frames
  1967                  ;
  1968 0BA3 21 F9 20    reverse_index:  lxi     h,curs_row      ; Move cursor up one row, in the same column
  1969 0BA6 3A 55 21                    lda     top_margin
  1970 0BA9 47                          mov     b,a             ; B <- top margin
  1971 0BAA 7E                          mov     a,m             ; A <- cursor row
  1972 0BAB B8                          cmp     b
  1973 0BAC CA B8 0B                    jz      at_margin_t     ; Need to scroll up if we are at the top margin
  1974 0BAF B7                          ora     a
  1975 0BB0 C8                          rz
  1976 0BB1 21 F9 20                    lxi     h,curs_row      ; Just move the cursor up one row
  1977 0BB4 35                          dcr     m
  1978 0BB5 C3 36 16                    jmp     move_updates
  1979                  ;
  1980 0BB8 3A A6 21    at_margin_t:    lda     setup_b1
  1981 0BBB E6 80                       ani     sb1_smooth
  1982 0BBD CA D9 0B                    jz      jump_scroll_dn
  1983 0BC0 0E 01                       mvi     c,1
  1984 0BC2 CD 8E 10                    call    wait_scroll
  1985 0BC5 3E FF                       mvi     a,0ffh
  1986 0BC7 32 C3 21                    sta     row_clearing
  1987 0BCA 35                          dcr     m               ; scroll_pending <- -1
  1988 0BCB CD EB 0F                    call    move_lines_dn
  1989 0BCE 3A C3 21    wait_clear2:    lda     row_clearing
  1990 0BD1 B7                          ora     a
  1991 0BD2 C2 CE 0B                    jnz     wait_clear2
  1992 0BD5 32 F4 20                    sta     char_und_curs
  1993 0BD8 C9                          ret
  1994                  ;
  1995 0BD9 CD 2D 10    jump_scroll_dn: call    start_jump_dn
  1996 0BDC C3 12 10                    jmp     inv_saved_row
  1997                  ;
  1998 0BDF 21 78 21    app_keypad:     lxi     h,keypad_mode
  1999 0BE2 36 01                       mvi     m,1
  2000 0BE4 C9                          ret
  2001                  ;
  2002 0BE5 21 78 21    num_keypad:     lxi     h,keypad_mode
  2003 0BE8 36 00                       mvi     m,0
  2004 0BEA C9                          ret
  2005                  ;
  2006 0BEB 21 A7 21    ansi_mode:      lxi     h,setup_b2
  2007 0BEE 7E                          mov     a,m
  2008 0BEF F6 20                       ori     sb2_ansi
  2009 0BF1 77                          mov     m,a
  2010                  ;
  2011 0BF2 3A A8 21    set_charsets:   lda     setup_b3
  2012 0BF5 E6 80                       ani     sb3_uk
  2013 0BF7 26 08                       mvi     h,8             ; H <- ASCII Set
  2014 0BF9 CA FE 0B                    jz      not_uk
  2015 0BFC 26 48                       mvi     h,48h           ; H <- United Kingdom Set
  2016 0BFE 6C          not_uk:         mov     l,h             ; L <- same set as H
  2017 0BFF 22 FD 20                    shld    g0_charset      ; Place in all four designators
  2018 0C02 22 FF 20                    shld    g2_charset
  2019 0C05 AF                          xra     a
  2020 0C06 32 FC 20                    sta     gl_invocation   ; Invoke G0 into GL
  2021 0C09 C9                          ret
  2022                  ;
  2023 0C0A 26 88       vt52gfxenter:   mvi     h,88h           ; H <- Special Graphics Character Set
  2024 0C0C C3 11 0C                    jmp     vt52gfx1
  2025                  ;
  2026 0C0F 26 08       vt52gfxexit:    mvi     h,8             ; H <- ASCII Set
  2027 0C11 6C          vt52gfx1:       mov     l,h             ; L <- same set as h
  2028 0C12 22 FD 20                    shld    g0_charset      ; Map G0 and G1 in a single operation
  2029 0C15 C9                          ret
  2030                  ;
  2031                  ; decll_action
  2032                  ;
  2033                  ;       DECLL - ESC [ Ps q [* BUG *]
  2034                  ;
  2035                  ;       Single Ps parameter:
  2036                  ;       0 - clears all LEDs
  2037                  ;       1 - lights L1
  2038                  ;       2 - lights L2
  2039                  ;       3 - lights L3
  2040                  ;       4 - lights L4
  2041                  ;
  2042                  ;       This routine either masks four lowest bits to zero for Ps = 0,
  2043                  ;       on constructs a single-bit-on mask that it can OR with the existing
  2044                  ;       LED state value. No writing of the LED state to the keyboard is done
  2045                  ;       here, so presumably that's a periodic thing elsewhere.
  2046                  ;
  2047                  ;       However, this routine has a bug. It checks for a zero parameter, and then
  2048                  ;       subtracts 5 from any non-zero parameter, bombs out for a positive result,
  2049                  ;       and then attempts to make a LED mask with the result (Ps - 5). However,
  2050                  ;       values of Ps between 133 and 255 will still be negative when 5 is subtracted,
  2051                  ;       meaning that some of them will make masks to affect bits 4 - 7, and these will
  2052                  ;       be sent to the keyboard without further censoring. The keyboard byte itself
  2053                  ;       is described in TM Figure 4-4-2 (see iow_keyboard)
  2054                  ;
  2055                  ;       This bug accounts for the strange behaviour documented by John 'Sloppy' Millington
  2056                  ;       in his "VT100 Oddities" document: https://vt100.net/dec/vt100/oddities
  2057                  ;
  2058                  ;       To find out which bit will be set by this routine, calculate (260 - Ps) % 9.
  2059                  ;       Result  Effect
  2060                  ;        0 - 3  lights one of the LEDs L1 to L4 in the normal fashion
  2061                  ;            4  lights the keyboard locked LED such that it cannot be unlit
  2062                  ;            5  lights the online/local LED such that it cannot be unlit
  2063                  ;            6  sets "start scan", which will result in a mad repeat rate
  2064                  ;            7  sets "speaker click", which will make the terminal beep continuously
  2065                  ;            8  leaves a 1 in the carry flags, so it is completely harmless
  2066                  ;
  2067 0C16 21 45 21    decll_action:   lxi     h,led_state
  2068 0C19 78                          mov     a,b             ; On entry to action routines, B is always the selection value
  2069 0C1A B7                          ora     a               ; zero is special case (all off)
  2070 0C1B C2 23 0C                    jnz     make_led_mask
  2071 0C1E 7E                          mov     a,m             ; A <- current LED state
  2072 0C1F E6 F0                       ani     0f0h            ; mask all LEDs to zero
  2073 0C21 77                          mov     m,a             ; place back
  2074 0C22 C9                          ret
  2075                  ;
  2076 0C23 D6 05       make_led_mask:  sui     5
  2077 0C25 F0                          rp                      ; maximum allowed parameter value is 4
  2078 0C26 47                          mov     b,a             ; Because LEDs register are reversed in order from the number of
  2079 0C27 AF                          xra     a               ; the selection parameter, b will count up to zero to find bit
  2080 0C28 37                          stc                     ; start with a "1" bit for our mask
  2081 0C29 17          rot_m:          ral                     ; shift mask bit left through register
  2082 0C2A 04                          inr     b
  2083 0C2B C2 29 0C                    jnz     rot_m
  2084 0C2E B6                          ora     m               ; A <- new mask
  2085 0C2F 77                          mov     m,a             ; led_state <- old value | new mask
  2086 0C30 C9                          ret
  2087                  ;
  2088                  ;       SCS - select character set
  2089                  ;       On entry, de is 20fdh (G0), and the earlier entry point, scs_g1, increments
  2090                  ;       it so that G1 is affected.
  2091                  ;
  2092 0C31 13          scs_g1:         inx     d
  2093 0C32 3A 7E 20    scs_g0:         lda     final_char
  2094 0C35 47                          mov     b,a             ; B <- charset name
  2095 0C36 21 4A 0C                    lxi     h,charset_list-2
  2096 0C39 23          nxtmap:         inx     h
  2097 0C3A 23                          inx     h
  2098 0C3B 7E                          mov     a,m
  2099 0C3C B7                          ora     a
  2100 0C3D C8                          rz                      ; end of table without finding a match?
  2101 0C3E B8                          cmp     b               ; is this the charset we're looking for?
  2102 0C3F C2 39 0C                    jnz     nxtmap
  2103 0C42 23                          inx     h               ; yes, so grab internal charset number
  2104 0C43 7E                          mov     a,m
  2105 0C44 12                          stax    d               ; and store it in either G0 or G1
  2106 0C45 C9                          ret
  2107                  ;
  2108                  ; single_shift
  2109                  ;       This routine is used to implement an action for SS2 and SS3, neither of which are
  2110                  ;       documented as being supported in the VT100 User Guide, though they are mentioned
  2111                  ;       the Technical Manual as being supported by the "VT100 Family."
  2112                  ;
  2113                  ;       SS2 (ESC N) is supposed to invoke G2 into GL for a single printable character,
  2114                  ;       after which the terminal reverts to the previous invocation, G0 or G1. SS3 (ESC O)
  2115                  ;       is supposed to do the same with G3.
  2116                  ;
  2117                  ;       What they actually do on the VT100 is to act identically, in increasing the current
  2118                  ;       invocation by two sets, so that they will both invoke G2 into GL if G0 was previously
  2119                  ;       there, or invoke G3 into GL if SHIFT OUT was previously in effect. After a single
  2120                  ;       displayed character, the invocation drops by two sets, leaving the terminal as before.
  2121                  ;
  2122                  ;       This behaviour is cheaper to implement than the correct one, and doesn't make any
  2123                  ;       difference because both G2 and G3 have the default character set (ASCII or UK)
  2124                  ;       designated at start-up, and there is no way to designate anything else.
  2125                  ;
  2126                  ;       In fact, the only way you can tell the difference between the VT100 ignoring these
  2127                  ;       controls completely and implementing them is by designating the Special Graphics Set
  2128                  ;       into G0, invoking it to GL and then using SS2 and sending a character that would be
  2129                  ;       different between the two sets. For example:
  2130                  ;
  2131                  ;               ESC ( 0 a ESC N a a
  2132                  ;
  2133                  ;       On a terminal that didn't support this sequence at all, the above sequence would
  2134                  ;       display three lowercase "a" characters. On the VT100, this will display a checkerboard,
  2135                  ;       a lowercase "a", and another checkerboard.
  2136                  ;
  2137 0C46 21 FC 20    single_shift:   lxi     h,gl_invocation
  2138 0C49 34                          inr     m
  2139 0C4A 34                          inr     m
  2140 0C4B C9                          ret
  2141                  ;
  2142 0C4C 41 48       charset_list:   db      'A',48h         ; United Kingdom Set
  2143 0C4E 42 08                       db      'B',08h         ; ASCII Set
  2144 0C50 30 88                       db      '0',88h         ; Special Graphics
  2145 0C52 31 00                       db      '1',0           ; Alternate Character ROM Standard Character Set
  2146 0C54 32 80                       db      '2',80h         ; Alternate Character ROM Special Graphics
  2147 0C56 00                          db      0               ; end of table
  2148
  2149 0C57 21 79 20    gfx_init:       lxi     h,gpo_flags
  2150 0C5A 7E                          mov     a,m
  2151 0C5B B7                          ora     a
  2152 0C5C CA 14 0A                    jz      pop_to_ground
  2153 0C5F 36 81                       mvi     m,81h
  2154 0C61 E1                          pop     h
  2155 0C62 21 AD 05    gfx_set_state:  lxi     h,gfx_det_esc
  2156 0C65 C3 18 0A                    jmp     install_action
  2157                  ;
  2158 0C68 21 02 21    save_cursor:    lxi     h,gfx_saved
  2159 0C6B 11 F8 20                    lxi     d,gfx_state
  2160 0C6E C3 77 0C                    jmp     copy_state
  2161                  ;
  2162 0C71 21 F8 20    restore_cursor: lxi     h,gfx_state
  2163 0C74 11 02 21                    lxi     d,gfx_saved
  2164 0C77 06 0B       copy_state:     mvi     b,0bh
  2165 0C79 CD 8B 03                    call    memcopy
  2166 0C7C C3 36 16                    jmp     move_updates
  2167                  ;
  2168                  ;       SGR - select graphics rendition
  2169 0C7F 21 8A 0C    sgr_action:     lxi     h,sgr_ps_table  ; we're looking for rendition value in this table
  2170 0C82 78                          mov     a,b             ; this is the selective parameter we're finding
  2171 0C83 B7                          ora     a
  2172 0C84 CA 97 0C                    jz      sgr_off         ; zero (all attributes off) is special
  2173 0C87 C3 FF 09                    jmp     find_action_a   ; employing tail recursion to take us back to execute_seq
  2174                  ;
  2175                  ; JUMP TABLE
  2176 0C8A 01          sgr_ps_table:   db      1               ; bold
  2177 0C8B A2 0C                       dw      sgr_bold
  2178 0C8D 04                          db      4               ; underscore
  2179 0C8E A7 0C                       dw      sgr_underscore
  2180 0C90 05                          db      5               ; blink
  2181 0C91 B3 0C                       dw      sgr_blink
  2182 0C93 07                          db      7               ; negative (reverse) image
  2183 0C94 BB 0C                       dw      sgr_reverse
  2184 0C96 00                          db      0               ; end of table
  2185
  2186 0C97 32 FB 20    sgr_off:        sta     char_rvid       ; A is zero on entry, so clear reverse video
  2187 0C9A 21 FA 20                    lxi     h,char_rend
  2188 0C9D 7E                          mov     a,m
  2189 0C9E F6 F7                       ori     0f7h            ; and set all the normal rendition bits
  2190 0CA0 77                          mov     m,a
  2191 0CA1 C9                          ret
  2192                  ;
  2193 0CA2 3E FB       sgr_bold:       mvi     a,rend_bold
  2194 0CA4 C3 B5 0C                    jmp     sgr_mask
  2195                  ;
  2196                  ; If we don't have AVO, then we are limited to the "base attribute", bit 7 of each character in
  2197                  ; screen RAM, which will render as underscore if the cursor is underline, or reverse video if the
  2198                  ; cursor is a block.
  2199                  ;
  2200 0CA7 3A C8 21    sgr_underscore: lda     avo_missing
  2201 0CAA B7                          ora     a
  2202 0CAB C2 BB 0C                    jnz     set_base_attr
  2203 0CAE 3E FD                       mvi     a,rend_under
  2204 0CB0 C3 B5 0C                    jmp     sgr_mask
  2205                  ;
  2206 0CB3 3E FE       sgr_blink:      mvi     a,rend_blink
  2207 0CB5 21 FA 20    sgr_mask:       lxi     h,char_rend
  2208 0CB8 A6                          ana     m
  2209 0CB9 77                          mov     m,a
  2210 0CBA C9                          ret
  2211                  ;
  2212                  ; Reverse attribute is available without AVO, if the cursor is a block. All the same, in that case,
  2213                  ; it is the only attribute available, and sgr_underscore will also set the base attribute.
  2214                  ;
  2215                  ; If you have AVO, the reverse is one of four attributes available.
  2216                  ;
  2217                  ; (Two names for the same entry point, just to clarify the above, and why sgr_underscore is jumping here.)
  2218                  ;
  2219                  sgr_reverse:
  2220 0CBB 3E 80       set_base_attr:  mvi     a,80h
  2221 0CBD 32 FB 20                    sta     char_rvid
  2222 0CC0 C9                          ret
  2223
  2224                  ;       ANSI entry point - ESC [ c
  2225 0CC1 3A 30 21    da_action:      lda     csi_p1
  2226 0CC4 B7                          ora     a               ; This sequence only allows no parameter or a parameter of 0
  2227 0CC5 C0                          rnz
  2228                  ;       ANSI/VT52 entry point - ESC Z
  2229 0CC6 21 72 21    identify:       lxi     h,pending_report
  2230 0CC9 7E                          mov     a,m
  2231 0CCA F6 04                       ori     pend_ident
  2232 0CCC 77                          mov     m,a
  2233 0CCD C9                          ret
  2234                  ;
  2235 0CCE 3E FB       ident_report:   mvi     a,~pend_ident
  2236 0CD0 CD BF 0D                    call    prepare_report
  2237 0CD3 3A A7 21                    lda     setup_b2
  2238 0CD6 E6 20                       ani     sb2_ansi        ; Are we in ANSI mode?
  2239 0CD8 C2 E3 0C                    jnz     ansi_identity
  2240                  ; now producing VT52 report, which is just ESC / Z. As we're at the character
  2241                  ; after the prepared template of "ESC [", we'll write the terminating 'Z' first
  2242                  ; (with bit 7 set), and then move backwards to overwrite the '[' with '/'.
  2243 0CDB 36 DA                       mvi     m,'Z'|80h       ; in reverse order, ladies and gentlemen
  2244 0CDD 2B                          dcx     h
  2245 0CDE 36 2F                       mvi     m,'/'
  2246 0CE0 C3 F3 0E                    jmp     send_report
  2247                  ;
  2248                                  ; Going to produce the sequence ESC [ ? 1 ; <n< c
  2249                                  ; where <n< is produced from the installed options.
  2250 0CE3 36 3F       ansi_identity:  mvi     m,'?'           ; add the fixed part of response
  2251 0CE5 23                          inx     h               ; to the buffer
  2252 0CE6 3E 31                       mvi     a,'1'
  2253 0CE8 CD B9 0D                    call    rep_char_semi
  2254 0CEB DB 42                       in      ior_flags
  2255 0CED 47                          mov     b,a             ; B <- flags byte
  2256 0CEE 2F                          cma                     ; complement because the AVO and GPO flags
  2257 0CEF E6 06                       ani     6               ; are "missing" flags, not "present" flags
  2258 0CF1 4F                          mov     c,a             ; C <- AVO and GPO presence flags
  2259 0CF2 78                          mov     a,b             ; A <- flags byte
  2260 0CF3 E6 08                       ani     iob_flags_stp   ; check STP
  2261 0CF5 CA F9 0C                    jz      stp_not_found   ; not present, skip
  2262 0CF8 0C                          inr     c               ; STP adds 1 to the result
  2263 0CF9 79          stp_not_found:  mov     a,c             ; A <- final answer
  2264 0CFA F6 30                       ori     '0'             ; ASCII-ify this digit
  2265 0CFC 77                          mov     m,a             ; into buffer
  2266 0CFD 23                          inx     h
  2267 0CFE 36 E3                       mvi     m,'c'|80h       ; terminate with 'c' + high bit
  2268 0D00 C3 F3 0E                    jmp     send_report
  2269                  ;
  2270 0D03 3A B8 21    dsr_action:     lda     csi_private     ; This sequence doesn't accept any private characters
  2271 0D06 B7                          ora     a
  2272 0D07 C0                          rnz                     ; so quit early if supplied
  2273 0D08 78                          mov     a,b             ; B is the selection parameter
  2274 0D09 21 72 21                    lxi     h,pending_report
  2275 0D0C FE 06                       cpi     6               ; is it "please report active position"?
  2276 0D0E CA 32 0D                    jz      get_active_pos
  2277 0D11 FE 05                       cpi     5               ; is it "please report status"?
  2278 0D13 C0                          rnz                     ; if not, quit
  2279 0D14 7E                          mov     a,m
  2280 0D15 F6 08                       ori     pend_devstat
  2281 0D17 77                          mov     m,a
  2282 0D18 C9                          ret
  2283                  ;
  2284 0D19 3E F7       devstat_report: mvi     a,~pend_devstat
  2285 0D1B CD BF 0D                    call    prepare_report
  2286 0D1E 06 03                       mvi     b,3             ; Assume the worst, which is a report of '3'
  2287 0D20 3A BD 21                    lda     test_results
  2288 0D23 B7                          ora     a
  2289 0D24 78                          mov     a,b
  2290 0D25 C2 29 0D                    jnz     devr2           ; If any tests failed, we were right
  2291 0D28 AF                          xra     a               ; Didn't find any errors, so report '0'
  2292 0D29 F6 30       devr2:          ori     '0'             ; Convert our value to ASCII digiit
  2293 0D2B 77                          mov     m,a             ; add it to the report buffer
  2294 0D2C 23                          inx     h
  2295 0D2D 36 EE                       mvi     m,'n'|80h       ; 'n' makes this a DSR sequence
  2296 0D2F C3 F3 0E                    jmp     send_report
  2297                  ;
  2298 0D32 7E          get_active_pos: mov     a,m
  2299 0D33 F6 01                       ori     pend_curpos     ; Received DSR triggers a cursor position report 
  2300 0D35 77                          mov     m,a
  2301 0D36 C9                          ret
  2302                  ;
  2303 0D37 3E FE       curpos_report:  mvi     a,~pend_curpos
  2304 0D39 CD BF 0D                    call    prepare_report
  2305 0D3C 3A F9 20                    lda     curs_row
  2306 0D3F 47                          mov     b,a
  2307 0D40 3A 01 21                    lda     origin_mode
  2308 0D43 B7                          ora     a
  2309 0D44 CA 4A 0D                    jz      skip_tm         ; Not in origin mode, so don't need to add margin 
  2310 0D47 3A 55 21                    lda     top_margin      ; In origin mode, so get cursor row relative to start of area
  2311 0D4A 4F          skip_tm:        mov     c,a             ; C <- margin (or 0)
  2312 0D4B 78                          mov     a,b             ; A <- cursor row (absolute)
  2313 0D4C 91                          sub     c               ; A <- cursor row (relative to top of scrolling area)
  2314 0D4D 3C                          inr     a               ; Reports call the top row 1, not 0, to match CUP/HVP
  2315 0D4E CD 7A 0D                    call    ascii_decimal
  2316 0D51 CD BB 0D                    call    report_semi
  2317 0D54 3A F8 20                    lda     curs_col
  2318 0D57 3C                          inr     a               ; Left column is 1, to match CUP/HVP
  2319 0D58 CD 7A 0D                    call    ascii_decimal
  2320 0D5B 36 D2                       mvi     m,'R'|80h       ; 'R' makes this a CPR sequence
  2321 0D5D C3 F3 0E                    jmp     send_report
  2322                  ;
  2323                  ; print_screen
  2324                  ;
  2325                  ;       Triggered in VT52 mode by ESC ], and in VT100 mode by ESC # 7, though that is not mentioned in the
  2326                  ;       User Guide. All of the work is done by the GPO, which is explained in the block diagrams in the TM.
  2327                  ;       The VT100 main terminal board just commands the print and waits for the GPO to become ready again.
  2328                  ;
  2329 0D60 DB 42       print_screen:   in      ior_flags
  2330 0D62 E6 04                       ani     iob_flags_gpo
  2331 0D64 C0                          rnz                     ; Exit if we don't have the graphics processor option
  2332 0D65 0E 81                       mvi     c,81h
  2333 0D67 CD 8E 10                    call    wait_scroll
  2334 0D6A 3E FF                       mvi     a,0ffh
  2335 0D6C D3 E2                       out     iow_graphics
  2336 0D6E 00                          nop
  2337 0D6F CD 88 14    wait_gpo_rdy2:  call    keyboard_tick
  2338 0D72 DB 42                       in      ior_flags
  2339 0D74 E6 04                       ani     iob_flags_gpo
  2340 0D76 C2 6F 0D                    jnz     wait_gpo_rdy2
  2341 0D79 C9                          ret
  2342                  ;
  2343                  ; ascii_decimal
  2344                  ;       Convert the unsigned number in A to an ASCII decimal string, loading it
  2345                  ;       into the buffer pointed to by HL.
  2346                  ;
  2347 0D7A 5F          ascii_decimal:  mov     e,a
  2348 0D7B 16 30                       mvi     d,'0'           ; digit to suppress
  2349 0D7D 0E 64                       mvi     c,100           ; try hundreds first
  2350 0D7F CD 99 0D                    call    ascii_digit     ; produce an ASCII hundreds digit
  2351 0D82 CA 88 0D                    jz      tens            ; suppress leading zero
  2352 0D85 77                          mov     m,a             ; store hundreds digit
  2353 0D86 23                          inx     h               ; next place in buffer
  2354 0D87 15                          dcr     d               ; if we've had a hundreds digit, then can't suppress tens!
  2355 0D88 0E 0A       tens:           mvi     c,10            ; now try tens
  2356 0D8A CD 99 0D                    call    ascii_digit     ; produce an ASCII tens digit
  2357 0D8D CA 93 0D                    jz      units           ; suppress leading zero (if possible!)
  2358 0D90 15                          dcr     d               ; again, can't suppress next digit (but we don't try)
  2359 0D91 77                          mov     m,a             ; store tens digit
  2360 0D92 23                          inx     h
  2361 0D93 7B          units:          mov     a,e             ; get the remaining single-digit number
  2362 0D94 F6 30                       ori     '0'             ; And make it ASCII
  2363 0D96 77                          mov     m,a             ; add to buffer
  2364 0D97 23                          inx     h               ; and increment buffer pointer
  2365 0D98 C9                          ret
  2366                  ;
  2367                  ; ascii_digit
  2368                  ;       Convert value in E to an ASCII digit for the decimal places given by C, which will probably
  2369                  ;       start at 100 and be reduced to 10 on the next call. Returns ASCII digit in A and will
  2370                  ;       return with Z flag set if that digit matches D (which is set to '0' before entry). The Z
  2371                  ;       flag enables skipping of leading zeroes on reported numbers. Look at routine above for the
  2372                  ;       cute trick to not suppress tens digits if we've produced a hundreds digit already.
  2373                  ;
  2374 0D99 7B          ascii_digit:    mov     a,e
  2375 0D9A 06 30                       mvi     b,'0'           ; lowest digit we could return
  2376 0D9C 04          repeat_sub:     inr     b               ; assume subtraction will succeed
  2377 0D9D 91                          sub     c               ; before attempting (because of flag states)
  2378 0D9E F2 9C 0D                    jp      repeat_sub      ; and if it worked (no overflow), keep going
  2379 0DA1 81                          add     c               ; last subtraction failed, so restore original minuend
  2380 0DA2 05                          dcr     b               ; and reduce the ASCII digit too
  2381 0DA3 5F                          mov     e,a             ; E <- remaining number
  2382 0DA4 78                          mov     a,b             ; A <- ASCII digit for this place
  2383 0DA5 BA                          cmp     d               ; Is it zero? (for leading digit suppression)
  2384 0DA6 C9                          ret
  2385                  ;
  2386 0DA7 3E 45       align_pattern:  mvi     a,'E'
  2387 0DA9 32 C7 21                    sta     cls_char
  2388 0DAC CD 81 03                    call    clear_display
  2389 0DAF 3E 45                       mvi     a,'E'
  2390 0DB1 32 F4 20                    sta     char_und_curs
  2391 0DB4 AF                          xra     a
  2392 0DB5 32 C7 21                    sta     cls_char        ; Back to normal clear screen 
  2393 0DB8 C9                          ret
  2394                  ;
  2395                  ; rep_char_semi
  2396                  ;       Add the character in A to the buffer pointed to by HL, and increment
  2397                  ;       pointer. Follow it up with a semi-colon.
  2398 0DB9 77          rep_char_semi:  mov     m,a
  2399 0DBA 23                          inx     h
  2400 0DBB 36 3B       report_semi:    mvi     m,3bh           ; Semi-colon, which asm8080 will not allow in quotes, boo.
  2401 0DBD 23                          inx     h
  2402 0DBE C9                          ret
  2403                  ;
  2404                  ; prepare_report
  2405                  ;       Setup a scratch buffer at 215ch with the start of an control sequence,
  2406                  ;       ESC [, ready to be appended to, and return the address of the next
  2407                  ;       character in HL. On entry, A contains the inverse bit mask of the report
  2408                  ;       that we are preparing, so it can be removed from the pending pile.
  2409                  ;
  2410 0DBF 21 72 21    prepare_report: lxi     h,pending_report
  2411 0DC2 A6                          ana     m
  2412 0DC3 77                          mov     m,a             ; this report won't be processed again
  2413 0DC4 21 5C 21                    lxi     h,report_buffer
  2414 0DC7 36 1B                       mvi     m,C0_ESC
  2415 0DC9 23                          inx     h
  2416 0DCA 36 5B                       mvi     m,'['
  2417 0DCC 23                          inx     h
  2418 0DCD C9                          ret
  2419                  ;
  2420 0DCE 42                          db      42h             ; CHECKSUM
  2421                  ;       TBC - tabulation clear
  2422 0DCF 78          tbc_action:     mov     a,b             ; A <- which tabs to clear (0 = here, 3 = all)
  2423 0DD0 B7                          ora     a
  2424 0DD1 C2 D8 0D                    jnz     check_tbc_opt
  2425 0DD4 CD E5 0D                    call    clear_this_tab
  2426 0DD7 C9                          ret
  2427                  ;
  2428 0DD8 FE 03       check_tbc_opt:  cpi     3               ; If the parameter is not zero or defaulted,
  2429 0DDA C0                          rnz                     ; it must be 3.
  2430 0DDB CD EC 0D                    call    clear_all_tabs
  2431 0DDE C9                          ret
  2432                  ;
  2433 0DDF CD 23 0E    set_tab_here:   call    tab_offs_curs
  2434 0DE2 B6                          ora     m               ; set the appropriate tab bit
  2435 0DE3 77                          mov     m,a
  2436 0DE4 C9                          ret
  2437                  ;
  2438 0DE5 CD 23 0E    clear_this_tab: call    tab_offs_curs
  2439 0DE8 2F                          cma                     ; complement the tab bit mask because we're clearing
  2440 0DE9 A6                          ana     m
  2441 0DEA 77                          mov     m,a
  2442 0DEB C9                          ret
  2443                  ;
  2444                  ; clear_all_tabs
  2445                  ;
  2446                  ;       Zeroes the entire 17-byte tab_settings area. Can be invoked either from SET-UP or by control sequence.
  2447                  ;
  2448 0DEC 21 91 21    clear_all_tabs: lxi     h,tab_settings
  2449 0DEF AF          clrnxt:         xra     a
  2450 0DF0 77                          mov     m,a
  2451 0DF1 23                          inx     h
  2452 0DF2 7D                          mov     a,l
  2453 0DF3 FE A2                       cpi     LOW tab_settings + tablen
  2454 0DF5 C2 EF 0D                    jnz     clrnxt
  2455 0DF8 C9                          ret
  2456                  ;
  2457                  ; c0_horiz_tab
  2458                  ;       Move the cursor to the next horizontal tab. Movement off the end of the tab array is checked for,
  2459                  ;       column by column, but movement beyond the 80th column will be caught by a margin check at the end.
  2460                  ;
  2461 0DF9 CD 23 0E    c0_horiz_tab:   call    tab_offs_curs
  2462 0DFC 0C          next_tab_col:   inr     c               ; increment cursor column
  2463 0DFD B7                          ora     a               ; clear carry
  2464 0DFE 1F                          rar                     ; rotate tab mask, so carry will mean next column's tab mask will
  2465 0DFF DA 0B 0E                    jc      next_tab_loc    ; be in next location
  2466 0E02 57          try_tab_loc:    mov     d,a             ; D <- tab mask
  2467 0E03 A6                          ana     m               ; Is there a tab set here?
  2468 0E04 7A                          mov     a,d             ; A <- tab mask
  2469 0E05 CA FC 0D                    jz      next_tab_col    ; no, try next column
  2470 0E08 C3 15 0E                    jmp     chk_rmargin     ; yes tab, check we don't stray beyond right margin before setting
  2471                  ;
  2472 0E0B 23          next_tab_loc:   inx     h               ; tab mask for next column is in next tab setting entry
  2473 0E0C 7D                          mov     a,l
  2474 0E0D FE A2                       cpi     LOW tab_settings + tablen       ; have we gone off end of array?
  2475 0E0F 3E 80                       mvi     a,80h           ; set up a fresh tab mask for new location
  2476 0E11 C2 02 0E                    jnz     try_tab_loc     ; not off end, try again
  2477 0E14 0D                          dcr     c               ; off end, so column number is before our prospective increment, above
  2478 0E15 3A 53 21    chk_rmargin:    lda     right_margin
  2479 0E18 B9                          cmp     c
  2480 0E19 DA 1D 0E                    jc      stop_at_margin
  2481 0E1C 79                          mov     a,c             ; We found a tab, not beyond right margin, all good
  2482 0E1D 32 F8 20    stop_at_margin: sta     curs_col
  2483 0E20 C3 36 16                    jmp     move_updates
  2484                  ;
  2485                  ; tab_offs_curs
  2486                  ;       Grab the cursor column and return HL with a pointer to correct location in tab settings,
  2487                  ;       and A with a single-bit mask that addresses that location, so we can determine whether
  2488                  ;       there is a tab set (or use the mask to change it).
  2489                  ;       Returns cursor column in C (as a bonus for the single caller that needs it.)
  2490                  ;
  2491 0E23 3A F8 20    tab_offs_curs:  lda     curs_col
  2492 0E26 4F                          mov     c,a
  2493                  ; tab_offs
  2494                  ;       Alternate entry point used for SET-UP A, where we are iterating over every column in
  2495                  ;       order to determine whether to print a 'T' or not. Register A contains column number.
  2496                  ;
  2497 0E27 57          tab_offs:       mov     d,a             ; D <- cursor column, while we destroy A
  2498 0E28 21 91 21                    lxi     h,tab_settings
  2499                  ; col_in_bits
  2500                  ;       Third entry point (value for money, this one), where A is the number of a bit offset
  2501                  ;       into a table pointed to by HL. For this entry point, D must be equal to A on entry.
  2502                  ;       On return, HL is advanced to the appropriate location and A is the bit mask
  2503                  ;
  2504                  ;       (It strikes me that the two preceding instructions could have been reversed, and this
  2505                  ;       entry point made one byte earlier, to grab the "mov d,a" instruction.)
  2506                  ;
  2507 0E2B 0F          col_in_bits:    rrc                     ; divide cursor position by 8, in order to get byte offset
  2508 0E2C 0F                          rrc
  2509 0E2D 0F                          rrc
  2510 0E2E E6 1F                       ani     1fh             ; range is really 0 to 16 for 132-column display
  2511 0E30 85                          add     l               ; offset to HL, assuming HL is aligned to allow single-byte add
  2512 0E31 6F                          mov     l,a             ; HL is now byte containing tab bit
  2513 0E32 7A                          mov     a,d             ; A <- cursor column again
  2514 0E33 E6 07                       ani     7               ; divided by 8 before, now just want remainder
  2515 0E35 57                          mov     d,a             ; which we will use as a count for a bit shift
  2516 0E36 3E 80                       mvi     a,80h           ; Bit 7 is tab at column 0, bit 6 is column 1, etc.
  2517 0E38 15          t_shft:         dcr     d               ;
  2518 0E39 F8                          rm                      ; if count's gone negative, A contains correct mask
  2519 0E3A 0F                          rrc                     ; rotate right for next column
  2520 0E3B C3 38 0E                    jmp     t_shft
  2521                  ;
  2522                  ;
  2523                  ; try_tx_byte
  2524                  ;       Returns with B containing local OR setup
  2525                  ;
  2526 0E3E 3A A5 21    try_tx_byte:    lda     local_mode      ; Do nothing in local mode
  2527 0E41 21 7B 20                    lxi     h,in_setup      ; or setup.
  2528 0E44 B6                          ora     m
  2529 0E45 47                          mov     b,a
  2530 0E46 C0                          rnz
  2531 0E47 DB 42       try_tx2:        in      ior_flags
  2532 0E49 E6 01                       ani     iob_flags_xmit  ; Can't send to PUSART until XMIT high
  2533 0E4B C8                          rz
  2534 0E4C 21 C1 21                    lxi     h,tx_xo_flag    ; Is there an XON/XOFF to transmit?
  2535 0E4F 7E                          mov     a,m
  2536 0E50 B7                          ora     a
  2537 0E51 36 00                       mvi     m,0
  2538 0E53 2B                          dcx     h               ; HL <- tx_xo_char
  2539 0E54 C8                          rz                      ; No, return
  2540 0E55 7E                          mov     a,m             ; A <- XON/XOFF
  2541 0E56 D3 00                       out     iow_pusart_data
  2542 0E58 C9                          ret
  2543                  ;
  2544 0E59 F3          try_report:     di
  2545 0E5A CD 3E 0E                    call    try_tx_byte
  2546 0E5D FB                          ei
  2547 0E5E 78                          mov     a,b             ; local mode OR setup
  2548 0E5F B7                          ora     a
  2549 0E60 C2 6D 0E                    jnz     X0e6d_          ; jump if local mode or setup
  2550 0E63 DB 42                       in      ior_flags
  2551 0E65 E6 01                       ani     iob_flags_xmit  ; Can't send to PUSART until XMIT high
  2552 0E67 C8                          rz
  2553 0E68 3A C2 21                    lda     received_xoff   ; Have we received XOFF?
  2554 0E6B B7                          ora     a
  2555 0E6C C0                          rnz                     ; We have, so can't transmit
  2556 0E6D 21 73 21    X0e6d_:         lxi     h,sending_report
  2557 0E70 7E                          mov     a,m
  2558 0E71 B7                          ora     a
  2559 0E72 CA 79 0E                    jz      pick_report     ; If we aren't sending a report already, pick the next one
  2560 0E75 2A 74 21                    lhld    report_action   ; otherwise, continue doing the current one
  2561 0E78 E9                          pchl
  2562                  ;
  2563 0E79 3A 72 21    pick_report:    lda     pending_report  ; Do we have any reports that need sending?
  2564 0E7C B7                          ora     a
  2565 0E7D C8                          rz                      ; No, return 
  2566 0E7E 1E 00                       mvi     e,0             ; Turn one bit into a report table offset
  2567 0E80 1F          next_report:    rar
  2568 0E81 DA 89 0E                    jc      find_report
  2569 0E84 1C                          inr     e
  2570 0E85 1C                          inr     e               ; 16-bit offsets
  2571 0E86 C3 80 0E                    jmp     next_report
  2572                  ;
  2573 0E89 16 00       find_report:    mvi     d,0             ; Ready for 16-bit add
  2574 0E8B 21 94 0E                    lxi     h,report_table
  2575 0E8E 19                          dad     d               ; Get to report routine 
  2576 0E8F 7E                          mov     a,m             ; A <- low byte of report routine
  2577 0E90 23                          inx     h
  2578 0E91 66                          mov     h,m             ; H <- high byte of report routine
  2579 0E92 6F                          mov     l,a             ; L <- low byte of report routine
  2580 0E93 E9                          pchl                    ; jump to it!
  2581                  ;
  2582                  ;       Address of routines that will produce reports that have
  2583                  ;       been asked for. In the ROM, they generally live very close to the
  2584                  ;       that set the "pending report" bit :-)
  2585                  ; JUMP TABLE
  2586                  report_table:
  2587 0E94 37 0D                       dw      curpos_report   ; DSR 6
  2588 0E96 F0 12                       dw      tparm_report    ; DECREQTPARM
  2589 0E98 CE 0C                       dw      ident_report    ; DECID/DA
  2590 0E9A 19 0D                       dw      devstat_report  ; DSR 5
  2591 0E9C 13 09                       dw      aback_report    ; Response to answerback request (C0 ENQ)
  2592 0E9E A0 0E                       dw      key_report      ; Sending a cursor key sequence
  2593                  ;
  2594                  ; key_report
  2595                  ;
  2596                  ;       Responsible for transmitting both single key presses and more multi-key presses, such
  2597                  ;       as cursor and other function keys. These are all handled as reports so that they can't
  2598                  ;       reports aren't interleaved with key presses or vice versa.
  2599                  ;
  2600                  ;       When a key report is being prepared, sending CR will also add LF if newline mode is
  2601                  ;       enabled, and both of those controls will enter the queue with high bits set. This routine
  2602                  ;       will clear "sending report" as soon as it encounters a character with high bit set. Does
  2603                  ;       this have any material impact on the sending of the following LF? Not sure how to test this.
  2604                  ;
  2605 0EA0 21 5C 20    key_report:     lxi     h,curkey_queue
  2606 0EA3 46                          mov     b,m             ; B <- first character
  2607 0EA4 54                          mov     d,h
  2608 0EA5 5D                          mov     e,l             ; DE <- HL
  2609 0EA6 13                          inx     d               ; DE points to second character
  2610 0EA7 0E 08                       mvi     c,8
  2611 0EA9 1A          shift_queue:    ldax    d               ; move all bytes up one place
  2612 0EAA 77                          mov     m,a             ; in queue, because we've removed the first
  2613 0EAB 23                          inx     h
  2614 0EAC 13                          inx     d
  2615 0EAD 0D                          dcr     c
  2616 0EAE C2 A9 0E                    jnz     shift_queue
  2617 0EB1 21 43 21                    lxi     h,curkey_qcount
  2618 0EB4 35                          dcr     m               ; mark that we've popped a byte
  2619 0EB5 7E                          mov     a,m
  2620 0EB6 4F                          mov     c,a             ; C <- remaining count
  2621 0EB7 C2 C1 0E                    jnz     q_not_empty
  2622 0EBA 21 72 21                    lxi     h,pending_report
  2623 0EBD 7E                          mov     a,m
  2624 0EBE E6 DF                       ani     ~pend_key       ; If we emptied the queue, we're done with this report
  2625 0EC0 77                          mov     m,a
  2626 0EC1 3A A5 21    q_not_empty:    lda     local_mode      ; If we're in local mode, keyboard is locked 
  2627 0EC4 B7                          ora     a
  2628 0EC5 C2 CF 0E                    jnz     upd_kbd_locked
  2629 0EC8 79                          mov     a,c             ; A <- remaining count
  2630 0EC9 FE 05                       cpi     5
  2631 0ECB D2 D2 0E                    jnc     key_tx          ; If got less than half a queue to transmit, can clear keyboard
  2632 0ECE AF                          xra     a               ; else, clear it
  2633 0ECF 32 44 21    upd_kbd_locked: sta     keyboard_locked
  2634 0ED2 78          key_tx:         mov     a,b             ; A <- first character
  2635 0ED3 E6 80                       ani     80h             ; isolate the high bit
  2636 0ED5 17                          ral                     ; rotate it into carry 
  2637 0ED6 3F                          cmc                     ; complement
  2638 0ED7 1F                          rar                     ; and back again
  2639 0ED8 32 73 21                    sta     sending_report  ; say that we're sending a report if character didn't have high bit set
  2640 0EDB 21 A0 0E                    lxi     h,key_report    ; Further characters in the queue will come through here
  2641 0EDE 22 74 21                    shld    report_action
  2642                                  ; Fall through. Now send the character we've just retrieved from the queue
  2643                  ;
  2644                  ; report_char
  2645                  ;
  2646                  ;       The routine that is responsible for either transmitted reports (including key presses)
  2647                  ;       to the host or, if we are in LOCAL or SETUP mode, reflecting them internally to cause
  2648                  ;       actions on the screen (so that cursor keys work, we can type normal characters, etc.)
  2649                  ;
  2650 0EE1 78          report_char:    mov     a,b
  2651 0EE2 E6 7F                       ani     7fh             ; This may be last character, so mask off top bit
  2652 0EE4 47                          mov     b,a
  2653 0EE5 3A A5 21                    lda     local_mode      ; Are we in local mode 
  2654 0EE8 21 7B 20                    lxi     h,in_setup
  2655 0EEB B6                          ora     m               ; or setup?
  2656 0EEC 78                          mov     a,b             ; A <- character to send
  2657 0EED C2 93 05                    jnz     reflect_char    ; jump if in local mode or setup -- denied!
  2658 0EF0 D3 00                       out     iow_pusart_data ; got a report character out of the door
  2659 0EF2 C9                          ret
  2660
  2661                  ; this routine is jumped to when a report has been prepared
  2662 0EF3 21 01 0F    send_report:    lxi     h,cont_report
  2663 0EF6 22 74 21                    shld    report_action
  2664 0EF9 AF                          xra     a
  2665 0EFA 32 71 21                    sta     rep_send_offset
  2666 0EFD 3C                          inr     a
  2667 0EFE 32 73 21                    sta     sending_report
  2668 0F01 21 71 21    cont_report:    lxi     h,rep_send_offset
  2669 0F04 7E                          mov     a,m             ; A <- offset into report buffer
  2670 0F05 34                          inr     m               ; increment offset to next character
  2671 0F06 21 5C 21                    lxi     h,report_buffer
  2672 0F09 85                          add     l               ; add offset to report buffer start
  2673 0F0A 6F                          mov     l,a             ; point to character to send
  2674 0F0B 46                          mov     b,m             ; B <- next character of report
  2675 0F0C 78                          mov     a,b
  2676 0F0D B7                          ora     a
  2677 0F0E F2 E1 0E                    jp      report_char     ; jump if not last character
  2678 0F11 AF                          xra     a
  2679 0F12 32 73 21                    sta     sending_report
  2680 0F15 C3 E1 0E                    jmp     report_char
  2681                  ;
  2682                  ; send_key_byte
  2683                  ;       Cursor keys and other function keys send multiple bytes, so they are gathered before transmission
  2684                  ;       like any other report.
  2685                  ;       Individual bytes of the sequence are sent through here, with the final byte of the sequence having
  2686                  ;       the high bit set.
  2687                  ;
  2688 0F18 E5          send_key_byte:  push    h
  2689 0F19 D5                          push    d
  2690 0F1A 57                          mov     d,a             ; D <- byte to store
  2691 0F1B 3A A7 21                    lda     setup_b2
  2692 0F1E E6 10                       ani     sb2_autoxon
  2693 0F20 CA 2B 0F                    jz      noautox         ; Auto XON/XOFF is OFF
  2694 0F23 7A                          mov     a,d
  2695 0F24 D6 91                       sui     91h
  2696 0F26 E6 FD                       ani     0fdh
  2697 0F28 CA 61 0F                    jz      xhigh           ; Jump if this is XON or XOFF with high bit set 
  2698 0F2B 21 72 21    noautox:        lxi     h,pending_report
  2699 0F2E 7E                          mov     a,m
  2700 0F2F F6 20                       ori     pend_key        ; mark the cursor key report as in progress
  2701 0F31 77                          mov     m,a
  2702 0F32 21 43 21    add_a_key:      lxi     h,curkey_qcount
  2703 0F35 7E                          mov     a,m             ; A <- count (= offset)
  2704 0F36 34                          inr     m
  2705 0F37 21 5C 20                    lxi     h,curkey_queue  ; HL <- buffer address 
  2706 0F3A 5F                          mov     e,a             ; E <- offset
  2707 0F3B 85                          add     l
  2708 0F3C 6F                          mov     l,a             ; Get to free location
  2709 0F3D 72                          mov     m,d             ; Store key byte
  2710 0F3E 3E 8D                       mvi     a,8dh           ; 
  2711 0F40 BA                          cmp     d               ; Is this CR with high bit?
  2712 0F41 C2 4E 0F                    jnz     key_not_cr
  2713 0F44 3A A8 21                    lda     setup_b3
  2714 0F47 E6 20                       ani     sb3_newline
  2715 0F49 16 8A                       mvi     d,8ah           ; Newline mode implies LF after CR
  2716 0F4B C2 32 0F                    jnz     add_a_key       ; so go round again.
  2717 0F4E 3A A5 21    key_not_cr:     lda     local_mode      ; If we are in local mode
  2718 0F51 B7                          ora     a
  2719 0F52 C2 5B 0F                    jnz     apply_lock      ; Lock keyboard
  2720 0F55 3E 05                       mvi     a,5             ;
  2721 0F57 BB                          cmp     e               ; Set carry if offset is greater than 5
  2722 0F58 3E 00                       mvi     a,0
  2723 0F5A 1F                          rar                     ; Rotate that carry into high bit of A, which will lock keyboard
  2724 0F5B 32 44 21    apply_lock:     sta     keyboard_locked
  2725 0F5E D1          popret:         pop     d
  2726 0F5F E1                          pop     h
  2727 0F60 C9                          ret
  2728                  ;
  2729                  ; Found XON or XOFF with high bit set, in send_key_byte
  2730                  ;
  2731 0F61 7A          xhigh:          mov     a,d
  2732 0F62 E6 7F                       ani     7fh             ; bring back to C0 range 
  2733 0F64 57                          mov     d,a
  2734 0F65 21 C1 21                    lxi     h,tx_xo_flag
  2735 0F68 7E                          mov     a,m             ; Is there an XON/XOFF to transmit?
  2736 0F69 B7                          ora     a               ;
  2737 0F6A C2 75 0F                    jnz     sendx           ; Yes
  2738 0F6D 2B                          dcx     h               ; HL <- tx_xo_char
  2739 0F6E 7E                          mov     a,m             ; A <- last XON/XOFF transmitted
  2740 0F6F BA                          cmp     d               ; is it the same as this one?
  2741 0F70 C2 75 0F                    jnz     sendx           ; No
  2742 0F73 23                          inx     h               ; HL <- tx_xo_flag
  2743 0F74 72                          mov     m,d             ; Flag this one to go
  2744 0F75 4A          sendx:          mov     c,d             ; C <- XON/XOFF
  2745 0F76 06 02                       mvi     b,2
  2746 0F78 CD 7E 0F                    call    send_xonoff
  2747 0F7B C3 5E 0F                    jmp     popret
  2748                  ;
  2749                  ; send_xonoff
  2750                  ;       On entry:
  2751                  ;               C is the character we went to sent, XON or XOFF
  2752                  ;               B is the reason we're sending:
  2753                  ;                       1 = receive buffer exhaustion [XOFF] or space available [XON])
  2754                  ;                       2 = user-initiated (pressing NO SCROLL)
  2755                  ;
  2756 0F7E 3A A7 21    send_xonoff:    lda     setup_b2        ; If we aren't configured for
  2757 0F81 E6 10                       ani     sb2_autoxon     ; auto XON/XOFF,
  2758 0F83 C8                          rz                      ; exit
  2759 0F84 3A A5 21                    lda     local_mode      ; Similarly, if we're in local mode
  2760 0F87 B7                          ora     a               ; then there is no point
  2761 0F88 C0                          rnz                     ; in sending XOFF
  2762 0F89 79                          mov     a,c
  2763 0F8A 21 BF 21                    lxi     h,why_xoff
  2764 0F8D FE 13                       cpi     C0_XOFF
  2765 0F8F 78                          mov     a,b             ; A <- mask for why we're XON/XOFF-ing
  2766 0F90 CA 9E 0F                    jz      is_xoff
  2767 0F93 2F                          cma                     ; If we're sending asking for XON, be aware that there is more
  2768 0F94 A6                          ana     m               ; than one reason for the XOFF, so only release the mask for
  2769 0F95 77                          mov     m,a             ; this reason.
  2770 0F96 F5                          push    psw
  2771 0F97 E6 02                       ani     2               ; If we're released NO SCROLL, mark that
  2772 0F99 32 C4 21                    sta     noscroll
  2773 0F9C F1                          pop     psw
  2774 0F9D C0                          rnz
  2775 0F9E B6          is_xoff:        ora     m               ; Add this XOFF reason to why_xoff
  2776 0F9F 77                          mov     m,a
  2777 0FA0 79                          mov     a,c
  2778 0FA1 21 C0 21                    lxi     h,tx_xo_char    ; If this Xany isn't the same as the last one,
  2779 0FA4 BE                          cmp     m               ; then we'll send it
  2780 0FA5 C8                          rz                      ; else quit
  2781 0FA6 77                          mov     m,a             ; write to tx_xo_char
  2782 0FA7 23                          inx     h               ; HL <- tx_xo_flag
  2783 0FA8 77                          mov     m,a             ; flag so that tx_xo_char gets sent
  2784 0FA9 C9                          ret
  2785                  ;
  2786                  ; wait_n_frames
  2787                  ;       Only ever called to wait one frame, when screens are (re)initialised and we want the DMA
  2788                  ;       to catch up.
  2789                  ;
  2790 0FAA 21 7F 20    wait_n_frames:  lxi     h,frame_count
  2791 0FAD 86                          add     m
  2792 0FAE BE          test_frames:    cmp     m
  2793 0FAF C8                          rz
  2794 0FB0 E5                          push    h
  2795 0FB1 F5                          push    psw
  2796 0FB2 3A 7B 20                    lda     in_setup
  2797 0FB5 B7                          ora     a
  2798 0FB6 CC 88 14                    cz      keyboard_tick
  2799 0FB9 F1                          pop     psw
  2800 0FBA E1                          pop     h
  2801 0FBB C3 AE 0F                    jmp     test_frames
  2802                  ;
  2803                  ; Change the extra line's attributes so it becomes part of the scrolling region and is normal width.
  2804                  ;
  2805 0FBE 2A 4E 20    reset_extra:    lhld    line_25_addr
  2806 0FC1 7C                          mov     a,h
  2807 0FC2 F6 F0                       ori     0f0h
  2808 0FC4 67                          mov     h,a
  2809 0FC5 22 4E 20                    shld    line_25_addr
  2810                  ;
  2811                  ; clear_row
  2812                  ;       On entry: HL is a pointer to start of line in screen RAM.
  2813                  ;
  2814 0FC8 3A 50 20    clear_row:      lda     screen_cols
  2815                  ;
  2816                  ; clear_part_row
  2817                  ;       On entry: HL is a pointer to screen RAM. A is number of positions to clear (forward)
  2818                  ;
  2819                  clear_part_row:
  2820 0FCB 47                          mov     b,a             ; B <- number of columns to clear
  2821 0FCC 7C                          mov     a,h             ; Make sure that HL is proper pointer (no attributes)
  2822 0FCD E6 0F                       ani     0fh
  2823 0FCF F6 20                       ori     20h
  2824 0FD1 67                          mov     h,a
  2825 0FD2 C6 10                       adi     10h             ; Now make an attribute RAM pointer, for DE 
  2826 0FD4 57                          mov     d,a
  2827 0FD5 5D                          mov     e,l
  2828 0FD6 3E FF                       mvi     a,0ffh          ; A <- default rendition
  2829 0FD8 36 00       clr_next_col:   mvi     m,0             ; clear screen RAM
  2830 0FDA 12                          stax    d               ; and attribute RAM
  2831 0FDB 13                          inx     d               ; next column
  2832 0FDC 23                          inx     h               ; ... in both
  2833 0FDD 05                          dcr     b
  2834 0FDE C2 D8 0F                    jnz     clr_next_col
  2835 0FE1 3C                          inr     a               ; zero A (was 0ffh throughout loop)
  2836 0FE2 32 C3 21                    sta     row_clearing    ; done clearing of row
  2837 0FE5 C9                          ret
  2838                  ;
  2839                  ; for scrolling up
  2840 0FE6 06 FF       move_lines_up:  mvi     b,0ffh
  2841 0FE8 C3 ED 0F                    jmp     move_lines
  2842                  ;
  2843                  ; for scrolling down
  2844 0FEB 06 01       move_lines_dn:  mvi     b,1
  2845                  ;
  2846                  ; at this point, B is either -1 or +1, for the direction of movement
  2847                  ;
  2848 0FED 21 55 21    move_lines:     lxi     h,top_margin
  2849 0FF0 56                          mov     d,m             ; D <- top margin
  2850 0FF1 23                          inx     h               ; HL ^ bottom_margin
  2851 0FF2 5E                          mov     e,m             ; E <- bottom margin
  2852 0FF3 78                          mov     a,b             ; A <- direction
  2853 0FF4 B7                          ora     a
  2854 0FF5 7A                          mov     a,d             ; A <- top margin
  2855 0FF6 F2 FA 0F                    jp      grab_phys       ; jump if scrolling down (i.e. at top margin)
  2856 0FF9 7B                          mov     a,e             ; A <- bottom margin (because scrolling up
  2857 0FFA CD E6 13    grab_phys:      call    phl_num         ; A <- physical line number of this row (HL points to entry in LATOFS)
  2858 0FFD 7B                          mov     a,e
  2859 0FFE 92                          sub     d               ; A <- bottom margin - top margin
  2860 0FFF 4F                          mov     c,a             ; C <- no. lines to scroll - 1
  2861 1000 3A 2B 21                    lda     latofs_last     ; physical line number of "25th line"
  2862 1003 56          shuffle_latofs: mov     d,m             ; D <- physical line number of old row (margin row, initially)
  2863 1004 77                          mov     m,a             ; replace this with row in A (initially, 25th row, because that will come into view)
  2864 1005 78                          mov     a,b             ; A <- direction
  2865 1006 85                          add     l               ; next logical line (one line nearer beginning when scrolling up)
  2866 1007 6F                          mov     l,a
  2867 1008 0D                          dcr     c               ; decrement number of lines to shift
  2868 1009 7A                          mov     a,d             ; A <- previous row (one we just overwrote)
  2869 100A F2 03 10                    jp      shuffle_latofs
  2870 100D E6 7F                       ani     7fh             ; remove any line attributes from line about to disappear
  2871 100F 32 2B 21                    sta     latofs_last     ; and this becomes the new "25th row"
  2872                                  ; fall through
  2873                  ;
  2874                  ; Other routines that perform major reorganisation of the screen, including changing the
  2875                  ; size of a line, jump here so that they can invalidate cursor row before do all the normal
  2876                  ; post cursor move actions. That way, we get a full recalculation of the cursor position,
  2877                  ; because we're claiming it was previously on row -1, which can't be the same as the current row.
  2878                  ;
  2879 1012 3E FF       inv_saved_row:  mvi     a,0ffh
  2880 1014 32 0E 21                    sta     saved_curs_row
  2881 1017 C3 36 16                    jmp     move_updates
  2882                  ;
  2883                  ;
  2884                  ; start a jump scroll up
  2885 101A CD 91 11    start_jump_up:  call    wait_for_x
  2886 101D 3A 56 21                    lda     bottom_margin
  2887 1020 CD 2F 12                    call    connect_extra   ; Clear extra line and connect it to screen list
  2888 1023 CD E6 0F                    call    move_lines_up   ; Move logical lines 
  2889 1026 3A 55 21                    lda     top_margin
  2890 1029 3D                          dcr     a
  2891 102A C3 3E 10                    jmp     scroll_common
  2892                  ;
  2893                  ; start a jump scroll down
  2894 102D CD 91 11    start_jump_dn:  call    wait_for_x
  2895 1030 3A 55 21                    lda     top_margin
  2896 1033 3D                          dcr     a
  2897 1034 CD 2F 12                    call    connect_extra
  2898 1037 CD EB 0F                    call    move_lines_dn
  2899 103A 3A 56 21                    lda     bottom_margin
  2900 103D 3D                          dcr     a
  2901 103E CD CE 11    scroll_common:  call    calc_shuf1
  2902 1041 FB                          ei
  2903 1042 32 7A 20                    sta     shuffle_ready   ; A is not zero here
  2904 1045 21 56 21                    lxi     h,bottom_margin
  2905 1048 7E                          mov     a,m
  2906 1049 2B                          dcx     h
  2907 104A 96                          sub     m
  2908 104B FE 17                       cpi     17h             ; Even jump scrolls can only be completed in one go 
  2909 104D C0                          rnz                     ; if they apply to full screen, else they must be
  2910                                                          ; delayed until frame refresh. (TM §4.7.5)
  2911                  ;
  2912                  ; shuffle
  2913                  ;
  2914                  ;       This performs a test-and-clear on the shuffle_ready semaphore and then moves the lines pointers
  2915                  ;       around as described on TM p.4-92. The connecting of the extra line has
  2916                  ;       been done in advance, and the working out of the other connections for shuffle data 1 and 2.
  2917                  ;       Now all that remains is for the pointers to be updated.
  2918                  ;
  2919 104E 21 7A 20    shuffle:        lxi     h,shuffle_ready
  2920 1051 7E                          mov     a,m
  2921 1052 36 00                       mvi     m,0
  2922 1054 B7                          ora     a
  2923 1055 C8                          rz
  2924 1056 2A 56 20                    lhld    shufdt2
  2925 1059 EB                          xchg
  2926 105A 2A 58 20                    lhld    shufad2         ; Place the DMA address in shufdt2 into shufad2
  2927 105D 72                          mov     m,d
  2928 105E 23                          inx     h
  2929 105F 73                          mov     m,e
  2930 1060 2A 79 21                    lhld    shufdt1
  2931 1063 EB                          xchg
  2932 1064 2A 75 20                    lhld    shufad1         ; Place the DMA address in shufdt1 into shufad1
  2933 1067 72                          mov     m,d
  2934 1068 23                          inx     h
  2935 1069 73                          mov     m,e
  2936 106A 21 56 20    reset_shuffle:  lxi     h,shufdt2
  2937 106D 22 58 20                    shld    shufad2
  2938 1070 22 75 20                    shld    shufad1
  2939 1073 C9                          ret
  2940                  ;
  2941                  ; curs_line_addr
  2942                  ;       Returns pointer to current line in screen RAM
  2943                  ;
  2944                  ;       On entry:       --
  2945                  ;       On exit:        HL is address of first location of line in screen RAM
  2946                  ;
  2947 1074 CD E3 13    curs_line_addr: call    curs_phl_num
  2948 1077 E6 80                       ani     80h             ; Grab double-width marker
  2949 1079 32 57 21                    sta     curr_line_dbl   ; and store it
  2950 107C CD 8D 13                    call    curs_phl_addr   ; HL <- start of cursor line in screen RAM
  2951 107F 22 4E 21                    shld    start_line_addr ; and store it
  2952 1082 C9                          ret
  2953                  ;
  2954                  ; memset - set DE bytes from (HL) to B
  2955 1083 70          memset:         mov     m,b
  2956 1084 23                          inx     h
  2957 1085 1B                          dcx     d
  2958 1086 7A                          mov     a,d
  2959 1087 B3                          ora     e
  2960 1088 C2 83 10                    jnz     memset
  2961 108B C9                          ret
  2962                  ;
  2963                  ; wait_scroll
  2964                  ;
  2965                  ;       Certain actions wait for a quiet screen, i.e. scrolling to finish, before they
  2966                  ;       take place. The TM says that entering SET-UP mode is one of those. Examination of the
  2967                  ;       code shows three other actions that will wait:
  2968                  ;       1. Changing the top and bottom margins
  2969                  ;       2. Scrolling up or down the display (RI or LF), because there is only one extra line,
  2970                  ;          and scrolling needs to have finished using it before we can connect is again
  2971                  ;       3. Print screen, which waits before telling GPO to go
  2972                  ;
  2973                  ; oddity - this routine uses C, but no one ever jumps to the line below, which
  2974                  ; sets C to 0. This routine feels like C=0 could be legitimate, but worthwhile finding
  2975                  ; out why it's never called like that. So the line below is UNUSED but is not
  2976                  ; this ROM's checksum address.
  2977                  ;
  2978                  ; C is a combination of two flags, in bit 0 and bit 7 (for ease of testing with rotate
  2979                  ; and carry testing).
  2980                  ; C = 01h allows keyboard processing to take place while waiting
  2981                  ; C = 80h makes routine exit through wait_for_x (still needs bottoming out)
  2982                  ;
  2983                  ; When using for scrolling, i.e. processing index or reverse_index, this routine is called
  2984                  ; with C=1, and that results in it exiting with HL pointing to scroll_pending. The callers
  2985                  ; then change scroll_pending through this pointer, which was quite hard to find!
  2986                  ;
  2987 108C 0E 00                       mvi     c,0             ; UNUSED
  2988 108E 21 51 20    wait_scroll:    lxi     h,scroll_pending
  2989 1091 F3                          di
  2990 1092 3A 65 20                    lda     smooth_scroll   ; in smooth scroll?
  2991 1095 B6                          ora     m               ; or scroll pending?
  2992 1096 FB                          ei
  2993 1097 CA A7 10                    jz      scroll_done
  2994 109A 79                          mov     a,c
  2995 109B 1F                          rar
  2996 109C C5                          push    b               ; save our wait flags 
  2997 109D DC 88 14                    cc      keyboard_tick
  2998 10A0 CD 93 14                    call    update_kbd
  2999 10A3 C1                          pop     b               ; restore wait flags
  3000 10A4 C3 8E 10                    jmp     wait_scroll
  3001                  ;
  3002 10A7 79          scroll_done:    mov     a,c
  3003 10A8 17                          ral                     ; Test bit 7 
  3004 10A9 D0                          rnc
  3005 10AA C3 91 11                    jmp     wait_for_x
  3006                  ;
  3007 10AD 21 70 06    init_screen:    lxi     h,0670h         ; DMA address (2006h)
  3008 10B0 22 04 20                    shld    line1_dma       ; blank screen by pointing direct to fill lines
  3009 10B3 AF                          xra     a               ; A <- 0
  3010 10B4 32 51 20                    sta     scroll_pending  ; there is no scroll pending 
  3011 10B7 32 65 20                    sta     smooth_scroll   ; we are not smooth scrolling now
  3012 10BA 32 5A 20                    sta     scroll_scan
  3013 10BD D3 A2                       out     iow_dc012       ; DC012 <- "low order scroll latch = 00"
  3014 10BF 3E 04                       mvi     a,4
  3015 10C1 D3 A2                       out     iow_dc012       ; DC012 <- "high order scroll latch = 00"
  3016 10C3 CD 6A 10                    call    reset_shuffle   ; feels like cleaning up shuffle
  3017 10C6 FB                          ei
  3018 10C7 3E 01                       mvi     a,1
  3019 10C9 CD AA 0F                    call    wait_n_frames   ; allow DMA to blank screen
  3020 10CC 21 D0 22                    lxi     h,main_video    ; (first byte of screen definition)
  3021 10CF 7C                          mov     a,h
  3022 10D0 C6 10                       adi     10h             ; Attribute RAM is Screen RAM + 1000h
  3023 10D2 57                          mov     d,a
  3024 10D3 5D                          mov     e,l             ; DE <- start of attribute RAM for default display
  3025 10D4 01 2F 0D                    lxi     b,0d2fh         ; 22d0h + 0d2fh = 2fffh
  3026                                                          ;
  3027                                                          ; clear screen, including AVO's additional 1K of screen RAM
  3028                                                          ;
  3029 10D7 3A C7 21    cls:            lda     cls_char
  3030 10DA 77                          mov     m,a             ; "clear" screen RAM (maybe to 'E', for alignment display) 
  3031 10DB 23                          inx     h
  3032 10DC 3E FF                       mvi     a,0ffh
  3033 10DE 12                          stax    d               ; Normal attributes
  3034 10DF 13                          inx     d
  3035 10E0 0B                          dcx     b
  3036 10E1 78                          mov     a,b
  3037 10E2 B1                          ora     c
  3038 10E3 C2 D7 10                    jnz     cls
  3039                  ;
  3040 10E6 3A C7 21                    lda     cls_char
  3041 10E9 32 F4 20                    sta     char_und_curs
  3042 10EC AF                          xra     a
  3043 10ED 32 F9 20                    sta     curs_row
  3044 10F0 32 F8 20                    sta     curs_col
  3045 10F3 3E FF                       mvi     a,0ffh
  3046 10F5 32 F5 20                    sta     rend_und_curs
  3047 10F8 C9                          ret
  3048                  ;
  3049                  ; make_screen
  3050                  ;       Write all the line terminators for a screen of a given width in normal screen RAM.
  3051                  ;       Writes pointers only; makes no attempt to zero the display.
  3052                  ;
  3053                  ;       On entry: C is number of columns on screen
  3054                  ;
  3055 10F9 AF          make_screen:    xra     a               ; A <- 0
  3056 10FA 32 51 20                    sta     scroll_pending  ; there is no scroll pending
  3057 10FD 3A 65 20    wait_smooth:    lda     smooth_scroll   ; are we in middle of smooth scroll?
  3058 1100 B7                          ora     a
  3059 1101 C2 FD 10                    jnz     wait_smooth
  3060 1104 21 D0 22                    lxi     h,main_video    ; HL <- start of screen RAM
  3061 1107 06 00                       mvi     b,0             ; BC <- number of columns
  3062 1109 09                          dad     b               ; advance to end of first line
  3063 110A CD BF 11                    call    last_row        ; B <- last row number (23 or 13)
  3064 110D 0C                          inr     c               ; C <- number of columns + 1 (oddity in make_lines)
  3065 110E CD 36 11                    call    make_lines
  3066 1111 E5                          push    h
  3067 1112 06 01                       mvi     b,1             ; Need a 24th line
  3068 1114 CD 36 11                    call    make_lines
  3069 1117 E1                          pop     h
  3070 1118 36 7F                       mvi     m,7fh           ; Terminate 23rd line (already done this?)
  3071 111A 23                          inx     h               ; HL <- 24th line DMA address (big endian) 
  3072 111B 22 54 20                    shld    UNREAD_X2054    ; Store this
  3073 111E 36 70                       mvi     m,70h           ; Make DMA address for 24th line 7006h (i.e. fill lines)
  3074 1120 23                          inx     h
  3075 1121 36 06                       mvi     m,6
  3076 1123 79                          mov     a,c             ; A <- number of columns + 1
  3077 1124 CD DE 13                    call    add_a_to_hl     ; Get to end of 24th line
  3078 1127 36 7F                       mvi     m,7fh           ; Write terminator (first time!)
  3079 1129 0D                          dcr     c               ; C <- number of columns
  3080 112A 3E 03                       mvi     a,3             ; Point initial screen DMA to line 1 (2003h)
  3081 112C 32 02 20                    sta     line0_dma + 1
  3082 112F 21 F2 D0                    lxi     h,main_video_be ; byte-swapped video address
  3083 1132 22 04 20                    shld    line1_dma       ; Point to our new screen
  3084 1135 C9                          ret
  3085                  ;
  3086                  ; Given a pointer HL, to end of a line, make another B lines of (C - 1) columns wide
  3087                  ; (create a full screen display)
  3088                  ; HL isn't advanced after writing low byte of screen address, so C is passed in
  3089                  ; with number of columns + 1 (small oddity)
  3090                  ;
  3091 1136 36 7F       make_lines:     mvi     m,7fh           ; write terminator
  3092 1138 23                          inx     h               ; advance to point at high byte (DMA order)
  3093 1139 54                          mov     d,h             ; DE <- HL
  3094 113A 5D                          mov     e,l
  3095 113B 13                          inx     d               ; In DMA terms, DE is byte-swapped, so advance 2 locations
  3096 113C 13                          inx     d
  3097 113D 7A                          mov     a,d             ; And add normal line attributes
  3098 113E F6 F0                       ori     0f0h
  3099 1140 77                          mov     m,a             ; Write high byte + attrs first
  3100 1141 23                          inx     h
  3101 1142 73                          mov     m,e             ; Then low byte
  3102 1143 79                          mov     a,c             ; A <- columns + 1 (didn't increment HL already)
  3103 1144 CD DE 13                    call    add_a_to_hl     ; advance to next line
  3104 1147 05                          dcr     b
  3105 1148 C2 36 11                    jnz     make_lines
  3106 114B C9                          ret
  3107                  ;
  3108                  ; make_line_t
  3109                  ;
  3110                  ;       Create the physical line address and logical offset tables for screen rows.
  3111                  ;       The logical offset table is what the TM calls LATOFS.
  3112                  ;
  3113                  ;       On entry, C is number of columns
  3114                  ;
  3115 114C 21 C2 20    make_line_t:    lxi     h,pline_addr    ; Start of table of physical line addresses
  3116 114F 0C                          inr     c               ; Each line is columns + 3 terminators bytes long
  3117 1150 0C                          inr     c
  3118 1151 0C                          inr     c
  3119 1152 11 D0 22                    lxi     d,main_video
  3120 1155 06 00                       mvi     b,0             ; Make BC <- columns + 3 (for 16-bit additions)
  3121 1157 3E 19                       mvi     a,25
  3122 1159 73          store_physline: mov     m,e             ; Store address of physical line in table
  3123 115A 23                          inx     h
  3124 115B 72                          mov     m,d
  3125 115C 23                          inx     h
  3126 115D EB                          xchg                    ; HL <- screen RAM address 
  3127 115E 09                          dad     b               ; get to next line, past terminators too
  3128 115F EB                          xchg                    ; DE <- next line screen RAM address
  3129 1160 3D                          dcr     a               ; Do all 24 lines
  3130 1161 C2 59 11                    jnz     store_physline
  3131 1164 32 55 21                    sta     top_margin      ; new screen, reset top margin
  3132 1167 CD BF 11                    call    last_row        ; B <- last row number
  3133 116A 78                          mov     a,b
  3134 116B 32 56 21                    sta     bottom_margin   ; and bottom margin 
  3135 116E 3C                          inr     a               ; row number of one line beyond bottom margin, 
  3136 116F 32 2B 21                    sta     latofs_last     ; as last place in latofs
  3137 1172 2A F2 20                    lhld    pline_extra     ; Now grab screen address of 0-based row 24
  3138 1175 3E 17                       mvi     a,23 
  3139 1177 B8                          cmp     b
  3140 1178 CA 7E 11                    jz      ok24            ; unless this is a 14-row screen, in which case
  3141 117B 2A DE 20                    lhld    pline_addr+(14*2)    ; use 0-based row 14 (20deh)
  3142 117E 7C          ok24:           mov     a,h
  3143 117F F6 F0                       ori     0f0h
  3144 1181 67                          mov     h,a
  3145 1182 22 4E 20                    shld    line_25_addr    ; extra line attributes to single width
  3146 1185 21 13 21                    lxi     h,latofs        ; Now write LATOFS, with all logical lines pointing 
  3147 1188 AF                          xra     a               ; to the same physical lines, so just 0 to 23.
  3148 1189 77          next_lof:       mov     m,a
  3149 118A 2C                          inr     l               ; [* fragile *] LATOFS can't go just anywhere
  3150 118B 3C                          inr     a
  3151 118C 05                          dcr     b
  3152 118D F2 89 11                    jp      next_lof
  3153 1190 C9                          ret
  3154                  ;
  3155 1191 F3          wait_for_x:     di
  3156 1192 3A 7A 20                    lda     shuffle_ready
  3157 1195 B7                          ora     a
  3158 1196 CA A0 11                    jz      no_shuffle
  3159 1199 FB          do_tick:        ei
  3160 119A CD 88 14                    call    keyboard_tick
  3161 119D C3 91 11                    jmp     wait_for_x
  3162                  ;
  3163 11A0 3A 65 20    no_shuffle:     lda     smooth_scroll   ; are we in middle of smooth scroll? 
  3164 11A3 B7                          ora     a
  3165 11A4 C2 B0 11                    jnz     next_scan
  3166 11A7 3A 51 20                    lda     scroll_pending
  3167 11AA B7                          ora     a
  3168 11AB FB                          ei
  3169 11AC C8                          rz                      ; no scroll pending, so exit
  3170 11AD C3 99 11                    jmp     do_tick
  3171                  ;
  3172 11B0 3A 5B 20    next_scan:      lda     scroll_dir
  3173 11B3 21 5A 20                    lxi     h,scroll_scan
  3174 11B6 86                          add     m
  3175 11B7 27                          daa
  3176 11B8 E6 0F                       ani     0fh
  3177 11BA CA 99 11                    jz      do_tick
  3178 11BD FB                          ei
  3179 11BE C9                          ret
  3180                  ;
  3181                  ; last_row
  3182                  ;       Returns with B set to last row number, i.e. 13 or 23
  3183                  ;
  3184                  last_row:
  3185 11BF E5                          push    h
  3186 11C0 06 17                       mvi     b,23            ; assume we have 24 lines
  3187 11C2 3A A2 21                    lda     columns_132     ; unless we're in 132-column mode
  3188 11C5 21 C8 21                    lxi     h,avo_missing
  3189 11C8 A6                          ana     m               ; AND we DON'T have AVO
  3190 11C9 E1                          pop     h
  3191 11CA C8                          rz
  3192 11CB 06 0D                       mvi     b,13            ; in which case, we only have 14 lines
  3193 11CD C9                          ret
  3194                  ;
  3195                  ; calc_shuf1
  3196                  ;       Work out which line will be the new top line and take the line that will disappear and store it as
  3197                  ;       the new extra line.
  3198                  ;
  3199                  ; On entry: A is the top or bottom margin - 1 (i.e. RI at top of full screen will enter here with A = 23)
  3200                  ;
  3201 11CE B7          calc_shuf1:     ora     a
  3202 11CF F2 E8 11                    jp      pos_margin
  3203 11D2 21 04 20                    lxi     h,line1_dma     ; HL <- address of video line (big-endian)
  3204 11D5 E5                          push    h
  3205 11D6 7E                          mov     a,m             ; A <- high byte the video address
  3206 11D7 23                          inx     h               ; HL points to low byte
  3207 11D8 6E                          mov     l,m             ; L <- low byte of video address
  3208 11D9 F6 F0                       ori     0f0h            ; Change attributes of high byte to be scrolling region, normal size
  3209 11DB 67                          mov     h,a             ; H <- high byte of video address
  3210 11DC 22 4E 20                    shld    line_25_addr    ; stow it as new extra line address
  3211 11DF CD 90 12                    call    next_line_addr  ; DE <- DMA address of next line
  3212 11E2 E1                          pop     h               ; HL <- address that will need changing to point to line 2 (new top line)
  3213 11E3 42                          mov     b,d
  3214 11E4 4B                          mov     c,e             ; BC <- DMA address of line 2
  3215 11E5 C3 0A 12                    jmp     store_shufad1
  3216                  ;
  3217 11E8 CD 7F 12    pos_margin:     call    to_line_n
  3218 11EB CD 90 12                    call    next_line_addr  ; DE <- DMA address of line we're losing from bottom
  3219 11EE E5                          push    h
  3220 11EF D5                          push    d
  3221 11F0 EB                          xchg                    ; HL <- DMA address of losing line
  3222 11F1 CD 90 12                    call    next_line_addr  ; DE <- address of line after (fill address)
  3223 11F4 42                          mov     b,d
  3224 11F5 4B                          mov     c,e             ; BC <- fill address
  3225 11F6 D1                          pop     d               ; DE <- losing line address, again
  3226 11F7 7A                          mov     a,d
  3227 11F8 F6 F0                       ori     0f0h            ; Restore its line attributes
  3228 11FA 67                          mov     h,a
  3229 11FB 6B                          mov     l,e             ; HL <- (corrected) losing line address
  3230 11FC 22 4E 20                    shld    line_25_addr    ; which now becomes available as the extra line
  3231 11FF E1                          pop     h               ; HL <- new bottom line address
  3232 1200 7C                          mov     a,h
  3233 1201 F6 9F                       ori     9fh             ; testing for double-width attributes
  3234 1203 3C                          inr     a
  3235 1204 C2 11 12                    jnz     not_sinwid1     ; If line isn't single-width, there are two pointers to next line
  3236 1207 CD 99 12                    call    eoline_addr
  3237 120A F3          store_shufad1:  di                      ; Make shufad1 & shufdt1 consistent before vertical refresh
  3238 120B 22 75 20                    shld    shufad1         ; Now store the address of the terminating info to be updated
  3239 120E C3 28 12                    jmp     store_shufdt1
  3240                  ;
  3241 1211 CD 9D 12    not_sinwid1:    call    real_addr
  3242 1214 3A 50 20                    lda     screen_cols
  3243 1217 0F                          rrc                     ; Need to get to halfway terminator because line is double-width
  3244 1218 57                          mov     d,a             ; D <- half screen width
  3245 1219 CD DE 13                    call    add_a_to_hl
  3246 121C 23                          inx     h               ; HL <- next DMA address
  3247 121D F3                          di
  3248 121E 22 75 20                    shld    shufad1         ; This is the address we'll write to during shuffle 
  3249 1221 7A                          mov     a,d             ; A <- half screen width
  3250 1222 CD DE 13                    call    add_a_to_hl     ; Get to the address we aren't using at the moment 
  3251 1225 70                          mov     m,b             ; and make it consistent with the one we'll update during shuffle
  3252 1226 23                          inx     h
  3253 1227 71                          mov     m,c
  3254 1228 60          store_shufdt1:  mov     h,b
  3255 1229 69                          mov     l,c
  3256 122A 22 79 21                    shld    shufdt1
  3257 122D FB                          ei
  3258 122E C9                          ret
  3259                  ;
  3260                  ; connect_extra
  3261                  ;       In advance of the shuffle happening, connect the extra line to the screen lines.
  3262                  ;       If we are indexing down, the extra line will be connected to the first line
  3263                  ;       (within margins). If we are reverse indexing then we are shifting all the lines
  3264                  ;       down and the extra line connection will be made to the second line. At the same
  3265                  ;       time, we write shufad2 and shufdt2, per TM Figure 4-7-9, so that one of the
  3266                  ;       existing lines will get a join to the extra line when the shuffle gets performed.
  3267                  ;
  3268                  ;       On entry: A is the row number of the bottom margin, or the top margin - 1 (so may be negative)
  3269                  ;
  3270 122F F5          connect_extra:  push    psw
  3271 1230 CD BE 0F                    call    reset_extra     ; make the cursor line scrollable
  3272 1233 F1                          pop     psw
  3273 1234 B7                          ora     a
  3274 1235 21 04 20                    lxi     h,line1_dma     ; set HL in case we're at the top and
  3275 1238 FA 48 12                    jm      at_top          ; the extra line will be connected after the fill line
  3276 123B CD 7F 12                    call    to_line_n       ; otherwise work out which line will be connected to extra
  3277 123E 7C                          mov     a,h             ; A <- high byte of screen RAM address with attrs
  3278 123F F6 9F                       ori     9fh
  3279 1241 3C                          inr     a
  3280 1242 C2 52 12                    jnz     not_sinwid2     ; jump if line attrs were not single width
  3281 1245 CD 99 12                    call    eoline_addr     ; HL <- next line DMA address
  3282 1248 F3          at_top:         di
  3283 1249 22 58 20                    shld    shufad2         ; points to line DMA address
  3284 124C 46                          mov     b,m
  3285 124D 23                          inx     h
  3286 124E 4E                          mov     c,m             ; BC <- line DMA address + attrs
  3287 124F C3 71 12                    jmp     conn_shuf2
  3288                  ;
  3289 1252 CD 9D 12    not_sinwid2:    call    real_addr
  3290 1255 3A 50 20                    lda     screen_cols     ; As the line we're connecting to extra isn't single
  3291 1258 0F                          rrc                     ; width, work out where the terminator is (i.e. half way)
  3292 1259 47                          mov     b,a
  3293 125A CD DE 13                    call    add_a_to_hl
  3294 125D 23                          inx     h               ; HL <- next DMA address
  3295 125E F3                          di
  3296 125F 22 58 20                    shld    shufad2         ; This is the address we'll write to during shuffle
  3297 1262 78                          mov     a,b             ; A <- half width 
  3298 1263 46                          mov     b,m             ; Grab the current DMA address
  3299 1264 23                          inx     h
  3300 1265 4E                          mov     c,m
  3301 1266 CD DE 13                    call    add_a_to_hl     ; move to full width (HL points one byte too far, to DMA low byte)
  3302 1269 EB                          xchg                    ; DE <- DMA low byte
  3303 126A 2A 4E 20                    lhld    line_25_addr
  3304 126D EB                          xchg
  3305 126E 73                          mov     m,e             ; Place LOW byte of extra line address
  3306 126F 2B                          dcx     h               ; Move back to high byte
  3307 1270 72                          mov     m,d             ; Place HIGH byte of extra line address
  3308                                                          ; Mid-line address (the active one) will be updated during shuffle
  3309 1271 2A 4E 20    conn_shuf2:     lhld    line_25_addr    ; Line 24 will have its end pointer updated
  3310 1274 22 56 20                    shld    shufdt2         ; to point to the extra line during shuffle
  3311 1277 CD 99 12                    call    eoline_addr
  3312 127A 70                          mov     m,b             ; Update extra line's next DMA address now 
  3313 127B 23                          inx     h
  3314 127C 71                          mov     m,c
  3315 127D FB                          ei
  3316 127E C9                          ret
  3317                  ;
  3318                  ; to_line_n
  3319                  ;       On entry:
  3320                  ;               A is the row number of physical screen to get to (0 gets to address line1_dma points to)
  3321                  ;       On exit:
  3322                  ;               HL points to first character of screen RAM of line
  3323                  ;
  3324 127F 3C          to_line_n:      inr     a
  3325 1280 47                          mov     b,a
  3326 1281 21 04 20                    lxi     h,line1_dma
  3327 1284 7E          adv_line:       mov     a,m             ; A <- high byte of screen DMA address (and attrs)
  3328 1285 23                          inx     h
  3329 1286 6E                          mov     l,m             ; L <-low byte
  3330 1287 67                          mov     h,a             ; H <- high byte
  3331 1288 05                          dcr     b
  3332 1289 C8                          rz
  3333 128A CD 99 12                    call    eoline_addr     ; advance to next DMA address
  3334 128D C3 84 12                    jmp     adv_line
  3335                  ;
  3336                  ; Given start of line address in HL, returns DE with address of next line
  3337                  ;
  3338 1290 E5          next_line_addr: push    h
  3339 1291 CD 99 12                    call    eoline_addr
  3340 1294 56                          mov     d,m             ; D <- high byte (incl. attrs)
  3341 1295 23                          inx     h
  3342 1296 5E                          mov     e,m             ; E <- low byte
  3343 1297 E1                          pop     h
  3344 1298 C9                          ret
  3345                  ;
  3346                  ; Given start of line address in HL, add screen width to get end of line
  3347 1299 CD DB 13    eoline_addr:    call    add_cols_to_hl
  3348 129C 23                          inx     h
  3349                  ; real_addr
  3350                  ;
  3351                  ;       On entry, HL is an address in screen RAM with the line attribute and scrolling region bits
  3352                  ;       in the top four bits. Mask out these attributes and add the 2000h offset to get a real address.
  3353                  ;
  3354 129D 7C          real_addr:      mov     a,h
  3355 129E E6 0F                       ani     0fh
  3356 12A0 F6 20                       ori     20h
  3357 12A2 67                          mov     h,a
  3358 12A3 C9                          ret
  3359                  ;
  3360 12A4 CD B0 12    line_attr_dwl:  call    double_line
  3361 12A7 50                          db      50h
  3362                  ;
  3363 12A8 CD B0 12    dhl_top_action: call    double_line
  3364 12AB 30                          db      30h
  3365                  ;
  3366 12AC CD B0 12    dhl_bot_action: call    double_line
  3367 12AF 10                          db      10h
  3368                  ;
  3369                  ; double_line
  3370                  ;
  3371                  ;       The single byte at the return address is the line attributes to be applied to the current line.
  3372                  ;       We are not going to go back there, so no attempt is made to restore the stacked address.
  3373                  ;       As these are all double-width line attributes, the right margin must be adjusted, and the cursor
  3374                  ;       pulled back onto screen if necessary.
  3375                  ;
  3376 12B0 CD 91 11    double_line:    call    wait_for_x
  3377 12B3 21 13 21                    lxi     h,latofs        ; Find current physical line number in LATOFS   `
  3378 12B6 3A F9 20                    lda     curs_row        ;                                                |  duplicates
  3379 12B9 85                          add     l               ;                                                 < curs_phl_num
  3380 12BA 6F                          mov     l,a             ;                                                |
  3381 12BB 7E                          mov     a,m             ;                                               ,
  3382 12BC F6 80                       ori     80h             ; Mark the current row in LATOFS as double width
  3383 12BE 77                          mov     m,a
  3384                                  ; We've now set bit 7 of the physical line number in LATOFS, but this doesn't affect
  3385                                  ; the next routine, even though it again looks up a physical line number in LATOFS,
  3386                                  ; as the (corrupted) line number it receives will be doubled in order to extract an
  3387                                  ; address from the table at pline_addr.
  3388                                  ;
  3389 12BF CD 8D 13                    call    curs_phl_addr   ; HL <- address of cursor line in screen RAM?
  3390 12C2 CD C3 13                    call    halve_line
  3391 12C5 E1                          pop     h               ; Grab return address (which we will discard)
  3392 12C6 7E                          mov     a,m             ; A <- line attributes to be applied
  3393 12C7 CD 95 13                    call    add_line_attrs
  3394 12CA 3A 53 21                    lda     right_margin
  3395 12CD 21 F8 20                    lxi     h,curs_col
  3396 12D0 B7                          ora     a               ; clear carry because RAR brings it into bit 7 (why not use RRC?)
  3397 12D1 1F                          rar                     ; A <- right margin / 2
  3398 12D2 BE                          cmp     m
  3399 12D3 D2 12 10                    jnc     inv_saved_row   ; jump if right margin / 2 is still greater than current cursor column
  3400                                  ;
  3401                                  ; The next two lines look odd because they aren't storing a screen RAM address in
  3402                                  ; cursor_address, as you'd expect. However, we're about to go into move_updates,
  3403                                  ; which is going to attempt to restore the old character under the cursor and the
  3404                                  ; old rendition in attribute RAM. If we allowed that to happen with the cursor just
  3405                                  ; beyond the margin, this restoration could wreck the new terminators, so by
  3406                                  ; storing the address of these locations in the cursor address, move_updates
  3407                                  ; will place the character under the cursor in char_und_curs, a cunning no-op.
  3408                                  ;
  3409 12D6 21 F4 20                    lxi     h,char_und_curs
  3410 12D9 22 F6 20                    shld    cursor_address
  3411 12DC C3 12 10                    jmp     inv_saved_row
  3412                  ;
  3413                  ;       Process DECREQTPARM - ESC [ <sol< x
  3414 12DF 3A 30 21    tparm_action:   lda     csi_p1
  3415 12E2 FE 02                       cpi     2               ; request parameter can only be 0 or 1
  3416 12E4 D0                          rnc
  3417 12E5 32 76 21                    sta     tparm_solicited
  3418                  ; If they are allowed, exiting SET-UP will send a TPARM report
  3419                  setup_tparm:
  3420 12E8 21 72 21                    lxi     h,pending_report
  3421 12EB 7E                          mov     a,m
  3422 12EC F6 02                       ori     pend_tparm
  3423 12EE 77                          mov     m,a
  3424 12EF C9                          ret
  3425                  ;
  3426                  ; tparm_report
  3427                  ;
  3428                  ;       Produce a DECREPTPARM sequence:
  3429                  ;       ESC [ <sol<; <par<; <nbits<; <xspeed<; <rspeed<; <clkmul<; <flags< x
  3430                  ;
  3431 12F0 3E FD       tparm_report:   mvi     a,~pend_tparm
  3432 12F2 CD BF 0D                    call    prepare_report
  3433 12F5 3A 76 21                    lda     tparm_solicited ; A <- solicited flag (0 or 1)
  3434 12F8 F6 32                       ori     '2'             ; convert to ASCII digit '2' or '3'
  3435 12FA CD B9 0D                    call    rep_char_semi   ; <sol<
  3436 12FD 3A A9 21                    lda     setup_b4        ; A <- comms + power settings
  3437 1300 F5                          push    psw             ; save for later, when we'd like bits/char
  3438 1301 E6 C0                       ani     0c0h            ; keep just parity bits
  3439 1303 F6 18                       ori     18h             ; mix in half an ASCII '0'
  3440 1305 87                          add     a               ; shift even/odd into carry
  3441 1306 3C                          inr     a               ; and convert "no parity" into '1'
  3442 1307 F2 10 13                    jp      skip_odd_even   ; skip to display if parity is off (new bit 7)
  3443 130A 1F                          rar                     ; bring carry back into bit 7
  3444 130B 07                          rlc                     ; and rotate it into bit 0
  3445 130C F6 04                       ori     4               ; making odd parity '4' and even '5'|80h
  3446 130E E6 7F                       ani     7fh             ; clean up that high bit
  3447 1310 CD B9 0D    skip_odd_even:  call    rep_char_semi   ; <par<
  3448 1313 06 31                       mvi     b,'1'           ; prepare "8 bits" answer
  3449 1315 F1                          pop     psw             ; A <- comms + power settings again
  3450 1316 E6 20                       ani     20h             ; bit 5 is zero for "7 bits"
  3451 1318 C2 1C 13                    jnz     skip_bit_adj    ; display if we've got right answer already
  3452 131B 04                          inr     b               ; change to '2' for "7 bits"
  3453 131C 78          skip_bit_adj:   mov     a,b
  3454 131D CD B9 0D                    call    rep_char_semi   ; <nbits<
  3455 1320 3A AB 21                    lda     tx_spd
  3456 1323 0F                          rrc
  3457 1324 CD 7A 0D                    call    ascii_decimal   ; <xspeed<
  3458 1327 CD BB 0D                    call    report_semi
  3459 132A 3A AC 21                    lda     rx_spd
  3460 132D 0F                          rrc
  3461 132E CD 7A 0D                    call    ascii_decimal   ; <rspeed<
  3462 1331 CD BB 0D                    call    report_semi
  3463 1334 3E 31                       mvi     a,'1'           ; <clkmul< is a constant
  3464 1336 CD B9 0D                    call    rep_char_semi
  3465 1339 3A AA 21                    lda     setup_b5        ; report STP switch values
  3466 133C E6 F0                       ani     0f0h            ; as with all other setup blocks, only top 4 bits
  3467 133E 0F                          rrc
  3468 133F 0F                          rrc
  3469 1340 0F                          rrc
  3470 1341 0F                          rrc                     ; which we shift down to report as number 0 to 15
  3471 1342 CD 7A 0D                    call    ascii_decimal
  3472 1345 36 F8                       mvi     m,'x'|80h       ; 'x' for DECREPTPARM sequence final
  3473 1347 C3 F3 0E                    jmp     send_report
  3474                  ;
  3475                  ;       DECSWL - single-width-line
  3476                  line_attr_swl:
  3477 134A 21 57 21                    lxi     h,curr_line_dbl
  3478 134D 7E                          mov     a,m
  3479 134E B7                          ora     a
  3480 134F C8                          rz                      ; Nothing to do if current line is already single width
  3481 1350 CD 91 11                    call    wait_for_x
  3482 1353 21 13 21                    lxi     h,latofs
  3483 1356 3A F9 20                    lda     curs_row        ; Retrieve logical line number entry
  3484 1359 85                          add     l
  3485 135A 6F                          mov     l,a
  3486 135B 7E                          mov     a,m
  3487 135C E6 7F                       ani     7fh             ; Zero bit 7, the double width indicator
  3488 135E 77                          mov     m,a             ; place back
  3489 135F CD 8D 13                    call    curs_phl_addr   ; HL <- screen address of cursor row
  3490 1362 3A 50 20                    lda     screen_cols
  3491 1365 0F                          rrc
  3492 1366 47                          mov     b,a             ; B <- half number of columns
  3493 1367 CD DE 13                    call    add_a_to_hl     ; Move halfway across line, to where the "half" terminator is
  3494 136A AF                          xra     a
  3495 136B 77          zap_2nd_half:   mov     m,a             ; Remove terminator, DMA address and all following characters
  3496 136C 23                          inx     h
  3497 136D 05                          dcr     b
  3498 136E C2 6B 13                    jnz     zap_2nd_half
  3499 1371 3E F0                       mvi     a,0f0h
  3500 1373 32 0E 21                    sta     saved_curs_row
  3501 1376 CD 95 13                    call    add_line_attrs
  3502 1379 AF                          xra     a
  3503 137A 32 57 21                    sta     curr_line_dbl
  3504 137D C3 36 16                    jmp     move_updates
  3505                  ;
  3506                  ; curs_phl_loc
  3507 1380 3A F9 20    curs_phl_loc:   lda     curs_row
  3508 1383 CD E6 13                    call    phl_num
  3509                  ; phl_loc
  3510                  ;       Given a physical line number in A, look up the address of this line
  3511                  ;       in an address table (doubling and adding, so we're returning a proper
  3512                  ;       address in HL).
  3513 1386 21 C2 20    phl_loc:        lxi     h,pline_addr
  3514 1389 87                          add     a
  3515 138A 85                          add     l               ; [* fragile *] pline_addr table must not go over 8-bit boundary
  3516 138B 6F                          mov     l,a
  3517 138C C9                          ret
  3518                  ;
  3519                  ; curs_phl_addr
  3520                  ;
  3521                  ;       Get the address of the physical line in screen RAM of the cursor row
  3522                  ;       Returns this in HL.
  3523                  ;
  3524 138D CD 80 13    curs_phl_addr:  call    curs_phl_loc
  3525                  ld_hl_from_hl:
  3526 1390 7E                          mov     a,m
  3527 1391 23                          inx     h
  3528 1392 66                          mov     h,m
  3529 1393 6F                          mov     l,a
  3530 1394 C9                          ret
  3531                  ;
  3532                  ; add_line_attrs
  3533                  ;
  3534                  ;       Apply the line attributes in A to the current (cursor) row. This involves finding the start address
  3535                  ;       of the previous line and marching across to its terminating bytes, because they determine the attributes
  3536                  ;       for this line, as well as the address. Two wrinkles:
  3537                  ;
  3538                  ;       1) If this is the first line, then we have to modify the attributes as stored in the initial
  3539                  ;          screen layout (at address line1_dma)
  3540                  ;       2) LATOFS stores the physical line number with bit 7 high if that line (the previous one, in our case)
  3541                  ;          is double-wdith. In that case, the terminators have been placed half across the line, instead of
  3542                  ;          screen width columns across.
  3543                  ;
  3544 1395 E6 70       add_line_attrs: ani     70h             ; Censor attributes (don't affect scrolling region, for example)
  3545 1397 47                          mov     b,a
  3546 1398 3A F9 20                    lda     curs_row        ; It's the terminating bytes for the previous line that decides
  3547 139B 3D                          dcr     a               ; the attributes (and address) for this one
  3548 139C 21 04 20                    lxi     h,line1_dma     ; Set up for initial line address if we were at row 0 anyway
  3549 139F FA BD 13                    jm      set_attrs       ; yep, we were
  3550 13A2 CD E6 13                    call    phl_num         ; As we weren't at beginning, grab physical line number from LATOFS
  3551 13A5 57                          mov     d,a             ; D <- physical line number
  3552 13A6 E6 7F                       ani     7fh             ; Get rid of any double line marker
  3553 13A8 CD 86 13                    call    phl_loc         ; HL <- address in physical line table
  3554 13AB CD 90 13                    call    ld_hl_from_hl   ; HL <- start of line in screen RAM
  3555 13AE 3A 50 20                    lda     screen_cols
  3556 13B1 0F                          rrc                     ; A <- screen width / 2
  3557 13B2 5F                          mov     e,a             ; E <- screen width / 2
  3558 13B3 23                          inx     h               ; 
  3559 13B4 14                          inr     d               ; 
  3560 13B5 15                          dcr     d               ; set flags for phys line number from LATOFS (i.e. was it doubled?)
  3561 13B6 16 00                       mvi     d,0             ; Set up DE to be offset of half-way across line
  3562 13B8 19                          dad     d
  3563 13B9 FC BD 13                    cm      set_attrs       ; If this line was double-width, do middle first (CALL)
  3564 13BC 19                          dad     d               ; and then (or just) the end of line
  3565 13BD 7E          set_attrs:      mov     a,m             ; A <- first (high) byte of DMA address + attrs
  3566 13BE E6 8F                       ani     8fh             ; Remove line attrs, keeping scroll flags + address
  3567 13C0 B0                          ora     b               ; Place new line attributes
  3568 13C1 77                          mov     m,a             ; Store new high byte of DMA address
  3569 13C2 C9                          ret
  3570                  ;
  3571                  ; On entry (from double_line at least), HL is the screen address of start of current line
  3572                  ; This routine halves the length of the line by moving this line's terminator and next screen address to half way.
  3573 13C3 3A 50 20    halve_line:     lda     screen_cols
  3574 13C6 0F                          rrc                     ; A <- screen width / 2
  3575 13C7 54                          mov     d,h             ; 
  3576 13C8 5D                          mov     e,l             ; DE <- start of current line
  3577 13C9 CD DE 13                    call    add_a_to_hl     ; HL is half way across line
  3578 13CC EB                          xchg                    ; DE <- half way, HL <- start
  3579 13CD CD DB 13                    call    add_cols_to_hl  ; HL <- addr of right margin + 1 (terminator)
  3580 13D0 06 03                       mvi     b,3
  3581 13D2 7E          end_byte_loop:  mov     a,m             ; get post-EOL bytes (terminator first, then two screen addresses)
  3582 13D3 12                          stax    d               ; place half way
  3583 13D4 13                          inx     d
  3584 13D5 23                          inx     h
  3585 13D6 05                          dcr     b
  3586 13D7 C2 D2 13                    jnz     end_byte_loop
  3587 13DA C9                          ret
  3588                  ;
  3589 13DB 3A 50 20    add_cols_to_hl: lda     screen_cols
  3590 13DE 85          add_a_to_hl:    add     l
  3591 13DF 6F                          mov     l,a
  3592 13E0 D0                          rnc
  3593 13E1 24                          inr     h
  3594 13E2 C9                          ret
  3595                  ;
  3596                  ; curs_phl_num
  3597                  ;       Return physical line number of cursor row in A
  3598                  ; phl_num
  3599                  ;       Return physical line number of logical row A, in A
  3600                  ;       
  3601 13E3 3A F9 20    curs_phl_num:   lda     curs_row
  3602                  ;
  3603 13E6 21 13 21    phl_num:        lxi     h,latofs
  3604 13E9 85                          add     l
  3605 13EA 6F                          mov     l,a
  3606 13EB 7E                          mov     a,m
  3607 13EC C9                          ret
  3608                  ;
  3609                  ;       RM - reset modes
  3610 13ED 0E 00       rm_action:      mvi     c,0
  3611 13EF C3 F4 13                    jmp     do_modes
  3612                  ;
  3613                  ;       SM - set modes
  3614 13F2 0E FF       sm_action:      mvi     c,0ffh
  3615                  ;
  3616                  ;       At this point, we are either setting (C = 0ffh) or resetting (C = 0) modes. The mode number is in B.
  3617 13F4 3A B8 21    do_modes:       lda     csi_private
  3618 13F7 B7                          ora     a
  3619 13F8 21 26 14                    lxi     h,ansi_mode_t   ; We're dealing with ANSI modes
  3620 13FB CA 04 14                    jz      mode_lookup     ;   if there isn't a private character
  3621 13FE FE 3F                       cpi     '?'             ; But if the private character isn't '?', quit
  3622 1400 C0                          rnz
  3623 1401 21 0A 14                    lxi     h,dec_mode_t    ; Look up DEC private modes
  3624 1404 78          mode_lookup:    mov     a,b             ; A <- mode number
  3625 1405 41                          mov     b,c             ; B <- set/reset
  3626 1406 CD FF 09                    call    find_action_a
  3627 1409 C9                          ret
  3628                  ; JUMP TABLE
  3629 140A 01          dec_mode_t:     db      1               ; DECCKM - cursor key
  3630 140B 2A 14                       dw      decckm_mode
  3631 140D 02                          db      2               ; DECANM - ANSI/VT52
  3632 140E 2F 14                       dw      decanm_mode
  3633 1410 03                          db      3               ; DECCOLM - column
  3634 1411 57 14                       dw      deccolm_mode
  3635 1413 04                          db      4               ; DECSCLM - scrolling
  3636 1414 47 14                       dw      decsclm_mode
  3637 1416 05                          db      5               ; DECSCNM - screen
  3638 1417 4E 14                       dw      decscnm_mode
  3639 1419 06                          db      6               ; DECOM - origin
  3640 141A 3F 14                       dw      decom_mode
  3641 141C 07                          db      7               ; DECAWM - auto wrap
  3642 141D 66 14                       dw      decawm_mode
  3643 141F 08                          db      8               ; DECARM - auto repeating
  3644 1420 5F 14                       dw      decarm_mode
  3645 1422 09                          db      9               ; DECINLM - interlace
  3646 1423 6D 14                       dw      decinlm_mode
  3647 1425 00                          db      0               ; end of table
  3648                  ;
  3649                  ;       Only a single ANSI mode can be set/reset: LNM - line feed new line mode
  3650 1426 14          ansi_mode_t:    db      20
  3651 1427 38 14                       dw      lnm_mode
  3652 1429 00                          db      0               ; end of table
  3653
  3654 142A 21 BC 21    decckm_mode:    lxi     h,mode_ckm
  3655 142D 70                          mov     m,b
  3656 142E C9                          ret
  3657                  ;
  3658 142F CD 76 14    decanm_mode:    call    apply_mask_sp
  3659 1432 A7 21                       dw      setup_b2
  3660 1434 20                          db      sb2_ansi
  3661 1435 C3 F2 0B                    jmp     set_charsets
  3662
  3663 1438 CD 76 14    lnm_mode:       call    apply_mask_sp
  3664 143B A8 21                       dw      setup_b3
  3665 143D 20                          db      sb3_newline
  3666 143E C9                          ret
  3667
  3668                  ; decom_mode [* BUG *]
  3669                  ;       This routine changes origin mode and sends the cursor to the home position.
  3670                  ;       However, this routine contains a bug because it calls cursor_home, and that routine
  3671                  ;       clears the first two CSI parameters before dropping into the general cursor positioning routine.
  3672                  ;
  3673                  ;       That means that, if DECOM is the first mode in an RM or SM sequence, the second mode will be
  3674                  ;       cleared by cursor_home and won't be executed. This can be shown by using a highly visible change
  3675                  ;       for the second parameter, such as DECSCNM (screen mode - light or dark).
  3676                  ;
  3677                  ;       For example:
  3678                  ;       1. Go into reverse screen mode: ESC [ ? 5 h
  3679                  ;       2. Go into normal screen mode:  ESC [ ? 5 l
  3680                  ;       3. Set origin mode and reverse screen mode: ESC [ ? 6 ; 5 h
  3681                  ;
  3682                  ;       The third sequence will completely ignore DECSCNM (private mode 5). However, third and
  3683                  ;       subsequent modes will still be processed, so this works:
  3684                  ;
  3685                  ;       4. Set origin mode and reverse screen mode: ESC [ ? 6 ; ; 5 h
  3686                  ;
  3687 143F 21 01 21    decom_mode:     lxi     h,origin_mode
  3688 1442 70                          mov     m,b
  3689 1443 CD 48 18                    call    cursor_home     ; *BUG* (see cursor_home)
  3690 1446 C9                          ret
  3691                  ;
  3692 1447 CD 76 14    decsclm_mode:   call    apply_mask_sp
  3693 144A A6 21                       dw      setup_b1
  3694 144C 80                          db      sb1_smooth
  3695 144D C9                          ret
  3696
  3697 144E CD 76 14    decscnm_mode:   call    apply_mask_sp
  3698 1451 A6 21                       dw      setup_b1
  3699 1453 20                          db      sb1_lightback
  3700 1454 C3 6B 03                    jmp     update_dc012
  3701
  3702 1457 21 A2 21    deccolm_mode:   lxi     h,columns_132
  3703 145A 70                          mov     m,b
  3704 145B CD 81 03                    call    clear_display
  3705 145E C9                          ret
  3706                  ;
  3707 145F CD 76 14    decarm_mode:    call    apply_mask_sp
  3708 1462 A6 21                       dw      setup_b1
  3709 1464 40                          db      sb1_autorep
  3710 1465 C9                          ret
  3711
  3712 1466 CD 76 14    decawm_mode:    call    apply_mask_sp
  3713 1469 A8 21                       dw      setup_b3
  3714 146B 40                          db      sb3_autowrap
  3715 146C C9                          ret
  3716
  3717 146D CD 76 14    decinlm_mode:   call    apply_mask_sp
  3718 1470 A8 21                       dw      setup_b3
  3719 1472 10                          db      sb3_interlace
  3720 1473 C3 42 03                    jmp     update_dc011
  3721
  3722                  ; apply_mask_sp
  3723                  ;       The stacked return address is the address of a setup switch block and the following byte
  3724                  ;       is a bit mask that must either be set (B = 0ffh) or reset (B = 0).
  3725                  ;
  3726 1476 E1          apply_mask_sp:  pop     h               ; HL <- return address
  3727 1477 5E                          mov     e,m             ; E <- byte after
  3728 1478 23                          inx     h               ;
  3729 1479 56                          mov     d,m             ; D <- byte after that
  3730 147A 23                          inx     h               ;
  3731 147B 7E                          mov     a,m             ; A <- mask value
  3732 147C 23                          inx     h               ; increment hl again, to make new return address
  3733 147D EB                          xchg                    ; DE <--< HL (DE is now return address)
  3734 147E 4F                          mov     c,a             ; C <- mask value
  3735 147F 2F                          cma                     ; A <- all the other bits set
  3736 1480 A6                          ana     m               ; clear our bit in location
  3737 1481 77                          mov     m,a             ; and write it back
  3738 1482 78                          mov     a,b             ; get all ones if we'd like to set, or all zeros to reset
  3739 1483 A1                          ana     c               ; either mask for set, or zero for reset
  3740 1484 B6                          ora     m               ; (we already reset the mode, so now possibly set it)
  3741 1485 77                          mov     m,a             ; apply to location
  3742 1486 EB                          xchg                    ; HL <- return address again
  3743 1487 E9                          pchl                    ; return
  3744                  ;
  3745 1488 CD 93 14    keyboard_tick:  call    update_kbd
  3746 148B E6 10                       ani     10h             ; Probably intended to prevent processing when keyboard locked,
  3747 148D CC AA 06                    cz      process_keys    ; but see notes below.
  3748 1490 C3 59 0E                    jmp     try_report
  3749                  ;
  3750                  ; If the keyboard is ready to receive and isn't locked, make up the status byte to transmit
  3751                  ; to it. Then do the cursor timer housekeeping.
  3752                  ; Return value:
  3753                  ;       If keyboard is not ready to receive, A is 0
  3754                  ;       If UNWRIT_X2077 had a purpose, that value would be returned in A
  3755                  ;       If smooth scrolling or pending scroll, A will be 1 or -1 (direction of scroll)
  3756                  ;       Otherwise it will be the OR of the high and low bytes of the cursor timer.
  3757                  ;
  3758                  ;       Because the keyboard locked flag in the keyboard status is 10h, it feels as if this routine
  3759                  ;       was originally intended to return when the keyboard was locked, which would naturally mean
  3760                  ;       that there was point calling process_keys.
  3761                  ;
  3762 1493 DB 42       update_kbd:     in      ior_flags
  3763 1495 E6 80                       ani     iob_flags_kbd
  3764 1497 C8                          rz                      ; Keyboard not ready to receive
  3765 1498 21 44 21                    lxi     h,keyboard_locked
  3766 149B 7E                          mov     a,m
  3767 149C B7                          ora     a
  3768 149D CA A2 14                    jz      kbd_not_locked
  3769 14A0 3E 10                       mvi     a,iow_kbd_locked
  3770 14A2 21 A5 21    kbd_not_locked: lxi     h,local_mode
  3771 14A5 B6                          ora     m               ; Local mode is 20h, to set LOCAL LED
  3772 14A6 21 45 21                    lxi     h,led_state     ; Make up a keyboard command from all status bytes
  3773 14A9 B6                          ora     m
  3774 14AA 23                          inx     h               ; HL <- kbd_online_mask (can also affect click if bell is sounding)
  3775 14AB B6                          ora     m
  3776 14AC 23                          inx     h               ; HL <- kbd_click_mask
  3777 14AD B6                          ora     m
  3778 14AE 36 00                       mvi     m,0             ; zero "click" after use: kbd_click_mask <- 0
  3779 14B0 23                          inx     h               ; HL <- kbd_scan_mask
  3780 14B1 B6                          ora     m
  3781 14B2 36 00                       mvi     m,0             ; zero "start scan" after use: kbd_scan_mask <- 0
  3782 14B4 D3 82                       out     iow_keyboard
  3783 14B6 21 74 20                    lxi     h,num_kbd_updates
  3784 14B9 34                          inr     m
  3785 14BA 3A 77 20                    lda     UNWRIT_X2077
  3786 14BD B7                          ora     a
  3787 14BE C0                          rnz
  3788 14BF 3A 65 20                    lda     smooth_scroll   ; are we in middle of smooth scroll?
  3789 14C2 21 51 20                    lxi     h,scroll_pending; or are we about to start one?
  3790 14C5 B6                          ora     m
  3791 14C6 C0                          rnz                     ; exit if either scroll condition is true
  3792 14C7 2A 2D 21                    lhld    cursor_timer    ; Maintain cursor timer 
  3793 14CA 2B                          dcx     h
  3794 14CB 7C                          mov     a,h
  3795 14CC B5                          ora     l
  3796 14CD CA D4 14                    jz      curs_timer_up
  3797 14D0 22 2D 21                    shld    cursor_timer
  3798 14D3 C9                          ret
  3799                  ;
  3800                  ; Cursor time has counted down to zero
  3801 14D4 3A BA 21    curs_timer_up:  lda     cursor_visible  ; toggle cursor visibility
  3802 14D7 EE FF                       xri     0ffh
  3803 14D9 32 BA 21                    sta     cursor_visible
  3804 14DC 21 12 02                    lxi     h,0212h         ; Cursor will be on for about 2/3 second
  3805 14DF C2 E5 14                    jnz     curs_was_off
  3806 14E2 21 09 01                    lxi     h,0109h         ; Cursor will be off for about 1/3 second
  3807 14E5 22 2D 21    curs_was_off:   shld    cursor_timer    ; Set new timer
  3808 14E8 2A F6 20                    lhld    cursor_address
  3809 14EB 46                          mov     b,m             ; B <- current char at cursor position
  3810 14EC 3A 59 21                    lda     curs_char_rend  ; A <- 0x80 or 0, depending on whether we're using base rendition
  3811 14EF A8                          xra     b               ; Flip bit, if bit fit to flip
  3812 14F0 77                          mov     m,a             ; Place back on screen
  3813 14F1 11 00 10                    lxi     d,1000h         ; Make an address in attribute RAM
  3814 14F4 19                          dad     d
  3815 14F5 3A 5A 21                    lda     curs_attr_rend  ; Again, flip cursor rendition
  3816 14F8 AE                          xra     m
  3817 14F9 77                          mov     m,a             ; and replace
  3818 14FA C9                          ret
  3819                  ;
  3820 14FB 74                          db      74h             ; CHECKSUM
  3821                  ;
  3822 14FC 78          el_action:      mov     a,b             ; A <- selective parameter
  3823 14FD B7                          ora     a
  3824 14FE CA 15 15                    jz      el_to_end       ; default is "erase from cursor to end of line"
  3825 1501 3D                          dcr     a
  3826 1502 CA 0A 15                    jz      el_to_start     ; Ps = 1 means "beginning of line to cursor"
  3827 1505 3D                          dcr     a
  3828 1506 C0                          rnz                     ; No options beyond Ps = 2
  3829 1507 CD 15 15                    call    el_to_end       ; Ps = 2 means "erase whole line"
  3830 150A 3A F8 20    el_to_start:    lda     curs_col
  3831 150D 47                          mov     b,a
  3832 150E 04                          inr     b               ; number of locations is cursor column (0-based) + 1
  3833 150F 2A 4E 21                    lhld    start_line_addr ; from the start of the line 
  3834 1512 C3 29 15                    jmp     blank_chars
  3835                  ;
  3836 1515 3A F8 20    el_to_end:      lda     curs_col
  3837 1518 47                          mov     b,a
  3838 1519 3A 57 21                    lda     curr_line_dbl
  3839 151C B7                          ora     a               ; NZ flag if current line is double width
  3840 151D 3A 50 20                    lda     screen_cols
  3841 1520 CA 24 15                    jz      not_double
  3842 1523 0F                          rrc                     ; divide columns by 2, on double width line
  3843 1524 90          not_double:     sub     b
  3844 1525 47                          mov     b,a             ; B <- screen columns - cursor column
  3845 1526 2A F6 20                    lhld    cursor_address
  3846                  ;
  3847                  ; blank the number of characters in B, from the screen RAM position in HL
  3848                  ;
  3849 1529 7C          blank_chars:    mov     a,h             ; Convert screen address in HL
  3850 152A C6 10                       adi     10h             ; to attribute RAM address in DE
  3851 152C 57                          mov     d,a
  3852 152D 5D                          mov     e,l
  3853 152E 3E FF                       mvi     a,0ffh          ; default rendition
  3854 1530 12          blank_loop:     stax    d               ; blank attributes
  3855 1531 36 00                       mvi     m,0             ; blank screen
  3856 1533 23                          inx     h               ; next addresses in screen RAM
  3857 1534 13                          inx     d               ; and attribute RAM
  3858 1535 05                          dcr     b
  3859 1536 C2 30 15                    jnz     blank_loop
  3860 1539 32 F5 20                    sta     rend_und_curs   ; default rendition is 0ffh
  3861 153C AF                          xra     a
  3862 153D 32 F4 20                    sta     char_und_curs   ; and character under cursor is blank, 00h
  3863 1540 C9                          ret
  3864                  ;
  3865                  ; ed_action
  3866                  ;
  3867                  ;       Implements the ED (erase in display) control sequence, which has a single selective parameter
  3868                  ;
  3869                  ;       Lines that are wholly erased are made single width. The cursor line's width attribute is only
  3870                  ;       changed if the cursor is in column 0 when "erase to end of screen" is invoked, or the entire
  3871                  ;       display is erased.
  3872                  ;
  3873 1541 78          ed_action:      mov     a,b             ; A <- selective parameter
  3874 1542 B7                          ora     a
  3875 1543 CA 6A 15                    jz      ed_to_end       ; Ps = null/0 is "erase from cursor to end of display"
  3876 1546 3D                          dcr     a
  3877 1547 CA 55 15                    jz      ed_to_start     ; Ps = 1 is "erase from start of screen to cursor"
  3878 154A 3D                          dcr     a
  3879 154B C0                          rnz                     ; No options beyond Ps = 2, which is "erase entire display"
  3880 154C CD 55 15                    call    ed_to_start
  3881 154F CD 6A 15                    call    ed_to_end
  3882 1552 C3 4A 13                    jmp     line_attr_swl
  3883                  ;
  3884                  ;
  3885 1555 21 F9 20    ed_to_start:    lxi     h,curs_row
  3886 1558 7E                          mov     a,m             ; A <- cursor row
  3887 1559 F5                          push    psw
  3888 155A 47                          mov     b,a             ; B <- cursor row
  3889 155B AF                          xra     a               ; 
  3890 155C 77                          mov     m,a             ; zero cursor row, for the sake of erase_n_lines
  3891 155D 0E FF                       mvi     c,-1            ; Pretend cursor row is -1
  3892 155F CD 86 15                    call    erase_n_lines
  3893 1562 F1                          pop     psw
  3894 1563 77                          mov     m,a             ; Now restore proper cursor row
  3895 1564 CD 74 10                    call    curs_line_addr  ; HL <- start of cursor line in screen RAM
  3896 1567 C3 0A 15                    jmp     el_to_start     ; this line does not have line attributes changed
  3897                  ;
  3898 156A 3A F8 20    ed_to_end:      lda     curs_col
  3899 156D B7                          ora     a               ; If the cursor is at the start of the line, the line is made
  3900 156E CC 4A 13                    cz      line_attr_swl   ; single width, otherwise not
  3901 1571 CD 15 15                    call    el_to_end
  3902 1574 21 F9 20                    lxi     h,curs_row
  3903 1577 7E                          mov     a,m             ; A <- cursor row (for restoration later)
  3904 1578 F5                          push    psw
  3905 1579 4F                          mov     c,a             ; C <- cursor row 
  3906 157A 3E 17                       mvi     a,23
  3907 157C 91                          sub     c               ; A <- number of complete lines to be erased
  3908 157D 47                          mov     b,a
  3909 157E CD 86 15                    call    erase_n_lines
  3910 1581 F1                          pop     psw
  3911 1582 77                          mov     m,a             ; restore original cursor row 
  3912 1583 C3 74 10                    jmp     curs_line_addr  ; correct relevant pointers
  3913                  ;
  3914                  ; erase_n_lines
  3915                  ;       On entry:       HL points to curs_row
  3916                  ;                       B is number of lines to erase (this may be zero)
  3917                  ;                       C is one less than the row number of the first line to be *wholly* erased
  3918 1586 05          erase_n_lines:  dcr     b
  3919 1587 F8                          rm                      ; done erasing whole lines
  3920 1588 0C                          inr     c               ; increment row number
  3921 1589 71                          mov     m,c             ; write cursor row
  3922 158A C5                          push    b
  3923 158B E5                          push    h
  3924 158C CD E3 13                    call    curs_phl_num    ; A <- physical line number of cursor row, HL <- LATOFS entry
  3925 158F E6 7F                       ani     7fh             ; ignore any double-width marker
  3926 1591 77                          mov     m,a             ; Erased lines become single width
  3927 1592 3E 70                       mvi     a,70h           ; single width line attributes
  3928 1594 CD 95 13                    call    add_line_attrs
  3929 1597 CD 74 10                    call    curs_line_addr  ; HL <- start of line in screen RAM
  3930 159A 3A 50 20                    lda     screen_cols
  3931 159D 47                          mov     b,a
  3932 159E CD 29 15                    call    blank_chars
  3933 15A1 E1                          pop     h
  3934 15A2 C1                          pop     b
  3935 15A3 C3 86 15                    jmp     erase_n_lines
  3936                  ;
  3937                  ;       Set top and bottom margins
  3938 15A6 0E 81       stbm_action:    mvi     c,81h
  3939 15A8 CD 8E 10                    call    wait_scroll
  3940 15AB CD BF 11                    call    last_row        ; B <- last row number
  3941 15AE 3A 30 21                    lda     csi_p1          ; A <- first parameter (top margin)
  3942 15B1 B7                          ora     a
  3943 15B2 CA B6 15                    jz      p1_def          ; If the first parameter is not zero (or zero by default)
  3944 15B5 3D                          dcr     a               ; reduce by one for internal numbering
  3945 15B6 57          p1_def:         mov     d,a             ; D <- top margin
  3946 15B7 3A 31 21                    lda     csi_p2          ; A <- second parameter (bottom margin)
  3947 15BA B7                          ora     a               ; Is the bottom margin defaulted, or zero?
  3948 15BB C2 C0 15                    jnz     supp_b          ; No, use it
  3949 15BE 78                          mov     a,b             ; else make it the bottom of the screen
  3950 15BF 3C                          inr     a
  3951 15C0 3D          supp_b:         dcr     a
  3952 15C1 5F                          mov     e,a             ; E <- bottom margin
  3953 15C2 78                          mov     a,b             ; A <- last row
  3954 15C3 BB                          cmp     e               ; If last row < bottom margin
  3955 15C4 D8                          rc                      ; don't do anything
  3956 15C5 7A                          mov     a,d             ; A <- top margin
  3957 15C6 BB                          cmp     e               ; If top margin < bottom margin
  3958 15C7 D0                          rnc                     ; don't do anything
  3959 15C8 21 55 21                    lxi     h,top_margin
  3960 15CB 72                          mov     m,d             ; store top margin
  3961 15CC 23                          inx     h
  3962 15CD 73                          mov     m,e             ; store bottom margin
  3963 15CE 4B                          mov     c,e             ; C <- bottom margin
  3964 15CF 42                          mov     b,d             ; B <- top margin
  3965 15D0 79                          mov     a,c
  3966 15D1 90                          sub     b
  3967 15D2 3C                          inr     a               ; A <- bottom - top + 1
  3968 15D3 5F                          mov     e,a             ; E <- lines in scrolling region
  3969 15D4 21 04 20                    lxi     h,line1_dma
  3970 15D7 78                          mov     a,b             ; A <- top margin
  3971 15D8 B7                          ora     a
  3972 15D9 CA E8 15                    jz      n_scroll_reg    ; jump if top margin is top row 
  3973 15DC 50                          mov     d,b             ; D <- top margin
  3974 15DD 7E          top_mar_loop:   mov     a,m             ; A <- high byte of DMA address 
  3975 15DE E6 7F                       ani     7fh             ; remove scroll region flag
  3976 15E0 77                          mov     m,a             ; put back
  3977 15E1 CD 2B 16                    call    advance_line
  3978 15E4 15                          dcr     d
  3979 15E5 C2 DD 15                    jnz     top_mar_loop    ; follow line links until we're at first line of new scroll region
  3980 15E8 7E          n_scroll_reg:   mov     a,m             ; A <- high byte of DMA address
  3981 15E9 F6 80                       ori     80h             ; add scroll region flag
  3982 15EB 77                          mov     m,a             ; put back
  3983 15EC CD 2B 16                    call    advance_line
  3984 15EF 1D                          dcr     e
  3985 15F0 C2 E8 15                    jnz     n_scroll_reg
  3986 15F3 3E 17                       mvi     a,23
  3987 15F5 91                          sub     c               ; A <- number of non-scrollable lines at bottom
  3988 15F6 CA 05 16                    jz      done_lines
  3989 15F9 57                          mov     d,a             ; D <- line count
  3990 15FA 7E          bot_mar_loop:   mov     a,m             ; A <- high byte of DMA address
  3991 15FB E6 7F                       ani     7fh             ; remove scroll region flag
  3992 15FD 77                          mov     m,a             ; put back
  3993 15FE CD 2B 16                    call    advance_line
  3994 1601 15                          dcr     d
  3995 1602 C2 FA 15                    jnz     bot_mar_loop
  3996 1605 78          done_lines:     mov     a,b             ; A <- top margin
  3997 1606 32 55 21                    sta     top_margin      ; store
  3998 1609 79                          mov     a,c             ; A <- bottom margin
  3999 160A 32 56 21                    sta     bottom_margin   ; store
  4000 160D 21 13 21                    lxi     h,latofs        ; Now work our way through the logical address offset table,
  4001 1610 0E 18                       mvi     c,24            ; halving each line again, if appropriate. This way, we grab
  4002                                                          ; the terminating line attributes at the full length of each line,
  4003                                                          ; which we've just modified, to re-copy them into the middle of each
  4004                                                          ; double-width line.
  4005 1612 7E          half_line_loop: mov     a,m             ; Grab physical number of logical line 
  4006 1613 B7                          ora     a               ; 
  4007 1614 F2 23 16                    jp      skip_single     ; if it isn't double width, skip it
  4008 1617 E5                          push    h
  4009 1618 CD 86 13                    call    phl_loc         ; HL <- addresss of pline_addr entry for this line
  4010 161B 7E                          mov     a,m
  4011 161C 23                          inx     h
  4012 161D 66                          mov     h,m
  4013 161E 6F                          mov     l,a             ; HL <- start of screen RAM for line
  4014 161F CD C3 13                    call    halve_line
  4015 1622 E1                          pop     h
  4016 1623 23          skip_single:    inx     h
  4017 1624 0D                          dcr     c
  4018 1625 C2 12 16                    jnz     half_line_loop
  4019 1628 C3 48 18                    jmp     cursor_home
  4020                  ;
  4021                  ; advance_line
  4022                  ;       On entry: HL points to high byte of DMA address, A contains that byte
  4023                  ;       On exit:  HL points to high byte of next DMA address
  4024                  ;
  4025 162B 23          advance_line:   inx     h               ; HL points to low byte of DMA address
  4026 162C 6E                          mov     l,m             ; L <- low byte
  4027 162D E6 0F                       ani     0fh             ; A <- high byte already, which
  4028 162F F6 20                       ori     20h             ; we convert into proper address (i.e. discard line attributes)
  4029 1631 67                          mov     h,a             ; HL <- points to first character of next line
  4030 1632 23                          inx     h               ; and increment
  4031 1633 C3 DB 13                    jmp     add_cols_to_hl  ; so on return HL <- high byte of next DMA address
  4032                  ;
  4033                  ; move_updates
  4034                  ;       This routine is called any time that cursor row or column have been updated. This has a lot of work to do:
  4035                  ;       1. Place the old character and rendition where the cursor was
  4036                  ;       2. Possibly adjusting cursor position if we've moved to a double-width line
  4037                  ;       3. Triggering margin bell if this movement was caused by receiving printable characters
  4038                  ;       4. Saving the character and rendition of the new cursor position.
  4039                  ;
  4040 1636 21 0D 00    move_updates:   lxi     h,0dh           ; set a tiny cursor timer
  4041 1639 22 2D 21                    shld    cursor_timer
  4042 163C AF                          xra     a               ; and record cursor visibility as off
  4043 163D 32 BA 21                    sta     cursor_visible  ; so it will come back quickly after movement
  4044 1640 2A F6 20                    lhld    cursor_address
  4045 1643 3A F4 20                    lda     char_und_curs
  4046 1646 47                          mov     b,a             ; B <- char under cursor
  4047 1647 77                          mov     m,a             ; place char back on screen
  4048 1648 7C                          mov     a,h             ; move HL to point to attribute RAM
  4049 1649 C6 10                       adi     10h
  4050 164B 67                          mov     h,a
  4051 164C 3A F5 20                    lda     rend_und_curs
  4052 164F 77                          mov     m,a             ; place old rendition
  4053 1650 3A 50 20                    lda     screen_cols
  4054 1653 3D                          dcr     a
  4055 1654 5F                          mov     e,a             ; E <- last column number
  4056 1655 3A 7B 20                    lda     in_setup
  4057 1658 B7                          ora     a
  4058 1659 C2 7B 16                    jnz     skip_half_col   ; In setup, can't have double-width cursor line
  4059 165C 3A F9 20                    lda     curs_row
  4060 165F 21 0E 21                    lxi     h,saved_curs_row
  4061 1662 BE                          cmp     m
  4062 1663 CA 7F 16                    jz      same_row
  4063 1666 77                          mov     m,a             ; save the new row
  4064 1667 CD 74 10                    call    curs_line_addr
  4065 166A AF                          xra     a               ; We didn't get here by typing, so clear the flag
  4066 166B 32 54 21                    sta     margin_bell     ; that allows the margin bell to be triggered by movement
  4067 166E 21 57 21                    lxi     h,curr_line_dbl ; Is current line double width?
  4068 1671 7E                          mov     a,m
  4069 1672 B7                          ora     a
  4070 1673 CA 7B 16                    jz      skip_half_col   ; No, so last column number is correct
  4071 1676 7B                          mov     a,e             ; Double-width, so halve last column number (e.g. 79 -< 39)
  4072 1677 3D                          dcr     a
  4073 1678 B7                          ora     a               ; clear carry for the rotate
  4074 1679 1F                          rar
  4075 167A 5F                          mov     e,a
  4076 167B 7B          skip_half_col:  mov     a,e             ; A <- last column number (adjusted, if necessary, for double width)
  4077 167C 32 53 21                    sta     right_margin
  4078 167F 21 53 21    same_row:       lxi     h,right_margin  ; test if cursor has moved beyond right margin
  4079 1682 11 F8 20                    lxi     d,curs_col
  4080 1685 1A                          ldax    d               ; A <- cursor column
  4081 1686 BE                          cmp     m
  4082 1687 DA 8C 16                    jc      less_rmargin    ; jump if less than margin
  4083 168A 7E                          mov     a,m             ; otherwise place margin
  4084 168B 12                          stax    d               ; into current column
  4085 168C 3A BB 21    less_rmargin:   lda     last_curs_col
  4086 168F C6 08                       adi     8               ; Margin bell is triggered exactly 8 columns from right edge
  4087 1691 96                          sub     m
  4088 1692 C2 A1 16                    jnz     no_margin_bell
  4089 1695 23                          inx     h               ; HL <- margin_bell
  4090 1696 B6                          ora     m               ; A was zero, so now NZ if we want a margin bell
  4091 1697 CA A1 16                    jz      no_margin_bell
  4092 169A CD 38 09                    call    c0_bell
  4093 169D AF                          xra     a               ; now don't permit another margin bell until typed char
  4094 169E 32 54 21                    sta     margin_bell     ; sets this flag again (see process_keys)
  4095 16A1 3A F8 20    no_margin_bell: lda     curs_col
  4096 16A4 32 BB 21                    sta     last_curs_col
  4097 16A7 2A 4E 21                    lhld    start_line_addr
  4098 16AA CD DE 13                    call    add_a_to_hl
  4099 16AD 22 F6 20                    shld    cursor_address  ; Make new cursor address
  4100 16B0 7E                          mov     a,m             ; Grab character "under" cursor
  4101 16B1 32 F4 20                    sta     char_und_curs   ; store it until cursor moves
  4102 16B4 47                          mov     b,a             ; B <- new character under cursor
  4103 16B5 7C                          mov     a,h             ; move HL to point to attribute RAM
  4104 16B6 C6 10                       adi     10h
  4105 16B8 67                          mov     h,a
  4106 16B9 7E                          mov     a,m             ; A <- rendition "under" cursor
  4107 16BA 32 F5 20                    sta     rend_und_curs   ; store it until cursor moves
  4108 16BD 2A F6 20                    lhld    cursor_address  ; HL <- new cursor address
  4109 16C0 70                          mov     m,b             ; place new/old character back
  4110 16C1 C9                          ret
  4111                  ;
  4112                  ; Entry point for CSI state, initialising parameters, private flag, intermediate
  4113                  ; and final characters
  4114 16C2 47          start_csi:      mov     b,a             ; B <- received char while we initialise state
  4115 16C3 21 F1 16                    lxi     h,gather_params ; private flag and params are first part of control sequence
  4116 16C6 22 40 21                    shld    char_action     ; set up processing for next character
  4117 16C9 AF                          xra     a
  4118 16CA 32 2F 21                    sta     param_value     ; zero out csi_param state
  4119 16CD 32 4B 21                    sta     num_params
  4120 16D0 32 7D 20                    sta     inter_chars
  4121                                  ; zero out the params array
  4122 16D3 21 30 21                    lxi     h,csi_params
  4123 16D6 0E 0F                       mvi     c,0fh
  4124 16D8 AF                          xra     a
  4125 16D9 77          zero_csi:       mov     m,a             ; zero this parameter
  4126 16DA 23                          inx     h
  4127 16DB 0D                          dcr     c
  4128 16DC C2 D9 16                    jnz     zero_csi
  4129                  ;
  4130 16DF 21 B8 21                    lxi     h,csi_private   ;
  4131 16E2 36 00                       mvi     m,0             ; mark that we haven't seen a private character
  4132 16E4 78                          mov     a,b             ; A <- received char
  4133 16E5 FE 40                       cpi     '@'             ; is it a final character?
  4134 16E7 D2 F2 16                    jnc     gather2         ; yes
  4135 16EA FE 3C                       cpi     '<'             ; less than flag character range?
  4136 16EC DA F2 16                    jc      gather2         ; yes, start building parameter value
  4137 16EF 77                          mov     m,a             ; store flag character
  4138 16F0 C9                          ret
  4139                  ;
  4140                  ; This is the entry point for all subsequent characters while we're in this state.
  4141                  ;
  4142                  ; This section has to deal with parameter values, which are sequences of ASCII digits '0' to '9',
  4143                  ; separated with ';' and recognising intermediate and final characters. Intermediates are from
  4144                  ; SP (20h) to '/' (2fh), Finals are from '@' (40h) to '~' (7eh) and most other characters will
  4145                  ; invalidate the sequence. Because the VT100 doesn't support any sequences with intermediates,
  4146                  ; it gathers them, along with other invalid characters, and only disposes of them when a Final
  4147                  ; character has been found and the sequence is executed.
  4148                  ;
  4149                  ; This section has a bug at the marked line. Any character other than a digit is supposed to be
  4150                  ; passed through to finish_param, which puts aside the current character while the gathered current
  4151                  ; numeric parameter (param_value) is placed at the end of the list (csi_params). Then, the current
  4152                  ; character is restored from the stack and we drop through to detect_i_f, to determine what to do
  4153                  ; with the current character. Characters above the digit range, like the semicolon or final characters,
  4154                  ; will be dealt with correctly here, but characters below digit range (intermediates) have already
  4155                  ; been corrupted by having 30h subtracted from their value, leaving them in the range 0f0h to 0ffh,
  4156                  ; and detect_i_f will incorrectly treat them as final characters, leading to immediate execution of
  4157                  ; the sequence, with a final character in this high range, which fails to match any of the tables.
  4158                  ;
  4159                  ; A valid but unsupported sequence like: ESC [ 9 ! p
  4160                  ; will be executed by the VT100 on reaching the '!', leaving the parser in "ground" state, which
  4161                  ; means that the final character "p" will be displayed on the screen.
  4162                  ;
  4163                  ; With this bug, the bytes from the label not_final to the next "ret" (17 bytes) are all unreachable.
  4164                  ;
  4165                  ; If the buggy line is replaced by:
  4166                  ;
  4167                  ;       cmp '0'
  4168                  ;
  4169                  ; then all the rest works as intended.
  4170                  ;
  4171 16F1 47          gather_params:  mov     b,a
  4172 16F2 78          gather2:        mov     a,b
  4173 16F3 FE 3A                       cpi     '9'+1           ; above digit range?
  4174 16F5 D2 17 17                    jnc     finish_param    ; then we've finished with this parameter (at least)
  4175 16F8 D6 30                       sui     '0'             ; convert to actual digit value [* BUG *]
  4176 16FA FA 17 17                    jm      finish_param    ; might have hit an intermediate, or something invalid
  4177 16FD 4F                          mov     c,a             ; C <- value of this digit
  4178 16FE 21 2F 21                    lxi     h,param_value
  4179 1701 7E                          mov     a,m             ; A <- current param value
  4180 1702 FE 1A                       cpi     1ah             ; if the current param value <= 26, multiplying by
  4181                                                          ; 10 would send us out of range,
  4182 1704 D2 13 17                    jnc     par_range       ; so limit it to 255
  4183                                                          ; otherwise, multiply current value by 10
  4184 1707 07                          rlc                     ; A <- param * 2
  4185 1708 47                          mov     b,a             ; B <- param * 2
  4186 1709 07                          rlc                     ; A <- param * 4
  4187 170A 07                          rlc                     ; A <- param * 8
  4188 170B 80                          add     b               ; A <- param * 8 + param * 2 (= param * 10)
  4189 170C DA 13 17                    jc      par_range       ; don't think this could be OOR now?
  4190 170F 81                          add     c               ; A <- param + current digit value
  4191 1710 D2 15 17                    jnc     par_store       ; but this could be OOR (e.g. 259)
  4192 1713 3E FF       par_range:      mvi     a,0ffh          ; out of range parameters are limited at 255
  4193 1715 77          par_store:      mov     m,a             ; store new param_value
  4194 1716 C9                          ret
  4195                                  ;
  4196                                  ; We've now collected a CSI parameter value in param_value (212fh)
  4197                                  ; and we want to store it in the list of parameters for this sequence.
  4198                                  ; We do this regardless of what we're going to do next, which might be to
  4199                                  ; start a new parameter, invalidate the sequence, or execute it.
  4200                                  ;       
  4201 1717 11 30 21    finish_param:   lxi     d,csi_params    ; base of csi_params array
  4202 171A F5                          push    psw             ; push the character we're working on
  4203 171B 21 4B 21                    lxi     h,num_params
  4204 171E 4E                          mov     c,m             ; retrieve offset into array
  4205 171F 06 00                       mvi     b,0
  4206 1721 EB                          xchg                    ; HL <- base of array, DE <- num_params
  4207 1722 09                          dad     b               ; HL <- addr of next param to store
  4208 1723 01 2F 21                    lxi     b,param_value
  4209 1726 0A                          ldax    b               ; A <- new param value
  4210 1727 77                          mov     m,a             ; store in array
  4211 1728 AF                          xra     a
  4212 1729 02                          stax    b               ; now we've stowed parameter, zero current value
  4213 172A 1A                          ldax    d               ; A <- num params (array offset)
  4214 172B FE 0F                       cpi     0fh             ; Limit ourselves to storing 16 params
  4215 172D CA 32 17                    jz      no_more_params  ; Have we just stored the 16th parameter?
  4216 1730 3C                          inr     a               ; Still OK
  4217 1731 12                          stax    d
  4218 1732 F1          no_more_params: pop     psw             ; A <- character we're working on
  4219                                  ;
  4220                                  ; This is a control sequence state where we're only collecting intermediates and
  4221                                  ; finals. This state also allows ';' parameter separators to appear, which would be
  4222                                  ; illegal after intermediate characters. However, the VT100 doesn't support any
  4223                                  ; sequences with intermediates, so this state can harmlessly use the next few lines
  4224                                  ; as part of two different states.
  4225                                  ;
  4226 1733 47          detect_i_f:     mov     b,a             ; B <- copy of original character
  4227 1734 FE 3B                       cpi     03bh            ; if it's a ';' separator, we're done for now
  4228 1736 C8                          rz
  4229 1737 E6 C0                       ani     0c0h            ; Control sequence finals are 040h to 07fh
  4230 1739 78                          mov     a,b
  4231 173A CA 43 17                    jz      not_final       ; not a final character
  4232 173D 32 7E 20                    sta     final_char
  4233 1740 C3 A2 0A                    jmp     execute_seq
  4234                                  ;
  4235                                  ; At this stage, we know the current character didn't form part of a parameter value
  4236                                  ; so, having tidied away the last parameter value, we now know that we're not
  4237                                  ; dealing with a parameter separator or a final character. This could only be an
  4238                                  ; intermediate or an illegal character (e.g. ':') or a character out of place, such
  4239                                  ; as another private flag. We'll untangle these later but for now we'll add them
  4240                                  ; into the intermediate store and change the action to collect the rest of the
  4241                                  ; sequence until a final arrives, without storing further numeric parameters.
  4242                                  ;
  4243 1743 21 7D 20    not_final:      lxi     h,inter_chars   ; Mix this character into the intermediate store, and we'll
  4244 1746 86                          add     m               ; untangle invalidity here when it's time to execute the sequence.
  4245 1747 D2 4C 17                    jnc     inter_range_ok  ; Mindful that we wouldn't want to wrap addition round to where
  4246 174A 3E FF                       mvi     a,0ffh          ; we appear to have a legal intermediate again.
  4247 174C 77          inter_range_ok: mov     m,a             ; Store our intermediate (or illegal mix)
  4248 174D 21 33 17                    lxi     h,detect_i_f    ; Only collect "intermediates" and detect finals from now on.
  4249 1750 22 40 21                    shld    char_action
  4250 1753 C9                          ret
  4251                  ;
  4252                  ; store_nvr
  4253                  ;       Store current settings back in NVR. The opposite of see recall_nvr.
  4254                  ;
  4255 1754 16 00       store_nvr:      mvi     d,0             ; direction is write
  4256 1756 06 01                       mvi     b,1             ; try once
  4257 1758 C3 62 17                    jmp     settings_nvr
  4258                  ;
  4259 175B CD C0 02    recall_nvr:     call    init_video_ram
  4260 175E 06 0A                       mvi     b,10            ; try 10 times
  4261 1760 16 01                       mvi     d,1             ; direction is read
  4262                  ;
  4263 1762 C5          settings_nvr:   push    b
  4264 1763 D5                          push    d
  4265 1764 CD BE 17                    call    print_wait      ; "Wait"
  4266 1767 D1                          pop     d
  4267 1768 F3                          di                      ; all NVR access is done with interrupts disabled
  4268 1769 21 7B 21                    lxi     h,aback_buffer
  4269 176C 1E 33                       mvi     e,33h           ; Number of bytes in NVR, including checksum
  4270 176E 0E 01                       mvi     c,1             ; C <- initial checksum
  4271 1770 AF                          xra     a
  4272 1771 32 AE 21    rw_byte_loop:   sta     nvr_addr 
  4273 1774 79                          mov     a,c
  4274 1775 32 AD 21                    sta     nvr_checksum
  4275 1778 D5                          push    d
  4276 1779 E5                          push    h
  4277 177A 7A                          mov     a,d             ; A <- direction flag
  4278 177B B7                          ora     a               ; Flag Z if storing settings
  4279 177C 7E                          mov     a,m             ; A <- settings byte
  4280 177D 32 AF 21                    sta     nvr_data        ; stow copy for working on
  4281 1780 CC AE 18                    cz      write_nvr_byte
  4282 1783 CD A3 18                    call    read_nvr_byte
  4283 1786 E1                          pop     h
  4284 1787 D1                          pop     d
  4285 1788 3A AF 21                    lda     nvr_data        ; A <- read back settings byte
  4286 178B 1D                          dcr     e
  4287 178C CA 9E 17                    jz      finished_rw
  4288 178F 77                          mov     m,a             ; place settings byte in scratch RAM
  4289 1790 3A AD 21                    lda     nvr_checksum    ; get checksum 
  4290 1793 07                          rlc                     ; rotate and exclusive-or this byte into it
  4291 1794 AE                          xra     m
  4292 1795 4F                          mov     c,a             ; C <- checksum
  4293 1796 23                          inx     h               ; point to next settings byte
  4294 1797 3A AE 21                    lda     nvr_addr        ; increase NVR address
  4295 179A 3C                          inr     a
  4296 179B C3 71 17                    jmp     rw_byte_loop
  4297                  ;
  4298 179E BE          finished_rw:    cmp     m               ; compare final byte (A) with checksum
  4299 179F C1                          pop     b
  4300 17A0 0E 00                       mvi     c,0             ; return result
  4301 17A2 CA AC 17                    jz      nvrchk_ok
  4302 17A5 05                          dcr     b               ; checksum wrong, so try again (on recall only)
  4303 17A6 C2 62 17                    jnz     settings_nvr
  4304 17A9 CD D0 17                    call    init_scratch    ; Otherwise give up and put reasonable defaults in scratch
  4305 17AC 79          nvrchk_ok:      mov     a,c
  4306 17AD B7                          ora     a               ; This routine returns Z flag for "ok, read NVR and checksum matched"
  4307 17AE F5                          push    psw
  4308 17AF 0E 40                       mvi     c,iob_flags_lba7
  4309 17B1 3E 63                       mvi     a,99            ; last address in NVR
  4310 17B3 CD 2B 19                    call    set_nvr_addr_a
  4311 17B6 21 F2 D0                    lxi     h,main_video_be ; To match screen address main_video
  4312 17B9 22 04 20                    shld    line1_dma       ; Put normal screen back
  4313 17BC F1                          pop     psw
  4314 17BD C9                          ret
  4315                  ;
  4316                  ; A tiny area of scratch RAM is used for the tiny screen definition used when
  4317                  ; NVR is being accessed and "Wait" is displayed.
  4318                  ;
  4319 17BE 11 EC 17    print_wait:     lxi     d,wait_display
  4320 17C1 06 07                       mvi     b,7
  4321 17C3 21 CC 21                    lxi     h,wait_addr     ; Copy "Wait" to video RAM, with terminators
  4322 17C6 CD 8B 03                    call    memcopy         ; that point to fill lines.
  4323 17C9 21 71 CC                    lxi     h,wait_addr_be
  4324 17CC 22 04 20                    shld    line1_dma
  4325 17CF C9                          ret
  4326                  ;
  4327                  ; init_scratch
  4328                  ;       Initialise all scratch locations from the answerback buffer up to rx_spd, inclusive
  4329                  ;       Returns C = 1, for the benefit of recall_nvr, which wants to return non-zero
  4330                  ;       if NVR couldn't be read (or checksum didn't match.) (see recall_nvr)
  4331                  ;
  4332 17D0 21 7B 21    init_scratch:   lxi     h,aback_buffer
  4333 17D3 06 27                       mvi     b,27h           ; Answerback buffer + tab settings
  4334 17D5 36 80       scribble_loop:  mvi     m,80h           ; scribble (80h is "end of string" for answerback and tab-every-8)
  4335 17D7 23                          inx     h
  4336 17D8 05                          dcr     b
  4337 17D9 C2 D5 17                    jnz     scribble_loop   ; HL finishes pointing to columns_132
  4338 17DC 11 F3 17                    lxi     d,scratch_defs
  4339 17DF 06 0B                       mvi     b,11            ; 11 locations to initialise
  4340 17E1 CD 8B 03                    call    memcopy
  4341 17E4 0E 01                       mvi     c,1             ; C <- 1 is for the benefit of recall_nvr
  4342 17E6 3E 30                       mvi     a,30h
  4343 17E8 32 78 20                    sta     bell_duration   ; going to sound bell
  4344 17EB C9                          ret
  4345                  ;
  4346                  ; "Wait" is followed by terminator and address that leads back to the fill lines.
  4347                  ;
  4348 17EC 57 61 69 74 wait_display:   db      'Wait',7fh,70h,06h
            7F 70 06 
  4349
  4350                  ; Initialisation values for scratch area from columns_132 to rx_spd, inclusive
  4351                  ; Note that failure of NVR leaves the terminal in VT52 mode rather than ANSI mode!
  4352                  ;
  4353 17F3 00          scratch_defs:   db      0               ; columns_132 <- 80 columns
  4354 17F4 08                          db      8               ; brightness <- three quarters brightness
  4355 17F5 6E                          db      6eh             ; pusart_mode <- 1 stop bit, no parity, 8 bits, 16x clock
  4356 17F6 20                          db      20h             ; local_mode <- "local"
  4357 17F7 D0                          db      0d0h            ; setup_b1 <- smooth scroll, autorepeat, dark background, block cursor
  4358 17F8 50                          db      50h             ; setup_b2 <- no margin bell, keyclick on, VT52 mode, auto xon/off
  4359 17F9 00                          db      0               ; setup_b3 <- ASCII, no autowrap, no newline, no interlace
  4360 17FA 20                          db      20h             ; setup_b4 <- no parity, 8 bits, power 60 Hz
  4361 17FB 00                          db      0               ; setup_b5 <- (don't care)
  4362 17FC E0                          db      0e0h            ; tx_spd <- 9600 baud
  4363 17FD E0                          db      0e0h            ; rx_spd <- 9600 baud
  4364                  ;
  4365                  ;       CUU - cursor up n rows
  4366 17FE 3A 55 21    cuu_action:     lda     top_margin
  4367 1801 01 FF 00                    lxi     b,00ffh         ; B <- limit of movement (row 0), C <- direction (-1 = up)
  4368 1804 C3 0F 18                    jmp     row_mv
  4369                  ;
  4370                  ;       CUD - cursor down n rows
  4371 1807 CD BF 11    cud_action:     call    last_row        ; B <- last row number (i.e. 13, or 23 with AVO)
  4372 180A 3A 56 21                    lda     bottom_margin
  4373 180D 0E 01                       mvi     c,1             ; C <- direction of movement (+1 = down)
  4374                  ;
  4375                  ; row_mv
  4376                  ;       On entry, B is limit of movement, which is 0 or last row (cursor movement can't cause scrolling)
  4377                  ;                 C is direction of movement, -1 for up, or +1 for down
  4378                  ;
  4379 180F 21 F9 20    row_mv:         lxi     h,curs_row      ; HL <- coordinate location we're affecting (row)
  4380 1812 C3 25 18                    jmp     cur_mv
  4381                  ;
  4382                  ;       CUF - cursor forward n columns
  4383 1815 3A 53 21    cuf_action:     lda     right_margin
  4384 1818 01 01 FF                    lxi     b,0ff01h        ; B <- limit of movement (column 255!), c is direction of movement (+1)
  4385 181B C3 22 18                    jmp     col_mv          ; shared code with CUB
  4386                  ;
  4387                  ;       CUB - cursor backward (left) n columns
  4388 181E AF          cub_action:     xra     a
  4389 181F 01 FF 00                    lxi     b,00ffh         ; B <- limit of movement (column 0), c is direction of movement (-1)
  4390 1822 21 F8 20    col_mv:         lxi     h,curs_col
  4391                  ; Row movement can also be processed here, by having HL initialised to the curs_row address and
  4392                  ; appropriate limits set
  4393 1825 57          cur_mv:         mov     d,a             ; D <- appropriate margin, to limit movement
  4394 1826 3A B8 21                    lda     csi_private     ; Check that we haven't got any private characters
  4395 1829 B7                          ora     a               ; because there is no private meaning for any of the
  4396 182A C2 15 0A                    jnz     to_ground       ; cursor movement sequences
  4397 182D 3A 30 21                    lda     csi_p1          ; Defaulting the first parameter: if the parameter
  4398 1830 B7                          ora     a               ; hasn't been supplied, or is supplied as zero,
  4399 1831 C2 35 18                    jnz     got_p1
  4400 1834 3C                          inr     a               ; then treat it as one
  4401 1835 5F          got_p1:         mov     e,a             ; E <- number of times to move
  4402 1836 7E                          mov     a,m             ; A <- current column
  4403 1837 BA          repeat_move:    cmp     d               ; check against margin
  4404 1838 CA 44 18                    jz      stop_move
  4405 183B B8                          cmp     b
  4406 183C CA 44 18                    jz      stop_move
  4407 183F 81                          add     c
  4408 1840 1D                          dcr     e               ; Do this movement until limited (conditions above)
  4409 1841 C2 37 18                    jnz     repeat_move     ; or until we've exhaused the count
  4410 1844 77          stop_move:      mov     m,a             ; store final column address
  4411 1845 C3 36 16                    jmp     move_updates
  4412                  ;
  4413                  ; cursor_home
  4414                  ;
  4415                  ;       Because this routine clears the first two CSI parameter locations in order to act like a CUP
  4416                  ;       or HVP sequence with defaulted row and column positions, it provokes a bug in DECOM.
  4417                  ;       _See_ decom_mode for details.
  4418                  ;
  4419 1848 21 00 00    cursor_home:    lxi     h,0
  4420 184B 22 30 21                    shld    csi_params
  4421 184E 3E FF                       mvi     a,0ffh
  4422 1850 32 0E 21                    sta     saved_curs_row
  4423
  4424                  ;
  4425                  ; curpos_action
  4426                  ;
  4427                  ;       The sequences CUP (CSI H) and HVP (CSI f) both come here, as they have identifical effects on all
  4428                  ;       models of DEC VTs.
  4429                  ;
  4430                  ;       This routine has a bug in that it starts off by storing the origin mode flag in the C register,
  4431                  ;       but can lose track of that and use C for a row number instead, but then make a further decision
  4432                  ;       as if it still contained the flag.
  4433                  ;
  4434                  ;       The fault path is triggered if we are not in origin mode, which means the code validates the
  4435                  ;       row parameter against the last row of the screen, not the bottom margin. At this point, C is
  4436                  ;       changed from storing the origin mode flag to storing the requested new row. Next, the column
  4437                  ;       number is validated by checking the column number against either the right margin (origin mode)
  4438                  ;       or the last column on the screen (non origin mode). If we are in origin mode, the test proceeds
  4439                  ;       as intended, but if we were not in origin mode, using the right margin or last column depends
  4440                  ;       on whether the new internal row number is 0 or not. If it is non-zero, we check the right margin
  4441                  ;       (i.e. acting like origin mode) instead of last column.
  4442                  ;
  4443                  ;       This bug constrains the cursor column update to the right margin even if origin mode is not in
  4444                  ;       effect. However, the VT100 doesn't have settable left and right margins, and the right margin is
  4445                  ;       only ever different from last column number on double width lines. So there is no case in which
  4446                  ;       this code will allow the setting of a column number beyond the right margin, which means the
  4447                  ;       corruption of the middle-of-line terminators on double width lines cannot occur. (In any case, all
  4448                  ;       cursor moves are then re-validated by see move_updates.)
  4449                  ;
  4450                  ;       The effect of the bug is that cursor moves from a line that is double-width to a line that is
  4451                  ;       single width are constrained by the right margin of the current line, provided the new row
  4452                  ;       is anything other than row 1, which is incorrect.
  4453                  ;
  4454                  ;       This is demonstrated by test t/margin-bug.txt. [* BUG *]
  4455                  ;
  4456 1853 3A 01 21    curpos_action:  lda     origin_mode
  4457 1856 4F                          mov     c,a
  4458 1857 21 30 21                    lxi     h,csi_p1
  4459 185A 7E                          mov     a,m             ; Pn1 is row
  4460 185B B7                          ora     a               ; default/0 will be top row 
  4461 185C CA 60 18                    jz      def_row 
  4462 185F 3D                          dcr     a               ; Otherwise, our internal rows are 0-based, not 1-based
  4463 1860 47          def_row:        mov     b,a             ; B <- row
  4464 1861 79                          mov     a,c             ; A <- origin mode flag
  4465 1862 B7                          ora     a
  4466 1863 CA 69 18                    jz      add_top         ; jump if not in origin mode
  4467 1866 3A 55 21                    lda     top_margin      ; In origin mode, move within margins
  4468 1869 80          add_top:        add     b
  4469 186A 47                          mov     b,a             ; B <- adjusted row
  4470 186B 79                          mov     a,c             ; A <- origin mode flag 
  4471 186C B7                          ora     a
  4472 186D 3A 56 21                    lda     bottom_margin   ; A <- bottom margin
  4473 1870 C2 79 18                    jnz     chk_bot_limit   ; jump if origin mode
  4474 1873 48                          mov     c,b             ; C <- new row
  4475 1874 CD BF 11                    call    last_row        ; B <- last row on screen
  4476 1877 78                          mov     a,b             ; A <- last row
  4477 1878 41                          mov     b,c             ; B <- new row
  4478 1879 B8          chk_bot_limit:  cmp     b               ; comparing new row against either bottom margin or last row
  4479 187A DA 7E 18                    jc      skip_row        ; if last row < new row, go store last row
  4480 187D 78                          mov     a,b             ; else new row is ok
  4481 187E 32 F9 20    skip_row:       sta     curs_row        ; store row
  4482 1881 23                          inx     h               ; HL points to csi_p2
  4483 1882 7E                          mov     a,m             ; A <- Pn2 (column)
  4484 1883 B7                          ora     a               ; default/0 will be left column
  4485 1884 CA 88 18                    jz      def_col
  4486 1887 3D                          dcr     a               ; Otherwise, our internal cols are 0-based, not 1-based
  4487 1888 47          def_col:        mov     b,a             ; B <- col
  4488 1889 79                          mov     a,c             ; A <- 1 if origin mode flag had been set, new row if unset
  4489 188A B7                          ora     a               ;
  4490 188B C2 95 18                    jnz     use_rmargin     ; jump if origin mode
  4491 188E 3A 50 20                    lda     screen_cols
  4492 1891 3D                          dcr     a               ; A <- last column number
  4493 1892 C3 98 18                    jmp     chk_rlimit
  4494                  ;
  4495 1895 3A 53 21    use_rmargin:    lda     right_margin
  4496 1898 B8          chk_rlimit:     cmp     b
  4497 1899 DA 9D 18                    jc      nolim_rm        ; if right margin < requested column, store margin
  4498 189C 78                          mov     a,b             ; else A <- request column
  4499 189D 32 F8 20    nolim_rm:       sta     curs_col        ; store
  4500 18A0 C3 36 16                    jmp     move_updates
  4501                  ;
  4502                  ; read_nvr_byte
  4503                  ;
  4504 18A3 0E 40       read_nvr_byte:  mvi     c,iob_flags_lba7
  4505 18A5 CD 28 19                    call    set_nvr_addr
  4506 18A8 CD C1 18                    call    read_nvr_data
  4507 18AB C3 BC 18                    jmp     nvr_idle
  4508                  ;
  4509                  ; write_nvr_byte
  4510                  ;
  4511                  ;       Writing a byte to the NVR involves addressing the location and an erase cycle first,
  4512                  ;       which involves commanding the erase operation and waiting; there is no positive
  4513                  ;       acknowledgement of completion.
  4514                  ;
  4515 18AE 0E 40       write_nvr_byte: mvi     c,iob_flags_lba7
  4516 18B0 CD 28 19                    call    set_nvr_addr
  4517 18B3 CD A8 19                    call    erase_nvr       ; includes 20 ms delay
  4518 18B6 CD 77 19                    call    nvr_accept
  4519 18B9 CD D6 19                    call    write_nvr       ; includes 20 ms delay
  4520 18BC 3E 30       nvr_idle:       mvi     a,30h           ; c000 d0 = "accept data" (inv)
  4521 18BE D3 62                       out     iow_nvr_latch
  4522 18C0 C9                          ret
  4523                  ;
  4524                  read_nvr_data:
  4525 18C1 DB 42       w1h:            in      ior_flags       ; ER1400 shows command acceptance on rising
  4526 18C3 A1                          ana     c               ; edge of clock. Wait for high, low,
  4527 18C4 CA C1 18                    jz      w1h             ; command, then wait for high again.
  4528 18C7 DB 42       w1l:            in      ior_flags
  4529 18C9 A1                          ana     c
  4530 18CA C2 C7 18                    jnz     w1l
  4531 18CD 3E 2D                       mvi     a,2dh           ; c110 d1 = "read" (inv)
  4532 18CF D3 62                       out     iow_nvr_latch
  4533 18D1 DB 42       w2h:            in      ior_flags
  4534 18D3 A1                          ana     c
  4535 18D4 CA D1 18                    jz      w2h
  4536 18D7 DB 42       w2l:            in      ior_flags
  4537 18D9 A1                          ana     c
  4538 18DA C2 D7 18                    jnz     w2l
  4539 18DD 3E 2F                       mvi     a,2fh           ; c111 d1 = "standby" (inv)
  4540 18DF D3 62                       out     iow_nvr_latch
  4541 18E1 21 D3 21                    lxi     h,nvr_bits
  4542 18E4 06 0E                       mvi     b,14            ; shifting 14 bits out of NVR
  4543 18E6 DB 42       w3h:            in      ior_flags
  4544 18E8 A1                          ana     c
  4545 18E9 CA E6 18                    jz      w3h
  4546 18EC DB 42       w3l:            in      ior_flags
  4547 18EE A1                          ana     c
  4548 18EF C2 EC 18                    jnz     w3l
  4549 18F2 3E 25                       mvi     a,25h           ; c010 d1 = "shift data out" (inv)
  4550 18F4 D3 62                       out     iow_nvr_latch
  4551 18F6 DB 42       w4h:            in      ior_flags       ; \
  4552 18F8 A1                          ana     c               ;  |
  4553 18F9 CA F6 18                    jz      w4h             ;  |
  4554 18FC DB 42                       in      ior_flags       ;  |
  4555 18FE 77                          mov     m,a             ;  | - squirrel away raw flags buffer value
  4556 18FF 23                          inx     h               ;   < 14 bits
  4557 1900 DB 42       w4l:            in      ior_flags       ;  |
  4558 1902 A1                          ana     c               ;  |
  4559 1903 C2 00 19                    jnz     w4l             ;  |
  4560 1906 05                          dcr     b               ;  |
  4561 1907 C2 F6 18                    jnz     w4h             ; /
  4562 190A 3E 2F                       mvi     a,2fh           ; c111 d1 = "standby" (inv)
  4563 190C D3 62                       out     iow_nvr_latch   ; 
  4564 190E 11 D3 21                    lxi     d,nvr_bits
  4565 1911 06 0E                       mvi     b,14            ; 14 bits again
  4566 1913 21 00 00                    lxi     h,0
  4567 1916 29          accum_bits:     dad     h               ; shift HL left, clearing bit 0 of L
  4568 1917 1A                          ldax    d               ; grab next bit, which is raw flags buffer read
  4569 1918 E6 20                       ani     iob_flags_nvr   ; isolate bit 5
  4570 191A 07                          rlc                     ; data is bit 6
  4571 191B 07                          rlc                     ; data is bit 7
  4572 191C 07                          rlc                     ; data is bit 0
  4573 191D B5                          ora     l               ; Add to HL
  4574 191E 6F                          mov     l,a
  4575 191F 13                          inx     d               ; next raw read
  4576 1920 05                          dcr     b               ; for all 14 bits 
  4577 1921 C2 16 19                    jnz     accum_bits
  4578 1924 22 AF 21                    shld    nvr_data        ; stow all 14 bits
  4579 1927 C9                          ret
  4580                  ;
  4581                  ; used for both read and write operations
  4582                  ; only routine that uses nvr_addr
  4583                  ;
  4584                  ; Addresses are represented by 20 bits are split into a tens and units part, each represented by ten
  4585                  ; bits. Addresses are clocked into the ER1400 with all address bits held high except for the single
  4586                  ; bit of the digit that represents your tens or units.
  4587                  ;
  4588                  ; In order to get the timing right, this routine pre-calculates all 20 bits of the address in memory,
  4589                  ; in the nvr_bits array, including the complete C1, C2, C3 for each NVR latch byte.
  4590                  ;
  4591 1928 3A AE 21    set_nvr_addr:   lda     nvr_addr
  4592 192B 06 FF       set_nvr_addr_a: mvi     b,-1            ; The address is broken up into tens and units
  4593 192D 04          addr_tens:      inr     b
  4594 192E D6 0A                       sui     0ah
  4595 1930 F2 2D 19                    jp      addr_tens
  4596 1933 C6 0A                       adi     0ah             ; Now B is tens, A is units
  4597 1935 21 D3 21                    lxi     h,nvr_bits
  4598 1938 1E 23                       mvi     e,23h           ; c001 d1 "accept address" (inv)
  4599 193A 16 14                       mvi     d,20            ; 20 address bits
  4600 193C 73          addr_const:     mov     m,e             ; some kind of constant lead-in?
  4601 193D 23                          inx     h
  4602 193E 15                          dcr     d
  4603 193F C2 3C 19                    jnz     addr_const
  4604 1942 36 2F                       mvi     m,2fh           ; c111 d1 "standby" (inv) 
  4605 1944 21 D3 21                    lxi     h,nvr_bits
  4606 1947 5F                          mov     e,a             ; 
  4607 1948 16 00                       mvi     d,0             ; DE <- units address
  4608 194A 19                          dad     d               ; Advance HL by units address
  4609 194B 36 22                       mvi     m,22h           ; c001 d0 "accept address" (inv)
  4610 194D 21 D3 21                    lxi     h,nvr_bits
  4611 1950 3E 0A                       mvi     a,0ah
  4612 1952 80                          add     b               ; A = address tens + 10
  4613 1953 5F                          mov     e,a             ; 
  4614 1954 19                          dad     d               ; Advance HL by 10 + tens address
  4615 1955 36 22                       mvi     m,22h           ; c001 d0 "accept address" (inv)
  4616 1957 DB 42       wa1l:           in      ior_flags
  4617 1959 A1                          ana     c
  4618 195A C2 57 19                    jnz     wa1l
  4619 195D 21 D3 21                    lxi     h,nvr_bits      ; Back to beginning of bits, ready for latching on clock lows
  4620 1960 06 15                       mvi     b,21            ; must be complete in 21 clock periods (including high after last bit)
  4621 1962 DB 42       wa1h:           in      ior_flags
  4622 1964 A1                          ana     c
  4623 1965 CA 62 19                    jz      wa1h
  4624 1968 05                          dcr     b
  4625 1969 F8                          rm                      ; limited cycles for entire write
  4626 196A DB 42       wa2l:           in      ior_flags
  4627 196C A1                          ana     c
  4628 196D C2 6A 19                    jnz     wa2l
  4629 1970 7E                          mov     a,m
  4630 1971 D3 62                       out     iow_nvr_latch
  4631 1973 23                          inx     h
  4632 1974 C3 62 19                    jmp     wa1h
  4633                  ;
  4634 1977 2A AF 21    nvr_accept:     lhld    nvr_data        ; grab 14 bits of data
  4635 197A 29                          dad     h               ; shift the bits 2 places left so that the
  4636 197B 29                          dad     h               ; next add will start forcing data into carry
  4637 197C 11 D3 21                    lxi     d,nvr_bits
  4638 197F 06 0E                       mvi     b,14            ; going to process 14 bits
  4639 1981 3E 20       next_split:     mvi     a,20h           ; c000 d0 = "accept data" (inv)
  4640 1983 29                          dad     h               ; shift top by of HL into carry flag
  4641 1984 17                          ral                     ; and pull it into bit 0 of A
  4642 1985 12                          stax    d               ; place in nvr_bits buffer
  4643 1986 13                          inx     d
  4644 1987 05                          dcr     b
  4645 1988 C2 81 19                    jnz     next_split
  4646 198B 3E 2F                       mvi     a,2fh           ; c111 d1 = "standby" (inv)
  4647 198D 12                          stax    d
  4648 198E 21 D3 21                    lxi     h,nvr_bits
  4649 1991 06 0F                       mvi     b,15            ; 14 data bits + standby terminator
  4650 1993 DB 42       wd1h:           in      ior_flags
  4651 1995 A1                          ana     c
  4652 1996 CA 93 19                    jz      wd1h
  4653 1999 DB 42       wd1l:           in      ior_flags
  4654 199B A1                          ana     c
  4655 199C C2 99 19                    jnz     wd1l
  4656 199F 7E                          mov     a,m
  4657 19A0 D3 62                       out     iow_nvr_latch
  4658 19A2 23                          inx     h
  4659 19A3 05                          dcr     b
  4660 19A4 C2 93 19                    jnz     wd1h
  4661 19A7 C9                          ret
  4662                  ;
  4663                  erase_nvr:
  4664 19A8 DB 42       wl1h:           in      ior_flags
  4665 19AA A1                          ana     c
  4666 19AB CA A8 19                    jz      wl1h
  4667 19AE DB 42       wl1l:           in      ior_flags
  4668 19B0 A1                          ana     c
  4669 19B1 C2 AE 19                    jnz     wl1l
  4670 19B4 3E 2B                       mvi     a,2bh           ; c101 d1 = "erase" (inv)
  4671 19B6 D3 62                       out     iow_nvr_latch
  4672 19B8 CD C0 19                    call    wait_nvr
  4673 19BB 3E 2F                       mvi     a,2fh           ; c111 d1 = "standby" (inv)
  4674 19BD D3 62                       out     iow_nvr_latch
  4675 19BF C9                          ret
  4676                  ;
  4677                  ; If we're expected to clock data out of NVR and we don't want it,
  4678                  ; we just wait out a number of clocks.
  4679                  ;
  4680 19C0 21 3B 01    wait_nvr:       lxi     h,315           ; 315 x LBA 7 cycles at 63.5 µs per cycle = 20 ms
  4681 19C3 DB 42       w20h:           in      ior_flags
  4682 19C5 A1                          ana     c
  4683 19C6 CA C3 19                    jz      w20h
  4684 19C9 DB 42       w20l:           in      ior_flags
  4685 19CB A1                          ana     c
  4686 19CC C2 C9 19                    jnz     w20l
  4687 19CF 2B                          dcx     h
  4688 19D0 7C                          mov     a,h
  4689 19D1 B5                          ora     l
  4690 19D2 C2 C3 19                    jnz     w20h
  4691 19D5 C9                          ret
  4692                  ;
  4693                  ; After an "accept data" operation, command the NVR to write the data. Then wait.
  4694                  ; The datasheet says that the ER1400 will finish in a maximum of 24 ms.
  4695                  write_nvr:
  4696 19D6 DB 42       wwwh:           in      ior_flags
  4697 19D8 A1                          ana     c
  4698 19D9 CA D6 19                    jz      wwwh
  4699 19DC DB 42       wwwl:           in      ior_flags
  4700 19DE A1                          ana     c
  4701 19DF C2 DC 19                    jnz     wwwl
  4702 19E2 3E 29                       mvi     a,29h           ; c100 d1 = "write" (inv)
  4703 19E4 D3 62                       out     iow_nvr_latch
  4704 19E6 CD C0 19                    call    wait_nvr
  4705 19E9 3E 2F                       mvi     a,2fh           ; c111 d1 = "standby (inv)
  4706 19EB D3 62                       out     iow_nvr_latch
  4707 19ED C9                          ret
  4708                  ;
  4709                  ; Processing next "received" character for SET-UP, which means handling a keypress, as we're now in
  4710                  ; LOCAL mode, and keypresses aren't going to the host, but being reflected internally. For the
  4711                  ; mechanism here, see report_char.
  4712                  ;
  4713                  ; On entry, A and B both contain the received character (see reflect_char)
  4714                  ;
  4715 19EE FE 20       setup_action:   cpi     20h             ; If SPACE is pressed,
  4716 19F0 0E 43                       mvi     c,43h           ; it is treated the same as RIGHT ARROW,
  4717 19F2 CA FF 01                    jz      setup_cursor    ; so perform cursor movement
  4718 19F5 21 B0 1B                    lxi     h,setup_ready
  4719 19F8 E5                          push    h               ; Push return address on stack
  4720 19F9 FE 0D                       cpi     C0_CR
  4721 19FB CA 4B 09                    jz      c0_return
  4722 19FE FE 09                       cpi     C0_HT
  4723 1A00 CA F9 0D                    jz      c0_horiz_tab
  4724 1A03 FE 3A                       cpi     '9'+1           ; Keys above numeric range are handled separately
  4725 1A05 D2 60 1B                    jnc     setup_keys
  4726 1A08 D6 30                       sui     '0'             ; Numeric keys are handled through a jump table,
  4727 1A0A F8                          rm                      ; bring down into range and reject any keys lower than '0'
  4728 1A0B 87                          add     a               ; double up code because addresses are two bytes
  4729 1A0C 21 A2 1A                    lxi     h,setup_key_t   ; HL <- base of key table
  4730 1A0F CD DE 13                    call    add_a_to_hl     ;
  4731 1A12 CD 90 13                    call    ld_hl_from_hl
  4732 1A15 78                          mov     a,b             ; A <- received char
  4733 1A16 11 AC 21                    lxi     d,rx_spd        ; DE <- rx_spd, which is convenient for toggle speed keys, at least
  4734 1A19 E9                          pchl                    ; jump to action routine
  4735                  ;
  4736                  ; Keyboard processing has detected SETUP key, which either takes us into or out of SET-UP
  4737 1A1A 32 77 21    setup_pressed:  sta     pending_setup
  4738 1A1D C3 12 08                    jmp     pk_click
  4739                  ;
  4740                  ; Pressing "SET UP" will mark a SET-UP action as pending, and then this routine will be called
  4741                  ; to either enter or exit.
  4742                  ;
  4743 1A20 3A 7B 20    in_out_setup:   lda     in_setup
  4744 1A23 EE FF                       xri     0ffh            ; Toggle in/out of SET-UP
  4745 1A25 32 7B 20                    sta     in_setup
  4746 1A28 CA 61 1A                    jz      exit_setup
  4747                  ;
  4748                  ; TM says that, on entering SET-UP, it waits for scrolls to finish.
  4749                  ;
  4750 1A2B 0E 80       enter_setup:    mvi     c,80h
  4751 1A2D CD 8E 10                    call    wait_scroll
  4752 1A30 2A 40 21                    lhld    char_action     ; We're going to redirect all key actions
  4753 1A33 22 11 21                    shld    saved_action    ; so save previous state
  4754 1A36 21 EE 19                    lxi     h,setup_action  ; and register the SET-UP action
  4755 1A39 22 40 21                    shld    char_action
  4756 1A3C 21 00 00                    lxi     h,0
  4757 1A3F 22 43 21                    shld    curkey_qcount   ; keyboard_locked <- 0 too
  4758 1A42 22 72 21                    shld    pending_report  ; sending_report <- 0 too
  4759 1A45 2A 04 20    setup_addrs:    lhld    line1_dma
  4760 1A48 22 C5 21                    shld    saved_line1_dma
  4761 1A4B 2A F8 20                    lhld    curs_col        ; HL <- curs_row and curs_col
  4762 1A4E 22 0D 21                    shld    saved_curs_col  ; save them
  4763 1A51 CD BA 1B                    call    extra_addr
  4764 1A54 22 4E 21                    shld    start_line_addr
  4765 1A57 AF                          xra     a
  4766 1A58 32 F8 20                    sta     curs_col
  4767 1A5B CD F9 1B                    call    setup_display
  4768 1A5E C3 36 16                    jmp     move_updates
  4769                  ;
  4770 1A61 2A 11 21    exit_setup:     lhld    saved_action    ; Restore character processing state (we could have been in the middle
  4771 1A64 22 40 21                    shld    char_action     ; of receiving a control sequence when SET-UP was pressed.)
  4772 1A67 CD BA 1B                    call    extra_addr      ; HL <- extra line screen RAM
  4773 1A6A 11 00 10                    lxi     d,1000h
  4774 1A6D 19                          dad     d               ; HL <- start of cursor line attribute RAM
  4775 1A6E 3A 50 20                    lda     screen_cols     ; Default attributes for entire line
  4776 1A71 36 FF       restore_attr:   mvi     m,0ffh
  4777 1A73 23                          inx     h
  4778 1A74 3D                          dcr     a
  4779 1A75 C2 71 1A                    jnz     restore_attr
  4780 1A78 21 0D 21                    lxi     h,saved_curs_col
  4781 1A7B 7E                          mov     a,m             ; A <- saved cursor column
  4782 1A7C 32 F8 20                    sta     curs_col
  4783 1A7F 3E FF                       mvi     a,0ffh          ; A <- 0ffh
  4784 1A81 23                          inx     h               ; HL points to saved_curs_row
  4785 1A82 77                          mov     m,a             ; destroy it
  4786 1A83 CD 36 16                    call    move_updates
  4787 1A86 2A C5 21                    lhld    saved_line1_dma
  4788 1A89 22 04 20                    shld    line1_dma
  4789 1A8C AF                          xra     a
  4790 1A8D 32 C4 21                    sta     noscroll
  4791 1A90 32 C2 21                    sta     received_xoff
  4792 1A93 DB 42                       in      ior_flags       ; Exiting SET-UP will trigger DECREPTPARM if we are 
  4793 1A95 E6 08                       ani     iob_flags_stp   ; allowed to send them unsolicited, and if we don't have STP.
  4794 1A97 CA A1 1A                    jz      skip_tparm
  4795 1A9A 3A 76 21                    lda     tparm_solicited
  4796 1A9D B7                          ora     a
  4797 1A9E CC E8 12                    cz      setup_tparm
  4798 1AA1 C9          skip_tparm:     ret
  4799                  ;
  4800                  ; Jump table for digit keys pressed in either SET-UP mode
  4801                  ;
  4802 1AA2 CF 1A       setup_key_t:    dw      restart         ; Key 0 - reset
  4803 1AA4 09 1B                       dw      do_nothing_key  ; Key 1 - has no function in SET-UP
  4804 1AA6 D0 1A                       dw      toggle_tab      ; Key 2 - set or clear tab      (SET-UP A only)
  4805 1AA8 F2 1A                       dw      setup_clr_tabs  ; Key 3 - clear all tabs        (SET-UP A only)
  4806 1AAA FE 1A                       dw      toggle_online   ; Key 4 - line/local
  4807 1AAC 0A 1B                       dw      toggle_a_b      ; Key 5 - SET-UP A/B
  4808 1AAE 22 1B                       dw      toggle_bit      ; Key 6 - Toggle 1/0            (SET-UP B only)
  4809 1AB0 40 1B                       dw      cycle_tx_speed  ; Key 7 - Transmit speed
  4810 1AB2 41 1B                       dw      cycle_rx_speed  ; Key 8 - Receive speed
  4811 1AB4 B6 1A                       dw      switch_columns  ; Key 9 - 80/132 columns
  4812                  ;
  4813 1AB6 CD 52 1B    switch_columns: call    setup_a_only
  4814 1AB9 AF                          xra     a
  4815 1ABA 32 0E 21                    sta     saved_curs_row
  4816 1ABD 32 0D 21                    sta     saved_curs_col
  4817 1AC0 3A A2 21                    lda     columns_132
  4818 1AC3 B7                          ora     a
  4819 1AC4 F5                          push    psw
  4820 1AC5 CC 77 0B                    cz      init_132col
  4821 1AC8 F1                          pop     psw
  4822 1AC9 C4 63 0B                    cnz     init_80col
  4823 1ACC C3 45 1A                    jmp     setup_addrs     ; same address-setting code as entering SET-UP
  4824                  ;
  4825 1ACF C7          restart:        rst     0
  4826                  ;
  4827 1AD0 CD 52 1B    toggle_tab:     call    setup_a_only
  4828                  ;
  4829 1AD3 3A F8 20                    lda     curs_col
  4830 1AD6 B7                          ora     a               ; Can't have a tab at first column on line
  4831 1AD7 C8                          rz                      ; so quit early
  4832 1AD8 CD 23 0E                    call    tab_offs_curs
  4833 1ADB AE                          xra     m               ; flip tab state
  4834 1ADC 77                          mov     m,a             ; and write back
  4835 1ADD CD 23 0E                    call    tab_offs_curs
  4836 1AE0 A6                          ana     m
  4837 1AE1 06 54                       mvi     b,'T'           ; Assume there'll be a tab stop
  4838 1AE3 C2 E8 1A                    jnz     is_tab
  4839 1AE6 06 00       no_tab:         mvi     b,0             ; B <- blank
  4840 1AE8 78          is_tab:         mov     a,b             ; A <- 'T'/blank 
  4841 1AE9 32 F4 20                    sta     char_und_curs
  4842 1AEC 3A F8 20                    lda     curs_col
  4843 1AEF C3 EA 1E                    jmp     disp_char_col
  4844                  ;
  4845 1AF2 CD 52 1B    setup_clr_tabs: call    setup_a_only
  4846 1AF5 CD EC 0D                    call    clear_all_tabs
  4847 1AF8 CD CD 1C                    call    clear_extra
  4848 1AFB C3 E6 1A                    jmp     no_tab
  4849                  ;
  4850                  ; SET-UP key 4 toggles line/local
  4851 1AFE 21 A5 21    toggle_online:  lxi     h,local_mode
  4852 1B01 7E                          mov     a,m
  4853 1B02 EE 20                       xri     20h             ; Toggle local mode
  4854 1B04 77                          mov     m,a
  4855 1B05 AF                          xra     a
  4856 1B06 32 44 21                    sta     keyboard_locked
  4857                  ; this is just a convenient "ret" for a key that does nothing in SET-UP
  4858 1B09 C9          do_nothing_key: ret
  4859                  ;
  4860 1B0A CD 4B 09    toggle_a_b:     call    c0_return
  4861 1B0D 21 5A 22                    lxi     h,setup_video+7 ; Screen location of final character of "SET-UP A" (or "B")
  4862 1B10 7E                          mov     a,m             ; A <- character
  4863 1B11 EE 03                       xri     3               ; A <- "A" ^ "B"
  4864 1B13 77                          mov     m,a             ; Write it back
  4865 1B14 32 65 22                    sta     setup_video+12h ; And same thing on bottom line, because it's double height
  4866 1B17 E6 01                       ani     1               ; And the low bit of the ASCII "A" conveniently matches
  4867 1B19 32 BE 21                    sta     in_setup_a      ; our flag for which screen we're on
  4868 1B1C CA D3 1C                    jz      draw_setup_b
  4869 1B1F C3 35 1C                    jmp     draw_setup_a
  4870                  ;
  4871                  ; toggle_bit allows any of the SET-UP B configuration blocks to be updated, with
  4872                  ; addressing made easy by a cunning screen layout; The screen looks like this:
  4873                  ;
  4874                  ; 1 1101  2 1111  3 0100  4 0010  5 0000 <- (optional block 5)
  4875                  ;
  4876                  ; As each setup block is only 4 bits in the top half of each setup byte, the
  4877                  ; placement of the bit blocks 4 columns apart, allows the col_in_bits
  4878                  ; routine to directly address the correct bit (after the starting column offset
  4879                  ; has been removed.)
  4880                  ;
  4881 1B22 CD 59 1B    toggle_bit:     call    setup_b_only
  4882 1B25 21 A6 21                    lxi     h,setup_b1      ; First block on screen (lowest in memory)
  4883 1B28 3A F8 20                    lda     curs_col
  4884 1B2B D6 02                       sui     2               ; Ignore the "1 " at the start of line
  4885 1B2D FE 28                       cpi     28h             ; To accommodate possible block 5
  4886 1B2F D0                          rnc                     ; Return if out of range
  4887 1B30 00                          nop                     ; Feels like last-minute code removal here
  4888 1B31 00                          nop
  4889 1B32 00                          nop
  4890 1B33 00                          nop
  4891 1B34 57                          mov     d,a             ; D <- A because col_in_bits uses both
  4892 1B35 CD 2B 0E                    call    col_in_bits
  4893 1B38 AE                          xra     m               ; toggle the appropriate bit
  4894 1B39 77                          mov     m,a             ; and write back
  4895 1B3A CD DB 1D                    call    program_pusart
  4896 1B3D C3 D3 1C                    jmp     draw_setup_b
  4897                  ;
  4898                  ; cycle_tx_speed
  4899                  ; cycle_rx_speed
  4900                  ;
  4901                  ;       SET-UP initialises DE to point to rx_spd before jumping to action routines, so cycle_tx_speed
  4902                  ;       just adjusts this location before dropping through to the generic routine.
  4903                  ;       SET-UP B ONLY
  4904                  ;
  4905 1B40 1B          cycle_tx_speed: dcx     d               ; DE <- tx_spd
  4906 1B41 EB          cycle_rx_speed: xchg                    ; HL <-< DE, so HL points at speed location
  4907 1B42 CD 59 1B                    call    setup_b_only    ; A is 0 on exit!
  4908 1B45 32 F8 20                    sta     curs_col
  4909 1B48 7E                          mov     a,m             ; Read current speed
  4910 1B49 C6 10                       adi     10h             ; add to the value in top 4 bits
  4911 1B4B 77                          mov     m,a             ; Store new value
  4912 1B4C CD DB 1D                    call    program_pusart
  4913 1B4F C3 D3 1C                    jmp     draw_setup_b
  4914                  ;
  4915                  ; setup_a_only
  4916                  ; setup_b_only
  4917                  ;       These guard routines are called by functions that should only work in one of the two
  4918                  ;       SET-UP modes, because key dispatching happens through the same table for both screens.
  4919                  ;       These routines are called by action routines and they will just return if the correct
  4920                  ;       SET-UP screen is in use. Otherwise, they'll pop the stack, to quit the action routine
  4921                  ;       early and return to the action routine's caller.
  4922                  ;
  4923 1B52 3A BE 21    setup_a_only:   lda     in_setup_a
  4924 1B55 B7                          ora     a
  4925 1B56 C0                          rnz                     ; Correct screen; return to caller
  4926 1B57 E1                          pop     h
  4927 1B58 C9                          ret                     ; else discard the action routine
  4928                  ;
  4929 1B59 3A BE 21    setup_b_only:   lda     in_setup_a
  4930 1B5C B7                          ora     a
  4931 1B5D C8                          rz                      ; Correct screen; return to caller
  4932 1B5E E1                          pop     h
  4933 1B5F C9                          ret                     ; else discard the action routine
  4934                  ;
  4935                  ; In SET-UP mode, deal with keys other than digits (which are handled through a table)
  4936                  ;
  4937 1B60 3A 69 20    setup_keys:     lda     last_key_flags  ; All other keys will need SHIFT to be pressed
  4938 1B63 E6 20                       ani     key_flag_shift
  4939 1B65 C8                          rz                      ; so if it isn't, exit
  4940 1B66 78                          mov     a,b
  4941 1B67 FE 53                       cpi     'S'             ; SHIFT S stores settings in NVR
  4942 1B69 C2 72 1B                    jnz     try_recall
  4943 1B6C CD 54 17                    call    store_nvr
  4944 1B6F C3 81 1B                    jmp     post_nvr
  4945                  ;
  4946 1B72 FE 52       try_recall:     cpi     'R'             ; SHIFT R recalls settings from NVR
  4947 1B74 C2 86 1B                    jnz     try_answerback
  4948 1B77 CD 5B 17                    call    recall_nvr
  4949 1B7A FB                          ei
  4950 1B7B CD A2 03                    call    init_devices
  4951 1B7E CD 2B 1A                    call    enter_setup
  4952 1B81 FB          post_nvr:       ei
  4953 1B82 CD F9 1B                    call    setup_display
  4954 1B85 C9                          ret
  4955                  ;
  4956 1B86 FE 41       try_answerback: cpi     'A'
  4957 1B88 C0                          rnz
  4958 1B89 CD 59 1B                    call    setup_b_only
  4959 1B8C CD 4B 09                    call    c0_return       
  4960 1B8F 3E 41                       mvi     a,'A'           ; extra line is initialised with "A= " and then you type a
  4961 1B91 CD E6 05                    call    print_char      ; delimiter, rest of message and another delimiter to finish.
  4962 1B94 3E 3D                       mvi     a,'='
  4963 1B96 CD E6 05                    call    print_char
  4964 1B99 3E 20                       mvi     a,' '
  4965 1B9B CD E6 05                    call    print_char
  4966 1B9E 21 9F 1E                    lxi     h,aback_entry
  4967 1BA1 22 40 21                    shld    char_action
  4968 1BA4 E1                          pop     h
  4969 1BA5 C9                          ret
  4970                  ;
  4971                  ; These 10 bytes are all valid 8080 instructions but are unreachable. The first byte is a little out of
  4972                  ; place, hence my assumption that it's the checksum for ROM 4 (1800h - 1fffh), as there are no unused
  4973                  ; non-zero bytes elsewhere in this ROM. The other 9 bytes would result in a lowercase character in the
  4974                  ; A register being made uppercase, returning all others unchanged. As this is very far from the keyboard
  4975                  ; routines, perhaps this is the remnants of some VT50 compatibility code. Who knows?
  4976                  ;
  4977 1BA6 98                          sbb     b               ; = 98h. Probably CHECKSUM (first byte of unused code)
  4978 1BA7 FE 61                       cpi     61h             ; UNREACHABLE (see note above)
  4979 1BA9 F8                          rm
  4980 1BAA FE 7B                       cpi     7bh
  4981 1BAC F0                          rp
  4982 1BAD E6 DF                       ani     0dfh
  4983 1BAF C9                          ret
  4984                  ;
  4985                  ; Gets us ready for next setup action. Used as a handy return point by being pushed on the stack.
  4986                  ;
  4987 1BB0 CD 94 03    setup_ready:    call    ready_comms
  4988 1BB3 21 EE 19                    lxi     h,setup_action
  4989 1BB6 22 40 21                    shld    char_action
  4990 1BB9 C9                          ret
  4991                  ;
  4992                  ; extra_addr 
  4993                  ;       Returns with HL containing address of the extra line in screen RAM.
  4994                  ;       This is the line that gets used as the cursor line in SET-UP mode, so in SET-UP A, it
  4995                  ;       shows all the tab stops; in SET-UP B the cursor moves along it to select a switch to be
  4996                  ;       changed, and the answerback message gets entered here. 
  4997                  ;
  4998 1BBA 2A 4E 20    extra_addr:     lhld    line_25_addr
  4999 1BBD C3 C3 1B                    jmp     real_addr2
  5000                  ;
  5001 1BC0 21 CC 21    wait_screen:    lxi     h,wait_addr
  5002                                                          ; fall through, pointlessly, as HL is already in screen range
  5003                  ; local copy of real_addr
  5004 1BC3 7C          real_addr2:     mov     a,h
  5005 1BC4 E6 0F                       ani     0fh
  5006 1BC6 F6 20                       ori     20h
  5007 1BC8 67                          mov     h,a
  5008 1BC9 C9                          ret
  5009                  ;
  5010                  ; finish_setup
  5011                  ;
  5012                  ;       The final part of drawing the setup screen is connecting the cursor line (23rd line = row 22), to the
  5013                  ;       "Wait" line that we're using as 24th line (row 23).
  5014                  ;
  5015 1BCA CD BA 1B    finish_setup:   call    extra_addr      ; HL <- extra line in screen RAM
  5016 1BCD CD C8 0F                    call    clear_row
  5017 1BD0 E5                          push    h               ; HL points to where terminator should be/is
  5018 1BD1 36 7F                       mvi     m,7fh           ; OK, put terminator there
  5019 1BD3 23                          inx     h               ; point to where screen DMA address will be
  5020 1BD4 EB                          xchg                    ; Stow it in DE
  5021 1BD5 CD C0 1B                    call    wait_screen     ; HL <- wait screen line
  5022 1BD8 7C                          mov     a,h             ; Add line attributes for use as DMA high byte
  5023 1BD9 E6 0F                       ani     0fh
  5024 1BDB F6 70                       ori     70h
  5025 1BDD 12                          stax    d               ; Next line address is "Wait" line
  5026 1BDE 13                          inx     d
  5027 1BDF 7D                          mov     a,l
  5028 1BE0 12                          stax    d
  5029 1BE1 E1                          pop     h               ; HL <- back to terminator address
  5030                  ;
  5031                  ; Now write a line containing a 'T' at every tab stop position.
  5032                  ; Other columns aren't written, so they remain blank.
  5033                  ;
  5034 1BE2 3A 50 20                    lda     screen_cols
  5035 1BE5 5F                          mov     e,a             ; E <- column number
  5036 1BE6 06 54                       mvi     b,'T'           ; B <- character drawn at tab stops
  5037 1BE8 7B          tloop:          mov     a,e
  5038 1BE9 CD 27 0E                    call    tab_offs
  5039 1BEC A6                          ana     m               ; If the bit in A is not set in (HL),
  5040 1BED CA F4 1B                    jz      tnotab          ; then there is no tab set here.
  5041 1BF0 7B                          mov     a,e             ; Otherwise, draw a 'T' at this column
  5042 1BF1 CD EA 1E                    call    disp_char_col
  5043 1BF4 1D          tnotab:         dcr     e               ; Loop for all columns
  5044 1BF5 C2 E8 1B                    jnz     tloop
  5045 1BF8 C9                          ret
  5046                  ;
  5047 1BF9 01 AD 1C    setup_display:  lxi     b,setup_a_string
  5048 1BFC 21 53 22                    lxi     h,setup_video   
  5049 1BFF 3E FA                       mvi     a,0fah          ; "SET-UP x" is bold and blinking
  5050 1C01 CD 71 1C                    call    display_zstr
  5051 1C04 0E 01                       mvi     c,1             ; terminate 1 line
  5052 1C06 06 10                       mvi     b,10h           ; line attributes *for next line*: bottom half, double height
  5053 1C08 CD 8A 1C                    call    term_line
  5054 1C0B 01 AD 1C                    lxi     b,setup_a_string
  5055 1C0E 3E FA                       mvi     a,0fah          ; still bold and blinking
  5056 1C10 CD 71 1C                    call    display_zstr
  5057 1C13 0E 01                       mvi     c,1
  5058 1C15 06 50                       mvi     b,50h           ; line attributes for next line: double width
  5059 1C17 CD 8A 1C                    call    term_line
  5060                  ; "To exit..." is underlined
  5061 1C1A 01 B6 1C                    lxi     b,to_exit_string
  5062 1C1D 3E FD                       mvi     a,rend_under
  5063 1C1F CD 71 1C                    call    display_zstr
  5064 1C22 0E 13                       mvi     c,13h           ; terminate 19 lines
  5065 1C24 CD 88 1C                    call    term_line1      ; use entry point that sets single width line attributes
  5066                                  ; Now the 3 top lines and 18 middle blank lines have been written,
  5067                                  ; we're about to terminate the 22nd line and point to the current
  5068                                  ; cursor line for the 23rd line of the setup screen, which is where
  5069                                  ; the cursor will be movign, and also where answerback can be entered.
  5070                                  ; This cursor line is the 25th line of the main screen, which is
  5071                                  ; known to have nothing needing preserving on it, as scrolling has been
  5072                                  ; completed.
  5073 1C27 36 7F                       mvi     m,7fh           ; Terminate the last blank line, so that's 22 lines terminated.
  5074 1C29 23                          inx     h               ; HL now points to where we're going to the write DMA addres
  5075 1C2A EB                          xchg                    ; Pop this into DE temporarily
  5076 1C2B CD BA 1B                    call    extra_addr      ; HL <- extra line address (25th line of main screen, i.e.
  5077 1C2E 7C                          mov     a,h             ;   main_video + (ncols + 3 ) * 24 = 2a98h (80) or 2f78h (132)
  5078 1C2F F6 70                       ori     70h             ; Add line attributes
  5079 1C31 12                          stax    d               ; Write high byte
  5080 1C32 13                          inx     d               ; advance to low byte of DMA address
  5081 1C33 7D                          mov     a,l             ; A <- low byte of cursor line address
  5082 1C34 12                          stax    d               ; Write low byte 
  5083                  ;
  5084 1C35 CD C0 1B    draw_setup_a:   call    wait_screen     ; HL <- wait screen
  5085                  ;
  5086                  ; The final line of the screen, which for SET-UP is the column numbers,
  5087                  ; will go in the same place the "Wait" display is created.
  5088                  ; Now draw the column numbers at the bottom. We start with '1' and increment the
  5089                  ; number, discarding tens. Every tenth position we change between normal video
  5090                  ; and reverse video.
  5091 1C38 3A 50 20                    lda     screen_cols
  5092 1C3B 47                          mov     b,a
  5093 1C3C 3E 31                       mvi     a,'1'
  5094 1C3E 77          n_col:          mov     m,a             ; write current digit
  5095 1C3F 23                          inx     h
  5096 1C40 4F                          mov     c,a             ; preserve number + rendition
  5097 1C41 E6 0F                       ani     0fh             ; just consider units column
  5098 1C43 79                          mov     a,c             ; restore number
  5099 1C44 C2 49 1C                    jnz     noflip          ; If we've just written a '0', we'll
  5100 1C47 EE 80                       xri     80h             ; flip between normal and reverse video
  5101 1C49 3C          noflip:         inr     a               ; next column digit
  5102 1C4A 27                          daa                     ; staying decimal
  5103 1C4B D2 50 1C                    jnc     normok          ; carry from daa if we are in reverse video
  5104 1C4E F6 80                       ori     80h             ; so restore the reverse video bit
  5105 1C50 E6 8F       normok:         ani     8fh             ; remove tens digit
  5106 1C52 F6 30                       ori     '0'             ; and ASCII-ify it
  5107 1C54 05                          dcr     b
  5108 1C55 C2 3E 1C                    jnz     n_col
  5109                  ;
  5110 1C58 CD 9F 1C                    call    end_screen
  5111 1C5B CD CA 1B                    call    finish_setup
  5112 1C5E 3E 01                       mvi     a,1             ; 1 = "SET-UP A"
  5113                  ;
  5114                  ; Now we've drawn the SET-UP screen, switch the Video DMA to point to it.
  5115                  ;
  5116 1C60 32 BE 21    display_setup:  sta     in_setup_a
  5117 1C63 21 53 22                    lxi     h,setup_video   ; Our address, 2253h, will be sent to Video RAM as 3000h+(2)253h
  5118 1C66 7C                          mov     a,h
  5119 1C67 65                          mov     h,l             ; Video RAM address are big-endian, so swap bytes
  5120 1C68 E6 0F                       ani     0fh
  5121 1C6A F6 30                       ori     30h             ; Force top bits to say: not scrolling region, double-height top half
  5122 1C6C 6F                          mov     l,a
  5123 1C6D 22 04 20                    shld    line1_dma
  5124 1C70 C9                          ret
  5125                  ;
  5126                  ; display_zstr
  5127                  ;       Display the zero-terminated string pointed to by BC, at the screen address given by HL,
  5128                  ;       with character attributes in A.
  5129                  ;       Returns screen address in DE, attribute address in HL
  5130                  ;
  5131 1C71 E5          display_zstr:   push    h               ; Given a pointer to character memory,
  5132 1C72 11 00 10                    lxi     d,1000h         ; Attributes are 1000h further on (somewhere beyond 3000h)
  5133 1C75 19                          dad     d
  5134 1C76 D1                          pop     d               ; swap so that HL <- attribute memory, DE <- character memory
  5135 1C77 F5          n_ch:           push    psw             ; push rendition while we grab character
  5136 1C78 0A                          ldax    b               ; Grab next character
  5137 1C79 B7                          ora     a
  5138 1C7A CA 86 1C                    jz      z_term          ; Zero byte to finish
  5139 1C7D 12                          stax    d               ; Place in character memory
  5140 1C7E F1                          pop     psw             ; A <- rendition
  5141 1C7F 77                          mov     m,a             ; Place rendition in attribute memory
  5142 1C80 23                          inx     h               ; inc. attr ptr
  5143 1C81 13                          inx     d               ; inc. char ptr
  5144 1C82 03                          inx     b               ; inc. string ptr
  5145 1C83 C3 77 1C                    jmp     n_ch
  5146                  ;
  5147 1C86 F1          z_term:         pop     psw             ; clean the last rendition push
  5148 1C87 C9                          ret
  5149                  ;
  5150                  ; term_line1
  5151                  ; term_line
  5152                  ;
  5153                  ;       This routine (with two entry points) helds construct SET-UP displays by providing a fast way
  5154                  ;       of terminating lines and writing video DMA addresses, with line attributes.
  5155                  ;
  5156                  ;       DE points to just beyond the last character of the line
  5157                  ;       B  holds the upper four bits of the video address, while is scroll region flag and line attributes (size);
  5158                  ;          term_line1 is a convenient second entry point to say "single height, single width"
  5159                  ;       C is the number of lines to terminate, effective performing line feeds, which is used to make the blank
  5160                  ;       space in the middle of the SET-UP screen.
  5161                  ;
  5162 1C88 06 70       term_line1:     mvi     b,70h
  5163 1C8A EB          term_line:      xchg                    ; DE <-< HL
  5164                  ;
  5165 1C8B 36 7F       term_loop:      mvi     m,7fh           ; (HL) <- line terminator
  5166 1C8D 23                          inx     h
  5167 1C8E 54                          mov     d,h             ; The characters for the next line will start two bytes
  5168 1C8F 5D                          mov     e,l             ; further on, so get DE pointing to where they'll be
  5169 1C90 13                          inx     d
  5170 1C91 13                          inx     d
  5171 1C92 7A                          mov     a,d             ; Now make up a DMA address high byte for DE
  5172 1C93 E6 0F                       ani     0fh             ; Mask off high nybble, '2', as that's a given
  5173 1C95 B0                          ora     b               ; and put in scrolling region and line attributes instead
  5174 1C96 77                          mov     m,a             ; Write DMA high byte
  5175 1C97 23                          inx     h               ; Point to DMA low byte
  5176 1C98 73                          mov     m,e             ; ... and that's our real address low byte
  5177 1C99 23                          inx     h               ; Move on, so that HL points to next character
  5178 1C9A 0D                          dcr     c               ; If C is non-zero here, the next line is empty and
  5179 1C9B C2 8B 1C                    jnz     term_loop       ; we'll just terminate again (line feed operation)
  5180 1C9E C9                          ret
  5181                  ;
  5182                  ; Write the terminator to mark this line as finished, and then write the next DMA address
  5183                  ; to point back to this terminator, to produce fill lines.
  5184                  ;
  5185 1C9F 36 7F       end_screen:     mvi     m,7fh           ; write terminator
  5186 1CA1 54                          mov     d,h             ; DE <- address of terminator, so we write back to here
  5187 1CA2 5D                          mov     e,l
  5188 1CA3 23                          inx     h               ; Point to high byte of DMA address
  5189 1CA4 7A                          mov     a,d             ; A <- high byte of terminator address
  5190 1CA5 E6 0F                       ani     0fh
  5191 1CA7 F6 70                       ori     70h             ; Write not scrolling, normal lines attributes, 2000h
  5192 1CA9 77                          mov     m,a             ; Write modified high byte
  5193 1CAA 23                          inx     h               ; Point to low byte
  5194 1CAB 73                          mov     m,e             ; Point low byte back to terminator too
  5195 1CAC C9                          ret
  5196                  ;
  5197 1CAD 53 45 54 2D setup_a_string: db      'SET-UP A',0
            55 50 20 41 
            00 
  5198 1CB6 54 4F 20 45 to_exit_string: db      'TO EXIT PRESS ',22h,'SET-UP',22h,0 ; asm8080 doesn't like nested quotes
            58 49 54 20 
            50 52 45 53 
            53 20 22 53 
            45 54 2D 55 
            50 22 00 
  5199                  ;
  5200                  ; clear_extra
  5201                  ;       Clear the extra line
  5202                  ;
  5203                  clear_extra:
  5204 1CCD CD BA 1B                    call    extra_addr
  5205 1CD0 C3 C8 0F                    jmp     clear_row
  5206                  ;
  5207                  ; SET-UP B screen initialisation
  5208                  ;
  5209                  ; Every change made to setup fields causes this line to be redrawn.
  5210                  ;
  5211 1CD3 CD CD 1C    draw_setup_b:   call    clear_extra
  5212 1CD6 CD C0 1B                    call    wait_screen     ; HL <- start of screen line
  5213 1CD9 E5                          push    h
  5214 1CDA 3E 4E                       mvi     a,4eh           ; Clear 78 positions
  5215 1CDC CD CB 0F                    call    clear_part_row  ; on current line to blanks and default rendition
  5216 1CDF E1                          pop     h               ; HL <- start of screen line
  5217 1CE0 E5                          push    h
  5218 1CE1 23                          inx     h               ; Leave first two locations blank for now,
  5219 1CE2 23                          inx     h               ; as we'll draw switch block numbers later.
  5220 1CE3 1E 00                       mvi     e,0             ; E <- switch number (starting from high bit in setup_b1)
  5221 1CE5 06 04                       mvi     b,4             ; assume 4 setup blocks
  5222 1CE7 DB 42                       in      ior_flags       ; read flag buffer
  5223 1CE9 E6 08                       ani     iob_flags_stp   ; check for STP
  5224 1CEB F5                          push    psw
  5225 1CEC CA F0 1C                    jz      next_block
  5226 1CEF 04                          inr     b               ; If we have STP, add another switch block
  5227                  ; must be drawing the setup blocks loop
  5228 1CF0 0E 04       next_block:     mvi     c,4             ; C <- switches per block
  5229 1CF2 CD CC 1D    nx_sw:          call    bit_from_setup  ; A <- 1 or 0 of switch bit E
  5230 1CF5 F6 B0                       ori     0b0h            ; ASCII-ify in reverse video
  5231 1CF7 77                          mov     m,a             ; place on screen
  5232 1CF8 1C                          inr     e               ; next switch bit
  5233 1CF9 23                          inx     h               ; next column on screen
  5234 1CFA 0D                          dcr     c
  5235 1CFB C2 F2 1C                    jnz     nx_sw
  5236                  ;
  5237                  ; Gaps of four blanks between switch blocks (we fill in the switch block numbers later)
  5238 1CFE 0E 04                       mvi     c,4
  5239 1D00 36 00       blank4:         mvi     m,0             ; write blank to screen
  5240 1D02 1C                          inr     e               ; next switch bit
  5241 1D03 23                          inx     h               ; next column on screen
  5242 1D04 0D                          dcr     c
  5243 1D05 C2 00 1D                    jnz     blank4
  5244 1D08 05                          dcr     b               ; next switch block
  5245 1D09 C2 F0 1C                    jnz     next_block
  5246                  ;
  5247                  ; Now draw the blanks between the last switch block and the baud rate fields
  5248 1D0C 0E 04                       mvi     c,4             ; At least 4 blanks
  5249 1D0E F1                          pop     psw
  5250 1D0F F5                          push    psw
  5251 1D10 C2 17 1D                    jnz     draw_blanks     ; We pushed the "test for STP" result, above
  5252 1D13 79                          mov     a,c             ; Without an STP switch block,
  5253 1D14 C6 08                       adi     8               ; we need another 8 blanks
  5254 1D16 4F                          mov     c,a
  5255 1D17 36 00       draw_blanks:    mvi     m,0             ; write blank to screen
  5256 1D19 23                          inx     h               ; next column on screen
  5257 1D1A 0D                          dcr     c
  5258 1D1B C2 17 1D                    jnz     draw_blanks
  5259                  ;
  5260                  ; Baud rate fields
  5261 1D1E 11 4B 1D                    lxi     d,t_speed_string
  5262 1D21 3A AB 21                    lda     tx_spd
  5263 1D24 CD 61 1D                    call    copy_baud_str
  5264                  ;
  5265 1D27 11 56 1D                    lxi     d,r_speed_string
  5266 1D2A 3A AC 21                    lda     rx_spd
  5267 1D2D CD 61 1D                    call    copy_baud_str
  5268                  ;
  5269 1D30 CD 9F 1C                    call    end_screen
  5270                  ;
  5271                  ; Finally, draw the numbers on the switch blocks
  5272 1D33 11 08 00                    lxi     d,8             ; 8 characters between switch block numbers
  5273 1D36 0E 04                       mvi     c,4             ; Again, 4 blocks by default
  5274 1D38 F1                          pop     psw             ; Flags NZ means "got STP"
  5275 1D39 CA 3D 1D                    jz      no_block_5
  5276 1D3C 0C                          inr     c               ; Add that fifth block for STP
  5277 1D3D 3E 31       no_block_5:     mvi     a,'1'           ; Switch block number one coming up
  5278 1D3F E1                          pop     h               ; HL <- start of screen line
  5279 1D40 77          next_block2:    mov     m,a
  5280 1D41 3C                          inr     a               ; next block number
  5281 1D42 19                          dad     d               ; skip past block bits we drew earlier
  5282 1D43 0D                          dcr     c
  5283 1D44 C2 40 1D                    jnz     next_block2
  5284 1D47 AF                          xra     a               ; A <- 0 means "in SET-UP B"
  5285 1D48 C3 60 1C                    jmp     display_setup
  5286                  ;
  5287 1D4B 20 20 20 54 t_speed_string: db      '   T SPEED '
            20 53 50 45 
            45 44 20 
  5288 1D56 20 20 20 52 r_speed_string: db      '   R SPEED '
            20 53 50 45 
            45 44 20 
  5289
  5290                  ; copy_baud_str
  5291                  ;       Comms speeds are encoded internally as values 0 to 15 in the upper 4 bits
  5292                  ;       of the locations tx_spd and rx_spd.
  5293                  ;       The baud strings are 5 characters long, stored sequentially in the table
  5294                  ;       below this routine, without terminators, so this routine copies the
  5295                  ;       appropriate string to the buffer pointed to by HL.
  5296                  ;
  5297                  ;       The placement of these values in the upper 4 bits of a byte is the same as the
  5298                  ;       switch block settings that are also on the SET-UP B screen. It feels as if they
  5299                  ;       might originally have been planned as switch block settings.
  5300                  ;
  5301 1D61 4F          copy_baud_str:  mov     c,a             ; C <- speed value (memcopy corrupts A)
  5302 1D62 06 0B                       mvi     b,11            ; speed strings above are 11 characters long
  5303 1D64 CD 8B 03                    call    memcopy         ; place in buffer
  5304 1D67 79                          mov     a,c             ; A <- speed value, in upper 4 bits
  5305 1D68 0F                          rrc                     ; To avoid shifting this value down 4 bits and
  5306 1D69 0F                          rrc                     ; multiplying by 5 (x * 4 + x * 1), we combine
  5307 1D6A 4F                          mov     c,a             ; the shift and multiply by calculating
  5308 1D6B 0F                          rrc                     ; x / 4 + x / 16. Same result but shorter by
  5309 1D6C 0F                          rrc                     ; four instructions.
  5310 1D6D 81                          add     c               ;
  5311 1D6E 4F                          mov     c,a             ;
  5312 1D6F 06 00                       mvi     b,0             ; BC <- offset of string from baud_strings
  5313 1D71 11 7C 1D                    lxi     d,baud_strings
  5314 1D74 EB                          xchg                    ; Swap HL <-< DE because can only add BC to HL
  5315 1D75 09                          dad     b               ; HL <- string location
  5316 1D76 EB                          xchg                    ; HL <-< DE because DE is "copy from" pointer
  5317 1D77 06 05                       mvi     b,5             ; 5 chars to copy
  5318 1D79 C3 8B 03                    jmp     memcopy         ; copy and return
  5319                  ;
  5320                  ; These strings aren't delimited because they are all five characters long
  5321 1D7C 20 20 20 35 baud_strings:   db      '   50'
            30 
  5322 1D81 20 20 20 37                 db      '   75'
            35 
  5323 1D86 20 20 31 31                 db      '  110'
            30 
  5324 1D8B 20 20 31 33                 db      '  134'
            34 
  5325 1D90 20 20 31 35                 db      '  150'
            30 
  5326 1D95 20 20 32 30                 db      '  200'
            30 
  5327 1D9A 20 20 33 30                 db      '  300'
            30 
  5328 1D9F 20 20 36 30                 db      '  600'
            30 
  5329 1DA4 20 31 32 30                 db      ' 1200'
            30 
  5330 1DA9 20 31 38 30                 db      ' 1800'
            30 
  5331 1DAE 20 32 30 30                 db      ' 2000'
            30 
  5332 1DB3 20 32 34 30                 db      ' 2400'
            30 
  5333 1DB8 20 33 36 30                 db      ' 3600'
            30 
  5334 1DBD 20 34 38 30                 db      ' 4800'
            30 
  5335 1DC2 20 39 36 30                 db      ' 9600'
            30 
  5336 1DC7 31 39 32 30                 db      '19200'
            30 
  5337
  5338                  ; bit_from_setup
  5339                  ;       Retrieve bit E from setup blocks 1 to 5, numbered with bit 0 being bit 7
  5340                  ;       of setup block 1.
  5341                  ;       Returns 1 or 0 in A for bit state (don't need a mask from this routine,
  5342                  ;       as we're just going to print the result.)
  5343                  ;
  5344 1DCC E5          bit_from_setup: push    h
  5345 1DCD 21 A6 21                    lxi     h,setup_b1
  5346 1DD0 53                          mov     d,e             ; It's a quirk of col_in_bits that the bit number
  5347 1DD1 7B                          mov     a,e             ; must be provided in A and D
  5348 1DD2 CD 2B 0E                    call    col_in_bits
  5349 1DD5 A6                          ana     m
  5350 1DD6 E1                          pop     h
  5351 1DD7 C8                          rz
  5352 1DD8 3E 01                       mvi     a,1
  5353 1DDA C9                          ret
  5354                  ;
  5355                  ; Combine rx_spd and tx_spd configuration locations into a single byte, which the
  5356                  ; Technical Manual says allows both speeds to be altered by a single write to the PUSART.
  5357                  ;
  5358 1DDB 3A AC 21    program_pusart: lda     rx_spd          ; A <- receive speed, in bits 7-4
  5359 1DDE E6 F0                       ani     0f0h            ; mask and shift down to low 4 bits
  5360 1DE0 0F                          rrc
  5361 1DE1 0F                          rrc
  5362 1DE2 0F                          rrc
  5363 1DE3 0F                          rrc
  5364 1DE4 47                          mov     b,a
  5365 1DE5 3A AB 21                    lda     tx_spd          ; A <- transmit speed, in bits 7-4
  5366 1DE8 E6 F0                       ani     0f0h            ; mask and combine with receive speed
  5367 1DEA B0                          ora     b
  5368 1DEB 32 58 21                    sta     tx_rx_speed     ; store combined speed byte
  5369 1DEE E6 F0                       ani     0f0h            ; Now look at just transmit speed
  5370 1DF0 FE 20                       cpi     20h             ; Is it 110 baud?
  5371 1DF2 3A A4 21                    lda     pusart_mode
  5372 1DF5 CA FF 1D                    jz      two_stop        ; jump if 110 baud
  5373 1DF8 E6 3F                       ani     3fh
  5374 1DFA F6 80                       ori     80h             ; Set 1½ stop bits (according to TM Figure 4-3-3 and 8251A datasheet)
  5375 1DFC C3 03 1E                    jmp     stow_m          ; Why not one stop bit? 
  5376                  ;
  5377 1DFF E6 3F       two_stop:       ani     3fh             ; Set two stop bits for transmit at low speed (TM §1.1)
  5378 1E01 F6 C0                       ori     0c0h
  5379 1E03 32 A4 21    stow_m:         sta     pusart_mode
  5380 1E06 DB 42                       in      ior_flags
  5381 1E08 E6 08                       ani     iob_flags_stp
  5382 1E0A CA 19 1E                    jz      add_parity
  5383 1E0D 21 A7 21                    lxi     h,setup_b2
  5384 1E10 7E                          mov     a,m
  5385 1E11 F6 10                       ori     sb2_autoxon     ; If we have STP, force Auto XON
  5386 1E13 77                          mov     m,a
  5387 1E14 3E 6E                       mvi     a,6eh           ; And set serial to 1 stop bit, no parity, 8 data bits
  5388 1E16 C3 31 1E                    jmp     stow_m2
  5389                  ;
  5390                  ; Make another PUSART mode by combining parity information and other bits
  5391 1E19 3A A9 21    add_parity:     lda     setup_b4
  5392 1E1C E6 C0                       ani     sb4_paritybits  
  5393 1E1E 47                          mov     b,a             ; B <- |pe|p1|  |  |  |  |  |  |
  5394 1E1F 3A A9 21                    lda     setup_b4
  5395 1E22 E6 20                       ani     sb4_8bits       ; A <- |  |  |8b|  |  |  |  |  |
  5396 1E24 0F                          rrc                     ; A <- |  |  |  |8b|  |  |  |  |
  5397 1E25 F6 20                       ori     20h
  5398 1E27 B0                          ora     b               ; A <- |pe|p1| 1|8b|  |  |  |  |
  5399 1E28 0F                          rrc     
  5400 1E29 0F                          rrc
  5401 1E2A 47                          mov     b,a             ; B <- |  |  |pe|p1| 1|8b|  |  |
  5402 1E2B 3A A4 21                    lda     pusart_mode     ; grab existing mode
  5403 1E2E E6 C3                       ani     0c3h            ; and preserve opposite bits from B
  5404 1E30 B0                          ora     b               ; combine
  5405 1E31 32 A4 21    stow_m2:        sta     pusart_mode     ; place back
  5406 1E34 3A A9 21                    lda     setup_b4
  5407 1E37 E6 10                       ani     sb4_50Hz
  5408 1E39 CA 3E 1E                    jz      is_60
  5409 1E3C 3E 10                       mvi     a,10h
  5410 1E3E C6 20       is_60:          adi     20h             ; A <- 20h for 60Hz, 30h for 50Hz
  5411 1E40 32 7C 20                    sta     refresh_rate    ; Ends up in DC011 video processor
  5412 1E43 3A A6 21                    lda     setup_b1
  5413 1E46 E6 10                       ani     sb1_curblock
  5414 1E48 CA 4D 1E                    jz      is_und
  5415 1E4B 3E 01                       mvi     a,1
  5416 1E4D CD 6E 1E    is_und:         call    cursor_type     ; A <- 1 for block cursor, 0 for underline
  5417 1E50 CD 29 03                    call    reset_pusart
  5418 1E53 3A F8 20                    lda     curs_col
  5419 1E56 FE 15                       cpi     15h
  5420 1E58 CC 42 03                    cz      update_dc011    ; Update DC011 for interlace setting
  5421 1E5B FE 1D                       cpi     1dh
  5422 1E5D CC 42 03                    cz      update_dc011    ; Update DC011 for power hertz
  5423 1E60 FE 0C                       cpi     0ch             ; 
  5424 1E62 CC F2 0B                    cz      set_charsets    ; Update charsets on ANSI/VT52 change. There is no difference
  5425                                                          ; between charset designation in ANSI/VT52 mode, but this
  5426                                                          ; action merely mirrors the SM/RM ANSI sequence action
  5427                                                          ; (see ansi_mode)
  5428 1E65 FE 12                       cpi     12h
  5429 1E67 CC F2 0B                    cz      set_charsets    ; Update charsets on ASCII/UK change
  5430 1E6A CD 36 16                    call    move_updates
  5431 1E6D C9                          ret
  5432                  ;
  5433                  ; cursor_type
  5434                  ;
  5435                  ;       Called with A = 0 for underline cursor, 1 = block cursor.
  5436                  ;       Sets whether the basic character attribute has to match the cursor selection (not required if AVO
  5437                  ;       is present), and flags the cursor selection to video processor through basic_rev_video (which gets
  5438                  ;       sent to DC012.)
  5439                  ;
  5440 1E6E B7          cursor_type:    ora     a               ; NZ if block cursor
  5441 1E6F CA 79 1E                    jz      und_cursor
  5442 1E72 32 5B 21                    sta     basic_rev_video ; block cursor selection will set basic attribute to reverse video
  5443 1E75 AF                          xra     a
  5444 1E76 C3 94 1E                    jmp     set_curs_rend
  5445                  ;
  5446 1E79 DB 42       und_cursor:     in      ior_flags
  5447 1E7B E6 02                       ani     iob_flags_avo
  5448 1E7D C2 90 1E                    jnz     und_curs_base
  5449 1E80 32 59 21                    sta     curs_char_rend  ; no character attribute (bit 7) change for cursor (because AVO present)
  5450 1E83 3E 01                       mvi     a,1
  5451 1E85 32 5B 21                    sta     basic_rev_video
  5452 1E88 3E 02                       mvi     a,2
  5453 1E8A 32 5A 21                    sta     curs_attr_rend
  5454 1E8D C3 6B 03                    jmp     update_dc012
  5455                  ;
  5456                  ; Without AVO, picking a cursor type will also set the basic attribute for characters
  5457                  ;
  5458 1E90 AF          und_curs_base:  xra     a
  5459 1E91 32 5B 21                    sta     basic_rev_video
  5460 1E94 32 5A 21    set_curs_rend:  sta     curs_attr_rend
  5461 1E97 3E 80                       mvi     a,80h           ; Set up character attribute use for cursor
  5462 1E99 32 59 21                    sta     curs_char_rend
  5463 1E9C C3 6B 03                    jmp     update_dc012
  5464                  ;
  5465                  ; The first character of an answerback message comes here, in order to store it as the answerback
  5466                  ; delimiters. The action then changes to answer_action for all the rest, up to a matching delimiter.
  5467                  ;
  5468 1E9F CD DD 1E    aback_entry:    call    answer_print
  5469 1EA2 21 7B 21                    lxi     h,aback_buffer
  5470 1EA5 77                          mov     m,a             ; First character becomes the delimiter
  5471 1EA6 23                          inx     h
  5472 1EA7 22 B4 21                    shld    answerback_ptr
  5473 1EAA 47                          mov     b,a             ; B <- delimiter
  5474 1EAB 11 14 00                    lxi     d,20
  5475 1EAE CD 83 10                    call    memset          ; Fill the answerback buffer with the delimiter
  5476 1EB1 21 B8 1E                    lxi     h,answer_action ; Switch to the "rest of the message" handler
  5477 1EB4 22 40 21                    shld    char_action
  5478 1EB7 C9                          ret
  5479                  ;
  5480                  ; answer_action
  5481                  ;
  5482                  ;       This routine handles keys that are pressed while we're entering an answerback message.
  5483                  ;
  5484 1EB8 CD DD 1E    answer_action:  call    answer_print
  5485 1EBB 2A B4 21                    lhld    answerback_ptr
  5486 1EBE 47                          mov     b,a             ; B <- character pressed
  5487 1EBF 3A 7B 21                    lda     aback_buffer    ; A <- delimiter
  5488 1EC2 4F                          mov     c,a             ; C <- delimiter
  5489 1EC3 B8                          cmp     b               ; Have we had the delimiter again?
  5490 1EC4 CA D3 1E                    jz      delim_found
  5491 1EC7 7D                          mov     a,l
  5492 1EC8 FE 90                       cpi     LOW aback_bufend ; have we exhausted the buffer?
  5493 1ECA CA D3 1E                    jz      delim_found
  5494 1ECD 70                          mov     m,b             ; add character to buffer
  5495 1ECE 23                          inx     h
  5496 1ECF 22 B4 21                    shld    answerback_ptr
  5497 1ED2 C9                          ret
  5498                  ;
  5499 1ED3 71          delim_found:    mov     m,c             ; delimit the buffer
  5500 1ED4 CD CD 1C                    call    clear_extra
  5501 1ED7 CD 4B 09                    call    c0_return
  5502 1EDA C3 B0 1B                    jmp     setup_ready
  5503                  ;
  5504                  ; answer_print:
  5505                  ;
  5506                  ;       Going to print_char, but making sure that control characters are shown as a diamond.
  5507                  ;
  5508 1EDD F5          answer_print:   push    psw
  5509 1EDE FE 20                       cpi     20h
  5510 1EE0 D2 E5 1E                    jnc     skip_ctrl_sub
  5511 1EE3 3E 01                       mvi     a,1             ; A <- diamond glyph to represent control characters
  5512 1EE5 CD E3 05    skip_ctrl_sub:  call    print_nomap
  5513 1EE8 F1                          pop     psw
  5514 1EE9 C9                          ret
  5515                  ;
  5516                  ; disp_char_col
  5517                  ;       Display the character in register B at the column in register
  5518                  ;       A on the current line. 
  5519                  ;
  5520 1EEA E5          disp_char_col:  push    h
  5521 1EEB 21 4E 20                    lxi     h,line_25_addr
  5522 1EEE 4F                          mov     c,a             ; C <- column
  5523 1EEF 3A A2 21                    lda     columns_132
  5524 1EF2 B7                          ora     a
  5525 1EF3 C2 FC 1E                    jnz     c132
  5526 1EF6 79                          mov     a,c             ; A <- column
  5527 1EF7 FE 50                       cpi     80
  5528 1EF9 F2 0F 1F                    jp      col_oor         ; column <= 80 in 80-column mode? Exit
  5529 1EFC 7E          c132:           mov     a,m             ; A <- low byte of addr    \
  5530 1EFD 23                          inx     h               ; HL <- point to high byte  | load_hl_from_hl, unrolled
  5531 1EFE 66                          mov     h,m             ; H <- high byte of addr    |
  5532 1EFF 6F                          mov     l,a             ; L <- low byte            /
  5533 1F00 7C                          mov     a,h             ; \
  5534 1F01 E6 0F                       ani     0fh             ;  | screen_addr, unrolled
  5535 1F03 F6 20                       ori     20h             ;  |
  5536 1F05 67                          mov     h,a             ; /
  5537 1F06 79                          mov     a,c             ; A <- column
  5538 1F07 85                          add     l               ; \
  5539 1F08 6F                          mov     l,a             ;  | add A to HL
  5540 1F09 D2 0D 1F                    jnc     no_h_inc        ;  |
  5541 1F0C 24                          inr     h               ; /
  5542 1F0D 70          no_h_inc:       mov     m,b             ; Place character on screen
  5543 1F0E 78                          mov     a,b             ; A <- character
  5544 1F0F E1          col_oor:        pop     h
  5545 1F10 C9                          ret
  5546                  ;
  5547                  ;       Called from DECTST
  5548 1F11 D5          data_loop_test: push    d
  5549 1F12 AF                          xra     a
  5550 1F13 32 A5 21                    sta     local_mode
  5551 1F16 06 00                       mvi     b,0             ; Start with lowest speed
  5552 1F18 78          test_next_spd:  mov     a,b
  5553 1F19 32 58 21                    sta     tx_rx_speed     ; Storing same speed as both transmit and receive
  5554 1F1C D3 02                       out     iow_baud_rate
  5555 1F1E 0E 01                       mvi     c,1             ; Going to send 01h, 02h, 04h, 08h, 10h, 20h, 40h
  5556 1F20 79          test_tx_ch:     mov     a,c
  5557 1F21 D3 00                       out     iow_pusart_data ; send a character
  5558 1F23 21 00 C0                    lxi     h,0c000h        ; a long counter
  5559 1F26 C5          test_wait_ch:   push    b
  5560 1F27 E5                          push    h
  5561 1F28 CD 75 06                    call    test_rx_q       ; Set NZ for characters available
  5562 1F2B E1                          pop     h
  5563 1F2C C1                          pop     b
  5564 1F2D C2 48 1F                    jnz     test_got_ch
  5565 1F30 23                          inx     h
  5566 1F31 7C                          mov     a,h
  5567 1F32 B5                          ora     l
  5568 1F33 C2 26 1F                    jnz     test_wait_ch
  5569 1F36 3E 05       data_failed:    mvi     a,5             ; command byte: rx enable, tx enable
  5570 1F38 D3 01                       out     iow_pusart_cmd
  5571 1F3A 3A 58 21                    lda     tx_rx_speed
  5572 1F3D D3 02                       out     iow_baud_rate
  5573 1F3F 3E 25                       mvi     a,25h           ; Into local mode and (briefly) light LEDs L2 and L4
  5574 1F41 32 A5 21                    sta     local_mode      ; (these are cleared during see after_tests)
  5575 1F44 AF                          xra     a
  5576 1F45 37                          stc                     ; set "test failed"
  5577 1F46 D1                          pop     d
  5578 1F47 C9                          ret
  5579                  ;
  5580 1F48 E6 7F       test_got_ch:    ani     7fh             ; Ignore parity
  5581 1F4A B9                          cmp     c               ; check received character is the same
  5582 1F4B C2 36 1F                    jnz     data_failed
  5583 1F4E 79                          mov     a,c
  5584 1F4F 07                          rlc                     ; rotate set bit to next higher position
  5585 1F50 FE 80                       cpi     80h
  5586 1F52 4F                          mov     c,a
  5587 1F53 C2 20 1F                    jnz     test_tx_ch
  5588 1F56 78                          mov     a,b             ; A <- speed
  5589 1F57 C6 11                       adi     11h             ; advance tx and rx speed together
  5590 1F59 47                          mov     b,a
  5591 1F5A FE 10                       cpi     10h             ; Last speed is 0ffh (+ 11h = 10h)
  5592 1F5C C2 18 1F                    jnz     test_next_spd
  5593 1F5F AF                          xra     a               ; clear carry - "test passed"
  5594 1F60 D1                          pop     d
  5595 1F61 C9                          ret
  5596                  ;
  5597                  ; modem_test
  5598                  ;       For this test, try commanding 7 combinations of three modem signals (except for all off)
  5599                  ;       and read back the resultant signals that should come from a modem or, in this case,
  5600                  ;       an appropriate loopback connector.
  5601                  ;
  5602 1F62 D5          modem_test:     push    d
  5603 1F63 16 07                       mvi     d,7             ; D <- signal combination
  5604 1F65 7A          modem_loop:     mov     a,d
  5605 1F66 CD 7B 1F                    call    modem_signals   ; command signals through PUSART and NVR latch
  5606 1F69 CD A1 1F                    call    read_modem      ; and read the response
  5607 1F6C BA                          cmp     d               ; which should be identical
  5608 1F6D C2 36 1F                    jnz     data_failed
  5609 1F70 15                          dcr     d
  5610 1F71 C2 65 1F                    jnz     modem_loop
  5611 1F74 3E 05                       mvi     a,5             ; command byte: rx enable, tx enable
  5612 1F76 D3 01                       out     iow_pusart_cmd  ; PUSART back to normal
  5613 1F78 AF                          xra     a
  5614 1F79 D1                          pop     d
  5615 1F7A C9                          ret
  5616                  ;
  5617                  ; modem_signals
  5618                  ;
  5619                  ;       Register A is a modem signal enable mask:
  5620                  ;               01h - RTS (ready to send)
  5621                  ;               02h - SPDS (speed select)
  5622                  ;               04h - DTR (data terminal ready)
  5623                  ;
  5624                  ;       RTS and DTR are controlled by sending a PUSART command word.
  5625                  ;       SPDS is an output from the NVR latch.
  5626                  ;
  5627                  ;       NVR latch bit 5 drives BV2 /SPDS, which goes to the EIA pin 11, "speed select"
  5628                  ;
  5629 1F7B 47          modem_signals:  mov     b,a
  5630 1F7C E6 02                       ani     2               ; A <- bit 1
  5631 1F7E 0F                          rrc                     ; A <- bit 0
  5632 1F7F 0F                          rrc                     ; A <- bit 7
  5633 1F80 0F                          rrc                     ; A <- bit 6
  5634 1F81 0F                          rrc                     ; A <- bit 5 (20h / 00h)
  5635 1F82 F6 10                       ori     10h             ; A <- bit 5 + bit 4 (30h / 10h)
  5636 1F84 32 C9 21                    sta     vint_nvr
  5637 1F87 D3 62                       out     iow_nvr_latch
  5638 1F89 0E 05                       mvi     c,5             ; command byte: rx enable, tx enable
  5639 1F8B 78                          mov     a,b
  5640 1F8C E6 01                       ani     1
  5641 1F8E CA 93 1F                    jz      no_rts
  5642 1F91 0E 25                       mvi     c,25h           ; command byte: RTS (request to send), rx enable, tx enable
  5643 1F93 78          no_rts:         mov     a,b
  5644 1F94 E6 04                       ani     4
  5645 1F96 CA 9D 1F                    jz      no_dtr
  5646 1F99 79                          mov     a,c
  5647 1F9A F6 02                       ori     2               ; mix in DTR (data terminal ready)
  5648 1F9C 4F                          mov     c,a
  5649 1F9D 79          no_dtr:         mov     a,c
  5650 1F9E D3 01                       out     iow_pusart_cmd
  5651 1FA0 C9                          ret
  5652                  ;
  5653                  ; read_modem
  5654                  ;
  5655                  ;       Reads modem buffer (read only I/O port 22h)
  5656                  ;       Not documented in TM
  5657                  ;       Looking at print set MP-00633-00 sheet 3 of 6, Modem Buffer is E41, an 81LS97 addressed
  5658                  ;       by signal BV2 MODEM RD L
  5659                  ;       DB 07 H is /CTS
  5660                  ;       DB 06 H is /SPDI
  5661                  ;       DB 05 H is /RI  -- ignored here
  5662                  ;       DB 04 H is /CD
  5663                  ;
  5664                  ;       This is designed to return a mask that matches the number passed to modem_signals, i.e.
  5665                  ;       01h will indicate that RTS (ready to send) worked,
  5666                  ;               because the modem returns CTS (clear to send) and CD (carrier detect)
  5667                  ;       02h will indicate that SPDS (speed select) worked,
  5668                  ;               because the modem returns SPDI (speed indicator)
  5669                  ;       04h will indicate that DTR (data terminal ready) worked
  5670                  ;               because the PUSART status indicates DSR (data set ready)
  5671                  ;
  5672 1FA1 DB 22       read_modem:     in      ior_modem
  5673 1FA3 47                          mov     b,a             ; B <- modem signals
  5674 1FA4 0E 01                       mvi     c,1             ; Assume RTS worked
  5675 1FA6 78                          mov     a,b
  5676 1FA7 E6 90                       ani     90h             ; mask /CTS and /CD
  5677 1FA9 CA B3 1F                    jz      test_spdi
  5678 1FAC FE 90                       cpi     90h             ; expecting both signals in same state
  5679 1FAE 3E FF                       mvi     a,0ffh
  5680 1FB0 C0                          rnz                     ; return with failure if not
  5681 1FB1 0E 00                       mvi     c,0             ; if CTS and CD are not enabled, mark 0 so far
  5682 1FB3 78          test_spdi:      mov     a,b             ; A <- modem signals
  5683 1FB4 E6 40                       ani     40h             ; mask /SPDI
  5684 1FB6 CA BD 1F                    jz      test_ri         ; not enabled
  5685 1FB9 79                          mov     a,c
  5686 1FBA F6 02                       ori     2               ; mix in "SPDS worked" to result
  5687 1FBC 4F                          mov     c,a
  5688 1FBD DB 01       test_ri:        in      ior_pusart_cmd  ; read PUSART status byte
  5689 1FBF 0F                          rrc                     ; Looking for DSR (bit 7) and we want it to be low
  5690 1FC0 0F                          rrc                     ; to 5,
  5691 1FC1 2F                          cma                     ; invert
  5692 1FC2 E6 20                       ani     20h             ; and mask
  5693 1FC4 A8                          xra     b               ; xor with /RI from modem signals
  5694 1FC5 E6 20                       ani     20h             ; and mask again (making the first mask pointless)
  5695 1FC7 3E FF                       mvi     a,0ffh          ; failure if they weren't in the same state
  5696 1FC9 C0                          rnz
  5697 1FCA 78                          mov     a,b             ; A <- modem signals
  5698 1FCB E6 20                       ani     20h             ; if /RI not found, skip marking DTR
  5699 1FCD C2 D4 1F                    jnz     modem_result
  5700 1FD0 79                          mov     a,c
  5701 1FD1 F6 04                       ori     4               ; mix in "DTR worked" to result
  5702 1FD3 4F                          mov     c,a
  5703 1FD4 B7          modem_result:   ora     a
  5704 1FD5 79                          mov     a,c
  5705 1FD6 C9                          ret
  5706                  ;
  5707                  ; Zeroes till end of ROM ...
  5708                  ;
  5709      1F FF                       org 1fffh
  5710 1FFF 00                          db      0
  5711
  5712                  ; C0 Codes
  5713      00 08       C0_BS           equ     08h
  5714      00 09       C0_HT           equ     09h
  5715      00 0A       C0_LF           equ     0ah
  5716      00 0D       C0_CR           equ     0dh
  5717      00 10       C0_DLE          equ     10h
  5718      00 11       C0_XON          equ     11h
  5719      00 13       C0_XOFF         equ     13h
  5720      00 18       C0_CAN          equ     18h
  5721      00 1A       C0_SUB          equ     1ah
  5722      00 1B       C0_ESC          equ     1bh
  5723
  5724                  ; I/O Ports
  5725                  ; From TM Table 4-2-2 "List of Hex I/O Addresses", p.4-17
  5726                  ;
  5727      00 00       ior_pusart_data equ     0
  5728      00 00       iow_pusart_data equ     0
  5729
  5730      00 01       ior_pusart_cmd  equ     1
  5731      00 01       iow_pusart_cmd  equ     1
  5732
  5733      00 02       iow_baud_rate   equ     2
  5734
  5735      00 22       ior_modem       equ     22h
  5736
  5737                  ; This is the flags buffer. This defined in the VT100 Print Set, MP-00633-00,
  5738                  ; VT100 Basic Video (Sheet 6 of 6). It is an 81LS97 chip, buffering the signal
  5739                  ; names (and active H/L) shown in the comments
  5740                  ; TM §4.6.2.7 says that LBA 7 is used for clocking the NVR, and the print set shows this
  5741      00 42       ior_flags       equ     42h
  5742      00 01       iob_flags_xmit  equ     01h ; BV3 XMIT FLAG H
  5743      00 02       iob_flags_avo   equ     02h ; BV1 ADVANCED VIDEO L
  5744      00 04       iob_flags_gpo   equ     04h ; BV1 GRAPHICS FLAG L
  5745      00 08       iob_flags_stp   equ     08h ; BV3 OPTION PRESENT H
  5746      00 10       iob_flags_unk4  equ     10h ; BV4 EVEN FIELD L
  5747      00 20       iob_flags_nvr   equ     20h ; BV2 NVR DATA H
  5748      00 40       iob_flags_lba7  equ     40h ; LBA 7 H
  5749      00 80       iob_flags_kbd   equ     80h ; BV6 KBD TBMT H
  5750
  5751                  ; On Print Set MP-00633-00, this appears to drive the D/A latch E49, a 74LS174 device,
  5752                  ; with five bits of data and a sixth "init" value.
  5753                  ; POST sends 0f0h and the other writes are from a scratch location that goes from
  5754                  ; 0 (brightest) to 1fh (dimmest), making it look as if the POST write says "mid-brightness, init"
  5755                  ;
  5756      00 42       iow_brightness  equ     42h
  5757
  5758                  ; iow_nvr_latch
  5759                  ;
  5760                  ;          7     6     5     4     3     2     1     0
  5761                  ;       +-----+-----+-----+-----+-----+-----+-----+-----+
  5762                  ;       |     |     |/SPDS| --- | C3  | C2  | C1  |data |
  5763                  ;       +-----+-----+-----+-----+-----+-----+-----+-----+
  5764                  ;
  5765                  ;       Not defined in TM - this from print set MP-00633-00.
  5766                  ;       Bit 4 is not connected. TM §4.2.7 says it was intended to disable receiver interrupts,
  5767                  ;               though it isn't used. In the earliest print set available, this drives a signal
  5768                  ;               called BV2 REC INT ENA H, which is ANDed with the "Receive Ready" BV3 REC FLAG H
  5769                  ;               signal from the PUSART.
  5770                  ;
  5771                  ;       Bit 5 drives BV2 /SPDS (active low), which goes to the EIA connector as "speed select"
  5772                  ;       Other modem signals are driven by PUSART command words.
  5773                  ;
  5774                  ;       The NVR latch is often driven (early initialisation and modem test, for instance) with
  5775                  ;       the command bits C3, C2 and C1 set to 0, the ER1400 "accept data" which the Technical
  5776                  ;       Manual explains as a handy idle command because the all zeroes protects the NVR from
  5777                  ;       spontaneous writes during power down.
  5778
  5779      00 62       iow_nvr_latch   equ     62h
  5780
  5781      00 82       ior_keyboard    equ     82h
  5782
  5783                  ; iow_keyboard
  5784                  ;
  5785                  ;          7     6     5     4     3     2     1     0
  5786                  ;       +-----+-----+-----+-----+-----+-----+-----+-----+
  5787                  ;       |     |     |     |     |L1 on|L2 on|L3 on|L4 on|
  5788                  ;       +-----+-----+-----+-----+-----+-----+-----+-----+
  5789                  ;          |     |     |     `-- keyboard locked LED
  5790                  ;          |     |     `-- online/local LED
  5791                  ;          |     `-- start scan
  5792                  ;          `-- speaker click
  5793
  5794      00 82       iow_keyboard    equ     82h
  5795      00 01       iow_kbd_led4    equ     01h
  5796      00 02       iow_kbd_led3    equ     02h
  5797      00 04       iow_kbd_led2    equ     04h
  5798      00 08       iow_kbd_led1    equ     08h
  5799      00 10       iow_kbd_locked  equ     10h
  5800      00 20       iow_kbd_local   equ     20h
  5801      00 40       iow_kbd_scan    equ     40h
  5802      00 80       iow_kbd_click   equ     80h
  5803
  5804                  ; Detailed in TM p.4-70
  5805      00 A2       iow_dc012       equ     0a2h
  5806
  5807      00 C2       iow_dc011       equ     0c2h
  5808
  5809      00 E2       iow_graphics    equ     0e2h
  5810
  5811                  ;
  5812                  ; Equates for RAM locations, 2000h - 2bffh (3 KiB)
  5813                  ;
  5814                  ; The Video Processor is hard-wired to DMA from this region, always starting a frame at 2000h.
  5815                  ;
  5816                  ; Memory layout
  5817      1F FF       rom_top         equ     1fffh
  5818                  ;
  5819      20 00       ram_start       equ     2000h   ; The VT100 without AVO has 3K of RAM, from 2000h to 2bffh
  5820      2B FF       ram_top         equ     2bffh
  5821      30 00       avo_ram_start   equ     3000h
  5822      3F FF       avo_ram_top     equ     3fffh   ; AVO adds another 4K of RAM
  5823
  5824      20 01       line0_dma       equ     2001h
  5825      20 04       line1_dma       equ     2004h
  5826                  ; There are two names for this location because, although it is stack_top,
  5827                  ; the stack always decrements before a push, so 204eh itself is available for some other
  5828                  ; purpose, and we'd like a name for that.
  5829      20 4E       stack_top       equ     204eh
  5830                  ;
  5831                  ; Extra line address, i.e. the line that is available to be scrolled onto the screen.
  5832                  ; From soon after reset, set to FA98, which is address of 25th (spare) line.
  5833                  ; If we go to bottom of screen, and execute LF, it becomes F2D0, i.e. the old 1st line.
  5834                  ;
  5835      20 4E       line_25_addr    equ     204eh
  5836                  ; screen_cols is either 80 or 132
  5837      20 50       screen_cols     equ     2050h
  5838                  ;
  5839                  ; zeroed on width change. read in vertint
  5840                  ; When processing index_down, when smooth scrolling is enabled and we reach the bottom margin,
  5841                  ; this gets set to 1 and when processing reverse index and the top margin is reached, it'll get
  5842                  ; set to -1, to indicate the direction of the pending scroll.
  5843                  ; Pending scrolls are acted upon during vertical interrupts, as they will then cause single scan line
  5844                  ; changes until the extra line has fully scrolled into view (over 10 scans).
  5845      20 51       scroll_pending  equ     2051h
  5846                  ; stores pointer to screen DMA address (can only see a single write, and no reads)
  5847                  ; this name will have _dma on the end
  5848      20 52       UNREAD_X2052    equ     2052h
  5849                  ; Pointer to 25th line DMA address in screen RAM. Never read; confirmed by tests.
  5850      20 54       UNREAD_X2054    equ     2054h
  5851                  ; stores a line address for shuffle
  5852      20 56       shufdt2         equ     2056h
  5853                  ; stores a line address for shuffle
  5854      20 58       shufad2         equ     2058h
  5855                  ; Scan line of current scroll (to DC012 scroll latch). Zeroed on width change
  5856      20 5A       scroll_scan     equ     205ah
  5857                  ; scroll direction, as initialised to 01h but could be 99h (=-1 daa)
  5858      20 5B       scroll_dir      equ     205bh
  5859                  ; Buffer where cursor and function keys are prepared for sending to host
  5860                  ; offset into buffer is curkey_qcount
  5861      20 5C       curkey_queue    equ     205ch ; 10 locs
  5862                  ; updated in vertical int, non-zero seems to hold up width change (fast loop, so must be waiting
  5863                  ; on vertical int to update it) Perhaps scroll to finish?
  5864      20 65       smooth_scroll   equ     2065h
  5865      20 66       UNUSED_X2066    equ     2066h
  5866                  ; scan code of the latest new key seen in silo
  5867      20 67       new_key_scan    equ     2067h
  5868                  ; key_flags low 3 bits are number of keys waiting to be processed, which
  5869                  ; can only be up to 4.
  5870                  ; Bits 4-7 are the "shift" key flags, which get processed by the interrupt
  5871                  ; routine instead of being buffered.
  5872                  ;
  5873                  ;          7     6     5     4     3     2     1     0
  5874                  ;       +-----+-----+-----+-----+-----+-----+-----+-----+
  5875                  ;       |EOS  |CAPS |SHIFT|CTRL |     | OVR key count   |
  5876                  ;       +-----+-----+-----+-----+-----+-----+-----+-----+
  5877                  ;
  5878      20 68       key_flags       equ     2068h
  5879      00 10       key_flag_ctrl   equ     10h
  5880      00 20       key_flag_shift  equ     20h
  5881      00 40       key_flag_caps   equ     40h
  5882      00 80       key_flag_eos    equ     80h
  5883                  ; last_key_flags are updated at the end of the keyboard processing
  5884      20 69       last_key_flags  equ     2069h
  5885                  ; Space for 4 keys pending processing 206ah to 206dh. TM says "SILO" throughout
  5886      20 6A       key_silo        equ     206ah ; 4 locs
  5887                  ; Buffer for previous keys which we'll attempt to match with silo
  5888      20 6E       key_history     equ     206eh ; 4 locs
  5889      20 72       key_rpt_timer   equ     2072h
  5890                  ; used while processing keys - initialised to 2 and decremented
  5891      20 73       key_rpt_pause   equ     2073h
  5892                  ; incremented every time we send byte to keyboard
  5893      20 74       num_kbd_updates equ     2074h
  5894                  ; storing a line address, possibly also to do with shuffle
  5895      20 75       shufad1         equ     2075h
  5896                  ; Confirmed unwritten by tests. Reading non-zero causes premature exit from update_kbd
  5897      20 77       UNWRIT_X2077    equ     2077h
  5898                  ; C0 BEL and margin bell add to this location, and it is processed in vertical interrupt with other keyboard bits
  5899      20 78       bell_duration   equ     2078h
  5900                  ;
  5901                  ; At start-up, GPO presence is read from the I/O flags buffer and sets bit 0 of these flags.
  5902                  ; Bit 7 is set if we are currently passing characters to the GPO, between DECGON and DECGOFF.
  5903                  ; No other bits are used.
  5904      20 79       gpo_flags       equ     2079h
  5905                  ; tested and cleared during vert. int.
  5906                  ; shuffle cannot happen until this is nonzero. made nonzero when extra line is connected
  5907      20 7A       shuffle_ready   equ     207ah
  5908                  ; checked in proc_func_key
  5909                  ; 0ffh = in setup, 0 = not in setup
  5910      20 7B       in_setup        equ     207bh
  5911                  ; refresh_rate is 20h for 60Hz, 30h for 50Hz
  5912      20 7C       refresh_rate    equ     207ch
  5913      00 20       refresh_60Hz    equ     20h
  5914      00 30       refresh_50Hz    equ     30h
  5915      20 7D       inter_chars     equ     207dh   ; intermediate chars are added together in this byte
  5916      20 7E       final_char      equ     207eh   ; final character of ESC or CSI sequence
  5917                  ; Count of vertical interrupts, at 60 Hz, used for timing BREAK and synchronising switch to SET-UP screen
  5918      20 7F       frame_count     equ     207fh
  5919                  ; Received characters go into this buffer, at the location 2000h + rx_head,
  5920                  ; and they are pulled out from 2000h + rx_tail. Both rx_head and rx_tail are
  5921                  ; initialised to 80h, and when they increment past 0bfh, that's where they
  5922                  ; wrap back to, so the buffer is 64 characters long.
  5923      20 80       rx_buffer       equ     2080h
  5924                  ; initialised to 80h, seems to be where next received character is placed,
  5925                  ; and will run up to 0bfh, then wrap back to 80h
  5926                  ; Receiver int
  5927      20 C0       rx_head         equ     20c0h
  5928      20 C1       rx_tail         equ     20c1h
  5929                  ; 20c2 is a table of 25 (including pline_extra) addresses of physical lines in the screen RAM.
  5930                  ; X20de is because it is the address of row 14 (0-based), so for a 14 line
  5931                  ; display it is the line beyond the last on the screen. Looking at the code
  5932                  ; around X117e, pline_extra seems to have a similar function.
  5933      20 C2       pline_addr      equ     20c2h ; 28 locs
  5934                  ; address of last line in screen ram?
  5935      20 F2       pline_extra     equ     20f2h
  5936                  ; move_updates seems to place this where cursor *was*
  5937      20 F4       char_und_curs   equ     20f4h
  5938      20 F5       rend_und_curs   equ     20f5h
  5939                  ; Must be cursor address in screen RAM
  5940      20 F6       cursor_address  equ     20f6h
  5941                  ; The graphics state that DECSC saves, and DECRC restores, is stored from 20f8h to 2101h.
  5942                  ; Not all of these bytes have been decoded yet.
  5943      20 F8       gfx_state       equ     20f8h   ; live graphics state (cursor position, SGR, etc.)
  5944      20 F8       curs_col        equ     20f8h   ; first location of state seems to be X (column)
  5945      20 F9       curs_row        equ     20f9h   ; this is absolute cursor row (regardless of origin mode)
  5946                  ; Default rendition is 0f7h (see sgr_off)
  5947                  ; blink      masks with 0feh (11111110b)
  5948                  ; underscore masks with 0fdh (11111101b)
  5949                  ; bold       masks with 0fbh (11111011b)
  5950      20 FA       char_rend       equ     20fah
  5951      00 FE       rend_blink      equ     0feh
  5952      00 FD       rend_under      equ     0fdh
  5953      00 FB       rend_bold       equ     0fbh
  5954                  ; reverse video is treated differently, with normal video being
  5955                  ; 0 and reverse video setting this to 80h.
  5956      20 FB       char_rvid       equ     20fbh
  5957      20 FC       gl_invocation   equ     20fch   ; 0 or 1, depending on whether G0 or G1 is invoked into GL
  5958      20 FD       g0_charset      equ     20fdh
  5959      20 FE       g1_charset      equ     20feh
  5960      20 FF       g2_charset      equ     20ffh   ; can't designate a charset into G2 or G3 but they are initialised
  5961      21 00       g3_charset      equ     2100h   ; with the same defaults as G0 and G1
  5962                  ; origin mode DECOM 1/0
  5963      21 01       origin_mode     equ     2101h
  5964
  5965      21 02       gfx_saved       equ     2102h   ; saved graphics state
  5966      21 04       saved_rend      equ     2104h   ; saved char_rend
  5967                  ; enter_setup stores curs_col and curs_row here
  5968      21 0D       saved_curs_col  equ     210dh
  5969                  ; initialised to 0ffh at startup. Something to do with margins, but looks more like a flag than value
  5970                  ; Seems to be very commonly invalidated by storing 0ffh (many places) and 0f0h (one place)
  5971      21 0E       saved_curs_row  equ     210eh
  5972      21 0F       UNUSED_X210f    equ     210fh
  5973      21 10       UNUSED_X2110    equ     2110h
  5974                  ; When we enter SET-UP, the previous action for received characters, char_action, is
  5975                  ; saved here.
  5976      21 11       saved_action    equ     2111h
  5977                  ;
  5978                  ; Detailed in TM §4.7.3.2, p. 4-92
  5979                  ; Each entry in this array contains the number of a line on the physical screen. What the TM
  5980                  ; doesn't say is that bit 7 might be set on the physical line number; this means that the line
  5981                  ; is double width, which in turn means that a second set of terminating bytes have been placed
  5982                  ; half-way across the line.
  5983                  ;
  5984                  ; LATOFS runs from 2113h (row 0) to 212a (row 23). I don't yet know whether X212b is related,
  5985                  ; as perhaps being the physical number of the 25th line.
  5986      21 13       latofs          equ     2113h
  5987                  ; Bottom row number + 1 is written in latofs_last, even for 14-line screens
  5988      21 2B       latofs_last     equ     212bh
  5989                  ; Counts down from 35h, toggles blink flip-flop every time it reaches zero
  5990      21 2C       blink_timer     equ     212ch
  5991                  ; 16-bit counts down in update_kbd and then flips cursor visibility
  5992      21 2D       cursor_timer    equ     212dh
  5993      21 2F       param_value     equ     212fh ; current CSI parameter value we're collecting
  5994
  5995                  ; TWO names for same location here. csi_params is used to refer to the array, when the
  5996                  ; code is going to iterate over params and the name csi_p1 is used when we're explicitly
  5997                  ; looking for the first parameter.
  5998      21 30       csi_params      equ     2130h ; list of 16 params for CSI sequence
  5999      21 30       csi_p1          equ     2130h ; parameter p1 is start of array
  6000      21 31       csi_p2          equ     2131h
  6001                  ; None of these names for further parameters are used in the source because there are no
  6002                  ; sequences on a VT100 with more than two fixed parameters. All other sequences have
  6003                  ; selection parameters, and they are addressed by iterating over the array as a whole.
  6004                  ; Nevertheless, the presence of names in the symbol table aids debugging.
  6005      21 32       csi_p3          equ     2132h
  6006      21 33       csi_p4          equ     2133h
  6007      21 34       csi_p5          equ     2134h
  6008      21 35       csi_p6          equ     2135h
  6009      21 36       csi_p7          equ     2136h
  6010      21 37       csi_p8          equ     2137h
  6011      21 38       csi_p9          equ     2138h
  6012      21 39       csi_p10         equ     2139h
  6013      21 3A       csi_p11         equ     213ah
  6014      21 3B       csi_p12         equ     213bh
  6015      21 3C       csi_p13         equ     213ch
  6016      21 3D       csi_p14         equ     213dh
  6017      21 3E       csi_p15         equ     213eh
  6018                  ; The 16th numeric parameter is here for convenience of processing. It can be written
  6019                  ; to, but it is never used during execution of a control sequence.
  6020      21 3F       csi_p16         equ     213fh
  6021                  ;
  6022      21 40       char_action     equ     2140h ; address of routine used to process next incoming character
  6023                  ; If we write to location on right-hand edge of screen and auto-wrap is on,
  6024                  ; need for a wrap on the next write is recorded here. Always cleared after
  6025                  ; a non-edge character is written.
  6026      21 42       pending_wrap    equ     2142h
  6027                  ; used in send_key_byte to hold offset into curkey_queue
  6028      21 43       curkey_qcount   equ     2143h
  6029      21 44       keyboard_locked equ     2144h
  6030                  ; Supposed to be a mask for four bits (0-3, here) that are masked with other
  6031                  ; bytes to form an instruction for the keyword. However, a bug (see decll_action)
  6032                  ; means that this byte can have bits 4-7 set, causing interference with the
  6033                  ; keyboard, and they cannot be then unset without resetting the terminal.
  6034                  ;
  6035                  ;          7     6     5     4     3     2     1     0
  6036                  ;       +-----+-----+-----+-----+-----+-----+-----+-----+
  6037                  ;       |     |     |     |     |L1 on|L2 on|L3 on|L4 on|
  6038                  ;       +-----+-----+-----+-----+-----+-----+-----+-----+
  6039      21 45       led_state       equ     2145h
  6040                  ; online/local mask for keyboard (directly ORed with led_state)
  6041      21 46       kbd_online_mask equ     2146h
  6042                  ; spkr./click mask (directly ORed with led_state)
  6043      21 47       kbd_click_mask  equ     2147h
  6044      21 48       kbd_scan_mask   equ     2148h
  6045                  ; Never see this being read, or jumped through but looks like address location
  6046                  ; I believe this always contains 07ffh. Confirmed by tests.
  6047      21 49       UNREAD_X2149    equ     2149h
  6048      21 4A       UNREAD_X214a    equ     214ah
  6049      21 4B       num_params      equ     214bh   ; offset into params array of the current param
  6050      21 4C       UNUSED_X214c    equ     214ch
  6051      21 4D       UNUSED_X214d    equ     214dh
  6052                  ;
  6053                  ; Start of the cursor line in video RAM
  6054      21 4E       start_line_addr equ     214eh
  6055                  ; a key scan code - checked when processing auto repeat
  6056      21 50       latest_key_scan equ     2150h
  6057                  ; This flag is only cleared when a column has been received, rather than when ESC Y has been
  6058                  ; recognised, which means that intervening C0 controls won't affect the very next printable
  6059                  ; characters from being recognised as a row or column.
  6060      21 51       got_vt52_row    equ     2151h
  6061      21 52       vt52_row_coord  equ     2152h
  6062                  ;
  6063                  ; Internal column number of right margin of the current line.
  6064                  ; This will be 79 or 131 for single-width lines. One of the jobs of see move_updates
  6065                  ; is to halve this for double-width lines.
  6066                  ;
  6067      21 53       right_margin    equ     2153h
  6068                  ;
  6069                  ; Set up if a margin bell is required. Stealth read at 1696h.
  6070      21 54       margin_bell     equ     2154h
  6071      21 55       top_margin      equ     2155h
  6072      21 56       bottom_margin   equ     2156h
  6073                  ; Gets 80h double-width marker if appropriate, or 0 for single line
  6074      21 57       curr_line_dbl   equ     2157h
  6075                  ; This tx_spd and rx_spd combined to make a single byte, sent to iow_baud_rate
  6076      21 58       tx_rx_speed     equ     2158h
  6077                  ;
  6078                  ; Next two locations control how the cursor is going to work, either by changing the
  6079                  ; base attribute in the video RAM for each character, which is what must happen without
  6080                  ; AVO present, or by changing attribute RAM, when AVO is present. Cursor setup records
  6081                  ; both options in these locations, which get XORed with video RAM and attribute RAM,
  6082                  ; respectively.
  6083                  ;
  6084                  ; without AVO, this is 80h, so toggles between normal and reverse video (or underline)
  6085      21 59       curs_char_rend  equ     2159h
  6086                  ; 0 for underline cursor, 2 for block
  6087      21 5A       curs_attr_rend  equ     215ah
  6088                  ; 0 = basic attribute is underline, 1 = basic attribute is reverse video
  6089      21 5B       basic_rev_video equ     215bh
  6090                  ; Reports to be sent out from the terminal are built in this buffer.
  6091                  ; Longest report is DECREPTPARM, which can reach 21 characters, making
  6092                  ; this buffer extend from 215ch to 2170h.
  6093                  ; DECREPTPARM: ESC [ 2 ; 1 ; 1 ; 120 ; 120 ; 1 ; 15 c
  6094      21 5C       report_buffer   equ     215ch
  6095                  ; Offset into report_buffer of the character to be sent next
  6096      21 71       rep_send_offset equ     2171h
  6097
  6098                  ; When a sequence is received that requires a response, the need for a response is placed in
  6099                  ; location pending_report, and then dealt with later, as part of the periodic keyboard
  6100                  ; tick routine.
  6101                  ;
  6102                  ; pend_<x< are the individual bit flags to say what needs reporting
  6103      21 72       pending_report  equ     2172h
  6104      00 01       pend_curpos     equ     01h ; DSR 6 (cursor position) has been requested
  6105      00 02       pend_tparm      equ     02h ; DECREQTPARM has been received
  6106      00 04       pend_ident      equ     04h ; DECID has been received
  6107      00 08       pend_devstat    equ     08h ; DSR 5 (device status) has been requested
  6108      00 10       pend_aback      equ     10h ; Answerback has been triggered
  6109      00 20       pend_key        equ     20h ; Keys to host (single or multi-byte)
  6110
  6111                  ; When a report has been prepared and send_report has been called, this goes to 1,
  6112                  ; then back to zero when last character of report has been grabbed from buffer
  6113      21 73       sending_report  equ     2173h
  6114                  ;
  6115                  ; Used to hold the address of a routine that will produce a report, then holds
  6116                  ; the address of the routine that pumps them out, which is cont_report for all
  6117                  ; reports other than cursor keys, because they are prepared in advance, one at
  6118                  ; a time, but several cursor key reports can be buffered.
  6119      21 74       report_action   equ     2174h
  6120
  6121                  ; Flag whether DECREPTPARM can be sent unsolicited (i.e. whenever SET-UP is exited),
  6122                  ; or only on request. This is controlled by the parameter sent with DECREQTPARM,
  6123                  ; so this flag will be 0 = unsolicited reports allowed, 1 = solicited only
  6124                  ; Status on reset will be 1, i.e. no unsolicited reports
  6125      21 76       tparm_solicited equ     2176h
  6126                  ; When SETUP is detected in keyboard processing, this flag is set to indicate that
  6127                  ; SET-UP should be entered.
  6128      21 77       pending_setup   equ     2177h
  6129                  ; keypad_mode: 0 = numeric, 1 = application
  6130      21 78       keypad_mode     equ     2178h
  6131                  ; For line shuffling
  6132      21 79       shufdt1         equ     2179h
  6133                  ; TM §4.7.11 details SET-UP scratch RAM (saying it is subject to change.)
  6134                  ; It confirms that the first area here is the answerback message, 22 bytes, with 20 characters and 2 delimiters.
  6135      21 7B       aback_buffer    equ     217bh
  6136      21 90       aback_bufend    equ     2190h
  6137                  ; 132/8 = 16.5 bytes, so tab buffer is 17 bytes, 2191h to 21a1h
  6138      21 91       tab_settings    equ     2191h
  6139      00 11       tablen          equ     17 ; bytes in tab settings area
  6140                  ; 1 = 132 columns, 0 = 80 columns
  6141      21 A2       columns_132     equ     21a2h
  6142                  ; brightest = 0, dimmest = 1fh
  6143      21 A3       brightness      equ     21a3h
  6144                  ; pusart_mode, based on TM §4.7.11
  6145      21 A4       pusart_mode     equ     21a4h
  6146                  ;
  6147                  ; local_mode flag is ORed into the keyboard byte in order to set the Local LED,
  6148                  ; so the value 20h is used as "local". Data loopback test will indicate failure
  6149                  ; by placing 25h, in order to briefly light LEDs L2 and L4 - these then get removed
  6150                  ; by after_tests.
  6151      21 A5       local_mode      equ     21a5h
  6152                  ;       21a6: SET-UP B block 1
  6153                  ;
  6154                  ;          7     6     5     4     3     2     1     0
  6155                  ;       +-----+-----+-----+-----+-----+-----+-----+-----+
  6156                  ;       |     |     |     |     |     |     |     |     |
  6157                  ;       +-----+-----+-----+-----+-----+-----+-----+-----+
  6158                  ;          |     |     |     `-- 1 = cursor block, 0 = cursor underline
  6159                  ;          |     |     `-- 1 = light background, 0 = dark background
  6160                  ;          |     `-- 1 = autorepeat on, 0 = autorepeat off
  6161                  ;          `-- 1 = smooth scroll, 0 = jump scroll
  6162                  ;
  6163      21 A6       setup_b1        equ     21a6h
  6164      00 10       sb1_curblock    equ     10h
  6165      00 20       sb1_lightback   equ     20h
  6166      00 40       sb1_autorep     equ     40h
  6167      00 80       sb1_smooth      equ     80h
  6168                  ;
  6169                  ;       21a7: SET-UP B block 2
  6170                  ;
  6171                  ;          7     6     5     4     3     2     1     0
  6172                  ;       +-----+-----+-----+-----+-----+-----+-----+-----+
  6173                  ;       |     |     |     |     |     |     |     |     |
  6174                  ;       +-----+-----+-----+-----+-----+-----+-----+-----+
  6175                  ;          |     |     |     `-- 1 = auto xon/xoff, 0 = no auto xon/off
  6176                  ;          |     |     `-- 1 = ANSI mode, 0 = VT52 (see ansi_mode)
  6177                  ;          |     `-- 1 = keyclick on, 0 = keyclick off
  6178                  ;          `-- 1 = margin bell on, 0 = margin bell off
  6179                  ;
  6180      21 A7       setup_b2        equ     21a7h
  6181      00 10       sb2_autoxon     equ     10h
  6182      00 20       sb2_ansi        equ     20h
  6183      00 40       sb2_keyclick    equ     40h
  6184      00 80       sb2_marginbell  equ     80h
  6185
  6186                  ;       21a8: SET-UP B block 3
  6187                  ;
  6188                  ;          7     6     5     4     3     2     1     0
  6189                  ;       +-----+-----+-----+-----+-----+-----+-----+-----+
  6190                  ;       |     |     |     |     |     |     |     |     |
  6191                  ;       +-----+-----+-----+-----+-----+-----+-----+-----+
  6192                  ;          |     |     |     `-- 1 = interlace on, 0 = interlace off (see decinlm_mode)
  6193                  ;          |     |     `-- 1 = ?, 0 = ?
  6194                  ;          |     `-- 1 = autowrap, 0 = no autowrap (see decawm_mode)
  6195                  ;          `-- 1 = UK, 0 = ASCII
  6196      21 A8       setup_b3        equ     21a8h
  6197      00 10       sb3_interlace   equ     10h
  6198      00 20       sb3_newline     equ     20h
  6199      00 40       sb3_autowrap    equ     40h
  6200      00 80       sb3_uk          equ     80h
  6201
  6202                  ;       21a9: SET-UP B block 4
  6203                  ;
  6204                  ;          7     6     5     4     3     2     1     0
  6205                  ;       +-----+-----+-----+-----+-----+-----+-----+-----+
  6206                  ;       |     |     |     |     |     |     |     |     |
  6207                  ;       +-----+-----+-----+-----+-----+-----+-----+-----+
  6208                  ;          |     |     |     `-- 1 - power 50 Hz, 0 = power 60 Hz
  6209                  ;          |     |     `-- bits/char: 1 = 8 bits, 0 = 7 bits
  6210                  ;          |     `-- 1 = parity on, 0 = parity off
  6211                  ;          `-- 1 = even parity, 0 = odd parity
  6212      21 A9       setup_b4        equ     21a9h
  6213      00 10       sb4_50Hz        equ     10h
  6214      00 20       sb4_8bits       equ     20h
  6215      00 40       sb4_parityon    equ     40h
  6216      00 80       sb4_evenparity  equ     80h
  6217      00 C0       sb4_paritybits  equ     (sb4_parityon|sb4_evenparity)
  6218                  ;
  6219      21 AA       setup_b5        equ     21aah ; only visible with STP option installed
  6220      21 AB       tx_spd          equ     21abh ; encoded transmit speed in high 4 bits
  6221      21 AC       rx_spd          equ     21ach ; encoded receive speed in high 4 bits
  6222      21 AD       nvr_checksum    equ     21adh
  6223                  ;
  6224      21 AE       nvr_addr        equ     21aeh
  6225      21 AF       nvr_data        equ     21afh ; 2 locs
  6226      21 B1       UNUSED_X21b1    equ     21b1h
  6227      21 B2       UNUSED_X21b2    equ     21b2h
  6228      21 B3       UNUSED_X21b3    equ     21b3h
  6229      21 B4       answerback_ptr  equ     21b4h
  6230      21 B6       UNUSED_X21b6    equ     21b6h
  6231      21 B7       UNUSED_X21b7    equ     21b7h
  6232      21 B8       csi_private     equ     21b8h ; CSI sequence private character is stored here
  6233      21 B9       found_action    equ     21b9h
  6234                  ; initialised to 0ffh at startup
  6235      21 BA       cursor_visible  equ     21bah
  6236                  ; updated post cursor move, used for margin bell calculations because it matters whether
  6237                  ; we got to the current column by typing or cursor positioning sequences.
  6238      21 BB       last_curs_col   equ     21bbh
  6239      21 BC       mode_ckm        equ     21bch
  6240                  ; POST results go here
  6241      21 BD       test_results    equ     21bdh
  6242                  ; in_setup_a is 1 for SET-UP A, 0 for SET-UP B.
  6243      21 BE       in_setup_a      equ     21beh
  6244                  ;
  6245                  ; If the host is allowed to send characters (receive buffer has enough space,
  6246                  ; user has pressed Ctrl/Q, we've come out of SETUP, etc.) then this will be zero,
  6247                  ; and an XON will probably have been sent. Otherwise, this records why the host has
  6248                  ; been told to stop, as a bit mask. 01h means the receive buffer is getting full,
  6249                  ; and 02h means the user has pressed NO SCROLL or Ctrl/S.
  6250                  ; 03h means the receive buffer was already getting full, and the user also panicked
  6251                  ; and hit NO SCROLL!
  6252                  ;
  6253      21 BF       why_xoff        equ     21bfh
  6254                  ; XON/XOFF character to transmit is pulled from here, as long as 21c1h is not zero
  6255      21 C0       tx_xo_char      equ     21c0h
  6256      21 C1       tx_xo_flag      equ     21c1h
  6257                  ; Contents of this ANDed with 0feh if we've received XON, ORed with 1 if we've received XOFF
  6258      21 C2       received_xoff   equ     21c2h
  6259                  ; gets zeroed by clear_part_row
  6260                  ; flag marked when smooth scroll and need to clear extra line, so non-interrupt path
  6261                  ; sets this flag and then waits until the clearance happens in vertical interrupt and
  6262                  ; the flag gets zeroed.
  6263      21 C3       row_clearing    equ     21c3h
  6264                  ; no_scroll_key stores 2 (sent XOFF) or 0 (sent XON) here
  6265      21 C4       noscroll        equ     21c4h
  6266                  ; When we enter SET-UP mode, we save the previous video pointer here
  6267      21 C5       saved_line1_dma equ     21c5h
  6268                  ; This is zero, except for alignment display, which makes it 'E' (and then zeroes again)
  6269      21 C7       cls_char        equ     21c7h
  6270                  ; Recorded early in start-up. Non zero if no AVO found
  6271      21 C8       avo_missing     equ     21c8h
  6272                  ; gets sent to iow_nvr_latch during vertical int
  6273                  ; Vertical interrupt outputs to the NVR latch, so any code that is writing to the latch while
  6274                  ; interrupts are still enabled also stores the value here, so it persists.
  6275      21 C9       vint_nvr        equ     21c9h
  6276      21 CA       UNUSED_X21ca    equ     21cah
  6277                  ; updated when blink timer goes off in vertical interrupt
  6278                  ; repeat test field
  6279                  ; If DECTST has requested some tests to be repeatedly executed and they fail, the screen field
  6280                  ; is toggled between normal and reverse on each cycle. This can be seen by sending the sequence
  6281                  ; ESC [ 2 ; 10 y (data loopback with repeat). Data loopback will always fail in the absence of
  6282                  ; a loopback connector, so the screen will show "8" for failure in the first character position,
  6283                  ; and the entire screen will blink between normal and reverse field.
  6284                  ;
  6285      21 CB       test_field      equ     21cbh
  6286                  ; This area holds the mini screen definition for "Wait", with terminators that
  6287                  ; point back to the fill lines. The entire screen definition is just 7 bytes long.
  6288      21 CC       wait_addr       equ     21cch
  6289                  ; wait screen address in DMA (big-endian) form, with normal line attributes
  6290      CC 71       wait_addr_be    equ     0cc71h
  6291      21 D3       nvr_bits        equ     21d3h ; must be at least 14 locations
  6292                  ; setup_video is where the SET-UP characters are drawn, and then the Video DMA is switched
  6293                  ; to point to here while we are in this mode.
  6294      22 53       setup_video     equ     2253h
  6295                  ;
  6296                  ; Normal screen display starts here in scratch RAM, and line1_dma gets initialised to point to it.
  6297                  ; screen_layout (the initial 18 bytes of definition) point here literally, and any other time
  6298                  ; we want to get back to this location, main_video_be will be loaded into HL and then
  6299                  ; written to line1_dma. This establishes the first line as part of the scrolling region,
  6300                  ; with normal lines attributes.
  6301                  ;
  6302      22 D0       main_video      equ     22d0h
  6303      D0 F2       main_video_be   equ     0d0f2h
  6304                  ;
  6305                                  end