4

I've been looking at a bunch of different 6502 assemblers recently (dasm, ca65, xa, kickass), and many of them have support for multiple code segments. Some of these assemblers can generate relocatable object code, and have a separate linker to create the final binary. Others don't, and I'm trying to understand the use of code segments in this second case.

example 1

For example, the following two snippets of dasm code produce the same output. First, one that uses an undefined (.u) segment to label some memory addresses:

        processor 6502

        seg.u zero
        org $0

tmp1    ds.b 1
tmp2    ds.b 1

        seg text
        org $200

        lda #$12
        sta tmp1
        lda #$34
        sta tmp2

example 2

And a second that just uses a pair of explicit labels instead:

        processor 6502

tmp1    = $00
tmp2    = $01

        org $200

        lda #$12
        sta tmp1
        lda #$34
        sta tmp2

Is there a practical difference between these two examples? Are there situations in which the use of segment directives either makes something substantially easier to implement or significantly impacts the clarity of the code?

3
  • This seems like what is called "program sections" or "control sections" in other contexts. They're a generalization of the idea that it's handy to be able to collect text (i.e. code) and data separately during assembly. A use-case on PDP-11 was that a macro could emit some instructions, go add an entry to a table in a dedicated program section, and then revert to the code section.
    – dave
    Jan 16, 2021 at 23:55
  • While this has been asked with a 6502 assembler as an example, it’s not particularly retrocomputing-specific. One may ask an analogous question about the relative merits of equ versus resb in NASM, which I think counts as contemporary technology. Jan 17, 2021 at 5:52
  • Would this assembler be able to switch segments more than once? If so, this might be for avoiding having data mixed with the code. Jan 17, 2021 at 11:47

2 Answers 2

6

TL;DR:

  • Using segments offloads program code management to dedicated tools.
  • Not using segments replaces these tools by the a programmer doing everything by hand.

While the difference may not be a big one with short programs, it gets relevant soon.


Segments are a way to organize code and/or data on a logical level. Segments group sections of code and/or data according to their use. When grouped, they can be used to assign meta-attributes needed to manage the program.

The basic organization of C (Unix) programs as three segments is a great example here.

  • TEXT - collects all executable data (code).
  • DATA - all non executable data fields (with predefined values)
  • BSS - joins all uninitialized data.

By assigning everything a program source generates during compilation to either segment, it allows the linker, as first step, to combine all chunks of each kind into a single element and adjust all addresses accordingly. The loader, as next stage, can place these unified blobs into memory and, if supported by architecture, for example set executable (TEXT) or non-executable (DATA) flags for protection. Similar it can enable write protection for code and so on.

So again, segments are about two basic mechanics:

  • grouping code/data
  • assigning attributes to these groups.

Of course, with rather simple programs, like the example given, without much structure, the grouping doesn't give much support. Similar, with rather simple systems like a bare 6502, there isn't much management that could be simplified by segments.

Still, even with a 6502, complex structures can be build, where usage of segments will simplify management - and more important reduce chance of programming errors.

Imagine a game, for example for an Atari VCS 2600; as soon as this project goes beyond basic PONG, it will outgrow the 4 KiB ROM space the console offers. So the game needs to be split into multiple code chunks that get paged in and out depending on program flow (essentially overlays). All of them will reside at the same address as viewed from the CPU, but different addresses within the ROM chip.

Such a program may of course still be handled the 'simple way' of our second example (without segments). But to do so, the whole project must reside in a single source file. Or at most, it can be split at physical addresses. One must keep very fine track of what to put where.

With segments, code can be structured (almost) freely, as they tell the linker how to join the various pieces, each compiled separately, into a single binary. In our example, one would simply define a segment name for each ROM page (like PAGE1/2/3/etc.). When writing code intended to go into page 1, it simply gets prefixed by .segment "PAGE1" (CA65 syntax). After that, the linker will take care of grouping all code for page 1 together, no matter in what source file it was written or compiled from and in which sequence. The programmer becomes free to organize source in a manner reflecting the way the game is organized, while compiler/linker/loader takes care of putting it into the sequence the computer needs to see it. The linker will also report when a segment outgrows the available space, so no-one has to count bytes by hand. After all, isn't the whole purpose of using tools, like an Assembler, to offload the grind to the computer, so the programmer can focus on the problem?

The same issue comes, BTW, as well up on larger 6502 systems than a VCS. Like having even more complex programs that outgrow even a 64 KiB RAM - or need to be shrunk by modularisation to keep some RAM free for user data. :))

Of course, more sophisticated usage also needs more sophisticated tools. XA65 is here rather on the simple side. CA65 in turn is quite capable of handling complex linking situations - which as well means it needs a bit more time to discover how to do it.


Bottom line, like any other management tool, segments shine with increasing complexity of the task to be done.

9

A convenient application of segments is the handling of zeropage addresses in ca65. Often, code pieces are written in a way that they use fixed coded ZP addresses (usually defined in some config file), which makes it very difficult to avoid and debug zeropage address conflicts when having more than one module.

The standard assembler configuration for the C64 c64-asm.cfg defines a zeropage segment from $02 to $ff, so that a program can use the segment to reserve memory for zeropage addresses instead of a fixed address.

Example with two modules:

A simple module using a zeropage address:

;program myprog1.s
.import display_chars
.zeropage
myvar: .res 1

.code
        jsr display_chars
        sta myvar
        lda $400
        sta $d020
        lda myvar
        rts

and a second module also using the zeropage.

;program myprog2.s
.export display_chars
.zeropage
zptr: .res 2

.code
display_chars:
        lda #<$400
        sta zptr
        lda #>$400
        sta zptr+1
        ldy #$00
loop:
        tya
        sta (zptr),y
        dey
        bne loop
        rts

Now after assembling and linking it with

ca65 myprog1.s
ca65 myprog2.s
cl65 myprog1.o myprog2.o -C c64-asm.cfg -u __EXEHDR__ -o myprog.prg

we get a program that uses three zeropage addresses ($2, $3, $4 in the standard configuration for C64) for the two modules.

You must log in to answer this question.

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