* * * * *
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]