Screen set 1 character 32 and 96 are both space.
Moderator: Moderators
The closest I have come: I've printed to screen (text matching background to make it invisible) to produce custom characters. When storing the custom character set at 7168, the on-screen data can be accessed as shift mode characters. Nothing spectacular, but if I've done it in BASIC, I'm sure someone would do the same in ML.
- Mike
- Herr VC
- Posts: 4838
- Joined: Wed Dec 01, 2004 1:57 pm
- Location: Munich, Germany
- Occupation: electrical engineer
From the CPU's view, there's no difference between RAM committed for screen display and other RAM. I've seen quite a few uses, for example decompressors which execute in the screen buffer to unpack data in the BASIC memory range. Just take a look at some C64 intros or demos.RJBowman wrote:Has anyone ever tried to produce executable machine code by putting characters on the screen?
Here's a particularly nice example:
... which can be entered like this (in quoted PETSCII):
Code: Select all
PRINT"{CLR,RVS ON}){RVS OFF}A{RVS ON}(Q{RVS OFF}+{SPACE}3{RVS ON,SHIFT-E,
RVS OFF}X{RVS ON}%{RVS OFF}"CHR$(34)CHR$(34)CHR$(20)"{SHIFT-POUND}B{RVS ON}E
{RVS OFF}-{RVS ON}%{RVS OFF}#{SHIFT-POUND}@{RVS ON}E{RVS OFF}.{SPACE,SHIFT-Y,
RVS ON,SHIFT-F,RVS OFF,SHIFT-L,C=-H,RVS ON,SHIFT-D}":SYS256*PEEK(648)
Pop-Quiz: what's the above code supposed to do?
I recall that there were a few microcomputer BASICs that did not offer the poke and peek command, or any other good way for the user to enter machine code. The print-code-to-screen technique could be used as a workaround for these constrained machines.
But the only home computer BASIC without poke and peek that comes to mind is the TI-994A, which has separate video memory that the CPU can't access directly.
But the only home computer BASIC without poke and peek that comes to mind is the TI-994A, which has separate video memory that the CPU can't access directly.
I just thought of the killer app for machine code printed to screen. To memory-optimize the BASIC code for poking machine code into memory.
Here's where the savings come in:
The standard method is to convert the machine code into a sequence of decimal numbers, which are put into data statements to be read, then poked into memory. This method requires that one to three data-statement bytes be used to represent each byte of machine code, with comma separators adding an additional data-statement byte per byte of code represented.
But if you convert the code to printable strings, you can print any of 128 values to the screen with a single byte of overhead, and with the <reverse> and <normal> control codes you can toggle to the other 128 possible values. So that works out to, at most, two "data" bytes per byte of code; in practice, you would not have to toggle the print mode for every code byte, so it might average 1.5 data bytes per code byte. And of course there would also be an overhead for the line number, print-command token, and quotes. I think that it would still be a substantial savings over poking from data statements.
Now you might point out that this system would limit you to machine code fragments that occupy screen memory. Not necessarily.
The first string of code printed to the screen could be a relocatable subroutine, who's purpose is to copy fragments from the screen to other parts of memory. Then the screen could be cleared and filled with more code to be copied by the code from the first iteration.
Why did no one think of this back in the day?
Here's where the savings come in:
The standard method is to convert the machine code into a sequence of decimal numbers, which are put into data statements to be read, then poked into memory. This method requires that one to three data-statement bytes be used to represent each byte of machine code, with comma separators adding an additional data-statement byte per byte of code represented.
But if you convert the code to printable strings, you can print any of 128 values to the screen with a single byte of overhead, and with the <reverse> and <normal> control codes you can toggle to the other 128 possible values. So that works out to, at most, two "data" bytes per byte of code; in practice, you would not have to toggle the print mode for every code byte, so it might average 1.5 data bytes per code byte. And of course there would also be an overhead for the line number, print-command token, and quotes. I think that it would still be a substantial savings over poking from data statements.
Now you might point out that this system would limit you to machine code fragments that occupy screen memory. Not necessarily.
The first string of code printed to the screen could be a relocatable subroutine, who's purpose is to copy fragments from the screen to other parts of memory. Then the screen could be cleared and filled with more code to be copied by the code from the first iteration.
Why did no one think of this back in the day?
- Mike
- Herr VC
- Posts: 4838
- Joined: Wed Dec 01, 2004 1:57 pm
- Location: Munich, Germany
- Occupation: electrical engineer
That looks like a general scheme you like to put questions. People in the past were likely smarter than you might think they have been.Why did no one think of this back in the day?
DATA loaders with numeric values were an easy method in those days to transcribe a (small) machine code program from a list of opcodes (with the address modes) into decimal, and then into memory. Otherwise, it was always possible to store the memory block as is onto disk or tape, and load it back from there.
You didn't answer my Pop-Quiz. What's the effect of the program I wrote above?
I kept messing up the code by entering C=-H literally until I looked closer to the above code in the screen cap.
Then looking at the code in ML made more sense in the jumps - it resets the End of Basic/Start of Variables pointer and displays a ready message and drops out.
That's as far as I can make out since I never played with the SOB/SOV before. Either the entire Basic is being relocated or its next line is being reset - to me it almost looks like a NEW command from what I can figure.
EDIT - OMG - this is over my head...lol. I did notice that there was a 0 when I LISTed the program. So there was a line number and that was it and all the RAM was displayed as FREe minus the single line number code. So it printed the line in the screen and then ran from screen memory $1E00 - did the above and dropped out to a ready code.
Then looking at the code in ML made more sense in the jumps - it resets the End of Basic/Start of Variables pointer and displays a ready message and drops out.
That's as far as I can make out since I never played with the SOB/SOV before. Either the entire Basic is being relocated or its next line is being reset - to me it almost looks like a NEW command from what I can figure.
EDIT - OMG - this is over my head...lol. I did notice that there was a 0 when I LISTed the program. So there was a line number and that was it and all the RAM was displayed as FREe minus the single line number code. So it printed the line in the screen and then ran from screen memory $1E00 - did the above and dropped out to a ready code.
Learning all the time...
- Mike
- Herr VC
- Posts: 4838
- Joined: Wed Dec 01, 2004 1:57 pm
- Location: Munich, Germany
- Occupation: electrical engineer
Indeed you get that or a similar (and admittedly useless) result when you run this line on a VIC-20 which has just be powered on.darkatx wrote:I did notice that there was a 0 when I LISTed the program.
You might want to use this code under other circumstances though ... citing examples would give it away already.
- freshlamb
- Vic 20 Dabbler
- Posts: 76
- Joined: Sun Apr 04, 2004 5:38 pm
- Website: http://www.rufnoiz.com
- Location: Prince Albert SK Can
I never would have figured that out in a million years due to my aversion of the NEW command. I just don't use it...ever. I normally keep hacking away at a program until it works and keep backing up before moving forward on every little change I make.
Programming makes me a packrat.
Programming makes me a packrat.
Learning all the time...
- Mike
- Herr VC
- Posts: 4838
- Joined: Wed Dec 01, 2004 1:57 pm
- Location: Munich, Germany
- Occupation: electrical engineer
Spot on!freshlamb wrote:[...]it looks like an UNNEW.
Here's an assembly listing, without instruction addresses as the code is fully relocatable (which is also the reason SYS256*PEEK(648) works at all):
Code: Select all
LDA #$01
TAY
STA ($2B),Y ; write a non-0 value to the first link-pointer
JSR $C533 ; re-chain lines (which mainly restores the first
; link-pointer)
; Now $23/$22 points to the last link-pointer of the program,
; which contains two zeroes. The variables are supposed to
; start 2 bytes later:
CLC
LDA $22
ADC #$02
STA $2D
LDA $23
ADC #$00
STA $2E
JSR $C659 ; CLR. Also initialises other pointers from $2E/$2D.
; At this stage, the entire stack has been purged, so we can't
; simply return to BASIC with RTS. Instead, we jump to the
; routine which first prints the 'READY.' prompt and then
; continues with keyboard entry for the screen editor.
JMP $C474
There's another version of this routine included in the MG batch suite. It loads with ',8,1' and starts with SYS320.Pretty neat! Too bad it would be hard to type this in every time you wanted to bring back a program.
I frankly admit I derived the PRINT version of this OLD routine from my earlier release just to prove the point: while it might look 'cool' to provide machine code this way, it's just annoying for those who'd have to type it in (as type-in from a magazine page, for example). When it comes to readability, this method easily finishes behind decimal data loaders or hex dump editors (like Compute!'s MLX).
It can also recover the program after a RESET or when the pointers to the start of the variables have not been set after a '?LOAD ERROR'. This error can happen just because the copy on tape had been cropped by accident. However, in that case there's an even easier way to restore the pointers, see here.darkatx wrote:my aversion of the NEW command. I just don't use it ... ever
Of course with cross-developing the work flow has changed a lot so the occasions where an OLD routine might save your day indeed have become sparse.
Last edited by Mike on Sun Jun 09, 2013 12:34 pm, edited 1 time in total.
Nice way to enter an emergency unnew.
You should be able to cut the jmp $c474 and make the routine end in jmp $c659.
This is because even though $c659 resets the stack pointer it first pops the return address and pushed it back after the sp reset.
This was previously discussed here: thread
Perhaps the print line could be made shorter and/or easier to type by opimizations specifically targetting the number of escape codes and/or the number of characters involving shifting?
You should be able to cut the jmp $c474 and make the routine end in jmp $c659.
This is because even though $c659 resets the stack pointer it first pops the return address and pushed it back after the sp reset.
This was previously discussed here: thread
Perhaps the print line could be made shorter and/or easier to type by opimizations specifically targetting the number of escape codes and/or the number of characters involving shifting?
- Mike
- Herr VC
- Posts: 4838
- Joined: Wed Dec 01, 2004 1:57 pm
- Location: Munich, Germany
- Occupation: electrical engineer
Indeed, that one would save 3 bytes. Wow, this thread is from 2005!tlr wrote:You should be able to cut the jmp $c474 [...] [t]his was previously discussed here: [...]
Doubtful. The shifted characters come mainly from the high-bytes of the JSR instructions pointing into BASIC ROM and the two ADC instructions. I also already used {RVS ON} and {RVS OFF} only where necessary. The single double-quote (which was necessary for the LDA $22 instruction) was especially annoying, as it had to be expressed as two double-quotes followed by delete to remain in non-quoted mode after.Perhaps the print line could be made shorter and/or easier to type by op[t]imizations specifically targetting the number of escape codes and/or the number of characters involving shifting?
Let's examine the relinker:Mike wrote:Doubtful. The shifted characters come mainly from the high-bytes of the JSR instructions pointing into BASIC ROM and the two ADC instructions. I also already used {RVS ON} and {RVS OFF} only where necessary. The single double-quote (which was necessary for the LDA $22 instruction) was especially annoying, as it had to be expressed as two double-quotes followed by delete to remain in non-quoted mode after.
Code: Select all
.C:c533 A5 2B LDA $2B
.C:c535 A4 2C LDY $2C
.C:c537 85 22 STA $22
.C:c539 84 23 STY $23
.C:c53b 18 CLC
.C:c53c A0 01 LDY #$01
.C:c53e B1 22 LDA ($22),Y
.C:c540 F0 1D BEQ $C55F ; only way out
.C:c542 A0 04 LDY #$04
.C:c544 C8 INY
.C:c545 B1 22 LDA ($22),Y
.C:c547 D0 FB BNE $C544
.C:c549 C8 INY
.C:c54a 98 TYA
.C:c54b 65 22 ADC $22
.C:c54d AA TAX
.C:c54e A0 00 LDY #$00
.C:c550 91 22 STA ($22),Y
.C:c552 A5 23 LDA $23
.C:c554 69 00 ADC #$00
.C:c556 C8 INY
.C:c557 91 22 STA ($22),Y
.C:c559 86 22 STX $22
.C:c55b 85 23 STA $23
.C:c55d 90 DD BCC $C53C ; always taken
.C:c55f 60 RTS
There is one exception. X will be left untouched in the case that there is no link at the first line but this will new happen as we just fabricated a link there before the call.
Assuming the above, then this should work:
Code: Select all
LDA #$01
TAY
STA ($2B),Y ; write a non-0 value to the first link-pointer
JSR $C533 ; re-chain lines (which mainly restores the first
; link-pointer)
; X=($22), Acc=$00, Y=$01, C=0
; Now $23/$22 points to the last link-pointer of the program,
; which contains two zeroes. The variables are supposed to
; start 2 bytes later:
TXA
ADC #$02
STA $2D
LDA $23
ADC #$00
STA $2E
; The stack pointer will be reset to $fa but the return address preserved
; by the call below.
JMP $C659 ; CLR. Also initialises other pointers from $2E/$2D.
Continuing to examine the start of NEW and CLR:
We can partially reuse the end address calculation used by NEW to save a few more bytes.
Assuming the above, then this should work:
Code: Select all
.C:c644 A9 00 LDA #$00 ; NEW
.C:c646 A8 TAY
.C:c647 91 2B STA ($2B),Y
.C:c649 C8 INY
.C:c64a 91 2B STA ($2B),Y
.C:c64c A5 2B LDA $2B
.C:c64e 18 CLC
.C:c64f 69 02 ADC #$02
.C:c651 85 2D STA $2D
.C:c653 A5 2C LDA $2C
.C:c655 69 00 ADC #$00 ; <--- our entry point
.C:c657 85 2E STA $2E
.C:c659 20 8E C6 JSR $C68E ; CLR
.C:c65c A9 00 LDA #$00
.C:c65e D0 2D BNE $C68D
.C:c660 20 E7 FF JSR $FFE7
...
Assuming the above, then this should work:
Code: Select all
LDA #$01
TAY
STA ($2B),Y ; write a non-0 value to the first link-pointer
JSR $C533 ; re-chain lines (which mainly restores the first
; link-pointer)
; X=($22), Acc=$00, Y=$01, C=0
; Now $23/$22 points to the last link-pointer of the program,
; which contains two zeroes. The variables are supposed to
; start 2 bytes later:
TXA
ADC #$02
STA $2D
LDA $23
; The stack pointer will be reset to $fa but the return address preserved
; by the call below. Also initialises other pointers from $2E/$2D.
JMP $C655 ; CLR with adc #0; sta $2e prepended