Sunday, 4 June 2023

6502 : BEN2 : Xmodem Enhancement 1 : Relocatable programs

 Intro

A simple Xmodem-like download is now working and, so far is proving reliable.  This is great news and isn't something I have been able to achieve previously.  I now want to build up the basic system so it provides a solid, convenient development environment.

Load Address

Initially we have included ".org 1000" in our programs and they are compiled so that they will run properly when the xm subroutine loads them to address $1000 in RAM.

If we want to load the program at a different address, say $2000, we can change the assembly program to include ".org $2000" and change the xm subroutine to copy the executable into RAM starting at address $2000.

Clearly we dont want to have to change the xm subroutine each time we want to load a program at a different address.  The xmodem protocol includes a load address in the first two bytes of the first data packet.  If we put the load address ($2000) at the start of our executable we can modify the xm subroutine to use this value as the starting address for copying the executable into RAM.

Step 1 : Prefix executable with load address

Initially I started testing, using the existing load address $1000.
Linux allows us to easily create a two byte file with two hex characters values:
  printf "\x00\x10" > 1000.ram
We can then concatenate the two byte file with the executable to create an upload file starting with the load address
  cat 1000.ram via-3-LED-1x.x > a.ram

I had a neat idea to make the associated change to xm as simple as possible.  xm stores the start address in a two byte pointer ptr+ptrh.  Instead of initially loading $1000 into the pointer I loaded $0FFE.  This means that the address is loaded to $0FFE and $0FFF then the executable loads starting at $1000 and works as it did previously.

To allow different load addresses to be used I simply change xm to save the load address in ptr+ptrh then subtract two and read in the program as previously.  A slight complication is that subtracting 2 from the pointer is a 16-bit operation as the start address pointer is two bytes.  My assembly programming isn't very good but I found a helpful webpage with loads of useful snippets including 16 bit arithmentic


Step 2 : Telling the compiler the correct load address

Next I wanted a flexible way of changing a program load address.  I would prefer to change a batch file when I want to save the program at a new address rather than the program source.

Googling on the subject it becomes clear that putting a ".org" statement in the source is generally a bad idea.  It is the linkers responsibility to set the start address not the assembly process.
I removed the .org statement from my source program and added a switch "-S 0x2000" to the ld65 linker command.
I can easily add a variable to my batch file which sets the load address. 
The batch file also contains a small script to prefix the executable with the load address.

Results

The new setup works well.  I can specify in the batch file where I want the program to run and, after it has been downloaded I can call call the program from the monitor using "s1000" "s2000" etc.  At the time of writing I only had two xmodem downloadable programs, one to turn on LEDs and the other to display an LCD message.  I loaded up both of these into RAM, one at $1000 and the other at $2000 and then I could run either of them at will.

Somehow this feels like a big step forward.  I have a monitor in ROM and I can load programs into RAM wherever I like and run them independently whilst they remain loaded.

Position independent code

I did consider experimenting with Position Independent Code (PIC) for my assembly programs.  When I assemble programs without an ORG statement the addresses are put into instructions relative to the start of the module.  When the program is linked, the linker program calculates absolute addresses and puts them into each instruction as necessary.  When the program is loaded the addresses are already hard-coded in binary so that the program can run without calculating addresses/offsets.

In fact my first LED program, which just initialises the VIA and turns LEDs on is PIC.  There are no branches or loops and data storage is "nearby" and can be addressed using offsets from the current location.  Consequently I can assemble the program with any load address and the code generated is identical.  I could copy the program anywhere in memory and it should run fine.  Thinking about it there are probably some restrictions due to page boundaries; but I am trying not to deal with or worry about those at the moment.

However it is unusual to have a program without branches and with only local data, and what is needed is programming style which utilises all relative addresses and / or an assembler that can generate the appropriate code.
Some instruction sets, including RISC-V, have a base register which can be utilised in instructions to offset them all, but the 6502 doesn't.  Without that capability it would be difficult to provide PIC and the 6502 assemblers dont try.

No comments:

Post a Comment