We are getting ready to merge the controller into development, and use the new MEGA65 ROM that uses it. But during testing, we have found some problems with some stock 1581s at least with it: The communications hangs very early when trying to load something, but only for some people's 1581s. I want tto investigate if something is out in our timing. The challenge is to find out exactly where.
We have the VHDL unit tests with their simulated 1581 drive, which was used to verify simple sending and receiving of DOS commands. However, they can't currently test LOADing a file, or even a directory, because the simulated drive has no disk. But it would be great if we could find a way to do this, as it would let us see where the 1581s CPU and our IEC controller first disagree on where they think they are in the protocol.
Implementing the 1702 Floppy controller IC and tying it to a D81 disk image is an option, but will take a pile of work and time. There is an easier option, in that the 1581 supports a track cache, so we could just reach into the simulation, and make the 1581 think that it has been populated with, say, track 40 with a valid directory. That way, we should be able to simulate loading a directory, without having to actually implement disk access in our simulations.
So the fun here, is that for all the years that VHDL has existed, they still don't seem to have added a sensible way to load binary files. So instead I have used the mkdriverom utility that I used to get around this last time, to also make a d81.vhdl file, which is prepopulated with the contents of the d81 disk image. This should work, but is annoying, because GHDL is quite slow at parsing these large pre-initialised memories. Or at least the version I am using. So maybe I should use dd to chop out track 40, so that d81.vhdl will be smaller and faster to synthesise in simulation.
@nobruinfo on Discord was kind enough to do some tests with a 1581 to confirm that the track cache information bytes at $95 and $97 function as I expected: $95 has the 1581 track number and $97 the side number. So after loading a directory, they have $27 and $00 respectively, which he confirmed by reading those addresses out of the RAM of the 1581 for me:
For some reason all my simulation unit tests of the 1581 are failing. So I need to figure out what has gone wrong there. This is using a JiffyDOS ROM. I did make a tool src/tools/iecwaveform.c in the mega65-core repository that lets me do a more human readable parsing of the huge logs that the VHDL simulation produces, and that shows the annotated memory locations of the 1581 ROM that are reached at various points in time, to help debug protocol problems, so I'll use that again here.
The unit tests are all failing with a DEVICE NOT PRESENT error, so I'm guessing it will be something pretty simple and stupid that is wrong -- like one of the IEC lines not being plumbed up correctly.
Now, this would be easier to debug if I had a good ROM disassembly of the 1581 ROM. I've not been able to find one previously, but I found one today in:
https://www.lyonlabs.org/commodore/onrequest/The_1581_Toolkit.pdf
It's even got selectable text behind the scanned pages -- but the layout of them is weird, so I can't just copy and paste the address lists. But the task of reorganising them isn't rocket science, so I'll use ChatGPT to do it. I find it's best to show ChatGPT what you want for a couple of entries in this kind of task, and then give it bigger slabs. I tried giving it more than one page at a time, without luck, but a page at a time is not too bad.
This has yielded me the following 1581 ROM memory map:
$8000-$8001 CHECKSUM CHECKSUM used by routines to verify the integrity of the DOS ROMs
$8002-$8003
$8004- EXECMD Execute command string
$804C- ENDCMD End of computer command
$8050- ENDO Generate an error message
$805B- END1 End command but don't write BAM
$8067- END2 End command ignore error
$8071- CLRCMD End command no error message prepared
$8085- SCAN Clear command buffer
$8099- SCANCOLON Look for ":" and drive number in command string
$80A2- TEST2FILES Look for colon in command string
$811C- SCANCHARINA Test command with two filenames for syntax
$8165- LOOKCMD Search input line for character in the accumulator
$81AF- CLRCMDVAR Set all flags and look at command string table
$81E5- FLASHOFF Clear and set back table, pointers and flags
$81F1- FLASHON Flash error LED off
$81FD- GETDRV Get drive number and set into file table
$8224- GETDRVFROMCMD Get drive number from command string
$8251- INITWITHLEDON Initialize drive and switch LED on
$8270- SETFILE Set and determine file type
$8295- CHKDRV Test valid drive number
$82A2- INTDRVFNAME Initialize drive given in filename
$82B9- FINDFILE Look for file entry in the directory
$8327- SCANDIR Search directory entry
$8336- SCANNEXT Search next entry
$83D7- NEWSEARCH Reinitialize file search flags
$83E2- QUITSEARCH Quit search for filename
$83FA- WCARD 1581 extended wild card check
$8424- SETSEARCH Set indicator to search in directory
$84AE- INTDSK Initialize diskette
$84EE- WRTNAME Copy filename from input buffer to directory buffer
$8508- COPYDATA Copy part of the input buffer and the current data buffer
$8526- FNAMELEN Search length of filename in input buffer (starting position in .X)
$854D- READFROMDIR Read file from directory
$855D- DIROUTPUT Establish directory for output to buffer
$861C- DELBUF Delete buffer for data name with empty character
$868B- BLKFRE Set up closing line of directory with "Blocks Free" message
$867C-$8687 BLKFREMSG "Blocks Free" message
$8688- SCRATCH Scratch command - "S"
$86DB- FRESIDE Free side sector blocks in a REL file
$8713- FREEUP Pursue sectors on hand and free up in BAM
$8732- FREEUP1 Free sector in BAM and continue
$873B- DELFILE File entry in file type of directory marked as scratched
$8746- NEW New routine for 1581 "NEW" command (format disk) - "N"
$876E- COPY Copy command for copying files - "C"
$8793- COPYFILE Copy files
$87F4- COPYSING Copy single files
$8800- COPYMULT Copy multiple files
$8841- OPENREAD Open channel to read file
$8876- GETBYTE Read a byte from a file
$8895- COPYREL Copy a relative file
$88C5- RENAME Rename a file command - IIRM
$8903- CHKEXIST See if file entry exists
$891E- CMPNAMES Compare with two filenames
$892F- MEMCMD Memory command routines
$8954- MEMREAD Memory read command get a byte from drive
$8983- MEMWRT Memory write command write a byte into drive memory - "M-W"
$898F- USER User commands to start programs in DOS buffer at $0500
$8996- BURSTCMD Routine for burst user commands ("U0")
$89CC- EXECUSER Execute a user command
$89E4- DIRECT "#" command - open a direct channel
$8A5D- BLKCMDS Block commands
$8A9F- BLKPARAM Get and set block command parameters
$8AAC- TESTPARAM Test block command parameters
$8AD0- ASCII2BIN Convert/set block command parameters from ASCII to binary
$8B20- BINVAL Binary value table $01, $0A, $64
$8B23- BLKFRE Block free command - free a block in the BAM - "B-F"
$8B2F- BLKALLOC Block allocate command - mark a block in the BAM as used - "B-A"
$8B65- TESTBR Test block read ("B-R") parameters and read sector into buffer
$8B6B- GETBUFBYTE Get byte from buffer
$8B71- READSECINTPTR Read sector from diskette to buffer and initialize pointer
$8B85- BLKREAD Read sector from diskette for "B-R" command
$8B8E- BREXTEND Block shift read command - extended track reader
$8B9A- USER1CMD Routine for "U1" command read sector from diskette
$8BAE- BLKWRT Routine for block write command "B-W"
$8BD1- BWEXTEND Block shift write command - extended track writer
$8BD7- USER2CMD Routine for "U2" command write sector to diskette
$8BE3- BLKEXEC Routine for block execute command read sector and execute code in DOS buffer "B-E"
$8BFA- BLKPTR Routine for block pointer command set buffer pointer ("B-P")
$8C0F- ALLOCOPEN Allocate buffer open channel
$8C2F- CHKPARAM And test parameters for valid sector assignment
$8C44- SKIPILLEGAL Same as above, but does not flag illegal tracks and/or sectors
$8C5C- ALLOCBUF Allocate RAM buffer - .A contains the buffer number (1 = buffer 0, 2 = buffer 2, etc.)
$8C61-$8C6A BLKCMDTAB Block command table "AFRWEPRW*"
$8C6B-$8C7E BLKADDR Addresses of 10 block commands in low, high byte format
$8C7F- AUTHOR Block-? command author/designer message in error channel - "B-?"
$8C84- DEDICATE Block-* command dedication message in error channel - "B-*"
$8C89- GETREC Get record relative file
$8CC1- NUMBYTES Compute number of bytes up to record
$8D06- DIV254 Division of math register by 254 (sector length)
$8D09- DIV120 Division of math register by 120 (record entries in side-sector)
$8D38- CLRMATH1 Clear math register 1
$8D41- MULTTIMES4 Multiply math register 2 four times
$8D44- DOUBLEREG2 Double math register 2
$8D4C- ADD1TO2 Add math register 2 to math register 1
$8D59- INTBUFCHAN Initialize buffer channel table
$8D68- TESTCHANNUM Test channel number in buffer channel table
$8D7D- MAMBUF Manage and assign buffers
$8E3C- FINDFREBUF Look for a free buffer
$8E4D- SETBUFSTATUS Toggle buffer from active to passive and back again
$8E5C- WRTINTERNAL Write bytes over internal channel into buffer
$8E78- WRT2FILE Write byte into file
$8EB1- WRT2BUF Write byte in current buffer
$8EC5- INITIALIZE Initialize command - "I0"
$8FD6- READ2BUF Read sector from diskette to buffer
$8FEA- READNEXT Read in given sector and sector after that
$8FFE- READSEC Read sector from disk
$9002- WRTSEC Write sector to disk
$9027- OPENREAD Open channel for reading
$9042- SCANANDOPEN Search for and open channel
$905F- GETFILETYPE Get current file type
$9069- GETCHANANDBUFF Get channel and matching buffer number
$9071- GETFROMBUF Get byte from current buffer
$909B- GETFROMFILE Get byte from file
$9112- WRT2FILE Write byte in file
$9138- NEXTCHAR Set current buffer pointer to next character
$9145- SWITCHAUTO Switch for autoloader boot on initialize or burst inquiry/query
$9157- SCANWRTCHAN Look for write channel and buffer
$915A- SCANREADCHAN Look for read channel and buffer
$919E- FRECHAN Free up channel
$91CE- FREBUFCHAN Free up buffer and corresponding channel
$9204- SCAN4BUF Look for buffer
$9228- SCAN4FREBUF Look for free buffer
$923E- FREINACT Free up all inactive buffers
$9252- FREEINDEX Free up buffer index
$9262- CLOSECHAN Close channels 0 to 14
$926E- FREEALLCHAN Free up all channels on current drive
$9291- GETBUFF Get a buffer
$92DB- LAYOUTFREE Seek and layout a free channel
$92F4- GETFROMCHAN Get a byte from a channel
$9303- READFROMFILE Read byte from a file
$933A- READFROMREL Get byte from a relative file
$9348- GETNEXT Get next byte from file
$934A- GETCURREN Get current byte from file
$9370- READERRCHAN Read error channel
$9396- ERRPTR Set pointer for error message pointer
$939F- INITERRCHAN Initialize error message channel
$93AA- READNXTSEC Read next sector of a file
$93B0- JOBREAD Take job code for read sector ($80)
$93C1- JOBWRT Take job code for write sector ($90)
$93CF- OPENSEQREAD Open sequential file for reading
$93E0- OPENSEQWRITE Open file for writing
$93E7- WRTNEXTDIR Write next directory sector
$9422- SETBUFPTR1 Set buffer pointer to given position
$9434- CLOSEINTERNAL Close internal channels
$9442- CURRENTBUF Determine current buffer pointer
$9445- SETBUFPTR2 Set buffer pointer (buffer number in .A)
$9450- GETBUFBYTE Read any byte from buffer (.A must contain position of the character)
$9460- CHKTRKSEC Test for valid track and sector numbers then set job code
$94A8- GETTRKSEC Get track and sector of current job from job memory
$94B5- RANGECHK Check current track and sector for allowable range
$94CB- FALSEFORMAT Display error message for false format
$94D3- SENDJOBCURBUF Send job for current buffer to job loop
$94DE- READNWAIT Send job code for read to job loop and wait until execution
$94E2- WRTNWAIT Same as above except write
$94E4- EXECJOB Execute job for current drive (job code in .A)
$94E6- EXECJOB2 Execute job code (job code in .A, buffer number in .X)
$94E8- EXECJOB3 Execute job
$94ED- JOBDONE Wait until job is executed and an error message is prepared
$94F8- SUPERVISE Supervise the current job run
$951A- NXTRKONERR Set head to next track after a read error - search some more
$9564- WAITTILDONE Job code executes until successful or until counter in $30 = 0
$9585- SENDCURRENT Send current track and sector numbers to job loop
$9588- SENDTRKSEC Send track and sector numbers to job loop (buffer in .A)
$959D- DIRECTCALL Direct 1581 controller call
$95AB- CLOSEFILE Close a file entry in the directory
$9678- OPENCMD Take on open command with a secondary address 0 to 14
$97A2- SAVEREPLACE Overwrite corresponding file entry
$984D- OPENREADFILE Open a file for reading
$9890- OPENWRTFILE Open a file for writing
$98AB- SETCMD Set up file type and file operation as command string
$98CC- APPEND2FILE Prepare file for append
$98F7- XMITDIR Transmit directory to computer
$995C- CLOSEAFILE Close a file
$9986- CLOSEALL Close all files
$999F- CLOSE2ND Files declared through secondary address closed
$9A2A- WRTLASTSEC Write the last sector of a file to diskette
$9A72- CLOSEWRT Close directory entry after write operation
$9B0D- OPENREADCHAN Open channel to read file
$9B9B- INITOPENPTR Initialize channel open pointer
$9BC3- OPENWRTCHAN Open channel to write to a file
$9C82- SETRELSS Set up a REL file side sector
$9CCA- WRT2SS Write a byte to current side sector
$9CD3- CHANINFT Channel number in file type flag set (carry = 1) or cleared (carry = 0)
$9CD5- CHFT1 Value combined in file type (bit = 1 is set)
$9CDB- CHFT2 Remove value from the file type flag (bit = 1 is taken out/not set)
$9CE4- CHFT3 Check for set file type flag (the flag value is in .A)
$9CE9- CHFT4 Check to see if job code is set up for writing
$9CF5- TESTFPTR Test file pointer
$9D2E- BUF2DSK Write buffer to disk
$9D3A- SETCHAIN Set chained bytes which point to the next sector
$9D49- GETCHAIN Get linked bytes which point to the next sector
$9D56- SETENDLNK Set sector link bytes as last sector in chain of linked bytes and/or sectors
$9D69- BUFPTRO Set current buffer pointer to zero
$9D79- GETCURTRKSEC Get current track and sector of current job/get chan of current secondary address
$9D7C- GETCUR2 Get track and sector of current job/determine buffer
$9D8E- SENDJOB Give job codes to job loop
$9DCE- NXTLINK Next sectors parameters set by linked bytes that are on hand
$9DDE- BUFCOPY Copy file from one buffer to another buffer. The .A contains the number of bytes to transfer, .YR is the source buffer number, and .XR is the destination buffer number.
$9DFA- CLRBUFF Clear the buffer number in .A with zeros
$9E0B- SETSSNUM Get the number of the current side sector
$9E15- SETBUFPTR Set the buffer pointers $64/$65 to any position in the buffer
$9E23- SETBUFPTR Set the buffer pointer
$9E32- READSIDE Read a side sector into a buffer and set up pointers
$9E56- READSEC Read a sector - the buffer pointer of the current buffer must use the track and sector parameters of the link bytes
$9E75- SETSSPTR Set the side sector pointer
$9E7D- CALCNUMSS Calculate the number of side sectors in a relative file
$9EE4- SSSTATUS Test status of a side sector
$9F11- CURBUF Determine number of the current buffer
$9F1C- CURBUFST Get current buffer status
$9F33- BUFFREORNOT Test whether buffer is free
$9F3E- TWOBUFF Activate buffers for two buffer operations
$9F4C- WRTREC Write a record for a relative file
$9FB6- PTR2LAST Set pointer to last character
$9FBF- PREPRECSEC Prepare sector of the record
$9FFC- WRTRECCHAR Write a character of the record into the buffer
$A033- WRTREC2DATABUF Write record to the data buffer
$A07B- FILREC Fill the rest of a record with empty bytes ($00/0)
$A08D- DATAALT Flag for buffer data altered is set
$A09C- DATANOTALT Flag for buffer data altered is cleared
$A0A6- GETFROMREC Get a byte from a record
$AOE1- OUTPUTAREC Get a record and output it
$AOEC- RECNOTHERE Record not present error
$AOFD- LSTRECCHAR Set pointer to last character of the record
$A143- FINDENDREC Search for the end of a record
$A15C- FINDENDREL Search for the end of a relative file
$A1A1- RECORD Record command routine ("P")
$A20D- READREC Read a record into the buffer
$A235- READRECSEC Read the record sector contained in the buffer
$A273- CHKSEC Check to see if sector is already in the buffer
$A298- ADDREC Enter a new record in the sector
$A2BC- CALCRECPOS Calculate position of a new record in the sector
$A2D6- INSERTNEWREC Insert a new record in a relative file
$A459- NEWSS Prepare a new side sector
$A547- SUPERSS Routines for handling super side sectors
$A602-$A730 ERRMSGS Error messages stored as ASCII text
$A602- E00 Error number "00" text "OK"
$A606- E02 Error number "02" text "Partition Selected"
$A61A- E20ETC Error numbers "20-24,27" text "Read Error"
$A625- E52 Error number "52" text "File Too Large"
$A631- E50 Error number "50" text "Record Not Present"
$A63C- E51 Error number "51" text "Overflow in Record"
$A649- E25 Error number "25" text "Write Error"
$A64D- E26 Error number "26" text "Write Protect On"
$A65A- E29 Error number "29" text "Disk ID Mismatch"
$A66C- E30ETC Error number "30-34" text "Syntax Error"
$A660- E60 Error number "60" text "Write File Open"
$A670- E63 Error number "63" text "File Exists"
$A679- E64 Error number "64" text "File Type Mismatch"
$A681- E65 Error number "65" text "No Block"
$A68A- E66ETC Error number "66-67" text "Illegal Track or Sector"
$A6A3- E61 Error number "61" text "File Not Open"
$A6A7- E39/62 Error number "39,62" text "File Not Found"
$A6AC- E01 Error number "01" text "Files Scratched"
$A6B9- E70 Error number "70" text "No Channel"
$A6C4- E71 Error number "71" text "Directory Error"
$A6C9- E72 Error number "72" text "Disk Full"
$A6D0- E73 Error number "73" text "Copyright CBM DOS V10 1581"
$A6EB- E74 Error number "74" text "Drive Not Ready"
$A6F8- E75 Error number "75" text "Format Error"
$A705- E76 Error number "76" text "Controller Error"
$A716- E77 Error number "77" text "Selected Partition Illegal"
$A731- E79 Error number "79" text "Software by David Siracusa. Hardware by Greg Berlin"
$A75F- E7A Error number "7A" text "Dedicated to My Wife Lisa"
$A779-$A7AD MESSAGE TOKENS
$A779- T09 Error token number "09" text "Error"
$A77F- TOA Error token number "0A" text "Write"
$A785- T03 Error token number "03" text "File"
$A78A- T04 Error token number "04" text "Open"
$A78F- T05 Error token number "05" text "Mismatch"
$A798- T06 Error token number "06" text "Not"
$A79C- T07 Error token number "07" text "Found"
$A7A2- T08 Error token number "08" text "Disk"
$A7A7- TOB Error token number "0B" text "Record"
$A7AE-$A850 DOERR Error message output routine. .A must contain the error number
$A7F1- PREPMSG Prepare error message
$A7F4- ACTMSG Activate error message
$A83E- BIN2BCD Convert a binary number to a BCD number
$A850- BCD2ASCII Convert a BCD number into two ASCII characters
$A862- OKMSG Prepare "00, OK" error message
$A867- ERRTRKSEC Output error message with track and sector numbers = 0
$A86D- ERRBUF Produce error message in buffer (buffer number in .A)
$A8AD- WRTMSG2BUF Write error message in text form to error buffer
$A8F8- WRTASCIIMSG Write ASCII characters into a buffer. Non-ASCII characters interpreted as error numbers
$A90E- ERRMSGFROMROM Get a character of error text from the error message text table in ROM
$A91C- GETBYTETAB Get the current byte from the table
$A926-$A937 AUTOFILENAME Auto execute file name. First character is the "&" command "©RIGHT CBM 86"
$A938- AUTOLOADER CBM auto loader routine
$A94C- DISABLEAUTO Return from the auto loader with the auto loader disabled
$A956- UTILITYCMD Utility loader command "&" find and get utility loader program block
$A9F5- GETUTL Read in a byte from utility loader program block
$AA07- UTLCHKSUM Implement a checksum for utility loader program block
$AA0F- SETERRVEC Set error vectors to routine at $A94C
$AA27- INTERLEAVE "UO>SXM" command set sector format for CBM diskettes (interleave)
$AA2D- READATTEMPT "UO>RX" command set number of read attempts
$AA33- SIEEETIMING "UO>IX" command function set SIEEE timing
$AA39- TESTROM "UO>T" command test ROM checksum aka ROM signature analysis
$AA3C- BURSTUTL Decode and execute drive status and control functions
$AA65- SETDEVICE Set device number via "UO>"+CHR$(X)
$AA83- SYNTAXERR Syntax error message
$AA88- BUSMODE "UOBX" command set serial bus mode (slow or fast)
$AA9A- VERIFYMODE "UO>VX" command sets verify mode selection (on or off)
$AAA8- BURSTMEMCMDS Burst memory read and write commands
$AAC3- BURSTMEMREAD Burst memory-read command
$AAD7- BURSTMEMWRT Burst memory-write command
$AB09- SETVERIFY Check verify mode select value either zero or one
$AB1D- SIGNATURERTN ROM signature analysis routine test ROM via checksum
$ABCF- ATN Routine for controlling the serial bus (serial bus ATN server)
$ACBB- BUS2INPUT Switch 1581 bus to input
$ACD4- BUS2OUTPUT Switch 1581 bus to output
$ACE8- DATALOW Data line set low
$ACF1- DATAHI Data line set high
$ACFA- CLOCKHI Clock line set high
$AD03- CLOCKLOW Clock line set low
$AD0C- SERVAL Values read from serial bus
$AD2F- DELAY1 Cycle delay
$AD34- DELAY2 Cycle delay
$AD3C- UICMD "UI" command bus mode command 1541/1540 speed
$AD5C- TALK Serial bus talk routine
$AEB8- LISTEN Serial bus listen routine
$AED9- RESETBUS Reset bus control register and wait for next command
$AEEA- SETSERIAL Setup fast serial direction as input or output (carry set = SPOUT, carry clear = SPINP)
$AEF2- RAMORROMERR RAM or ROM error (test/checksum)
$AF24- TESTRAM&ROM Test the 1581's RAM and ROM
$AFCA- INITZPG Initialize zero page
$AFDE- PUPMSG Generate DOS power up message
$B0B3- INITLAYOUT Initialize disk layout variables (max track, dir track, etc.)
$BOCF- FORMATNORM Set up a "normal" DOS format for burst format command
$BOFO- IDLE Main idle loop
$B17C- LOADDIR Load directory "$"
$B201- DIREND Directory output ended
$B237- COPYDIR Copy directory entry into current buffer
$B245- GETDIRBYTE Get a byte from the directory
$B262- VALIDATE Validate command routine
$B286- REPAIRBAM All blocks of a file put into BAM allocates blocks according to file link bytes
$B2C7- TSTBLOCKS All blocks following a file are tested for validity
$B2EF- TSTILLEGAL Check for illegal system track or sectors
$B348- NEW/FORMAT Format command (new)
$B390- NEWBAM Create a new BAM (all sectors free)
$B430- CLRBAMBUF Clear BAM buffers
$B44A- NEWBAM2 Produce a new 1581 BAM for validate command
$B546- FRESEC Sector released and marked as free
$B572- SECUSED Mark a sector in the BAM as used. If none are free then a "Disk Full" error is produced.
$B5B4- DRVNOTREADY Produces "Drive Not Ready" error
$B5D8- BAMPTR BAM buffer pointer set to bit for current sector and bit retrieved
$B5EA- ISOLATEMASKS Masks to isolate BAM bits
$B668- SCANFREBLK Look for next free block in the BAM
$B6BF- NXTFREONTRK Look for next free sector on this track
$B6ED- NXTOPTSEC Layout next optimum sector
$B75E- NUMFREALL Check number of free blocks in BAM for every track
$B781- PARTITION Command to create or switch partitions
$B7F7- SETSUBDIR Move through sub directories to root directory
$B888- ILLEGALPAP An illegal partition was selected
$B8D5- FASTLOAD Fastload file over 1581 bus (PRG, SEQ, USR)
$B95F- XFERFAST Fast sector transfer
$B990- XFERLAST Fast load last file sector
$B9D3- SHOWERR Display error messages
$B9DF- LOADERR Load error
$BA06- FNAMESETUP Shift file name to beginning of input buffer
$BA40- SENDFAST Byte sent over 1581 bus for fast load
$BA64- SETUPCRASH This routine sets the RAM error vectors to point to ROM. The vector at $01BA is set to $DFDF which will crash the DOS.
$BA7C- SAVEVEC Store error vectors in save vector area
$BA95- RESTOREVEC Retrieve error vectors from save vector area
$BAB3- BURSTREAD Burst read track and sector handler
$BAD6- BURSTREAD2 Burst read number of sectors handling routine
$BAF7- IDMISMATCH Disk ID mismatch error
$BAF9- DRVNOTREADY Drive not ready error
$BAFC- COMMSTERROUT Combine command status flag and output with error
$BBO2- EVENTERR Eventual error output (otherwise return)
$BBOA- DOERRINXR Output error message number (number in .XR)
$BB11- BURSTREADCMD Burst read command
$BC01- BURSTWRTCMD Burst write command
$BCB2- INQUIREDISK Burst command inquire disk
$BD06- MOREVALUES Values $00, $10, $0A, $05
$BDOA- DRVNOTREADY2 Drive not ready error
$BD12- BURSTFORMAT Burst format command
$BD4A-$BD5D FORMATSTRING "NO:COPYRIGHT CBM,86" in ASCII (used to do 1581 default burst format)
$BD5E- FORMATSTANDARD Format using standard DOS format via burst format command
$BD7C- CUSTOMFORMAT Custom format via burst format command/setup format variables
$BDF8- MOREVALUES2 Values $0E, $16, $26, $44
$BDFC- SYNTAXERR Syntax error
$BE06- QUERYDISK Burst query disk format command
$BE79- SENDQUERY Send out the results of the query disk format
$BEBB- INQUIRESTATUS Burst inquire status command
$BEF1- SETSTATUS Set command status byte
$BEF8- SYNTAXERR Syntax error
$BF02- DUMPCACHE Burst command dump track cache buffer
$BF66- PREPERROUT Prepare error byte output
$BF7F- PREPDATA Values $00, $10, $20, $30
$BF86- SENDBYTEFAST Send byte over serial bus using burst protocol
$BFE3- DUMPTRK Dump a track from cache buffer to disk
$C097- SMTOGRT Determine smallest and greatest sector numbers
$C0BE- JMPCTRLER Jump to the disk controller routine
$C163-$C183 CTRLBYTES Drive controller bytes/codes containing 8 flag bits for each of the 33 available jobs
BIT TEXT
7 Translate track and sector
6 Cache out
5 Start motor
4 Wait until motor is up to speed
3 Seek track
2 Check cache dirty
1 Step required
0 Side select
$C184-$C1A4 CTRLBYTES2 Drive controller bytes/codes containing 3 more flag bits for each of the 33 available jobs
BIT TEXT
7 By-pass track and sector translation
6 Logical/physical flag
5 Read/write operator
$C1A5-$C1E7 JOBCMDS Job queue vectors
NAME COMMAND NUMBER ADDR
$C1A5- READ DV $80 $C900
$C1A7- RESET DV $82 $C2E7
$C1A9- MOTON DV $84 $C390
$C1AB- MOTOFF DV $86 $C393
$C1AD- MOTONI DV $88 $C396
$C1AF- MOTOFFI DV $8A $C3A9
$C1B1- SEEK DV $8C $C3AF
$C1B3- FORMAT DV $8E $C3BB
$C1B5- WRSTD DV $90 $C900
$C1B7- DISKIN DV $92 $C6D7
$C1B9- LEDACTON DV $94 $C546
$C1BB- LEDACTOFF DV $96 $C54F
$C1BD- ERRLEDON DV $98 $C558
$C1BF- ERRLEDOFF DV $9A $C561
$C1C1- SIDE DV $9C $C56A
$C1C3- BUFMOVE DV $9E $C589
$C1C5- WRTVER DV $A0 $C9E1
$C1C7- TRKWRT DV $A2 $C5AC
$C1C9- SP READ $A4 $C800
$C1CB- SP WRITE $A6 $C700
$C1CD- PSEEK DV $A8 $C6D7
$C1CF- TREAD DV $AA $CB09
$C1D1- TWRT DV $AC $CAE4
$C1D3- SEEKHD DV $B0 $CB0F
$C1D5- TPREAD DV $B2 $CB26
$C1D7- TPWRT DV $B4 $CB26
$C1D9- DETWP DV $B6 $CB35
$C1DB- SEEKPHD DV $B8 $C900
$C1DD- RESTORE DV $C0 $C900
$C1DF- JUMPC DV $D0 $C900
$C1E1- EXBUF DV $E0 $C900
$C1E3- FORMATDK DV $F0 $CB76
$C1E5- CTRLERR_DV None $CB85
$C1E7-$C2E6 DATABYTES Eight data bytes for each of the controller commands listed above/job index for 33 jobs
$C2E7- JOBRESET Reset command - resets the disk controller and variables ($82)
$C30C- SETWDCMDS Restore default WD177X command table
$C390- JOBMOTON Motor on command - turns on the drive spindle motor ($84)
$C393- JOBMOTOFF Motor off command - turns off the drive spindle motor ($86)
$C396- JOBMOTONI Motor on immediately ($88)
$C3A9- JOBMOTOFFI Motor off immediately ($8A)
$C3AF- JOBSEEKTRK Seeks track command ($8C)
$C3BB- JOBFORMATTRK Format one physical track ($8E)
$C3EC- WRTINDEX Write track index/save after index hole
$C52C- STOPITRKFORMAT Terminate physical track format
$C546- JOBACTLEDON Turn on disk activity LED ($94)
$C54F- JOBACTLEDOFF Turn off disk activity LED ($96)
$C558- JOBERRLEDON Turn on disk error LED ($98)
$C561- JOBERRLEDOFF Turn off disk error LED ($9A)
$C56A- JOBSETSIDE Set up side select electronics to the value in the sides table ($9C)
$C589- JOBMOVEDATA Move data between the job queue buffers and track cache ($9E)
$C5AC- JOBDUMPCACHE Dumps track cache to disk (if "dirty") ($A2)
$C5AF-$C5FF DUMPOLD Dump old track cache data
$C600- DUALJOB Checks to see if a disk is in the drive and seeks a preset physical track ($92 and $A8)
$C6D7- JOBWRTPHYS Write a physical sector directly ($A6)
$C6DD-$C6FF JOBREADPHYS Read a physical sector directly ($A4)
$C700- MULTIJOB Executes controller commands for:
- Reading sectors ($80)
- Writing sectors ($90)
- Seeking headers ($B8)
- Bump to track 0 ($C0)
- Execute program ($D0)
- Execute buffer ($E0)
$C9E1- JOBVERCACHE Verify cache data against a logical track's data ($A0)
$C9F6-$C9FF FORMATVER Not used by DOS
$CA00- JOBWRTLOG Write a logical address without transfer from job queue buffer ($AC)
$CAE4- JOBREADLOG Read a logical address without transfer from job queue buffer ($AA)
$CB09- JOBREADHDR Read header data from first disk sector found ($B0)
$CB0F- JOBWRTPHYS Write a physical address without transfer from job queue buffer ($B2)
$CB26- JOBREADPHYS Read a physical address without transfer from job queue buffer ($B4)
$CB26- JOBWRTPROTECT Checks to see if the current disk is write protected ($00 = no, $08 = yes) ($B6)
$CB35- JOBFORMATDSK Format the disk with the default physical format ($F0)
$CB76- MOTORON Spindle motor on
$CBB1- MOTOROFF Spindle motor off
$CBBA- ACTLEDOFF Green activity LED off
$CBC3- ACTLEDON Green activity LED on
$CBCC- WDCMDWAIT Wait until current command on WD177X is done
$DA63- CHKSUM Routine to do the cyclic redundancy checksum
$DAFD- IRQRTN 1581 IRQ routine
$DB40- $BAFA - Drive not ready
$DB42- $BD12 - Burst format diskette
$DB44- $BD12 - Burst format diskette
$DB46- $BDFC - Syntax error #31
$DB48- $BDFC - Syntax error #31
$DB4A- $BE06 - Burst determine sector sequence
$DB4C- $BAFA - Drive not ready
$DB4E- $BEBB - Burst inquire status
$DB50- $BAFA - Drive not ready
$DB52- $BEF8 - Syntax error #31
$DB54- $BEF8 - Syntax error #31
$DB56- $BB11 - Burst read sector
$DB58- $BAFA - Drive not ready
$DB5A- $BC01 - Burst write sector
$DB5C- $BBF9 - Drive not ready
$DB5E- $BCB2 - Burst read sector header
$DB60- $BAFA - Drive not ready
$DB62- $BD12 - Burst format diskette
$DB64- $BD12 - Burst format diskette
$DB66- $89CB - RTS instruction no function
$DB68- $89CB - RTS instruction no function
$DB6A- $BE06 - Burst determine sector sequence
$DB6C- $BAFA - Drive not ready
$DB6E- $BF02 - Read next sector header
$DB70- $BF02 - Read next sector header
$DB72- $AA3C - Execute 1581 status command
$DB74- $B8D5 - Fast load a file over the 1581 bus
$DB76- BAMUSE Number of bytes in BAM for each disk track ($06/6)
$DB77- NAMEOFFSET Disk name offset in BAM ($04/4)
$DB78-$DB83 DOSCMDTABLE Table of DOS commands V, I, /, M, B, U, P, &, C, R, S, N
$DB84-$DB8F DOSCMDLO DOS command vectors low bytes
$DB90-$DB9B DOSCMDHI DOS command vectors high bytes
V - $FF09
I - $FF0C
/ - $FF0F
M - $FF12
B - $FF15
U - $FF18
P - $FF1B
& - $FF1E
C - $FF21
R - $FF24
S - $FF27
N - $FF2A
$DB9C-$DBA0 CMDIMAGES Structure images for DOS commands
$51 - Disk copy
$DD - Rename a file (not parsed)
$1C - Scratch a file (not parsed)
$9E - Format a disk (not parsed)
$1C - Load a file
$DBA1-$DBA4 FILEMODE Mode table
$DBA1- $52 R = Read mode
$DBA2- $57 W = Write mode
$DBA3- $41 A = Append mode
$DBA4- $4D M = Modify mode (read an improperly closed file)
$DBA5-$DBBC FILETYPE File type table
$DBA5-$DBAA FTO First byte of file type (D, S, P, U, L, C) for file operations
$DBAB-$DBBO FT1 First byte of file type (D, S, P, U, R, C)
$DBB1-$DBB6 FT2 Second byte of file type (E, E, R, S, E, B)
$DBB7-$DBBC FT3 Third byte of file type (L, Q, G, R, L, M)
Valid file types are: DEL, SEQ, PRG, USR, REL, CBM
$DBBD-$DBC1 ERRFLAG Error flag variables for use by BIT commands
$DBC2-$DBC6 ERROFFSETS Offsets for error recovery
$DBC7- SPINPATCH Spin routine patch to clear the shift register
$DBE0- SPOUTPATCH Spinout routine patch to clear the shift register
$DBEE- RELEASESEC Release sectors in BAM after scratching a file/used after scratching a partition file to update the disk BAM
$DBF4- SEEKNCHK Seek a header and check disk format
$DC01-$DC37 CRMSG Commodore DOS copyright message "©1987 Commodore Electronics Ltd., All Rights Reserved"
$DC38-$FEFF Not used by the DOS
$FF00- IDLE Execute the JIDLE routine via an indirect jump to the vector at $0190. JIDLE at $BOFO
$FF03- IRQ Execute the JIRQ routine via an indirect jump to the vector at $0192. JIRQ at $DAF0
$FF06- NMI Execute the JNMI routine via an indirect jump to the vector at $0194. JNMI at $AFCA
$FF09- VERDIR Execute the JVERDIR routine via an indirect jump to the vector at $0196. JVERDIR at $B262
$FF0C- INTDRV Execute the JINTDRV routine via an indirect jump to the vector at $0198. JINTDRV at $8EC5
$FF0F- PART Execute the JPART routine via an indirect jump to the vector at $019A. JPART at $B781
$FF12- MEM Execute the JMEM routine via an indirect jump to the vector at $019C. JMEM at $892F
$FF15- BLOCK Execute the JBLOCK routine via an indirect jump to the vector at $019E. JBLOCK at $8A5D
$FF18- USERVEC Execute the JUSER routine via an indirect jump to the vector at $01A0. JUSER at $898F
$FF1B- RECORD Execute the JRECORD routine via an indirect jump to the vector at $01A2. JRECORD at $A1A1
$FF1E- UTLODR Execute the JUTLODR routine via an indirect jump to the vector at $01A4. JUTLODR at $A956
$FF21- DSKCOPY Execute the JDSKCPY routine via an indirect jump to the vector at $01A6. JDSKCPY at $876E
$FF24- RENAMEVEC Execute the JRENAME routine via an indirect jump to the vector at $01A8. JRENAME at $88C5
$FF27- SCRTCH Execute the JSCRTCH routine via an indirect jump to the vector at $01AA. JSCRTCH at $8688
$FF2A- NEW Execute the JNEW routine via an indirect jump to the vector at $01AC. JNEW at $B348
$FF2D- ERROR Execute the ERROR routine via an indirect jump to the vector at $01AE. ERROR at $A7AE
$FF30- ATNSERV Execute the JATNSRV routine via an indirect jump to the vector at $01B0. JATNSRV at $ABCF
$FF33- TALK Execute the JTALK routine via an indirect jump to the vector at $01B2. JTALK at $AD5C
$FF36- LISTEN Execute the JLISTEN routine via an indirect jump to the vector at $01B4. JLISTEN at $AEB8
$FF39- LCC Execute the JLCC routine via an indirect jump to the vector at $01B6. JLCC at $C0BE
$FF3C- TRANSTS Execute the JTRANSTS routine via an indirect jump to the vector at $01B8. JTRANSTS at $CEDC
$FF3F- CMDERR Execute the CMDERR routine via an indirect jump to the vector at $01BA. CMDERR at $A7F1
$FF42-$FF53 STROBECTRLER Not used by DOS
$FF54- CBMBOOT Execute the JSTROBE_CTRLER routine via a direct jump to $FF54. JSTROBE_CTRLER at $959D
$FF57- CBMBOOTRTN Execute the JCBMBOOT routine via a direct jump to $FF57. JCBMBOOT at $A938
$FF5A- SIGNATURE Execute the JCBMBOOTRTN routine via a direct jump to $FF5A. JCBMBOOTRTN at $A94C
$FF5D- DEJAVU Execute the JSIGNATURE routine via a direct jump to $FF5D. JSIGNATURE at $AB1D
$FF60- SPINOUT Execute the JDEJAVU routine via a direct jump to $FF60. JDEJAVU at $9145
$FF63- ALLOCBUFF Execute the JSPINOUT routine via a direct jump to $FF63. JSPINOUT at $AEEA
$FF66- TESTTRKSEC Execute the JALLOCBUFF routine via a direct jump to $FF66. JALLOCBUFF at $8C5C
$FF69- TESTTRKSEC Execute the JTESTTRKSEC routine via a direct jump to $FF69. JTESTTRKSEC at $9460
$FF6C- DUMPTRK Execute the dump track to disk routine at $BFE3
$FF6F-$FF74 Not used by DOS
$FF75-$FFA0 RAMVECDATA Vectors for RAM jump table at $0190 (defaults see $FF00-$FF3F above)
$FFA1-$FFAC Not used by DOS
$FFAD- INITJMP Routine to initialize RAM jump table at $0190
$FFC8-$FFE9 Not used by DOS
$FFEA-$FFFF USER command jump table
$FFEA- USER1 "U1/A" vector points to $8B9A
$FFEC- USER2 "U2/B" vector points to $8BD7
$FFEE- USER3 "U3/C" vector points to $0500
$FFF0- USER4 "U4/D" vector points to $0503
$FFF2- USER5 "U5/E" vector points to $0506
$FFF4- USER6 "U6/F" vector points to $0509
$FFF6- USER7 "U7/G" vector points to $050C
$FFF8- USER8 "U8/H" vector points to $050F
$FFFA- NNMI "U9/I" NNMI routine points to $AD3C
$FFFC- DSKINT "U:/J" DSKINT routine points to $AF24
$FFFE- SYSIRQ "UK" SYSIRQ routine points to $FF03 (sending a "UK" command crashes the drive)
Note that the source for the 1581 ROM is also available on Zimmer's archive. But that doesn't tell us the addresses in the binary of the ROM.
So from that I, have massaged all of those into the iecwaveform.c tool I made that helps to parse the huge logs that the VHDL unit tests generate. That's all in commit https://github.com/mega65/mega65-core/commit/dd805ef34326cd9dfc1ec7960aa2edc796bbddc1.
This means I can now take a look at what is going on:
DEBUG: Fetching IEC data trace...
EC: Allowing time for 1581 to boot
Resetting protocol timing to default
$AF24 1581: TESTRAM&ROM Test the 1581's RAM and ROM
$AFCA 1581: INITZPG Initialize zero page
$FFAD 1581: INITJMP Routine to initialize RAM jump table at $0190
$8D59 1581: INTBUFCHAN Initialize buffer channel table
$959D 1581: DIRECTCALL Direct 1581 controller call
$959D 1581: DIRECTCALL Direct 1581 controller call
$A938 1581: AUTOLOADER CBM auto loader routine
$AA0F 1581: SETERRVEC Set error vectors to routine at $A94C
$BA7C 1581: SAVEVEC Store error vectors in save vector area
$A956 1581: UTILITYCMD Utility loader command "&" find and get utility loader program block
$84AE 1581: INTDSK Initialize diskette
$B0CF 1581: FORMATNORM Set up a "normal" DOS format for burst format command
$B0B3 1581: INITLAYOUT Initialize disk layout variables (max track, dir track, etc.)
$9204 1581: SCAN4BUF Look for buffer
$9228 1581: SCAN4FREBUF Look for free buffer
$9588 1581: SENDTRKSEC Send track and sector numbers to job loop (buffer in .A)
$94E4 1581: EXECJOB Execute job for current drive (job code in .A)
$94E6 1581: EXECJOB2 Execute job code (job code in .A, buffer number in .X)
$94E8 1581: EXECJOB3 Execute job
$94D3 1581: SENDJOBCURBUF Send job for current buffer to job loop
$959D 1581: DIRECTCALL Direct 1581 controller call
$94ED 1581: JOBDONE Wait until job is executed and an error message is prepared
$94F8 1581: SUPERVISE Supervise the current job run
$959D 1581: DIRECTCALL Direct 1581 controller call
$926E 1581: FREEALLCHAN Free up all channels on current drive
$9204 1581: SCAN4BUF Look for buffer
$9228 1581: SCAN4FREBUF Look for free buffer
$9588 1581: SENDTRKSEC Send track and sector numbers to job loop (buffer in .A)
$94E4 1581: EXECJOB Execute job for current drive (job code in .A)
$94E6 1581: EXECJOB2 Execute job code (job code in .A, buffer number in .X)
$94E8 1581: EXECJOB3 Execute job
$94D3 1581: SENDJOBCURBUF Send job for current buffer to job loop
$959D 1581: DIRECTCALL Direct 1581 controller call
$94ED 1581: JOBDONE Wait until job is executed and an error message is prepared
$94F8 1581: SUPERVISE Supervise the current job run
$959D 1581: DIRECTCALL Direct 1581 controller call
$94DE 1581: READNWAIT Send job code for read to job loop and wait until execution
$94E4 1581: EXECJOB Execute job for current drive (job code in .A)
$94E6 1581: EXECJOB2 Execute job code (job code in .A, buffer number in .X)
$94E8 1581: EXECJOB3 Execute job
$94D3 1581: SENDJOBCURBUF Send job for current buffer to job loop
$959D 1581: DIRECTCALL Direct 1581 controller call
$94ED 1581: JOBDONE Wait until job is executed and an error message is prepared
$94F8 1581: SUPERVISE Supervise the current job run
$9588 1581: SENDTRKSEC Send track and sector numbers to job loop (buffer in .A)
$94DE 1581: READNWAIT Send job code for read to job loop and wait until execution
$94E4 1581: EXECJOB Execute job for current drive (job code in .A)
$94E6 1581: EXECJOB2 Execute job code (job code in .A, buffer number in .X)
$94E8 1581: EXECJOB3 Execute job
$94D3 1581: SENDJOBCURBUF Send job for current buffer to job loop
$959D 1581: DIRECTCALL Direct 1581 controller call
$94ED 1581: JOBDONE Wait until job is executed and an error message is prepared
$94F8 1581: SUPERVISE Supervise the current job run
$9588 1581: SENDTRKSEC Send track and sector numbers to job loop (buffer in .A)
$94DE 1581: READNWAIT Send job code for read to job loop and wait until execution
$94E4 1581: EXECJOB Execute job for current drive (job code in .A)
$94E6 1581: EXECJOB2 Execute job code (job code in .A, buffer number in .X)
$94E8 1581: EXECJOB3 Execute job
$94D3 1581: SENDJOBCURBUF Send job for current buffer to job loop
$959D 1581: DIRECTCALL Direct 1581 controller call
$94ED 1581: JOBDONE Wait until job is executed and an error message is prepared
$94F8 1581: SUPERVISE Supervise the current job run
$81FD 1581: GETDRV Get drive number and set into file table
$8224 1581: GETDRVFROMCMD Get drive number from command string
$82B9 1581: FINDFILE Look for file entry in the directory
$82A2 1581: INTDRVFNAME Initialize drive given in filename
$84AE 1581: INTDSK Initialize diskette
$FF3F 1581: CMDERR Execute the CMDERR routine via an indirect jump to the vector at $01BA. CMDERR at $A7F1
$A94C 1581: DISABLEAUTO Return from the auto loader with the auto loader disabled
$BA95 1581: RESTOREVEC Retrieve error vectors from save vector area
$AFDE 1581: PUPMSG Generate DOS power up message
$A867 1581: ERRTRKSEC Output error message with track and sector numbers = 0
$A86D 1581: ERRBUF Produce error message in buffer (buffer number in .A)
$A8AD 1581: WRTMSG2BUF Write error message in text form to error buffer
$A91C 1581: GETBYTETAB Get the current byte from the table
$A91C 1581: GETBYTETAB Get the current byte from the table
...
ote): IEC: Commencing sending DEVICE 8 LISTEN ($2B) byte under ATN
ote): IEC: atn_tx_byte($28)
: IEC: REG: register write: $28 -> reg $9
: IEC: REG: register write: $30 -> reg $8
IEC: Command Dispatch: $30
iec_state = 100
iec_state = 120
iec_state = 121
$A91C 1581: GETBYTETAB Get the current byte from the table
...
$A91C 1581: GETBYTETAB Get the current byte from the table
iec_state = 122
iec_state = 123
: IEC: Attention timeout: No devices on bus
So this is all quite interesting to see how the 1581 boots up. I find it odd, that it gets stuck at $A91C, rather than in the idle loop.
Ah, it just hasn't finished booting. If I allow more time for it to boot, it does get further:
$A91C 1581: GETBYTETAB Get the current byte from the table
$A90E 1581: ERRMSGFROMROM Get a character of error text from the error message text table in ROM
...
$A90E 1581: ERRMSGFROMROM Get a character of error text from the error message text table in ROM
$A8F8 1581: WRTASCIIMSG Write ASCII characters into a buffer. Non-ASCII characters interpreted as error numbers
$A83E 1581: BIN2BCD Convert a binary number to a BCD number
$A850 1581: BCD2ASCII Convert a BCD number into two ASCII characters
$A83E 1581: BIN2BCD Convert a binary number to a BCD number
$A850 1581: BCD2ASCII Convert a BCD number into two ASCII characters
$ACBB 1581: BUS2INPUT Switch 1581 bus to input
$DBC7 1581: SPINPATCH Spin routine patch to clear the shift register
$FF00 1581: IDLE Execute the JIDLE routine via an indirect jump to the vector at $0190. JIDLE at $B0F0
$B0F0 1581: IDLE Main idle loop
So, it does get to the idle loop, which is good.
But from there, it doesn't do the right thing. I think it might be thinking that ATN is pulled low:
$FF30 1581: ATNSERV Execute the JATNSRV routine via an indirect jump to the vector at $01B0. JATNSRV at $ABCF
$ABCF 1581: ATN Routine for controlling the serial bus (serial bus ATN server)
$ACBB 1581: BUS2INPUT Switch 1581 bus to input
$DBC7 1581: SPINPATCH Spin routine patch to clear the shift register
$AD03 1581: CLOCKLOW Clock line set low
$ACF1 1581: DATAHI Data line set high
$ACBB 1581: BUS2INPUT Switch 1581 bus to input
$DBC7 1581: SPINPATCH Spin routine patch to clear the shift register
$FF00 1581: IDLE Execute the JIDLE routine via an indirect jump to the vector at $0190. JIDLE at $B0F0
$B0F0 1581: IDLE Main idle loop
$FF30 1581: ATNSERV Execute the JATNSRV routine via an indirect jump to the vector at $01B0. JATNSRV at $ABCF
$ABCF 1581: ATN Routine for controlling the serial bus (serial bus ATN server)
$ACBB 1581: BUS2INPUT Switch 1581 bus to input
$DBC7 1581: SPINPATCH Spin routine patch to clear the shift register
...
It just keeps looping through this kind of sequence. So let's see if we can confirm this theory. Here is the part of the IDLE loop that checks whether it should jump to $FF30, which is the vector to enter the ATN service routine:
B0F0 78 SEI
B0F1 A9 10 LDA #$10
B0F3 8D 01 40 STA $4001
B0F6 58 CLI
B0F7 A5 7B LDA $7B
B0F9 F0 0A BEQ LB105
B0FB A9 00 LDA #$00
B0FD 85 7B STA $7B
B0FF 20 04 80 JSR L8004
B102 20 BB AC JSR LACBB
B105 58 LB105 CLI
B106 A9 01 LDA #$01
B108 24 76 BIT $76
B10A F0 03 BEQ LB10F
B10C 4C 30 FF JMP LFF30 ; Jump to the ATN service routine
B10F A5 87 LB10F LDA $87
This seems to correspond to the following in the 1581 ROM source:
idle sei
lda #atna
sta pb ; release serial bus
cli {ensh}; wait for something to do..
lda cmdwat ; any commands pending ?
beq 1$ ; no command waiting
lda #0
sta cmdwat
jsr parsxq ; parse and execute command
jsr spinp ; fast serial input
1$
cli
lda #bit0
bit fsflag ; any attentions pending ?
beq 2$
jmp jatnsrv ; service the attention
2$ lda dirty ; dirty?
From this we can figure out ath $76 contains the fsflag value, bit 0 of which indicates that there are pending ATN calls. I have a vague recollection that this gets set by an IRQ or an NMI that is triggered on the ATN line.
Debugging this is a little tricky in the unit tests, because the simple_cpu6502.vhdl instruction logger doesn't show the target address of an instruction, because it reports the opcode while it is decoding the instruction, i.e., before the argument is even fetched. To work around this, I'll add a trace into internal1581.vhdl that checks for writes to $76:
if address = x"0076" and ram_write_enable='1' then
report "1581: FSFLAGS writing $" & to_hexstring(wdata);
end if;
Bit 0 never gets set in FSFLAGS. Hmm, it looks like the BIT command is actually the problem: The Z flag should be set when using BIT $76, when A=$01 and $76 contains $08 or $0A, which are the only values that get written to it.
Yup, the BIT instruction in that CPU does this:
when I_BIT => set_nz(data_i); flag_v <= data_i(6);
But the BIT instruction is defined for the 6502 like this:
- BIT
-
Test Bits in Memory with Accumulator
bits 7 and 6 of operand are transfered to bit 7 and 6 of SR (N,V);
the zero-flag is set according to the result of the operand AND
the accumulator (set, if the result is zero, unset otherwise).
This allows a quick check of a few bits at once without affecting
any of the registers, other than the status register (SR).
A AND M -> Z, M7 -> N, M6 -> V
So we need to check for A & M and set Z based on that. Like this:
when I_BIT => flag_n <= data_i(7);
flag_v <= data_i(6);
if (reg_a and data_i) = to_unsigned(0,8) then
flag_z <= '1';
else
flag_z <= '0';
end if;
Okay, that gets the 1581 past that, but we still see DEVICE NOT PRESENT. It looks like the 4541 hardware IEC controller is timing out waiting for the 1581 to respond to the ATN call way too fast.
Digging through, the cause of this is that when I refactored all the bus timing stuff in the 4541 to be run-time changeable, I didn't make it so that the default timing values would be loaded until the /RESET line on it goes low. For real hardware, this is not a problem. But the VHDL unit tests don't do a reset sequence unless we make them. So I have added code to do this, and also fix that the 1581 CPU was running at 1MHz instead of 2MHz:
diff --git a/src/vhdl/tb_iec_serial_1581.vhdl b/src/vhdl/tb_iec_serial_1581.vhdl
index 21c9da4a3..b25b131ff 100644
--- a/src/vhdl/tb_iec_serial_1581.vhdl
+++ b/src/vhdl/tb_iec_serial_1581.vhdl
@@ -72,6 +72,9 @@ architecture test_arch of tb_iec_serial is
signal d81_address : unsigned(19 downto 0);
signal d81_rdata : unsigned(7 downto 0);
+
+ signal host_reset : std_logic := '0';
+ signal host_reset_countdown : integer range 0 to 7 := 7;
begin
@@ -98,6 +101,7 @@ begin
port map (
clock => clock41,
clock81 => pixelclock,
+ reset_in => host_reset,
fastio_addr => fastio_addr,
fastio_write => fastio_write,
@@ -226,8 +230,16 @@ begin
drive_cycle_countdown <= drive_cycle_countdown - 1;
f1581_cycle_strobe <= '0';
else
- drive_cycle_countdown <= 40;
+ drive_cycle_countdown <= 20;
f1581_cycle_strobe <= '1';
+ -- Release /RESET line on 4541 IEC host controller after a few cycles.
+ -- This is necessary as host timing values are only loaded by the
+ -- 4541 when reset is low.
+ if host_reset_countdown > 0 then
+ host_reset_countdown <= host_reset_countdown - 1;
+ else
+ host_reset <= '1';
+ end if;
end if;
end if;
end if;
Unfortunately it hasn't fixed the DEVICE NOT PRESENT error yet.
The 1581 IRQ handler gets called, but it doesn't set the ATN flag. This is done by looking for bit 4 of $400D, which is the edge detection bit of the FLAG pin. The ATN line on the 1581 is connected to this, as well as to bit 7 of port B. I'll check if the bit gets set in the CIA we are using in the 1581 implentation. Note that we are using a 6526 rather than an 8520, but the differences don't matter for this function (the differences are only in the time of day clock, and I assume, the maximum clock speed, as the 8520 had to work in a 7MHz Amiga).
Yes, the CIA sees the FLAG line go low, so that's good. But it looks like the FLAG bit gets cleared before the 1581's CPU reads the value.
Now, there is some funny stuff related to this in our CIA implementation, because the CIA is clocked at 41MHz, not 1MHz, and has 1 wait state. But for our 1581, the CPU runs at 2MHz, so we effectively have 20 wait states to delay clearing the bits, instead of a single cycle (the CPU in the MEGA65 internally runs at 41MHz the whole time, and just pauses between instructions if a slower clock speed has been requested).
To deal with this, I've forked the 6526 CIA and increased the time before clearing the ISR when reading in this commit. That now gets a non-zero value being read in the ISR, but the DEVICE NOT PRESENT still persists.
The FSFLAGS byte now gets bit 0 set, so that whole problem is solved. So why do we still get DEVICE NOT PRESENT? The whole sequence on the 1581 side now seems to be doing what it should:
$B0F0 1581: IDLE Main idle loop
ote): IEC: Commencing sending DEVICE 8 LISTEN ($2B) byte under ATN
ote): IEC: atn_tx_byte($28)
: IEC: REG: register write: $28 -> reg $9
: IEC: REG: register write: $30 -> reg $8
IEC: Command Dispatch: $30
iec_state = 100
iec_state = 120
$FF03 1581: IRQ Execute the JIRQ routine via an indirect jump to the vector at $0192. JIRQ at $DAF0
$DAFD 1581: IRQRTN 1581 IRQ routine
iec_state = 121
$FF30 1581: ATNSERV Execute the JATNSRV routine via an indirect jump to the vector at $01B0. JATNSRV at $ABCF
$ABCF 1581: ATN Routine for controlling the serial bus (serial bus ATN server)
$ACBB 1581: BUS2INPUT Switch 1581 bus to input
$DBC7 1581: SPINPATCH Spin routine patch to clear the shift register
$AD03 1581: CLOCKLOW Clock line set low
$ACF1 1581: DATAHI Data line set high
iec_state = 122
iec_state = 123
: IEC: Attention timeout: No devices on bus
So why doesn't the 4541 see that there is a device? It looks like the 1581 doesn't actually set the CLK line low. The routine at $DC04 that is supposed to do this is called, but the bit it modifies was already clear, and yet the CLK line wasn't pulled low. This is bit 3 on port B of the CIA.
The logic in internal1581.vhdl for driving the IEC DATA and CLK line were a bit messed up, which I have fixed now. That now gets it a little further -- but not because it should. Rather, the DATA line is now held at 0V by the 1581 the whole time.
Now it gets a bit further, with the 1581 pulling CLK and DATA low as it should. But it then fails to release DATA to 5V to indicate ready.
There is still something wrong with the DATA line control, which is messing things up, I think. My reasoning here is that the DATA line is pulled low by the 1581 on reset, and then stays low. Actually, it might be a bit more subtle than that: The 1581 has dedicated "ATN acknowledge" circuitry, that I don't think I am modelling right now. Here is the 1581 IEC serial circuit, with the CIA on the left, and the ports on the right. You might need to click on it to see it at full size:
Now, here's the same diagram, with some crayon lines on it showing the parts that matter for us:
The red line is the normal output path for the DATA line. Like on the 1541, it goes through an open-collector inverting buffer. That means that 5V on the CIA pin will result in 0V on the DATA line, but that 0V on the CIA pin will leave the DATA pin floating high at 5V, but not driven at 5V, so that something else can pull it down to 0V if it wants.
The green line is the input path for reading the DATA line. Nothing surprising there.
The purple line is the ATN line coming in to the CIA. And this is where it starts getting interesting: It also goes into the NAND gate of U7. A NAND gate will output 0V if both inputs are 5V. So if either the ATN line or the ATN_ACK line are 0V, then it's output will be 5V, but if both of them are 5V (i.e., ATN is not being asserted, and the ATN_ACK line is set high to 5V), then it will output 0V. That signal then goes through an open-collector non-inverting buffer, so that if-and-only-if ATN = 5V and ATN_ACK = 5V, then the DATA line on the port will be pulled to 0V.
The 1581 ROM does indeed use this: In the ATN routine at $AC60 the ATN_ACK line is set, and in the routine at $ABF0 it is cleared. So I'll implement that. Also, I noticed another problem with the CIA plumbing to the DATA and CLK lines, which I have fixed.
With those two fixed, the 1581 now responds to the ATN request, and thus gets quite a bit further.
The unit test now fails when checking that the 1581 correctly received the byte sent. This is done in the unit test by checking for writes to address $85 in the 1581's memory. I'm suspecting that that is a copy-paste from the simulated 1541. Time to dive into the 1581 ROM source and disassembly again, to find out the correct address to check.
I'm thinking to go back over the other unit tests that were pretty much all failing, to try to work out the remaining problems in a more incremental manner. What is encouraging, is that most 1581 related tests now pass:
==== Summary ====================================================================================================
pass lib.tb_iec_serial.Simulated 1581 runs (23.2 seconds)
pass lib.tb_iec_serial.Debug RAM can be read (3.5 seconds)
pass lib.tb_iec_serial.ATN Sequence with VHDL 1581 device succeeds without JiffyDOS or C128 FAST (21.6 seconds)
pass lib.tb_iec_serial.ATN Sequence with VHDL 1581 device succeeds (20.9 seconds)
pass lib.tb_iec_serial.Read Error Channel (15) of VHDL 1581 succeeds without JiffyDOS or C128 FAST (29.3 seconds)
pass lib.tb_iec_serial.Read from Error Channel (15) of VHDL 1581 device succeeds (31.1 seconds)
pass lib.tb_iec_serial.Read from Error Channel (15) of VHDL 1581 with SRQ low (30.0 seconds)
pass lib.tb_iec_serial.Read from Error Channel (15) of VHDL 1581 with delay before turn-around (35.9 seconds)
pass lib.tb_iec_serial.Drive Byte RX Bit Order and Polarity (35.8 seconds)
pass lib.tb_iec_serial.Write to and read from Command Channel (15) of VHDL 1581 device suceeds (64.8 seconds)
fail lib.tb_iec_serial.ATN Sequence with no device gets DEVICE NOT FOUND (3.7 seconds)
fail lib.tb_iec_serial.ATN Sequence with dummy device succeeds (6.1 seconds)
fail lib.tb_iec_serial.Load directory (33.2 seconds)
=================================================================================================================
So the first failing one is the DEVICE NOT PRESENT case with no device. That's really weird. So let's get to the bottom of that.
That one is failing because the 4541 doesn't report ready after some action. But it's hard to see exactly what is going on, because this test holds the simulated 1581 under reset, which generates a bunch of messages every cycle. So I need to add a way to mute those messages in that situation.
Ah, the bug is the ATN_ACK line being effective when the internal drive is under reset, because of how the CIA lines default, and the NAND gate. I'll just make it so that I can disable the ATN_ACK circuit when the simulated drive is held under reset. I'll have to do the same for some of the other tests, that have the 1581 held under reset, too.
That got the 1581 command channel test working, leaving only the ATN sequence with dummy device and the 1581 LOAD"$" test failing.
The ATN sequence with dummy should hopefully be fairly easy to fix. I'm guessing some other hangover from the ATN_ACK plumbing is interfering with it.
Ah, the problem here is that when we hold the 1581 under reset, it is also holding the 4541 IEC controller under reset, so of course it is not working. For the other test that uses this configuration, we added a mechanism to hold the 4541 under reset for a time at boot. So I should just need to add that delay in this test to get it to pass... which it does!
So now we are finally at the starting line -- all 1581-based tests are passing, except for loading the directory.
The test currently fails encountering a DEVICE NOT FOUND error. The question is at what point in the transaction? I've added some CHECKPOINT report statements, so that I can see which point it gets to... And it gets to checkpoint 9, which is here:
report "IEC: Sending $ filename";
iec_tx_eoi(x"24"); -- $
report "CHECKPOINT 6";
report "IEC: Sending UNLISTEN to device 8";
atn_tx_byte(x"3F");
report "CHECKPOINT 7";
report "Clearing ATN";
atn_release;
report "CHECKPOINT 8";
report "IEC: Allow 1581 time to process the $ filename.";
wait_a_while(300_000);
report "CHECKPOINT 9";
report "IEC: Request read command channel 0 of device 8";
atn_tx_byte(x"48");
report "CHECKPOINT 10";
It looks like I may not be allowing enough time for the drive to process the $ filename. But I also find it a bit odd, that we get device not present during that situation. I would have thought that the drive should hold the IEC host off in some way. Anyway, extending that delay somewhat did the trick, in that it now gets to checkpoint 12, which is here:
report "IEC: Allow 1581 time to process the $ filename.";
wait_a_while(3_000_000);
report "CHECKPOINT 9";
report "IEC: Request read command channel 0 of device 8";
atn_tx_byte(x"48");
report "CHECKPOINT 10";
atn_tx_byte(x"60");
report "CHECKPOINT 11";
report "IEC: Commencing turn-around to listen";
tx_to_rx_turnaround;
report "CHECKPOINT 12";
report "IEC: Trying to receive a byte";
-- Check for "00, OK,00,00" message
iec_rx(x"30");
iec_rx(x"30");
Note that I haven't changed the bytes expected to be received, but as it doesn't actually read any bytes yet, that doesn't matter. When it gets to that point, it will give me a clear error in any case, to indicate that the incorrect value was read.
So let's try to figure out what is going wrong here. The problem is happening in a turn-around, where the drive switches from listening to talking. Now, we have the same event occuring in the command channel write and read-back. And that seems to work. Actually, turn-around happens in several of the other tests, so it must in general work.
It looks like the turn-around process completes okay, but then the 1581 seems to give up and stop wanting to talk:
: IEC: TURNAROUND COMMAND received
iec_state = 200
iec_state = 201
DEBUG: r=0, Line = '/home/paul/Projects/mega65/mega65-core/src/vhdl/tb_iec_serial_1581.vhdl:212:9:@50985381141ps:(report note): IECBUSSTATE: ATN='1',
CLK(c64)='0', CLK(1581)='1', DATA(c64)='1', DATA(1581)='0', DATA(dummy)='1''
$ACE8 1581: DATALOW Data line set low (5V)
DEBUG: r=0, Line = '/home/paul/Projects/mega65/mega65-core/src/vhdl/tb_iec_serial_1581.vhdl:212:9:@51010196601ps:(report note): IECBUSSTATE: ATN='1',
CLK(c64)='0', CLK(1581)='1', DATA(c64)='1', DATA(1581)='1', DATA(dummy)='1''
$ACFA 1581: CLOCKHI Clock line set high (0V)
DEBUG: r=0, Line = '/home/paul/Projects/mega65/mega65-core/src/vhdl/tb_iec_serial_1581.vhdl:212:9:@51020048709ps:(report note): IECBUSSTATE: ATN='1',
CLK(c64)='0', CLK(1581)='0', DATA(c64)='1', DATA(1581)='1', DATA(dummy)='1''
iec_state = 202
DEBUG: r=0, Line = '/home/paul/Projects/mega65/mega65-core/src/vhdl/tb_iec_serial_1581.vhdl:212:9:@51025382181ps:(report note): IECBUSSTATE: ATN='1',
CLK(c64)='1', CLK(1581)='0', DATA(c64)='0', DATA(1581)='1', DATA(dummy)='1''
$AD2F 1581: DELAY1 Cycle delay
iec_state = 203
iec_state = 204
iec_state = 205
): IEC: TURNAROUND complete
iec_state = 206
$FF33 1581: TALK Execute the JTALK routine via an indirect jump to the vector at $01B2. JTALK at $AD5C
$AD5C 1581: TALK Serial bus talk routine
$9027 1581: OPENREAD Open channel for reading
$AD2F 1581: DELAY1 Cycle delay
DEBUG: r=0, Line = '/home/paul/Projects/mega65/mega65-core/src/vhdl/tb_iec_serial_1581.vhdl:212:9:@51134125749ps:(report note): IECBUSSTATE: ATN='1', CLK(c64)='1', CLK(1581)='1', DATA(c64)='0', DATA(1581)='1', DATA(dummy)='1''
$ACBB 1581: BUS2INPUT Switch 1581 bus to input
$DBC7 1581: SPINPATCH Spin routine patch to clear the shift register
$FF00 1581: IDLE Execute the JIDLE routine via an indirect jump to the vector at $0190. JIDLE at $B0F0
$B0F0 1581: IDLE Main idle loop
So we can see that it starts to talk at $AD5C, and then opens the channel for reading at $9027, but then switches back to input soon after. The question is why?
I'm going to trace through this from further back, when the TALK is issued. The call to TALK seems to be from this part of the 1581 source:
a12{ensh}bit fsflag {ensh}; talk?
bpl a13
jsr dathi ; release data line
jsr clklo
jsr delay40{ensh}; slow down for plus4 series
jsr jtalk
jsr delay40{ensh}; slow down for plus4 series
Interesting that they put some fix-ups in there fore the Plus/4. Not yet sure if it matters for us, but it would seem that this means that after issuing a TALK there is a minimum delay required, before the drive will be paying attention again.
Anyway, let's trace it through: The jtalk routine has an indirect call via the vtalk vector, which I assume then calls the talk routine, which looks like this:
talk sei ; find if open channel
jsr fndrch
bcs 1$ ; no one home
2$ ldx lindx
lda chnrdy,x
bmi 3$
1$ rts
So let's see what the fndrch routine has to say: Does it abort thinking we don't have a read channel available?
And, yes, that's exactly what happens here: There is no read channel available.
Ok, so now let's go right back, and find where the file open for $ is, and try to understand why it fails. I'm suspecting that the EOI flag might not be correctly detected. Without this, the drive won't process the filename. The EOI flag gets stored in ZP address $51, I think. The EOI flag contains $00 if EOI has been received.
The weird thing, however, is that the EOI flag doesn't seem to get read anywhere obvious based on grepping through the ROM source. I've put a VHDL debug message on accesses to location $51, so that I can see where it is being written and read from.
This reveals two things: First, the EOI is being detected by the 1581, as I see $51 get set to $00 at $AE85 in the ACPTR routine. So let's just make sure that the ACPTR routine is reading the bytes correctly. To do this I have added a check to peek location $54 in the VHDL unit tests whenever we send a byte to the 1581 via IEC.
Hmm... It looks like the directory does get opened, ready for sending. Or at least tries to. It might be that the 1581 is now trying to access the disk. So time to activate my back-channel mechanism for loading the 1581's track cache. Well, that causes the 1581 to issue a BRK instruction.
I think the problem here is that my 8KB drive RAM for the 1581 was wrong: It was wrapping around at 4KB, and thus overwriting the IRQ vectors. I've fixed that now, and also corrected its use in the simulated 1541, where it is also used. With that all fixed, it's back to tracking through everything. It looks like we are getting a 74 DOS error:
$84AE 1581: INTDSK Initialize diskette
$FF3F 1581: CMDERR Error = $74
This makes sense, since we haven't implemented the floppy controller in the simulated 1581. Let's look at how the 1581 checks for the presence of a disk. Hopefully we can easily spoof it.
The trouble is matching the disassembly routine names back to the 1581 ROM source, so that I can identify exactly which routine I am looking at at any point in time. Without this direct mapping, it's a bit of a pain.
$82B1 is the point where the error code $74 is loaded into the accumulator. This happens if the routine ata $84AE returns with Z flag clear.
It looks like $6E and $8A both need to have $00 to make the 1581 think that there is a disk present without an error. Setting those in my back-channel track loader causes the simulated CPU to bale with an inimplemented addressing mode of the ADC instruction -- so that's kind of reassuring that we now have a different code path executing. Interestingly, this is during the 1581 boot sequence, I assume when it looks to see if the disk is bootable.
Okay, with that implemented, it now looks like our directory load is doing something, because it returns a byte $01 instead of the stale $30 expectation I had, when I copied the test from an 00, OK, 00,00 status read. We should now see $01, $04 for the start address of a directory listing.
So now I need to get the complete expected listing, so that I can make my test work correctly for that, as otherwise it will take about 70 seconds each run to read and compare the next byte, and report it doesn't match, while I update the test to reflect the data that is actually returned.
Okay, I've put that correct data in, and part of it is correct, but the bytes that come from the data sectors of the disk are wrong. To track this down, I have further instrumented the simulated 1581, so that I can see where it reads from the sector buffers. Interestingly, it doesn't read from the track cache directly, but instead reads from the buffers at $0900 (data buffer 6), $0A00 (BAM buffer for first 80 tracks) and $0800 (data buffer 5). So I'll have to populate those buffers via the back-channel, too.
The BAM sector buffers are fairly simple, because what goes in those is basically set. The $0800 and $0900 buffers are different. One should have the first directory sector, and the other should have the disk header block. Well, 50/50 chance of getting them right, so here goes nothing!
They were wrong way around of course. After fixing that, it gets further, but the buffer at $0800 is getting written to by the 1581 DOS before being read out. The trick is to find out where from.
However, I'm not going to pursue that line any further right now, as is clear that the 1581 is not hanging when responding to a LOAD"$". So where I previously thought that the simulation was reproducing the problem, that's unfortunately not the case.
Well, while I didn't get to the bottom of it, we have a much better idea of what is right, and also of the operation of the 1581, which could also be used to make a VHDL 1581 in the MEGA65 core, e.g., to allow the internal 3.5" drive to be connected to the IEC bus, instead of only via the C65 CBDOS, for improved compatibility for some legacy C64 software that supports the 1581.