19

In the era of C64, Apple][ GS, and SNES, did the games use hardcoded memory locations, or did they let the assembler help them (like modern assemblers)? If yes, how did they manage the memory?

10
  • 5
    This is somewhat broad. I'd lay money that some modern games use hard-coded memory locations. For pretty much all proprietary games you won't get an answer as the code isn't published. For the rest - some yes, some no.
    – Chenmunka
    Nov 1, 2016 at 13:22
  • There are lots of different tags for different machines on this question. Could you narrow the question down to use just one?
    – wizzwizz4
    Nov 1, 2016 at 21:12
  • 6
    Does it still count as hardcoded if you let the assembler hardcode the location for you?
    – user253751
    Nov 2, 2016 at 3:07
  • 1
    Those are three different systems with three radically different toolchains. One of the toolchains was probably highly custom. So, maybe? It depends?
    – user12
    Nov 8, 2016 at 3:40
  • Related question: retrocomputing.stackexchange.com/questions/532/… Nov 28, 2016 at 17:50

12 Answers 12

17

On the C64, even hobbyist assemblers like HypraAss supported symbols, so no, I don't think anyone used hardcoded addresses if there was a way to use a symbol instead.

But you had to be careful how you arrange your data in memory. For example, on the C64, it wasn't possible to put video or sprite data into RAM locations 0x1000..0x1fff and 0x9000..0x9fff because the VIC chip had a fixed mapping to read the character generator ROM when using these addresses. So programmers preferred these locations for program code.

There had been other restrictions on the locations of video data, too. For example, the VIC could only address 16k of memory and the C64 had a bank-switching mechanism to make it access all the memory. But it had another bank-switching mechanism for the CPU, so memory addressing was quite a hassle, especially in the 0xd000..0xdfff area, where the I/O ports and the color RAM were mapped along the main RAM. So, these areas had to be spared from program code mostly.

I general, you needed a lot of knowledge about the address layout of the machine, and let the assembler only help you to move code snippets around as needed without having to change all addresses by hand. It was very common to have a lot of .origin statements in one's code.

12
  • I don't agree. I can't imagine everyone had assemblers capable of using symbols. Especially that kid in his bedroom making games by hand and using nothing more than a machine monitor. Plus, I don't think many first assemblers supported symbols. Merlin, for example, didn't support it for a while IIRC.
    – cbmeeks
    Nov 4, 2016 at 14:36
  • I was that kid in the bedroom and I had HypraAss. And used the machine monitor of the C16 and the Apple II. The latter one was especially horrible, not having an assembler, only a disassembler. I ended up writing my own little assembler for the Apple II (in Applesoft basic), and that one of course had symbols. It's not black magic. To be specific, it hadn't much more than that, symbols and branch calculation. One had to write the opcodes instead of the names.
    – Janka
    Nov 5, 2016 at 0:03
  • 2
    I was also that kid in the bedroom with a TI99-4/a and a C64 (couldn't afford an Apple II). I used mainly type-in assemblers I found in magazines saving to cassette tape. So I guess our experiences were radically different.
    – cbmeeks
    Nov 14, 2016 at 20:54
  • 3
    We were so poor when I was a kid that when we went to KFC we had to lick other peoples' fingers.
    – cbmeeks
    Nov 16, 2016 at 21:21
  • 1
    This isn't about "well known addresses" but about managing labels for your own code.
    – Janka
    Jul 31, 2017 at 22:43
14

The 6502 (the processor in many of the computers mentioned) had a special addressing mode for the 'zero page' (see Wikipedia article by the same name). The zero page was faster to access and use. A three cycle LDA $0000 could instead be written as a two cycle LDA $00 accessing the same location in memory. Thus, the zero page was a location where significant amounts of convention was stored. One could possibly work with an assembler that had labels and go through and label each of those 256 memory locations, but it was safer to work with them as hard coded values to make sure you were using the LDA $00 form of the instruction which translated to A5 00 rather than AD 00 00.

The zero page map for the Apple ][+ can be seen at Jon Relay's Apple II Info Archives - Zero Page Addresses. If you wanted to get the horizontal location of the cursor, this was stored at $24. Thus, hard coded addresses were certainly used for accessing locations of things on the zero page.

Furthermore, page 1 on the 6502 was the stack. $0100 - $01FF was the stack - no way around that. The instructions TXS, TSX, PHA, PLA, PHP, and PLP were working on the stack. Along with JSR for a subroutine which would push the return address on to the stack.

Hard coded locations in ROM were especially useful if there was a card necessary. The code on the card didn't know what slot it was loaded into (each of the slots had a different memory range), just that it was invoked. So jumping to a known RTS memory instruction in ROM would push the return address onto the stack, and then pop it and return. Then an examination of the stack pointer register and accessing one byte (little endian) just past the top of the stack would tell you the high byte of the memory you came from - and that would tell you what slot your code ran in.

Memory management on the Apple ][+ within Applesoft, was largely handled by the zero page location $6F which had the top of free memory and the ROM routine at $E452 (GETSPACE) which would do essentially a malloc of the accumulator bytes and store the location of that memory in the X and Y registers. There was also a garbage collection routine at $E484 and a memory reorganization routine at $E5E2. And while there were symbolic names for these routines, and a sufficiently advanced assembler could use them (not the one at $F666), they were well known and unchanging addresses.

While a large download, the pdf Inside the Apple //e has a significant amount of information about the structure of the processor and programs that ran.

3
  • 2
    The original Forth language for the 6502 used the zeropage as a "second stack" for the typical Forth data manipulation. It was utilizing the X register as a secondary stack pointer, which was neat because the 6502 had that (indirect,X) addressing mode, in which you could select a memory location by a pointer in the zero page given from the X register and an offset. That was a great way to use that hacky mode.
    – Janka
    Nov 2, 2016 at 1:07
  • Not two and three cycle, two and three byte, for three and four cycles. E.g. in the zero page case: (1) read operation; (2) read operand; (3) read from address. For an absolute read, throw in an extra cycle for reading an extra byte of address.
    – Tommy
    May 8, 2017 at 15:48
  • The first part is not true. People often created labels for the zero page locations they used. Every 6502 assembler I ever used would check the address of the memory referencing instructions and use the zero page version automatically if possible.
    – JeremyP
    May 9, 2017 at 8:31
11

More than hardcoded addresses, I'd say hardcoded memory regions. For example, in the ZX Spectrum series, a developer should avoid the memory region between $4000 and $7FFF if he wants to put time-sensitive code or data, as this region is under contention between the CPU and the ULA.

OTOH, there are routines that benefits from putting data on certain boundaries, such as LUTs. A 256 entry LUT can provide an output in only a few machine cycles if the LUT base address is on a 256 byte boundary. That is, if its base address is in the form $XX00. That way, the translation (Z80 code) is as fast as:

LD H,$XX  ;high byte of base address
LD L,A    ;A is the value we want to translate from
LD A,(HL) ;A gets the value translated from the LUT

IM 2 vector tables (for the Z80 CPU also) are also subject of address hardcoding. There are machines which may put a random value in the data bus during an INTA bus cycle, so the called vector is unknown. In these cases, the complete IM 2 vector table must be filled with the same value $XY so the address of the IM 2 routine will be $XYXY

1
  • 2
    When writing 6502 code for the Atari 2600, I defined macros "sbne", "sbcc", etc. which would squawk if the the target wasn't in the same 256-byte page as the byte following the instruction, since branches across page boundaries incur a one-cycle penalty. In a lot of 2600 code, an extra cycle can cause object locations or updates to be 3 pixels off, cause everything below the current screen location to get shifted down a line, or cause various other bad things to happen. I don't explicitly force loop locations in most cases, but make sure that nothing crosses a boundary.
    – supercat
    Nov 1, 2016 at 23:45
4

It really depended on which developer was creating the games. Atari had a very sophisticated system that their developers used and they wrote proper assembler and compiled it on mainframes. Independent developers often did not have such resources available to them and would hack away in a monitor, which is typically how hard-coded addresses wind up in games.

1
  • and programmers like Nasir Gebelli coded directly into memory on the Apple II - no assembler tools! - so no symbols there. Feb 12, 2017 at 4:14
3

We certainly always used labels. There was interesting things done to reuse start up code as data. We were not above stealing buffers and things from the supposed OS but they still got labels.

People also put variables into code which nobody seems to even know about any more.

If you take Z80 then it's faster to do

       LD HL,constant

than

       LD HL, (var)

So you can save two bytes and some clocks by making the fastest load of the variable a constant load and we'd do this

       _var:  LD HL,constant
       var equ _var + 1

and everywhere else use (var)

2

For the 6502 CPU, writing a machine code program to be relocatable and still work when loaded at different memory addresses is difficult because dynamic branching can't go up or down more than 128 bytes from the current program pointer position (BNE, BEQ, BMI, BPL).

It is possible to do an indirect jump using a lookup table JMP ($4000) but calls to subroutines are fixed JSR $300

Making a program relocatable is possible, but requires keeping track of all the direct JMPs and JSRs, and doing a one-time initialization that figures out where the program was loaded into memory, and then go through the program code editing all the JMPs and JSRs with the offset from the default loading address, so they will work in the new location.

 

Beyond this, many program's read and write locations would also have to be tracked and manually adjusted, as it was common for programs to be self-modifying.

Rather than using a pointer or zero page location to read or write data, the program would directly modify its own code. A typical simple memory copier that modifies itself:

300: A9 04       LDA #$04     ; Source page $#04
302: 8D 0E 03    STA $030E    ; Modify read address
305: A9 08       LDA #$08     ; Destination page $#08
307: 8D 11 03    STA $0311    ; Modify write address
30A: A2 00       LDX #$00     ; Init X register at 0
30C: BD 00 04    LDA $0400,X  ; Read byte from page
30F: 9D 00 08    STA $0800,X  ; Write byte to page
312: E8          INX          ; increment X register
313: E0 00       CPX #$00     ; Check if it rolled over
315: D0 F5       BNE $030C    ; No? Do it some more
317: EE 0E 03    INC $030E    ; Add 1 to page number
31A: EE 11 03    INC $0311    ; Add 1 to page number
31D: AD 0E 03    LDA $030E    ; Get source page number
320: C9 08       CMP #$08     ; Is it at the ending value?
322: D0 E8       BNE $030C    ; No? Do it some more
324: 60          RTS          ; Exit

Although there are no JMP or JSR here, the code modifies locations $30E and $311, and to be relocatable it is necessary to change the code so it modifies itself correctly, for the opcodes at $302, $307, $317, $31A, and $31D.

So relocatable code can be done but it is fiddly, and nothing in the hardware enables this directly, in a manner that the programmer doesn't have to directly and intimately be involved with making it work.

2

The Apple IIgs running GS/OS did indeed support a more modern memory management model.

Applications (.S16, .EXE, CDA, NDA, etc.) were stored in Object Module Format. All of the major Apple IIgs development tools supported the creation of OMF files. OMF files included a relocation information which Loaders could use to relocate the object code into a specific memory location.

2
  • Welcome to Retrocomputing. Thanks for the answer. I for one never knew that Apple machines used the .EXE format!
    – wizzwizz4
    Dec 30, 2016 at 9:22
  • 1
    @wizzwizz4 Not actually the same format as Windows machines. Apple IIgs EXE files were shell commands that returned control via a simple RTL instruction, rather than using the GS/OS Quit system call.
    – Lucas
    Feb 14, 2017 at 19:13
2

On Elektronika BK, using hardwired memory locations in ROM to access useful subroutines was quite popular among game developers.

1

Many Amiga games used hard coded addresses, for example to detect if extra memory had been installed by looking for it at specific locations.

The introduction of floppy disks for storage also made multi-loading more practical. Most tape games tried to avoid it because of very slow load times, but with floppy disks it was common to load between levels.

Using fixed addresses had two benefits for multi-load games. First it meant that any code loaded did not need to be relocatable. Jumps could be absolute, and there was no need for jump tables of trampolines or complex communication between modules. Many programmers were used to doing things that way on 8 bit systems.

The other benefit was preventing memory management issues like fragmentation. Repeatedly allocating and freeing chunks of memory can cause memory allocation to fail eventually.

Finally, one other big benefit of using fixed addresses is that it makes debugging easier. Popular debuggers like the Action Replay allowed the programmer to inspect memory, so knowing exactly where things are located really helps. Of course the compiler will produce a map file but for speed and ease of use many people preferred not to have to look up addresses every time.

0

All code I did on the 8bits started at the beginning of usable RAM (after OS data, etc); but the tools let us select where each block of code or data starts, so it was up to the user, knowing that some zones had OS uses and some may be mirrored on machines with less ram (16kb vs 64kb for example).

0

Honestly I suspect most folks used an assembler with labels (rather than a monitor type program). It's possible some Apple ][ folks wrote stuff using the mini assembler but even I as a penny less school kid wrote a wrapper around it that added labels and org statements. I suspect a lot of the early Z80 developers were cross compiling from CP/M to target systems.

However I wrote a load of games in assembler without ever using a linker and memory management of locations was pretty manual using .org statements. I stopped using my home brew assembler once I got the ProDOS development kit.

0

At least several answers (and comments) seem to interpret the question with an implied idea that one can have an assembler that does not resolve symbolic addresses. I think that this interpretation is misguided. In theory, yes, a program like this can be developed (there are some examples of such programs in the comments). However, in practice, starting with the very first real assembler written for IBM 701, the whole point of having the assembler was not so much resolving the mneumonics into specific binary commands, but the resolution of symbolic addresses (see Rochester, N. (1953) Symbolic programming, Transactions of the IRE Professional Group on Electronic Computers, (1), 10-15).

Just to make my point even clearer, this is the abstract of the cited N. Rochester's paper:

Automatic calculators can be programmed to interpret programs which have been written with symbolic instead of actual addresses. This method allows the calculator to assume much of the clerical burden which must otherwise be borne by the programmer.

At the same time, when programming micros, especially when developing natively, it was often impossible to pass the whole burden of memory management to the assembler. For example, in my youth I developed a simple Battle City clone for ZX Spectrum using a ZX Spectrum 48K with a tape drive. For writing my code I used Zeus, a native assembler, which of course supported symbolic labels. However, having the assembler in memory meant that it was not possible to have my whole game in memory at the same time. Therefore, I did my development in batches: a group of routines would be developed and debugged using assembly, then built into a binary and saved onto the tape. I had a special notebook with addresses of key routines needed externally, so if I needed to refer to any of these routines later, I could call them directly by their address. Thus, low- to mid-level building and linking was done in assembler. However, all higher-level linking was done effectively by hand. I am guessing that a lot of homebrew developers at the time must have used similar approaches.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .