* * * * * 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< 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 Sean Conner at sean@conman.org .