6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Fri Nov 22, 2024 6:05 pm

All times are UTC




Post new topic Reply to topic  [ 14 posts ] 
Author Message
PostPosted: Thu Jun 09, 2022 1:35 am 
Offline
User avatar

Joined: Mon Oct 05, 2020 6:57 pm
Posts: 37
Location: Central VA, USA
I've been working on porting GWMON-80 from Intel 8080 to NMOS 6502 (naturally, called GWMON-65). Part of the requirement for the port is trying to fit GWMON-65 in 512 bytes, just like GWMON-80. There's a practical reason for this: I have 6502 systems that use 1702A 256-byte EPROMs!

I've got the monitor basically ported and started working on shrinking it down to meet the goal. Originally it was 13 bytes too big, and didn't include the vectors since development has been done at 0x1000 in RAM. Going through the source, I noticed that there was a lot of space consumed by strings in general: the actual strings and printing routine of course, but especially in the setup for calling the string printer. I was loading two zero page variables with the pointer to the string, which requires eight bytes by itself! So, I came up with a way to get that down to two bytes:

Code:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;PRTSTR -- Print a high bit terminated string
;
;Destroys contents of Y register.
;
;pre: Y register contains offset into STRNGS of string to be
;     printed, minus one
;post: string printed to console
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PRTSTR: INY                     ;Point to start of string
        LDA     STRNGS, Y       ;A = next char in string
        JSR     COUT
        BPL     PRTSTR          ;Done if high bit is set
        RTS

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;Message Strings
;
;These strings are indexed by the labels following STRNGS.
;These indexes may be fed into PRTSTR, PRTCLS, or PRTERR in
;the Y register.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
STRNGS          = *
SIGNON          = ^(* - STRNGS - 1)
        .byte   "GWMON-65 0.1 S" ,'M' + $80

PROMPT          = * - STRNGS - 1
        .byte   LF, '>' + $80

CSERR           = * - STRNGS - 1
        .byte   "CKSUM "

ERRSTR          = * - STRNGS - 1
        .byte   "ERRO", 'R' + $80

Do note that the high byte termination is being stripped in the COUT routine.

Now, the string printer is called like this:

Code:
        LDY     #SIGNON         ;Y = offset to SIGNON string
        JSR     PRTSTR          ;Print signon message

The indexes are calculated at assembly time, so that makes this kinda ugly solution a little more bearable. An index needs to be one less than its actual offset from the start of the string block, as this makes the string printer slightly shorter. The ^ byte cast operator had to be used on the first offset since it's a very large negative otherwise.

Anyway, I thought I'd share since I haven't run across this solution in particular in any old documents, and a cursory search on the Internet didn't reveal anything exactly like this. It's obviously limited to string blocks that are not longer than 255 characters, which does make the high bit termination more attractive. I can't see using this other than for very space-constrained applications.


Top
 Profile  
Reply with quote  
PostPosted: Thu Jun 09, 2022 1:45 am 
Offline

Joined: Thu Jan 21, 2016 7:33 pm
Posts: 282
Location: Placerville, CA
Not a bad little approach, though, if space is at a premium and you don't need too much text :)


Top
 Profile  
Reply with quote  
PostPosted: Thu Jun 09, 2022 4:14 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8543
Location: Southern California
You might not have enough memory or strings to make it worth it, but I'll mention this anyway. If the string is only used in one place, you could put it right after the JSR PRTSTR, and make the subroutine use the stacked return address to find the string address, and then adjust the return address so the RTS jumps over the string data instead of trying to execute it as instructions. It makes the subroutine longer, but with enough strings it will more than pay for itself because you don't need to store string addresses into any registers or variables. I have a couple of examples in the "Inlining subroutine data" page of the 6502 stacks treatise, at http://wilsonminesco.com/stacks/inlinedData.html . If you do need to use a few of the strings in more than one place, they still have addresses of course, which you could use with a different print routine that requires the addresses.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Thu Jun 09, 2022 10:18 am 
Offline

Joined: Sun Apr 26, 2020 3:08 am
Posts: 357
I am not sure if it is normal for a ROM print routine to preserve the processor bits on return, but I don't think it should be taken for granted. Normally it is done this way.

PRTSTR: INY ;Point to start of string
LDA STRNGS, Y ;A = next char in string
BMI DONE
JSR COUT
JMP PRTSTR

DONE JMP COUT


Top
 Profile  
Reply with quote  
PostPosted: Thu Jun 09, 2022 10:46 am 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1488
Location: Scotland
IamRob wrote:
I am not sure if it is normal for a ROM print routine to preserve the processor bits on return, but I don't think it should be taken for granted. Normally it is done this way.

PRTSTR: INY ;Point to start of string
LDA STRNGS, Y ;A = next char in string
BMI DONE
JSR COUT
JMP PRTSTR

DONE JMP COUT


One mans normal is another mans "Huh?"

Today I mostly have in-line strings because I want to:

PRINT "Hello, world"

so rather than "load address of string into register ; call print", I:

Code:
    jsr    strout
    .byte  "Hello, world",13,10,0
    ... more code


If I were tight on space I could do the top-bit set thing to terminate the string, but I'm not, but on a system I worked on some 40+ years ago where I wrote a lot of 8080 ASM code was (initially) right on ROM space, so I had to have a plan B, so I made strings containing tokens where each token as a lookup to a table of words, so I could re-use words to make sentences out of the most commonly used words or phrases. It was tricky, but I semi-automated the process by machine generating the data where I could.

So... give 10 programmers a problem, get 11 answers still applies ;-)

-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Thu Jun 09, 2022 11:45 am 
Offline
User avatar

Joined: Mon Oct 05, 2020 6:57 pm
Posts: 37
Location: Central VA, USA
GARTHWILSON wrote:
If the string is only used in one place, you could put it right after the JSR PRTSTR, and make the subroutine use the stacked return address to find the string address, and then adjust the return address so the RTS jumps over the string data instead of trying to execute it as instructions.


I'd seen your tutorial before, and encountered the inline strings method in the wild when disassembling some Ohio Scientific code! It ended up being somewhat longer in this particular instance, but was something I considered/tried. Neat trick, too!

IamRob wrote:
I am not sure if it is normal for a ROM print routine to preserve the processor bits on return, but I don't think it should be taken for granted. Normally it is done this way.

Code:
PRTSTR: INY                     ;Point to start of string
        LDA  STRNGS, Y       ;A = next char in string
        BMI  DONE
        JSR  COUT
        JMP PRTSTR

DONE JMP COUT


I'm with drogon...what? That method is more bytes and doesn't strip the high bit, which will produce garbage on some (many) serial terminals when running in 8N1 mode.


Top
 Profile  
Reply with quote  
PostPosted: Thu Jun 09, 2022 2:44 pm 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1949
Location: Sacramento, CA, USA
I get what Rob is saying. There is a dependence in your code for COUT to preserve the N flag. His solution addresses that potential issue. I did something similar years ago:
Code:
;-------------------------------------;
; EMIT A HI-BIT-SET TERMINATED STRING ;
;   @ OFFSET Y AND EXIT WITH Y @ THE  ;
;   BEGINNING OF THE NEXT STRING.     ;
;-------------------------------------;
PUTS:
   LDA TXT,Y       ;GRAB A STRING CHAR
   INY             ;ADVANCE STRING PTR
PUTCH:
   PHA
   ORA #ORMASK
   AND #ORMASK+127 ;FORMAT CHAR FOR ECHO
   JSR ECHO        ;SHOOT IT TO CONSOLE
   PLA
   BPL PUTS        ;LOOP IF APPROPRIATE
   RTS
ORMASK would be defined as $80 for an Apple ][ style COUT and $00 for most others, so if you are really pressed for space you can eliminate one of the instructions that uses it and save two bytes. The PUTCH label was a sneaky way to print a single lead character before the string, or just the single char if its high bit is set.

_________________
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)


Last edited by barrym95838 on Thu Jun 09, 2022 3:01 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Thu Jun 09, 2022 3:00 pm 
Offline
User avatar

Joined: Mon Oct 05, 2020 6:57 pm
Posts: 37
Location: Central VA, USA
barrym95838 wrote:
I get what Rob is saying. There is a dependence in your code for COUT to preserve the N flag. His solution addresses that issue.


Ah, yeah, I didn't show COUT, which does preserve the A register. Last thing it does is pull it back off the stack. Required for other parts of the program, so it's a freebie optimization :P


Top
 Profile  
Reply with quote  
PostPosted: Thu Jun 09, 2022 3:10 pm 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1949
Location: Sacramento, CA, USA
drogon wrote:
If I were tight on space I could do the top-bit set thing to terminate the string, but I'm not, but on a system I worked on some 40+ years ago where I wrote a lot of 8080 ASM code was (initially) right on ROM space, so I had to have a plan B, so I made strings containing tokens where each token as a lookup to a table of words, so I could re-use words to make sentences out of the most commonly used words or phrases. It was tricky, but I semi-automated the process by machine generating the data where I could.

Nice! I thought of something similar for a text-heavy program that used BRK instead of JSR to save two bytes, and packed the strings as six-bit chars (or maybe it was five-bit chars and the carry flag to indicate which set to access). I never finished that project, and no one missed it. :P

_________________
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)


Top
 Profile  
Reply with quote  
PostPosted: Thu Jun 09, 2022 4:18 pm 
Offline
User avatar

Joined: Sun Nov 01, 2020 10:36 am
Posts: 37
Location: Tatooine
if COUT is in your code I'd do this:

Code:
PrePRTSTR:
        JSR COUT
PRTSTR:
        INY
        LDA STRNGS,Y
        BPL PrePRTSTR

COUT:   ;Here the code for COUT, and PRTSTR flows into it at the end


You can even have that .Y is the exact offset and do this:

Code:
PrePRTSTR:
        JSR COUT
        INY
PRTSTR:
        LDA STRNGS,Y
        BPL PrePRTSTR

COUT:   ;Here the code for COUT, and PRTSTR flows into it at the end


Top
 Profile  
Reply with quote  
PostPosted: Thu Jun 09, 2022 5:27 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8543
Location: Southern California
IamRob wrote:
I am not sure if it is normal for a ROM print routine to preserve the processor bits on return, but I don't think it should be taken for granted. Normally it is done this way.

PRTSTR: INY ;Point to start of string
LDA STRNGS, Y ;A = next char in string
BMI DONE
JSR COUT
JMP PRTSTR

DONE JMP COUT

I would write that with my structure macros as:
Code:
PRTSTR:  BEGIN
            INY              ; Point to start (or next chr) of string.
            LDA  STRNGS, Y   ; Get next char in string.
         WHILE_PLUS          ; As long as the top bit is _not_ set,
            JSR  COUT        ; output it
         REPEAT              ; and go back for more;
         JMP  COUT           ; but if it _was_ set, output it, without
                             ; going back for more.  (JMP=JSR, RTS)

and it would assemble the same thing (except that the JMP PRTSTR would be replaced with BRA PRTSTR, saving a byte); but the source code eliminates the "Huh?" factor, and it makes it clear that COUT does not need to preserve the flags, only leave Y intact. COUT would have to strip off the high bit one way or another, too. If COUT were next, there would be no need for the JMP COUT.

I had never thought of doing it that way, because from the beginning, I've always also been using the special characters that have the high bit set. It is nice though.

drogon's idea of making strings contain tokens where each token as a lookup to a table of words is pretty slick too.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Thu Jun 09, 2022 6:50 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1488
Location: Scotland
barrym95838 wrote:
drogon wrote:
If I were tight on space I could do the top-bit set thing to terminate the string, but I'm not, but on a system I worked on some 40+ years ago where I wrote a lot of 8080 ASM code was (initially) right on ROM space, so I had to have a plan B, so I made strings containing tokens where each token as a lookup to a table of words, so I could re-use words to make sentences out of the most commonly used words or phrases. It was tricky, but I semi-automated the process by machine generating the data where I could.

Nice! I thought of something similar for a text-heavy program that used BRK instead of JSR to save two bytes, and packed the strings as six-bit chars (or maybe it was five-bit chars and the carry flag to indicate which set to access). I never finished that project, and no one missed it. :P


FWIW: the Acorn MOS (BBC Micro) uses BRK to indicate an error of sorts and includes the error code and message in-line. So:

Code:
    brk
    .byte    42
    .byte    "End of the world.",0


Adapting that to a generic string print would be trivial - unless you wanted to use BRK for anything else.

-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Thu Jun 09, 2022 6:56 pm 
Offline
User avatar

Joined: Mon Oct 05, 2020 6:57 pm
Posts: 37
Location: Central VA, USA
BB8 wrote:
if COUT is in your code I'd do this:


I'd thought about doing that, but I will likely split COUT from the main monitor as I did with the drivers paradigm for GWMON-80. Right now CIN falls into COUT to effect echo, but that will likely get split up too, since CIN stays in the main monitor (a CIN without echo goes in the driver).

drogon wrote:
FWIW: the Acorn MOS (BBC Micro) uses BRK to indicate an error of sorts and includes the error code and message in-line. So:

Code:
    brk
    .byte    42
    .byte    "End of the world.",0


Adapting that to a generic string print would be trivial - unless you wanted to use BRK for anything else.

-Gordon


Considered this one too, but I figured a monitor shouldn't take control of BRK -- might want to use it elsewhere, or for actually setting breakpoints!


Top
 Profile  
Reply with quote  
PostPosted: Sat Jul 09, 2022 4:26 pm 
Offline
User avatar

Joined: Tue Aug 11, 2020 3:45 am
Posts: 311
Location: A magnetic field
I thought that null terminated strings were awful. Now we have negative terminated strings! To make the string one byte smaller and make the loop two bytes smaller!

glitch on Thu 9 Jun 2022 wrote:
I figured a monitor shouldn't take control of BRK -- might want to use it elsewhere, or for actually setting breakpoints!


It might be useful to split uses of BRK:

  • BRK zero: output literal string.
  • BRK positive: error message.
  • BRK negative: breakpoint.

_________________
Modules | Processors | Boards | Boxes | Beep, Beep! I'm a sheep!


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 14 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 30 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: