Apple II Binary Files and FPGA Reprogramming
I’m seeking help from all the Apple II programming gurus, with what may be an obvious question. During the development of the Yellowstone FPGA-based disk controller, I’ve written several times about my goal of providing a way for users to reprogram the FPGA without needing special hardware, in case there are bug fixes or feature improvements after the initial Yellowstone release. From a hardware standpoint, I think I’ve found a way to do this from the Apple II itself, by bit-banging SPI using some address lines and the slot’s /DEVSEL input signal. But from an Apple II software standpoint, I’m embarrassed to admit I don’t know what I’m doing.
I’d imagined something like this: a BASIC program asks the user what slot the Yellowstone card is installed in. It uses this to determine what Apple II address range will trigger the slot’s /DEVSEL. Then it reads binary data from a separate firmware file, maybe 1K at a time, and transmits it to the Yellowstone card via SPI using a carefully-crafted sequence of POKE statements. Or perhaps some helper assembly language routine instead of POKEs, for speed.
But as far as I can tell, Applesoft BASIC with DOS3.3 or ProDOS has no facility for reading binary files. Can that possibly be true? I fear I’m missing something obvious. Can I BLOAD a binary file from within a running BASIC program? How do I know what address is safe to load it at? Should I try storing the firmware in a zillion DATA statements in the BASIC program? I’m not sure it will all fit in the Apple II’s memory at once.
If I want to craft a small helper routine in assembly, to use within the BASIC program, what’s the best way to do that? I could encode it as DATA statements, but I think I’d need to copy it elsewhere before I could run it, and once again I’m unsure what address range would be safe to use.
There must be some example programs that already do something like this, but I’m drawing a blank.
Read 23 comments and join the conversation23 Comments so far
Leave a reply. For customer support issues, please use the Customer Support link instead of writing comments.
You should be able to BLOAD a binary to a particular address with something like this in DOS 3.3 or ProDOS:
10 print chr$(4) “bload filename,A$2000”
If you don’t specify an address, then the binary will load at the “default” address for the binary. In ProDOS, you can see that address in “catalog” output. I don’t recall how to get that address from DOS 3.3. I would suggest loading it at a fixed address anyway.
For a small assembly helper, $300 is a good location. If you can fit between $300 and $3cf, that is ideal. You can BLOAD a binary copy of your machine code, use DATA statements and probably a number of other ways.
Jeremy is correct; you print a ^D before the dos command.
How big are these binary files going to be? You can use “A$” to specify a load location, but in DOS 3.3 you don’t have the freedom to load parts of it (https://fjkraan.home.xs4all.nl/comp/apple2faq/app2doscmdfaq.html).
ProDOS does give you that freedom (see page 99 in https://vintageapple.org/macbooks/pdf/Inside_Apples_ProDOS_1984.pdf). For ProDOS you have
]BLOAD pn [,A#] [,B#] [,L#IE#] [Ttype] [,S#] [,D#]
A is the location in memory
L is the length / E is the last memory location (pick one)
B is the offset in the file
which means you have the freedom to load the file in chunks. ProDOS sounds like a better option.
The downside of ProDOS is that it might prevent some HW from running it. DOS 3.3 runs on everything. Some versions of ProDOS require a //e or better. It _may_ be that some version of ProDOS will run on the oldest Apple //’s but I don’t personally know for sure.
That said, how big are these images? If they don’t fit in memory, it would be good to use the bload options in ProDOS that Avi has described. Will they fit on a floppy disk? The larger these binaries are, the more difficult it will be to make sure that people can get them onto their Apple //.
If DOS is preferred for portability reasons, it should still be possible to break the firmware into more manageable chunks, and BLOAD them individually as needed.
I don’t recommend doing the actual transfer in BASIC – it’ll be orders of magnitude slower. Better to write a small helper routine in machine language, that the BASIC program pokes into $300, to do a tighter memory-copy loop.
Jeremy, great points. I’m thinking about ProDOS 2.4 (https://prodos8.com/releases/prodos-24/) which is a release by John Brooks that will run on all Apple II computers.
In any case, though, the question that we’re both dancing around is size. How big are these firmware images? How much memory will the host computers be required to have? (I’m imagining that 4k Apple IIs aren’t targeted.)
Will the firmware image fit on a single 143k floppy? If not, then the discussion gets quite a bit more complicated 🙂
The .bit file for the FPGA is 21 KB right now. The final version might be slightly larger.
Here’s what I’ve got. First the BASIC program must write a magic value to a specific memory location in the card’s address space in order to enable the slave SPI configuration interface. Let’s say it’s writing $D5 to $CFF9. Then assuming the card is in slot 6, it can send a 0 bit by reading from $C0EA or send a 1 bit by reading from $C0EB. Any data coming back from the SPI interface will be in bit 0 of the read value. When finished, the program can write to a second magic memory location to disable the configuration interface.
Unfortunately it’s not as simple as just bit-banging the firmware image. It’s this whole crazy command interface: check device ID, enable transparent configuration mode, check busy flags, erase the flash memory sectors, transmit the write address, transmit data, repeat for next sector, verify, refresh… Documentation is confusingly spread across half a dozen different Lattice docs and examples. I wonder if anyone’s ever implemented this for Apple II before.
Back of the envelope speed estimate: 21 KB is 168 Kbits. With all the command overhead let’s say it’s 250 Kbits to transmit. With an assembly code helper routine, maybe it can transmit a bit every 20 clock cycles of the Apple II. That would be a total time of 5 seconds. Sounds fine.
Steve, you may want to reach out to Dean Claxton and Jeff Mazur at https://theromexchange.com/
They have a way to load ROM images from DISK to their ROMx/c/e products. I’m not sure if what they are doing is in anyway similar to what you need, but they may have some suggestions for you.
Bill T.
Thanks, I’ll reach out to the ROMX guys. Cool device!
I think I need the secret decoder ring for Apple II memory addresses and knowing which addresses are safe to use. It looks like $2000-$3FFF is the hires screen memory? Is this always safe to use, assuming a BASIC program doesn’t use hires graphics? What about hires screen 2 at $4000-$5FFF? And what’s $300 to $3CF – I couldn’t find any docs or information about that range. That isn’t used by BASIC? Is there any place I can read more about this?
Hi Steve. Yes I’m doing exactly what you want in the ROMX firmware. Best to do it all in machine language but I can help you split it up to write parts in BASIC. The newer ROMX products also implement an I2C buss to interface with the clock chip. And we do all this with just the signals available on a ROM socket!
So it should be much simpler to do from a card slot. Email me the particulars and I’ll try to send you in the right direction. I’m sure I have some code snippets you could reuse.
Yep, that knowledge now only exists in the heads of some and on random web pages like http://www.kreativekorp.com/miscpages/a2info/memorymap.shtml. Briefly:
Yes, $300 is the standard “a place to dump routines”. It’s pretty small, but workable.
Yes, if you’re not using HGR, you can totally repurpose HGR1 and HGR2. The only issue is that for very old Apple II computers you might not have that memory. On a 4K Apple II, you don’t have any HGR space, and there are various configs that end-users could do (see https://retrocomputing.stackexchange.com/questions/12190/small-ram-4-kb-on-the-early-apple-ii) that ended up with various holes in the memory map.
You probably should require a 48K Apple II.
When I want to know things like the memory map, I’ll do quick google searches and find pages like the first one I linked. For heavier reading (also recommended!) just raid https://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Books/. Jim Sather’s books are required reading for anyone and are mind-blowingly in depth. Sams ComputerFacts have circuit diagrams. etc.
Good luck, and the community is very helpful. @a2_4am, @deater78, @antoine_vignau are some names that I can think of offhand. Everyone’s ready to help; you only need ask.
Yeah, I agree it’s probably reasonable to require a 48K Apple II. Even if somebody is using the card with a 4K system, they’re probably a collector who also has another system with more RAM, that they could use temporarily for reprogramming the card. If not, then they’d need a JTAG programmer or just live with the original FPGA firmware.
Since this is for a UniDisk 3.5 controller card, I don’t think it’s too much to expect a 64K Apple II running ProDOS. According to the UniDisk 3.5 Owner’s Manual, “You can use your UniDisk 3.5 with any Apple II computer that has at least 64 kilobytes of random-access memory (RAM).” Stock DOS 3.3 doesn’t work with anything but a Disk ][ type drive, and the UniDisk 3.5 manual again notes that “You can use your UniDisk 3.5 with either the ProDOS or Pascal 1.3 operating system”.
So, expecting a UniDisk 3.5 controller card to be installed in a 64K Apple II running ProDOS doesn’t seem like too much to expect.
And I agree with Avi above regarding BLOADing a file using the B and L options under ProDOS if it’s not possible to BLOAD the entire file at one time. $2000 is what I think of as kind of the default location to load an arbitrary binary file, but if the file didn’t all fit between $2000 and $9600 (the beginning of BASIC.SYSTEM, although I believe strings start at the top of memory and work their way down, and BASIC.SYSTEM may allocate some buffers, so additional research/experimentation will be required in this regard), you could load it lower. Applesoft programs start at $800, so if that program isn’t all that big, you could probably BLOAD it at $1000 (but non-string variables start at the end of an Applesoft program and grow up, so again this will need to be taken into consideration).
If you put an ATMega88 on the board, you can put a USB -> serial + bootloader on the board. Have Atmega hold the RESETN line low, while you program the eeprom, sending it serially over USB from a PC / Mac / Linux. In addition to it being faster than doing it with apple II, it gives you a nice recovery option.
There is basically no “excellent” spot to put things in memory on an Apple II. 🙂 On the cheat-sheet spreadsheet I made for myself, only that narrow space from $300-$3CF is considered _completely_ “free and clear” for typical configurations.
The best spot to “steal” memory from, is your BASIC program (and/or the HGR pages). A BASIC program gets all the memory from $800-$BFFF (if you boot directly to BASIC). If you have a DOS installed, typically that goes in $9600-$BFFF and your BASIC program’s HIMEM gets set to $9600.
Both of the high-res graphics pages occupy space that can also be used by your BASIC program data (it is a particular risk that your string allocation memory, growing down from HIMEM, will collide with HGR2, which is why the manuals caution that you should consider setting HIMEM to $4000 or below if you plan to use HGR2). The same applies if you plan to use HGR2’s memory for other purposes. 🙂
As to how you could’ve known about $300-$3CF being free and clear – I was informed of this by one of the 6502 programming books I learned from (Assembly Language for the Applesoft Programmer, Finley & Myers), but the Apple //c Technical Reference manual’s Appendix B (Memory Map) explicitly calls out that most of page $3 is available, and mentions what features and systems will use $3D0 and above.
Thanks everyone. So if the BASIC program requires a 48K Apple II, it should be able to BLOAD data into the region from $2000 to $9600, which is 30 KB of space. I think that’ll be enough to load the whole FPGA bit image in a single block. If I’m understanding correctly, I can maybe also use the region from $1000 to $2000 if the BASIC program is small enough. But I maybe can’t use memory all the way up to $9600 if the program uses strings or allocates buffers for BASIC file I/O. I’m not sure how to test that… maybe try zeroing that region of memory in the Apple II monitor, then run the program, then use the monitor again to see if any bytes changed in that region of memory.
Actually, just set HIMMEM to the start of your program, and then BASIC’s string allocation will grow down from (just before) there!
For a magic wake-up sequence, how about watching for four consecutive accesses to $C0s1, which could be triggered via INC $C0xx,X or DEC $C0xx,X instruction, or on the NMOS 6502 by the same addressing mode in shift instructions, but not via any other means?
I could write you some code for a USR function to perform SPI I/O, if you like. Perhaps something like saying that USR(x) will exchange one byte of data D if x is 256+D (returning the shifted value as a number 256-511, and will exchange N bytes of data stored in memory starting at address A if the argument is 16777216+N*65536+ADDR, and return the last byte shifted.
As a slight hardware recommendation, though, I’d suggest using bit 7 rather than bit 0 for the input (notice that Apple does that rather consistently). If the X register holds 128, the instruction CPX $C0EA or CPX $C0EB will set or clear the carry flag based upon bit 7 of the value read, without regard for what’s in the accumulator, thus allowing the accumulator to be used hold the data being shifted. This will allow I/O to be performed quite a bit faster than would be possible using bit 0 for input.
John, thanks for the ideas!
More perverse approaches than BLOAD include trying to use the Text file support which was an extremely crude fixed-length record format (I don’t recall if it was 8-bit clean). Or the lower level poorly documented generic file I/O layer which I believe allowed partial file reads.