Sunday 6 October 2024

Modular MEGAphone: Prototyping castellated modules

 In the last post, I described a concept for the modules for the MEGAphone based on the following approach:

B. Half-Round Castellated Pins

C. Key Pins & Varied Module Dimensions.

D. Carrier Board Cut-Outs for Reduced Height / Reverse Component Mounting

E. Carrier Board Cut-Outs for Ease of Removal

F. Bridges for Power and Ground Thermal Relief

I'm going to add to that:

G. Image Registration Reference Markers

The reason here, is to make it easier to check if a device has been tampered with, by making it possible for someone to make an image registration-based blink comparator, to quickly and easily check of the glitter polish has been tampered with. I'm not going to implement that right now, but I want to make it easy to do.


Let's start with making a footprint in KiCad for a simple version of the carrier board half of this interface.  This requires the surface-mount pads for the pins, and the cut-outs for the ease of removal, and the cut-out to allow for reverse-side components on the modules, as well as the image reference markers.

So let's start with measuring the size of the hooks on the end of an IC puller I have here, so that we can size the cut-outs at the ends. The hooks are only about 3mm wide and  2mm deep, so it looks like 4mm x 4mm should be big enough. These should stick out far enough, so that the hook can be got into place, but still have enough of the cutout beneath the module for the hooks to grab onto.  The edges of the module will restrain the IC puller when used, but won't restrain a screwdriver or other improvised tool, if one is used. Therefore, the cut-outs for the ends should not be contiguous with the cut-out for the module components.

Let's take a look at what this looks like for the 2x5 format:


We have the 2 rows of 5 pads in the middle, where the module will sit on top.  Then above and below that, we have the A1,A2 and B1,B2 jumper pairs: These are for putting the jumpers on that connect the module to the possible large and heat sucking GND and VCC planes of the carrier board -- and vice-versa, cutting and removing the jumpers disconnects the module from those planes, and thus making it much easier to desolder or resolder, because none of the pins are going to be massive heat-sinks.

Back to the missing pin 2: This is what allows the user to know which way around the module should go: You can't rotate it or flip it, and still have the missing pad line up with the missing pad on the module half, that looks like this:

I've included exclusion zones for everything except for tracks on the rear side, except for the area marked in the middle that corresponds to the cut-out in the carrier board. The pry zones should have exclusion zones for everything, including tracks, so that using hard tools there can't mess up the function of the module.  I've not got those quite right here, which I'll have to fix, but hopefully you get the idea.

On the front side, there is no exclusion zone, because those physical problems don't apply there.

On the rear side of the module, we can see the pry zones, where no components should be placed, because a user may be using a hard tool against that surface to try to lift the board off.


Any components on the rear side should go in that square in the middle.

Now, for a symbol for the module, I've gone with this, so that those power and ground plane isolation jumpers are explicit:

For modules, a regular 10 pin symbol can be used for now, so I haven't made an explicit symbol just yet.

As I discussed in the previous post, I'd like for different modules that are not electrically compatible with one another, that they shouldn't fit. To do that, we can most easily remove different pins, or combinations of pins.  For removing only a single pin, there are only two choices: a corner, or a pin next to a corner. Removing a middle pin won't work, as then the module could be flipped vertically. So we can have a "minus pin 1" and a "minus pin 2" version.  If we want more variants, then we need to remove two pins, with "minus pins 1,2", "minus pins 1,3", "minus pins 1,4", "minus pins 1,7", "minus pins 1,8" and "minus pins 1,9" are the only options that maintain resistance to Murphy's Law. That would give us 8 total variants. 

I'll get to those, but first, I wanted to make a 20 pin version, with narrow and wide variants, depending on the space required on the module:



At this point, I have enough done that I can design up some simple test PCBs for fabrication, so that I can see whether they really are easy to solder and desolder etc.

For that, I'll make a 100x100mm carrier board (US$5 for 5 pieces from PCBWay and friends) with places for various modules, on both sides of the board, as well as simple examples of the modules, with hole-throughs that go to each pin, so that I can easily verify that the connections have been made. I'm not going to worry about the various missing-pin combinations, as that's less relevant right now.

Here's the test carrier board, with six of these footprints, spread over both sides, with every pin broken out to some standard 0.1" headers for ease of testing:


Looking at this, I'm slightly concerned that I have the inter-pad spacing a bit tight, and that they will be prone to bridging. I know that 0.1" pin headers are super easy to solder, so I should be able to reduce the pad heights to match that ratio of pad to space. They were 2mm, so only just over 0.5mm between them. I've now reduced them to 1.75mm, so that they now have just over 0.75mm between them, which feels a lot better:

Now for the test modules. I decided to add them as snap-offs on the PCB, as this should be cheaper than submitting 4 different PCB orders. I've not done this before, nor worked with PCBWay for castellated edge connectors. So let's see if they complain about my design :)


Actually, it occurred to me that it might be cheaper to do it in two parts, since the modules are forced to be 4 layer in this arrangement, even though they are just simple adaptors right now. But maybe I'll leave them all together, as it will still confirm that PCBWay can do this with 4-layer boards. In which case I should ideally put GND and power layers in them, with thermal relief connections to the pins, so that the thermal issues from that for soldering are reproduced.  Once I get the review feedback from PCBWay (I submitted it in the middle of the night), I'll make a decision on it, as I'll be pleasantly surprised if they accept it first time around.

As expected, I got some feedback from PCBWay, who pointed me at this resource

Basically I need to make my tabs wider (2mm+), with rounded ends, as well as the gap between the boards (1.6mm+), and I can then put drill holes (diameter > 0.45mm) along the edge, to make it easier to break them off.  Here is how the connections work now:

So let's see how PCBway goes with that. It passed, but the price is pretty high for five pieces, due to the castellations, and the 6 different designs on the panel. I'll live with that for now, as my priority right now is testing the module design.

Hopefully, it will work when it arrives...



Sunday 29 September 2024

Modular MEGAphone Kick-Off -- Designing a simple and effective module interface

Thanks to the support of the fantastic folks at the NLnet Foundation, I have support to progress the hand-held version of the MEGA65, building on the past work which NLnet supported. The brief for this stage of the work is as follows:

The previous MEGAphone project laid the groundwork for creating personal communications devices that are secure through simplicity. This project extends that work by making the hardware modular, at some cost of minimum size, so that it becomes much more feasible for small communities to produce and maintain their own units, even in the face of supply chain challenges and other contributors to the "digital winter", i.e., the situation where open innovation becomes more difficult due to number of factors. This will also make it easier to include diverse resilient communications options, whether RF, optical or acoustic, so that peer-to-peer communications networks can be sustained even in environments that are hostile to freedom of communications. For this reason energy sovereignty will also be part of the design, so that even if all civil infrastructure is denied, that basic communications and computing functions can be sustained, with a single device whose security can be much more easily reasoned about.

 If you want a bit more background on the motivations for this, I'd recommend watching this presentation and this presentation I gave just before the COVID19 pandemic, which rather uncomfortably rapidly confirmed my hypothesis that a Digital Winter could occur.

I'll dig into the work more deeply as it progresses, but the first part we will focus on, is the ability to develop a mobile-phone like device, that can be build by hand, without requiring advanced skills, and that is nimble to supply chain shocks of various forms, that characterise a Digital Winter.

Let's first agree what we mean by can be built by hand, without requiring advanced skills. Some skills will be unavoidably necessary, but we can minimise these. and perhaps more importantly, we can reduce the scale at which those skills are able to be mustered -- this is one of the key motivations for the modularisation of the MEGAphone. Put another way, we can minimise the impact of an error when building a MEGAphone. This has the significant benefit of allowing someone whose skills allow them a low but non-zero probability of achieving some particular task to increase their probability of eventual success.

Why Modularisation Makes So Much Sense

In the first instance, modularisation contributes significantly to this goal, by allowing an assembly error in a specific module to not impact on the rest of the unit. Consider the key example of surface-mount soldering of small and fiddly components.  I know first-hand, how much of a pain it can be to solder these things by hand, and how easy it is for someone who has some rudimentary hand-soldering skills to nonetheless be thwarted by the narrow pitch of pins on surface-mount components, or even more difficultly, to correctly mount surface-mount components that have ball grids, concealed thermal pads or other particularly diabolical characteristics for the hand-solderer.

By isolating such components to be on their own little PCBs, these components can be the focus of attention, and if their attachment goes wrong, only that little PCB will be wasted: The rest of the complex board on which it needs to be mounted will be preserved, and all that's needed is to have another go at making the little PCB.  

Also, it becomes much easier to track down problems with the attachment of the surface-mount components through their isolation. Short-circuits and open-circuits are some of the most common things that can go wrong, and are much easier to track down if there is only one component that needs to be investigated.

A further benefit, is that for components that truly need surface-mount techniques, it is much more likely that in austere environments that a very small SMD reflow oven could be rigged up and operated correctly, than if a whole board needed to be reflowed.  For boards that are perhaps just a few centimeters long and wide, getting an even temperature will be much easier.  

This isolation of the need for SMD processing skills onto small modules, also allows for a community where those skills (or the equipment necessary) are available, but scarce, that they can be focused on exactly those parts of the design where they are unavoidable, while the rest of the assembly process is performed using much simpler hand-soldering skills.

Finally, the modularisation allows for ordering these little modules in bulk, if such services were still available, which one assumes that except in a deep Digital Winter, that they may well be. And certainly, prior to that, it would be a very convenient option.  The attraction of this is increased if we reuse some modules in the design. For example, the MEMS microphone module will likely be replicated at least 2x and possibly 4x, to allow for a nice way to suppress background noise in the hardware domain (we'll talk about this more in a future post, but the short version is that you can use the difference between pairs of microphones to subtract out common mode background noise). The voltage regulators are also likely to be used more than once, as well.  This would allow individuals or small communities to order modest size batches at affordable cost from online PCB providers, and either used by themselves, or further distributed.

This approach also helps us to mitigate a bunch of the common problems with PCB design, too, and generally allows us to apply the strengths of a Systems Engineering approach, by breaking down a complex system into several smaller ones, that we can then integrate in a methodical and reliable manner.

Module Interface Requirements

So given that we are going to use a modular approach, let's now think about how we can design a nice module interface.  We'll start by looking at a number of the requirements for such an interface to be effective in our use-case, in no particular order.

1. Large Pad Size

First up, the solder pads need to be big enough, that someone who has only spent an hour or two learning to solder can work with them, without significant risk of creating short-circuits or other problems.

2. Effective Thermal Relief for Hand Soldering

Soldering power rails and ground pins by hand can be a royal pain, when those pins are connected to ground or power planes in the PCB, because the heat gets sucked out of the spot you are trying to solder. Without a solution to this, a reflow oven or similar approach would then be required, that can heat the entire ground or power plane to be heated, so that it can't cause this problem.

3. Unambiguous Orientation and Placement

The original formulation of Murphy's Law is all that we need as justification:

If there are two or more ways to do something and one of those results in a catastrophe, then someone will do it that way.[1]

Therefore it should not be possible to place a module with incorrect orientation, nor in the incorrect place on the PCB. This indicates that means that each module should have only one, correct, orientation, where they fit into the PCB receptacle, and that the PCB receptacles and modules should only mate in places where the modules are electronically compatible. 

4. No Sharp Protrusions on Rear

The MEGAphone is not going to be a particularly small device, because miniaturisation and modularisation aren't usually the best of friends, and even more so hand assembability and miniaturisation are more or less sworn enemies.  Nonetheless, we don't want it to be any bigger than necessary. 

To assist with this goal, we don't want to have to have any clearance between the PCB assembly and other components, like the rechargeable battery, which its probably a good idea to avoid sharp metal pins and solder lumps poking holes into [2]. We could avoid this by putting a spacer or bracket in place, but that takes space -- it would be much better to just make the rear of the PCB -- including attached modules -- be smooth and flat, so that the problem is simply avoided.  Apart from being be best option from the Heirarchy of Control perspective [3], i.e., we have removed rather than managed the battery piercing hazard, it also follows the principle of "the best part is no part", made famous by Elon Musk [4].  

(We'll also be using LiFePO4 cells, rather than Lithium Ion, to also reduce the risk, as they behave much more sedately when damaged, as can be seen in this video).

5. Ease of Desoldering for Diagnosis, Repair, Replacement, Substitution or Scavenging

It's not just important that a module can be easily added to the system, but also that they can be easily and reversably removed. Maybe you want to be check a suspect faulty module by swapping it out with another one, or maybe you are in an extremely austere environment, and need to make one working MEGAphone out of two or more damaged units, or swap out a module with a newer (or older) one that avoids a security vulnerability of some sort.  This should be possible using only simple hand soldering tools, if possible.

6. Small Size

We have already mentioned size, but this is really critical: We want these modules to be as small as possible in surface area, and also in height, as possible, so that the MEGAphone doesn't become the GIGAphone, or require a backpack to carry around.

7. Ease of Testability / Verification

We have also touched on this one, in terms of being able to remove or replace a module easily, but we'd also like to be able to easily test a module before it is first installed. This includes during development, but also for post-assembly of modules to test that they have been built correctly, especially when hand assembling modules with tricky surface-mount parts.  

It also makes it easier to check modules that have been assembled by 3rd parties for correctness. While a sufficiently determined adversary can almost always hide malicious functionality in a device, by breaking it down into smaller simpler modules, we at least make it as easy as possible for us to detect the cheaper and easier approaches, thus increasing the cost of such attacks, and/or increasing our chance of detecting them.

This can be further enhanced by applying some tricks to make it harder for components to contain side-channels for communications, such as using low-pass filters on signals, but that's probably well outside of our current scope, for now at least.

8. Tamper Evidence

But one thing that we can design for, is to make it easier to detect when a device has been tampered with, in particular, by swapping out a module, or by reflowing it to swap out a component in the field without the owners knowledge, e.g., via an Evil Maid/Manservant attack [5]

While nothing is perfect, not even glitter polish seals [6], they are still a pretty good approach in that they are quick and easy to apply [7], but take considerable time and effort to replicate -- the time factor being critical here: It is pretty implausible that someone could replicate a glitter seal in just a few moments -- especially if the seal is not flat, but has to conform around some slightly odd shape, a point that we'll come back to later.

In short, if we can meet these design criteria, we will be well on our way to a good solution.

Considering Some Module Design Characteristics

A. Hole-through vs Surface-Mount

Let's start with the key dichotomy we have to deal with: Should we make our modules use a hole-through or a surface-mount attachment mechanism? Let's compare how these interact with our 8 design criteria:

Approach 1 2 3 4 5 6 7 8
Hole-Through 😐
😐 😐 💩 😍 💩 😍 😐
Surface Mount 😐 😐 💩 😍
💩 😍 💩 😐

As we can see, neither has the upper hand: Both have characteristics that are great, really unhelpful, or are rather neutral.  Surface mount has the slight edge here. But what if we break it down, and instead say that the PCB should be surface mount, and the module have hole-through characteristics, and see how many of those poops we can get rid off:


Approach 1 2 3 4 5 6 7 8
Hole-Through Module
😐
😐 😐 😍 😍 😐 😍 😐
Surface Mount Carrier
😐 😐 😐 😍
😍 😍 😍 😐

Look at that: we have flushed all of the poop away, in many cases turning them into strengths.  Not surprisingly, such hybrid approaches already exist, in particular in the form of crenelated/castellated modules, e.g., this module.

B. Half-Round Castellated Pins

If the castellations are made as half-rounds with spacing that matches standard hole-through spacing of breadboards etc, then they can also be just easy to test in that context. This also makes it easier to hand-solder, without causing bridges between adjacent pads, because the shape helps surface tension to keep the blob of solder away from the neighbouring pads.

We are probably stuck with 2.54mm spacing, as the smallest pitch that is easily hand-soldered, and would offer good compatibility with existing prototyping aids, such as breadboards and DIP IC sockets.

Another benefit of this approach is cost: There are no sockets or connectors involved, delivering another benefit of "the best part is no part" philosophy.

C. Key Pins & Varied Module Dimensions.

By using judiciously placed key pins, we can make it impossible to insert the module around the wrong way, including up-side down -- which is important if we want to be able to minimise the assembled height (see below).

Also, by varying the module dimensions, primarily through changing the number of pads, and the width of the module, we can effectively key each type of electrically compatible module, so that they can't be put in an electrically incompatible place on the board. The module widths could match existing DIP spacing options to make prototyping and testing as simple as possible.

Together, these measures would effectively deal with Murphy's Law.

D. Carrier Board Cut-Outs for Reduced Height / Reverse Component Mounting

In some cases, it might be desirable to mount the components on the under-side of the module. This makes it possible to reduce the overall PCB stack-height, by reusing the thickness of the carrier PCB to offset the height of the components. If modules are assembled on both sides of the carrier, then components can be the height of the carrier board and one module PCB thickness -- which is probably sufficient for most components -- without further increasing the height of the assembly, and ensuring that there are no non-smooth protrusions on either side of the PCB assembly, thus helping to avoid the need for spacers or brackets on either side.

Mounting components on the under-side of the modules also makes it easier to use simple surface-mount techniques, such as a hot air gun, to reflow the modules into place, or to remove them later. This is because the attached surface mount components on the module will not be exposed to direct heat, and thus are unlikely to move or fall off.

E. Carrier Board Cut-Outs for Ease of Removal

Another use that can be made of cut-outs in the carrier board, is to make it easier to remove modules using either a hot-air gun or hand desoldering techniques, as the module can then be gently pried using a small screw-driver, tweezers or and old-fashion DIP chip puller, like this one:

While it's not the best way to remove a castellated module, as it would require you to reflow the pads repeatedly, until you could get enough movement to get it free, it would work, and doesn't require a great deal of skill or precision.  And of course, with a hot air-gun, this becomes almost trivial. So all in all, it makes sense to allow.

F. Bridges for Power and Ground Thermal Relief

Another design element we can consider is to completely avoid the thermal relief problem, as well as helping with making testing and "bring-up" of the system, by having the power and GND of the module route to jumpers or pads, that then have to be bridged to the power and ground planes of the carrier.

Once the module was soldered in place, those pads would be bridged with a short piece of desoldering braid, to ensure that the link could carry high current loads.  As only localised melting of solder onto those pads would be required, the thermal aspects would be greatly simplified.  To desolder and remove the module, the bridge could be first cut,  and then the two ends separately removed, so that it wouldn't be necessary to heat both ends at the same time.  Using desoldering braid as the link is convenient, because its widely available, has good surface area for carrying current, and of course takes solder very easily.

Summary and Requirements Cross-Check

In short, this combination of design elements should yield a solution that satisfies our design requirements from above. Let's go over them again, just to be sure:

  1. Large Pad Size -- Addressed by A & B
  2. Effective Thermal Relief for Hand Soldering -- Addressed by F
  3. Unambiguous Orientation and Placement -- Addressed by C
  4. No Sharp Protrusions on Rear -- Addressed by A, B & D
  5. Ease of Desoldering for Diagnosis, Repair, Replacement, Substitution or Scavenging - Addressed by E & F
  6. Small Size - Addressed by A, B & D
  7. Ease of Testability / Verification -- Addressed by B & E
  8. Tamper Evidence  -- Addressed by A & B: Glitter polish can be applied over the castellated solder joints, creating an uneven surface for the polish, that would be hard to rework without being detectable.

So this design approach has the potential to meet our needs. Now, I don't claim that it is the only possible approach, nor necessarily the best possible approach. Rather, only that it is an approach that we can use in this project, to make rapid progress.  If you have better ideas, or suggested improvements to this approach, please let me know in the comments or via email!

In the meantime, I'll probably try to design up a module or two and a simple carrier board to test this concept, before I move onto the rest of the hardware design, in particular, gathering the requirements for the device as a whole.

Sunday 18 August 2024

Tracking down 1581 lock-up on R3 boards

Some folks are encountering 1581 drives locking up with certain cores on MEGA65 R3/R3A boards.  While I'm here in Germany with easy access to one of the machines and 1581 drives that is exhibiting this problem, we are taking the opportunity to try to track this problem down and fix it.

I'm fairly confident that the problem is to do with the SRQ line, that the 1581 uses for the C128 fast serial protocol.  This theory explains why only the 1581 would have the problem, because:

1. The 1541 doesn't use it, so shouldn't have problems.

2. The 1571 can use it, but powers on in 1541 mode, so is also unlikely to exhibit problems (it only switches to 1571 mode when it has seen 8 negative edges on the SRQ line).

3. The 1581 starts up in 1581 mode, where it does pay attention to the SRQ line.

The first step was to make a half an IEC cable, so that we could probe the IEC lines easily with the oscilloscope:

The next step was to fix which CIA the SRQ line is connected to in the MEGA65 core, as I had mistakenly put it on the 2nd CIA at $DD00 (where the other IEC lines are connected), instead of the first CIA, where the C65 had it.  This should mean that the M65 ROM (being derived from the C65 ROM) should correctly tri-state the SRQ line.  Which it probably does, but doesn't solve the problem.

We are currently fighting with the multi-meter, to get it to show the actual DC voltage on a probe, instead of trying to auto-centre the signal vertically, which means that the voltage drifts to "0" over a couple of seconds.  There must be a way to disable this, but we haven't seen it yet.

Anyway, digging through the schematics for the R3 and R6 boards, I was reminded that the output driver circuit for the IEC lines changed between the two, and an important difference in that circuit is the polarity of the output enable for the IEC lines: We already had DATA and CLK the right way around on the R3 target, but the SRQ line had not been flipped around to the right polarity, as can be seen in the following screenshots:


So we have a totally plausible mechanism for the problem, and I'm cooking a bitstream right now that should correct that. The fix itself is (as is often the case) quite simple:

commit 619acc8893eb593f017bfe07b32fa7082ca80501 (HEAD -> 736-hardware-iec-accel, origin/736-hardware-iec-accel)
Author: Paul Gardner-Stephen <paul@m-e-g-a.org>
Date:   Sun Aug 18 18:49:27 2024 +0930

    fix inverted SRQ en line for R3 #736

diff --git a/src/vhdl/mega65r3.vhdl b/src/vhdl/mega65r3.vhdl
index 2648a689c..e53d6725b 100644
--- a/src/vhdl/mega65r3.vhdl
+++ b/src/vhdl/mega65r3.vhdl
@@ -123,7 +123,7 @@ entity container is
          iec_atn_en_n : out std_logic;
          iec_data_en : out std_logic;
          iec_clk_en : out std_logic;
-         iec_srq_en_n : out std_logic;
+         iec_srq_en : out std_logic;
          iec_clk_o : out std_logic := '0';
          iec_data_o : out std_logic := '0';
          iec_srq_o : out std_logic := '0';
@@ -1194,7 +1194,7 @@ begin
       -- Finally, because we have the output value of 0 hard-wired
       -- on the output drivers, we need only gate the EN line.
       -- But we only do this if the DDR is set to output
-      iec_srq_en_n <= iec_srq_en_n_drive;
+      iec_srq_en <= not iec_srq_en_n_drive;
       iec_clk_en <= not iec_clk_en_n_drive;
       iec_data_en <= not iec_data_en_n_drive;
 
diff --git a/src/vhdl/mega65r3.xdc b/src/vhdl/mega65r3.xdc
index f228c5d3d..38486cf64 100644
--- a/src/vhdl/mega65r3.xdc
+++ b/src/vhdl/mega65r3.xdc
@@ -132,7 +132,7 @@ set_property -dict {PACKAGE_PIN Y19  IOSTANDARD LVCMOS33} [get_ports iec_clk_o]
 set_property -dict {PACKAGE_PIN Y18  IOSTANDARD LVCMOS33 PULLUP true} [get_ports iec_clk_i]
 set_property -dict {PACKAGE_PIN U20  IOSTANDARD LVCMOS33} [get_ports iec_srq_o]
set_property -dict {PACKAGE_PIN AA18 IOSTANDARD LVCMOS33} [get_ports iec_srq_i]
-set_property -dict {PACKAGE_PIN AB20 IOSTANDARD LVCMOS33} [get_ports iec_srq_en_n]
+set_property -dict {PACKAGE_PIN AB20 IOSTANDARD LVCMOS33} [get_ports iec_srq_en]
 
 # C64 Cartridge port control lines
 # *_dir=1 means FPGA->Port, =0 means Port->FPGA


And it looks like that has fixed it up: 

The 74 DRIVE NOT READY error is just because the disk wasn't inserted when I first tried.

We also just made sure it works both with and without an HW IEC enabled ROM, and all is fine.  Also from GO64 mode, everything seems to work.

So, bug found and fixed :)

Wednesday 14 August 2024

Hardware Accelerated IEC Controller -- Part 5

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 "&COPYRIGHT 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

NZCIDV
M7+---M6

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.