Friday, 9 July 2010

It's Not Big or Clever...

I spent an hour last night finalising the mapping rules to convert PETSCII to Screen Codes, and then writing the code to implement them. I guess I've spent a total of three hours on this task, although the first half of that was occupied trying to figure-out how another mapping routine I found actually worked.

It was very clever, in that it used only the input value given in the Accumulator, and no other registers or memory. By shifting and masking bits in the PETSCII value it was given, and then selectively testing for specific resultant values, it derived the mapping value and left it in .A upon exit. The problem with it was that it is very difficult to trace the logic, because at any moment in the execution path you have to be aware of whatever operations have been performed on .A up to that point, what the current value in .A is, and how that relates to the original PETSCII value. Aside from some comments asserting the mapping rules at the top of the code, none of the actual logic had any annotation, so I went through it line-by-line and added my own as I worked out what it was doing.

By the time I'd finished, I'd almost lost the will to live. Whilst I'd figured-out what the routine did, it was still pretty cryptic to look at, and worst of all I'd found a discrepancy between what the comments at the top said the results should be and what the code actually did. However, at that point I conceded defeat - I couldn't face working through the logic yet again to confirm the discrepancy, and I wasn't 100% sure I hadn't made a mistake myself and the code was actually right after all. So with a possible glitch in a 43-byte routine I'd spent 90 minutes looking at and still wasn't absolutely certain I understood properly, I decided enough was enough, and abandoned it.

I fired-up a copy of Excel and produced two simple columns with the PETSCII characters and Screen Code characters side-by-side. I then converted their decimal values to binary, and analysed the differences where one mapped to the other - which gave me a simple rule in each case for what to do to the PETSCII value to turn it into a Screen Code value. I didn't have to do this for all 256 characters, because they actually map in blocks of 32 (except for value 255) so I ended-up with 9 rules - and there were duplicates, so the actual number of distinct rules is only 6.
    PETSCII Value    Mapping Rule              Logical Operation
      0-31            No change   
      32-63           No change   
      64-95           Clear bit 6               and #%10111111
      96-127          Clear bit 5               and #%11011111
      128-159         No change   
      160-191         Set bit 7, clear bit 6    ora #%10000000, and #%10111111
      192-223         Clear bit 7               and #%01111111
      224-254         Clear bit 7               and #%01111111
      255             Output 94                 lda #94
I then wrote the code in it's simplest form, just testing for specific values and mapping appropriately - which gave me a routine 57 bytes long. I then spent a few minutes combining the duplicate rules, which reduced the byte count to 49. And finally I optimised it so that it re-used rule fragments where possible, and the code dropped to 40 bytes. More importantly, that's 40 bytes of readily-understandable logic, with comments. It only manipulates .A, uses no other registers or memory, and is a simple drop-through routine which only actually changes the value in .A when it performs the mapping, rather than twiddling bits as it progresses.
;-------------------------------------------------------------------------------
; CHAR2SCREEN
; Converts PETSCII values to the appropriate Screen Code.
; Notes: Load .A with PETSCII value - converted value is returned in .A
;     Uses no other registers or memory
;
;
; PETSCII Value    Mapping Rule              Logical Operation
;   0-31            No change
;   32-63           No change
;   64-95           Clear bit 6               and #%10111111
;   96-127          Clear bit 5               and #%11011111
;   128-159         No change
;   160-191         Set bit 7, clear bit 6    ora #%10000000, and #%10111111
;   192-223         Clear bit 7               and #%01111111
;   224-254         Clear bit 7               and #%01111111
;   255             Output 94                 lda #94

char2scrn SUBROUTINE
     cmp #255       ; Input = 255?
     bcc .tst192      ; Less than 255, skip to next test
     lda #94        ; Input = 255, reset to 94
     rts          ; Exit

.tst192  cmp #192       ; Input >= 192?
     bcc .tst160      ; Less than 192, skip to next test
     and #%01111111    ; Input >= 192, clear bit 7
     rts          ; Exit

.tst160  cmp #160       ; Input >= 160?
     bcc .tst128      ; Less than 160, skip to next test
     ora #%10000000    ; Input >= 160, so set bit 7
.clear6  and #%10111111    ; Clear bit 6
     rts          ; Exit

.tst128  cmp #128       ; Input >= 128?
     bcc .tst96      ; Less than 128, skip to next test
     rts          ; Exit (leave unchanged)

.tst96  cmp #96        ; Input >= 96?
     bcc .tst64      ; Less than 96, skip to next test
     and #%11011111    ; Input >= 96, clear bit 5
     rts          ; Exit

.tst64  cmp #64        ; Input >= 64?
     bcs .clear6      ; Input >= 64, so clear bit 6
     rts          ; Less than 64, leave unchanged

It might not be as 'clever' as the routine I found, but it's understandable, and shorter. Which is nice.

0 comments:

Post a Comment