* * * * *

        The difficulties in supporting “write-only memory” in assembly

When I last wrote about this [1], I had one outstanding problem with static
analysis of read-only/write-only memory, and that was with hardware that
could be input or output only. It was only after I wrote that that I realized
the solution—it's the same as a hardware register having different semantics
on read vs. write—just define two labels with the semantics I want. So for
the MC6821 [2], I could have:

-----[ Assembly ]-----
               org     $FF00
PIA0.A          rmb/r   1       ; read only
               org     $FF00
PIA0.Adir       rmb/w   1       ; write only, to set the direction of each IO pin
PIA0.Acontrol   rmb     1       ; control for port A
-----[ END OF LINE ]-----

So that was a non-issue. It was then I started looking over some existing
code I had to see how it might look. I didn't want to just jump into an
implementation without some forethought, and I quickly found some issues with
the idea by looking at my maze generation program [3]. The code in question
initializes the required video mode (in this case 64×64 with four colors).
Step one involves writing a particular value to the MC6821:

-----[ Assembly ]-----
               lda     #G1C.PIA ; 64x64x4
               sta     PIA1.B
-----[ END OF LINE ]-----

So far so good. I can mark PIA1.B as write-only (technically, it also has
some input pins so I really can't, but in theory I could).

Now, the next bit requires some explaining. There's another 3-bit value that
needs to be configured on the MC6883 [4], but it's not as simple as writing
the 3-bit value to a hardware register—each bit requires writing to a
different address, and worse—it's a different address if the bit is 0 or 1.
So that's six different addresses required. It's not horrible though—the
addresses are sequential:

Table: 6883 VDG (Video Display Generator) Addressing Mode
bit     0/1     address
------------------------------
V0      0       $FFC0
V0      1       $FFC1
V1      0       $FFC2
V1      1       $FFC3
V2      0       $FFC4
V2      1       $FFC5

Yeah, to a software programmer, hardware can be weird. To set bit 0 to 0, you
do a write (and it does not matter what the value is) to address $FFC0. If
bit 0 is 1, then it's a write to $FFC1. So with that in mind, I have:

-----[ Assembly ]-----
               sta     SAM.V0 + (G1C.V & 1<<0 <> 0)
               sta     SAM.V1 + (G1C.V & 1<<1 <> 0)
               sta     SAM.V2 + (G1C.V & 1<<2 <> 0)
-----[ END OF LINE ]-----

OOh. Yeah.

I wrote it this way so I wouldn't have to look up the appropriate value and
write the more opaque (to me):

-----[ Assembly ]-----
               sta     $FFC1
               sta     SFFC2
               sta     $FFC4
-----[ END OF LINE ]-----

The expression (G1C.V & 1<<n <> 0) checks bit n to see if it's set or not,
and returns 0 (for not set) or 1 (for set). This is then added to the base
address for bit n, and it all works out fine. I can change the code for, say,
the 128×192 four color mode by using a different constant:

-----[ Assembly ]-----
               lda     #G6C.PIA
               sta     PIA1.B
               sta     SAM.V0 + (G6C.V & 1<<0 <> 0)
               sta     SAM.V1 + (G6C.V & 1<<1 <> 0)
               sta     SAM.V2 + (G6C.V & 1<<2 <> 0)
-----[ END OF LINE ]-----

But I digress.

This is a bit harder to support. The address being written is part of an
expression, and only the label (defining the address) would have the
read/write attribute associated with it. At least, that was my intent. I
suppose I could track the read/write attribute by address, which would solve
this particular segment of code.

And the final bit of code to set the address of the video screen (or frame
buffer):

-----[ Assembly ]-----
               ldx     #SAM.F6         ; point to frame buffer address bits
               lda     ECB.grpram      ; get MSB of frame buffer
mapframebuf     clrb
               lsla
               rolb
               sta     b,x             ; next bit of address
               leax    -2,x
               cmpx    #SAM.F0
               bhs     mapframebuf
-----[ END OF LINE ]-----

Like the VDG Address Mode bits, the bits for the VDG Address Offset have
unique addresses, and because the VDG Address Offset has seven bits, the
address is aligned to a 512 byte boundary. Here, the code loads the X
register with the address of the upper end of the VDG Address Offset, and the
seven top most bits of the video address is sent, one at a time, to the B
register, which is used as an offset to the X register to set the appropriate
address for the appropriate bit. So now I would have to track the read/write
attributes via the index registers as well.

That is not so easy.

I mean, here, it could work, as the code is all in one place, but what if
instead it was:

-----[ Assembly ]-----
               ldx     #SAM.F6
               lda     ECB.grpram
               jsr     mapframebuf
-----[ END OF LINE ]-----

Or an even worse example:

-----[ Assembly ]-----
costmessage     fcc/r   "A constant message" ; read only text
buffer          rmb     18

               ldx     #constmessage
               ldy     #buffer
               lda     #18
               jsr     memcpy
-----[ END OF LINE ]-----

The subroutine memcpy might not even be in the same source unit, so how would
the read/write attribute even be checked? This is for static analysis, not
runtime.

I have one variation on the maze generation program that generates multiple
mazes at the same time, on the same screen (it's fun to watch) and as such, I
have the data required for each “maze generator” stored in a structure:

-----[ Assembly ]-----
explorec        equ     0       ; read-only
backtrackc      equ     1       ; read-only
xmin            equ     2       ; read-only
ymin            equ     3       ; read-only
xstart          equ     4       ; read-only
ystart          equ     5       ; read-only
xmax            equ     6       ; read-only
ymax            equ     7       ; read-only
xpos            equ     8       ; read-write
ypos            equ     9       ; read-write
color           equ     10      ; read-write
func            equ     11      ; read-write
-----[ END OF LINE ]-----

This is from the source code, but I've commented each “field” as being “read-
only” or “read-write.” That's another aspect of this that I didn't consider:

-----[ Assembly ]-----
               lda     explorec,x      ; this is okay
               sta     explorec,x      ; this is NOT okay
-----[ END OF LINE ]-----

Not only would I have to track read/write attributes for addresses, but for
field accesses to a structure as well. I'm not saying this is impossible,
it's just going to take way more thought than I thought. I don't think I'll
have this feature done any time soon …

[1] gopher://gopher.conman.org/0Phlog:2024/01/31.3
[2] https://en.wikipedia.org/wiki/Peripheral_Interface_Adapter
[3] gopher://gopher.conman.org/0Phlog:2023/11/27.1
[4] https://archive.org/details/Motorola_MC6883_Synchronous_Address_Multiplexer_Advance_Sheet_19xx_Motorola

Email author at [email protected]