Contents

What is the Launch Vehicle Digital Computer (LVDC)?

The Launch Vehicle Digital Computer (LVDC) was a computer that resided in the Instrument Unit (IU) that perched above the Saturn IVB that was the second stage in a Saturn IB rocket and the third stage in a Saturn V rocket.  The LVDC was a completely separate computer system from the AGC, with a different architecture, different instruction-set, and different runtime software.  The purpose of the LVDC was to precisely control the Saturn from shortly before liftoff until the point at which the Saturn was discarded by the CSM. 

Factoid
People generally think that the guidance computer (AGC) of the command module controlled the Saturn rocket, but it isn't true.  During burns of the S-II and S-IVB rocket stages, it was possible for the CSM's AGC to control the steering, as a backup to the LVDC.  That backup capability was never used in a mission.  This was not possible during burns of the first stage (S-IB or  S-IC).  However, the AGC's ability to directly control the Saturn IVB was used for other purposes during the mission.  Also, see below.



The LVDC has a less-visible role in people's eyes than the AGC, not only because it was used for only a very small portion of the individual missions, but also because it had no user interface as such.  In other words, it was a black box that responded to inputs from sensors in the Saturn and to ground telemetry, but there was no panel into which astronauts could enter commands or get feedback from it.  One might well call the LVDC "The Forgotten Computer", even more so that the computer of the LM's Abort Guidance System.  Nevertheless, the LVDC's importance is great, because you need to actually get the Command Module and Lunar Module into space and headed toward the moon if you expect any landings to occur!  As I understand it, the folks that actually worked on the Saturn referred to the Instrument Unit as "The Brain", and that term might as well be applied to the LVDC, since it was the brain of The Brain.







The LVDC was developed by IBM Federal Systems Division, rather than by the MIT Instrumentation Lab that developed the AGC, so there was no overlap in development personnel between the LVDC and AGC systems.  Furthermore, there was almost no overlap in engineering technique, other than that both groups of necessity were generally constrained by the technology available at the time.  For example, both needed to use memory based on ferrite-core technology.  Moreover, there was no interaction between the LVDC and AGC systems.

Actually, there was some interaction between the development groups, in the sense that at some point in 1963 or before the idea arose that the LVDC might be used in place of the AGC in the CM and LM, and that this move might save some time and money.  In fact, IBM produced a 300+ page report detailing the differences between the two systems with the apparent object of arguing this point, and the MIT Instrumentation Laboratory's antibodies flooded in to destroy the invader with critiques and reports negative of the IBM report.  In the end, the Instrumentation Lab won this particular battle, relegating the LVDC to the relatively small share of public attention that it presently enjoys.



Peripheral Devices

As with any of the other computer systems we cover here at Virtual AGC, the LVDC does not stand alone and do its computing in a computing vacuum (so to speak).  The subsystems involved are:
At the end of the preceding section was an illustration of a very simplified logical view of the interconnection of the LVDC to various peripherals.  Here is a somewhat more complete illustration for the Saturn IB:


and for the Saturn V:

Saturn Interaction with the AGC

The LVDC and the AGC did not actually have any direct interaction, so we may as well talk about how the AGC interacted with the Saturn before immersing ourselves in too much detail about the LVDC.

Flight
                  Control ComputerIf you look at either of the graphics at the end of the preceding section, you'll see the four ways that the Instrumentation Unit (IU) in the Saturn IVB and the Spacecraft (i.e, the Command Module) — which are separated by a horizontal dotted line near the tops of the two graphics — interacted:

  1. The Abort Decision signal.
  2. The Status signal.
  3. The Mode Command signal.
  4. The Alternate Steering Commands signals.
The Saturn was always steered by the so-called Flight Control Computer (depicted in the picture at the right), an analog computer whose salient characteristic for our purposes is that it was not the LVDC.  However, the flight-control computer did not operate on its own, and thus itself needed to be supervised.  Normally, that supervision was performed, by default, by the LVDC, indirectly through the LVDA, the Launch Vehicle Data Adapter.

However, it was also possible for the spacecraft to send the flight control computer a signal, the Mode Command, which instructed it to accept Alternate Steering Commands from the AGC rather than the default steering commands from the LVDC/LVDA.  Thus, the AGC could steer the Saturn IVB (but not some of the other Saturn stages) by this mechanism.

Of course, it was also desirable for the spacecraft to be able to monitor the activity of the Saturn, even under normal conditions when the LVDC was controlling the rocket.  Since the spacecraft had its own Inertial Measurement Unit (IMU), it knew its own orientation and acceleration — and hence the Saturn's — at all times, and the AGC could integrate these quantities to know the velocity and position at all times.  Thus it was not necessary for the IU to communicate that information to the spacecraft in order for the AGC to monitor the physical motion of the rocket and to display it for the astronauts on the DSKY.

I actually have an interesting graphic of the monitoring process to show you.  This graphic is not from physical system.  Rather, Riley Rainey has used the "equation defining document" which specified how the Instrumentation Unit (IU) was supposed to behave, to model the physical behavior of the rocket and the spacecraft's IMU, allowing Virtual AGC to monitor the launch behavior on a simulated DSKY.  Here's a short movie he has created of that simulation.  It's admittedly a little fuzzy, since I blew it up by about 2×, but perhaps we'll be able to get a better one sometime in the future:

Riley Rainey's simulation video

Of course, at the left in the video, you can see the simulated FDAI and DSKY.  At the right, you can see telemetry from the AGC.  You'll notice that at the beginning of the movie, the DSKY's UPLINK indicator is on.  That's apparently because Riley's simulated DSKY (which is his own, and not the simulated DSKY we provide) isn't fully functional in the sense of accepting keypad input, so Riley is instead feeding the AGC commands via the digital uplink.

LVDC Documentation

Sadly, documentation we've been able to collect for the LVDC lags far behind that of the AGC or even that of the Abort Guidance System, though it's getting better over time.  What little survives that we have been able to access can be found in our Document Library.

I'm also told that there are a number of published (but not necessarily free-of-charge) research papers about the LVDC.  These may be of assistance if you're an enthusiast, but I cannot provide any of them for you, for legal reasons.  Specifically, you can go to the AIAA's website, and search for "LVDC".  Or as another example, via the IEEE's website, you can get an article about the "Interactive Saturn Flight Program Simulator", as pointed out to me by one of the article's authors.  (Thanks, Tom Dillon!)

LVDC Software

Overall Structure

Describing the overall structure of the software loaded into the LVDC is a bit tricky at the present time.  That's because documentation is scarce, our cache of original LVDC software is sparse, and the original development process seemed quite compartmentalized.  By the latter, I mean that programmers concentrated on the specific areas to which they were assigned, and often seem to have had little cognizance of even the most basic features of the software when those features happened to be outside their narrow specialization.  Plus the set of LVDC programmers available to me is limited, so I don't have representatives of all of those specializations to consult with.

In short, important aspects of my descriptions in these sections concerning the gross structure of  the software are based on my own inferences and on the recollections of developers unfamiliar with the details.  So my comments about the program structure with a large grain of salt.

With that said, let's contrast the overall structure of the LVDC code vs the software source code for the Apollo Guidance Computer (the programs COLOSSUS, LUMINARY, and so on) and for the Abort Guidance System.  All of these non-LVDC programs were monolithic in nature.  What I mean by that is that although the AGC and AGS software was structured into various semi-independent sections, for which the development of each was presided over by specialists in those specific areas, the source code for them was nevertheless presented to the programmers in a single large chunk — i.e., a single, unified program listing.  Every AGC or AGS developer saw the entire source code, regardless of whether it pertained to them or not.  The natural result was that it was possible (and even likely) for an AGC or AGS developer to have some grasp of the large-scale structure of the software, beyond his or her own narrow area of specialization.  Similarly, every word stored in the AGC or AGS core memory came from that source code.  In that sense, each AGC or AGS program listing was entirely self-contained.  If you were able to assemble those program listings, then you obtained a rope image that could be loaded into the computer and run.  Conversely, every word in core-memory either came directly from the associate program listing or from some action taken by the code in that program listing.  When you look at a program listing for (say) LUMINARY, you see the entire contents of the Lunar Module's AGC's core memory.

The overall structure of the LVDC software, however, is fundamentally different.  Simultaneously loaded into the LVDC core memory were several different logically-distinct "programs", each with different sets of source code, assembled separately from each other, and having different areas of specialization.  Thus assembly of any given one of these programs did not produce a full core-rope image: merely a partial rope image.  A full rope image could be obtained only by merging all of the partial core-rope images from the different assemblies of the several sets of source code.  The separate programs I'm aware of are discussed individually in the sections that follow, but in brief, they were:

A similar situation arises in modern computer systems, where you typically have an "operating system" program and "application programs" running in the computer at the same time.  The application programs rely on the operating system for certain functionality, but have no understanding of how the operating system provides that functionality.  All the application program needs to know is the exact method for requesting the desired function from the operating system.  Similarly, the operation system stands ready to provide the desired functionality, but has no knowledge of the internal workings of the application program requesting service.

In the LVDC, the method by which interaction between independent but simultaneously-loaded programs worked was for there to be an agreed-upon set of specific memory addresses hard coded into the programs.  For example, one program would know that to obtain a certain type of service, it had to call a routine at a certain fixed address in memory.  Another program would know that it had to put code providing certain types of services at certain fixed addresses, but have no other knowledge of the program(s) utilizing that functionality.

Because of this much higher degree of compartmentalization, programmers working on (say) the Flight Program might have no cognizance at all of the Preflight Program, the developers of which might have no cognizance of the Executive Control Program, and so on.  And unfortunately, that means that we don't, either.

Preflight Program

I don't know anything at all about the Preflight Program at present.  I.e., there is no surviving documentation or source code for it as far as I know.  I will provide information about it if/when it becomes available.

As it relates to the AS-206RAM Flight Program, however, I do have a couple of reasons to believe that the AS-206RAM Flight Program would have been used in conjunction with a Preflight Program:

  1. In the AS-206RAM Flight Program (1967), there are jumps to various memory locations in regions of core memory at which AS-206RAM itself does not define any code or variables.  It stands to reason that something is stored there.  This is discussed in more detail below.
  2. One of the original LVDC software developers (thanks, Pat Woods!) tells me that he believes that the Flight Program shared memory with a Preflight Program that ran before liftoff.

Executive Control Program

Original LVDC software developer Pat Woods tells me that there was no Executive Control Program (ECP) in use until the Apollo 12 mission.  This anecdotal information is backed up by the fact that the Saturn Launch Vehicle Astrionics Systems Handbook has 14 pages of descriptive material about the ECP in its November 15 1969 release (see section 11.2.1 in particular), but does not even mention the ECP at all in its November 1 1968 release.

I won't describe the ECP further here, since we have no source code for it, but if you are interested you should consult the later release of the Astrionics Systems Handbook mentioned above.

It is unfortunately not clear from the description whether the ECP constituted a program separate from the Flight Program with which it was used — i.e., whether it had a separate set of source code that was assembled separately from the Flight Program — or whether the two had an integrated code base that was assembled as a single operation.

Flight Program in General

The software was apparently known simply as the Flight Program, and didn't have a catchy name such as "Luminary".

You may also see references to the Generalized Flight Program (GFP) or generalized Flight Program System, in use from Apollo 12 onward.  You may recall from the preceding section that the Executive Control Program also came into use from Apollo 12 onward.  My supposition would be that this simply means that

GFP = ECP + FP
Was the software classified?  No.  Or at least it was not classified at the time period from which we begin to have any information.  Several people associated with the development have stated to me that it was classified.  But classified material must be stamped with one of the designations CONFIDENTIAL, SECRET, or TOP SECRET.  The available software listing is not so stamped, and therefore should not be considered classified.  Undoubtedly IBM Federal Systems Division considered it confidential at the time, under the common usage of the word, but that doesn't make it classified.

AS-206RAM Flight Program

To a computer programmer, the most important thing about any computer program is its source code, and at present a single revision of the flight program is available to us.  It is an engineering revision of the software, from September 1967, designated "AS-206RAM LVDC FLIGHT PROGRAM".  If you were to Google this (don't do it!), you may unnecessarily excite yourself by noting that Saturn IB launch vehicle AS-206 was used for the Skylab 2 mission (Conrad/Weitz/Kerwin).  You may then be chagrined to realize that the Skylab 2 mission was in 1973, far past the 1967 time frame in which this revision of the program is developed.  What gives?  The answer, is that AS-206 was originally intended for an unmanned mission that was canceled after the Apollo 1 fire.  The software we have is not even for that canceled early AS-206 mission, but rather for a proposed backup to the canceled mission.  So whatever software the LVDC had when AS-206 eventually launched as Skylab 2, is not this software.  That doesn't alter the fact that this software is ancestral to the versions that followed it ... or at least a very close cousin to the ancestor of the versions following it.  There are some references in the software to AS-205, which is what would have been Apollo 2 (the 2nd manned Apollo mission) had the tragic Apollo 1 fire not occurred; naturally, Apollo 2 was canceled thereafter.  The designation AS-205 was later used instead for Apollo 7, though considering the time-frames involved, it's likely that the reference in the source code is to Apollo 2 rather than Apollo 7.  In other words, the AS-206RAM flight program we have had likely been branched off from the LVDC software being developed for the never-flown Apollo 2 mission.

From the preceding sections and from the date of the software, you'll note that there is likely no Executive Control Program (ECP) associated with this software, but that there should be a separate Preflight Program (which we know essentially nothing about) interacting with it through shared memory.  Thus the AS-206RAM Flight Program we have does not completely describe the contents of LVDC core memory, and thus is not a complete LVDC program as it stands.  More on this below.

As far as an AS-206RAM mission specifically is concerned, "RAM" stands for Restart Alternate Mission.  We have the document "AS-206 S-IVB RESTART ALTERNATE MISSION LAUNCH VEHICLE OPERATIONAL FLIGHT TRAJECTORY", which I expect would be pretty invaluable in understanding the expected operation of the flight program.  To quote the document itself,
The basic purpose of the Apollo Saturn 206 S-IVB Restart Alternate Mission is to place the S-IVB stage into orbit and test its restart capability, simulating the AS-501 mission profile. In the event S-IVB restart problems occur in the early Saturn V flights, this mission will be flown to help correct or solve the problems. The primary objective of the SA-206 Launch Vehicle is to insert the S-IVB/IU/Payload configuration into a near earth 100 nautical mile circular orbit. The payload consists of a Spacecraft LM Adapter (SLA) and a 25° Nose Cone (NC #2).
As usual in these matters, what we have is not the punch cards on which the assembly-language source code was originally provided to the assembler program, but the "assembly listing" output by the assembler.  Unfortunately, the status of the assembly process for it found 41 warnings and 7 errors — meaning that there were problems in the source code and that the assembly process failed.  Thus the program wouldn't actually work as-is anyway, even assuming we had an LVDC or a simulation of an LVDC in which to run it.  That doesn't reduce its instructional value any, though, and it doesn't mean that some enterprising individual couldn't fix it up now to make it work!



When I say "we" have a copy of this assembly listing, however, there's an unfortunate proviso.  In the U.S. there is something called the International Traffic in Arms Regulations (ITAR), which prevents export of certain technologies from the U.S. except under strict controls.  Under my non-expert, non-lawyer reading of the ITAR, the LVDC would be such a device whose export is prohibited.  However, I cannot personally figure out whether the LVDC's software would also therefore be restricted.  If it were restricted, then freely providing the software online would be regarded as "exporting" it, and would therefore also be prohibited!

As it happens, the source code from the assembly-listing printout has been entirely transcribed into machine readable form.  That's a lot more convenient to deal with that scanned page images, since you can do things like text searches on it, or even assemble it using the nifty new LVDC assembler I've written (see below).  The problem, of course, is that the transcribed source code is just as much subject (or hopefully, it will eventually turn out, not subject) to ITAR export restrictions as the scanned images are, so this LVDC source code is not presently available in our software repository.

Yes, I know that all sounds very silly, since there haven't been any Saturns since 1973, so nobody is likely to load one of them up with a warhead and fire it at anybody ... and never would have anyway, due to the tremendous cost involved.  And this software couldn't target anything on the ground anyway, since its sole purpose is to get stuff into orbit.  Nevertheless, although the law may be an ass (with apologies to Charles Dickens), it is still the law.  Until this uncertainty about the ITAR status of the LVDC software is cleared up, I am forced to regard it as being restricted.  According to wikipedia, that means that I am allowed to give it to only a "United States person".  Therefore, if you want the program listing, you must obtain it from me personally and provide proof that you are a United States person as just described. I will retain a record of everyone to whom I the program listing.  The easiest way I've thought of so far of doing this, without forcing you to come to me physically, is to have a video call (say, using Skype) in which you present a U.S. passport or some other suitable picture ID that lists your citizenship, so that I can compare it to your actual face.

With all that unpleasantness out of the way, it doesn't seem to me that suitably-abridged subsets of the assembly listing would be restricted by ITAR, if they do not pertain to guidance, control, or other technical aspects of the launch vehicle.  Such an abridged transcription of the source code does indeed appear in our software repository.   The transcribed code, unlike the full program, assembles without error and could actually be run on an LVDC or LVDC simulator ... or at least could be run once I flesh it out enough to do something useful.  That's an evolving effort, so feel free to take or leave the abridged LVDC code as you see fit.

Since the abridged source code is, as I mentioned, a work in progress, I can't really base a discussion on it without the burden of having to update the discussion frequently.  So for the sake of discussion, let's just work with the scanned page image.  Here are various images of pages of the assembly listing that illustrate things like how constants and variables are defined by the software, how some standard mathematical functions are encoded, and some of the tabular data generated by the assembler:

 

     

   

   

There's some additional description of the anatomy of these assembly listings farther down on this page.

The middle group of pages above shows a few auxiliary subroutines for computing the sine, cosine, arctangent, and spare root functions, plus a 3×3 matrix-multiply routine.  Note that these are some of the very algorithms described in section 13 of the EDD (LVDC Equation Defining Document), so the source code can actually be compared to the defining documentation if one so desired.  The two images at the top show an area of the program where some constants are defined, while the two at the bottom show a portion of the assembly listing's cross-reference table. 

Additionally, each of the flown Saturns is associated with a report known as its "launch vehicle flight evaluation report", and these reports are available for most of the missions, though there are a few gaps.  Chapter 2 of each of the reports divides the mission into a set of "time bases", called T0, T1, ..., T5, T5A, T6, ..., T9, and each time base itself consists of a series of events that are supposed to occur at different times.  For example, time base T0 is always the "Guidance Reference Release", and comprises events such as "S-IC Engine Start Sequence Command", "S-IC Engine No. 1 Start", and so on.  But in general, not all missions use all of the time bases, and the time bases aren't necessarily used for the same thing on different missions.

As noted above, we're pretty sure that for Apollo 12 and beyond, a single Generalized Flight Program (GFP) was used, but we have no good information about preceding missions.  By examining the time bases on a mission-by-mission basis, it's possible to roughly deduce which missions may have flown with the same LVDC software (though with differing preloaded constants) vs the missions which must necessarily have used a different revision of the LVDC software.  Thanks to Nik Beug for pointing this out.  While such an analysis has not been done comprehensively, a rough analysis of the gross similarities in the time bases might indicate the need for at least the following additional LVDC software revisions other than the GFP:

Regarding preloaded constants for LVDC memory, all missions (I think!) were associated with a report called the "launch vehicle operational flight trajectory", and these documents (among other things) listed the LVDC preload settings.  Unfortunately, most of these reports are presently unavailable, though we do have a few of them.  For example, the AS-202 report says that "LVDC symbol" T1i, the time-to-go for first IGM stage, is preloaded with 299.25 sec, while Vex1, the J2 exhaust velocity for first IGM stage, is loaded with 4165.45 m/sec, and so on.

Finally, I claimed earlier that the AS-206RAM Flight Program is not, of itself, a complete program.  In that assessment, I'm not referring to the fact that when you try to assemble it you find that there are a few missing symbols, associated with variables that haven't been allocated.  That problem is simply due to the fact that the listing we have is an engineering version of the code that had never been debugged to the point of being released.  It's quite easy, I think, to fix up the assembly-time errors and warnings in the AS-206RAM so that it assembles error-free, and is entirely self-contained in that sense.  But it is still not complete in the larger sense I mean.

Rather, when I say that AS-206RAM is incomplete, I mean that it references code at specific hard-coded addresses which are not defined by the AS-206RAM program.  Indeed, there are large areas of core memory left undefined by the program.  Even the location in memory at which the power-up entry point should be stored is left undefined.  But for example, consider the concrete example of the code necessary for processing commands uploaded to the LVDC from mission control, as described in the Up-data section of this web-page.  When such a command is uploaded to the LVDC, an interrupt occurs.  The software then looks in an interrupt-vector table, which appears on p. 207 of the program listing, and looks like the following:


What the interrupt-vector table contains is HOP instructions that cause control to be transferred to the routines for servicing the various interrupt types ... in this case, an uploaded-command interrupt, the instruction that will be executed is HOP HCCMDC.  The symbol HCCMDC refers to a variable that holds a HOP constant defining the location in memory and the data-memory setup for the appropriate interrupt-service routine, as defined on p. 20 of the listing:



In other words, the interrupt-service routine for a command-upload is in memory module 4, sector 17, and its entry point is syllable 1 of location 267.  Similarly, the data-memory environment associated with that interrupt-service routine is module 4, sector 17.

And yet ... the source code contains no code in module 4, sector 17.  Indeed, module 4 sector 17 does not even appear at all in the octal listing of the assembled AS-206RAM code.  Some initialization routines do actually dynamically initialize a handful of constants in that sector, but they certainly do not seem to put any code at those locations. 

Thus if a command-upload interrupt were to occur, the result would be that the software jumped into the middle of a no-man's land of uninitialized memory.  And it's not just the command-upload interrupt, you can see from the image above that other no-man's-land jumps could occur as well:  to the self-test program, to the hardware evaluation program, or upon telemetry-station acquisition.

My inference from all that is that there is a separately assembled program which provides the code in those locations, and as discussed earlier, the best candidate for that at the moment is the Preflight Program ... for which we have no source code and no description whatsoever.

That's not to say that AS-206RAM cannot be run.  One workaround for this specific problem is to simply modify the interrupt-vector table to contain a HOP HCIRTN instruction in place of the HOP HCCMDC instruction it contains now.  Another workaround would be to simply disable the command-upload interrupt.  But alas, that's just one example of the potential range of problems the missing Preflight Program might cause.  Until a complete survey of all jumps into no-man's land is made, it's impossible to know yet whether all of them can be worked around so easily.

Architecture of the LVDC

References

Unlike the AGC or AGS/AEA, there is no single document or couple of documents we've discovered so far that pull together the complete details of how the LVDC operates.  You can look at the full set of LVDC documents we've collected in our document library.  But the specific documents helpful for piecing together this section, none of which were originally intended as documentation for developers, are the following:
  1. 1 October 1963:  Apollo Study Report, Volume II, which was part of IBM's feasibility study for using the LVDC in place of the AGC in the LM and CM.
  2. 31 October 1963:  IBM's Saturn V Guidance Computer, Semiannual Progress Report.
  3. 30 November 1964:  Laboratory Maintenance Instructions: Saturn V Launch Vehicle Digital Computer, Simplex Models, Volume I: General Description and Theory.  This is IBM's documentation for the LVDC "breadboard model II" system.  (I'm not presently aware of any existing documentation of the production "TMR" LVDC.) 
  4. 1 February 1966:  "The Astrionics System of Saturn Launch Vehicles" by Rudolf Decher.
  5. 1 November 1968:  Astrionic System Handbook, Saturn Launch Vehicles, chapters 11 and 15.
  6. 1 November 1968:  Saturn Flight Manual, SA-503, chapter VII, particularly data on interrupts and i/o.
  7. 30 September 1972:  Skylab Saturn IB Flight Manual, chapter VI, again for data on interrupts and i/o.
Among these, it would be fair to state that reference #3 was used primarily (the instruction set is covered in table 2-8 on pp. 2-16 through 2-20), and the others were used to cross-check or to provide guidance or information missing from reference #3.  (In retrospect, however, I would recommend reference #4 as a better starting point to those readers who don't find my musings amusing, though it does not cover the instruction set.)  Where there were discrepancies between earlier documents and later documents, the later documents were treated as definitive.

General Characteristics of the Computer



The illustration above shows a simplified block diagram of the LVDC.  The device itself was designed and manufactured by IBM Federal Systems Division.  Mechanically, the LVDC had dimensions of about 29.5"×12.5"×10.5", and weighed about 72.5 pounds.  A number of different power supplies were needed:  +6V, -3V, +12V, and +20V, at roughly 150W. 

However, the LVDC and the Launch Vehicle Data Adapter (LVDA) really operated as a pair, and neither is of use without the other, so in considering the mechanical and electrical characteristics of the LVDC one really needs to include those of those of the LVDA into their thinking.  The LVDA had dimensions of about TBD"×TBD"×TBD", and weighed about 214 pounds.  Its electrical budget was about 320W and it accepted +28V power.  The purpose of the LVDA was basically to intermediate between the LVDC and the remainder of the rocket.

In computer terms, LVDC had the following characteristics:
The CPU had the following additional registers not addressable as normal memory:
There are some memory words in the "residual sector" (see below) that are real memory (unlike 0775), but nevertheless have special purposes and so should be avoided for general usage:
Finally, address 000—I believe, in module 0 sector 0 but am not sure—stores a HOP Register code loaded at reset.

Layout of Memory Words




The illustration above depicts the data organization within a memory word.   For numeric data, there is a sign bit (designated "S" in the illustration), with the bit labeled "1" being the most-significant bit and the bit labeled "25" being the least-significant bit.  For non-numeric data, the bits were designated instead as "1" through "26", with no overlap between the designation of the bits for numeric data.  A very curious system indeed, in modern thinking!

The situation is even curiouser, to paraphrase Alice, when considering instructions stored in the syllables in place of numeric data.  In those cases the bits are interpreted as in the following illustration:


The instruction in Syllable 0 is processed first, and the instruction in Syllable 1 is processed second.  In most cases, the bits labeled OP1-OP4 contain an instruction code and the bits A1-A9 contain an address in the range 0-511 on which the instruction operates.  There are, however, a few instructions in which A9 and/or A8 also form a part of the instruction code, in which case the addressable range is smaller.  Note that bits A1-A9 are neither in consecutive order within the memory word, nor are they in an order which would be consistent with the ordering of bits in data words.

These oddities in bit-ordering are due to the fact that memory was read serially into the CPU, so the ordering of the bits is optimized to mimic the order in which the CPU needed to use them.

For the purpose of executing instructions, memory is considered as being divided into "sectors" of 256 words each.  Address bits A1-A8 select an offset into a sector, while A9 (known as the "residual bit") selects which sector is being addressed.  When A9 is 0, the currently-selected sector is being addressed, while when A9 is 1 a special sector called the "residual sector" is being addressed.  The "residual sector" is always sector 017 (octal) in the current memory module.  Incidentally, the "residual sector" can only be used for addressing memory, and there's no way to access instructions in it unless it happens to be the currently-selected instruction sector as explained in the paragraph that follows.  In essence, the "residual sector" is good for storing global variables whilst the currently-selected data sector is good for storing local variables.

Memory-sector selection and the "residual sector" become clearer when contemplating the HOP Register mentioned earlier:



The meanings of these fields may already be clear to you, but just in case they are not:
Upon reset, the CPU loads the HOP Register with the value stored at address 000 (presumably in memory module 0 sector 0), and this determines where program execution starts.

Below are some photos sent to us by Dmitris Vitoris of an LVDC "page assembly", which I suppose (but am not sure) is a memory module.  (I'm not really sure which is the best photo, so I've just included them all.)

LVDC page assembly, end view
LVDC page assembly
LVDC page assembly,
                        top view
LVDC page assembly,
                        top view
LVDC page assembly,
                        top view


CPU Instructions



Mnemonic

A
8

A
9
O
P
4
O
P
3
O
P
2
O
P
1
Timing
(computer
cycles)


Description of the instruction
HOP
X
X
0
0
0
0
1
This instruction combines an unconditional jump instruction with various other configuration options, such as memory-sector selection.  The way it works is that the address A1-A9 points to a memory word that contains a "HOP constant", and the HOP instruction transfers that HOP constant into the HOP register.  Recall that A1-A8 select the offset within a 256-word sector, and A9 is the "residual bit" that selects between the current sector and the "residual sector".  There is no provision for a partial HOP constant, and the full HOP constant needs to be given every time a HOP instruction is used.  See also CDS and TRA.

Although the machine instruction requires the address of the HOP constant to be provided as its operand, the assembler is flexible enough to allow the operand to instead be a left-hand symbol for the target location in the code.  When it encounters this situation, the assembler transparently performs a workaround.  For the sake of discussion, imagine assembly-language code something like the following:
        HOP     HIGTHR
        .
        .
        .
HIGTHR  ... more code ...
What the assembler does in a case like this is:
  1. If the target address is in the current instruction-memory sector (either in the same or the opposite syllable), transparently replace the HOP instruction by a TRA instruction.  Otherwise, proceed to step 2.
  2. Allocate a data word in the current data sector.  Do this by searching upward in the data sector, looking for the first unused location.  The residual sector is searched if there's no room left in the current sector.
  3. Create a HOP constant for the target location, and store it in the newly-allocated data word.
  4. Use the address of the newly-allocated data word as the operand for the HOP instruction.
Whenever such a substitution was performed by the assembler, it noted it in the assembly listing by printing "HOP*" as the name of the instruction rather than "HOP" as what would have been used on the actual punch card.
MPY
X
X
0
0
0
1
1
(results
available after 4)
This is a multiplication instruction.  It multiplies two 24-bit numbers to produce a 26-bit product.  The accumulator provides the address of one operand, and the address embedded in the instruction points to the other operand.  Recall that A1-A8 select the offset within a 256-word sector, and A9 is the "residual bit" that selects between the current sector and the "residual sector".  In both cases, the most-significant 24-bits of the operands are used, and the least-significant 2 bits of the operand are ignored.  A partial product (24 bits from the addressed memory times the 12 less-significant bits from the accumulator) can be fetched from the P-Q Register (0775 octal) on the 2nd instruction (or more accurately, two computer cycles) following MPY, though there is no need to do so if that value isn't desired by the program.  The full product is available from the accumulator or from the P-Q Register on the 4th instruction (more accurately, 4 computer cycles) following MPY.  However, the result will remain in the P-Q register until the next MPH, MPY, or DIV
SUB
X
X
0
0
1
0
1
Subtracts the contents of a word pointed to by the address embedded within the instruction from the accumulator, and puts the result back into the accumulator.  Recall that A1-A8 select the offset within a 256-word sector, and A9 is the "residual bit" that selects between the current sector and the "residual sector".  See also RSU.

Regarding borrow from the operation, the CPU provides no direct way of accessing it, and thus no easy way to perform multi-precision subtraction.  Refer to the notes for the ADD instruction for more information.
DIV
X
X
0
0
1
1
1
(results
available
after 8)
This is the division instruction.  The contents of the accumulator are divided by the operand pointed to by the address A1-A9 embedded within the instruction to produce a 24-bit quotient.  Recall that A1-A8 select the offset within a 256-word sector, and A9 is the "residual bit" that selects between the current sector and the "residual sector".  The quotient is available in the P-Q Register (0775 octal) on the 8th instruction (more accurately, 8 computer cycles) following the DIV.  However, the result will remain in the P-Q register until the next MPH, MPY, or DIV.
TNZ
X
X
0
1
0
0
1
This is a conditional jump instruction, which branches to the address embedded in the instruction if the accumulator is not zero, but simply continues to the next instruction in sequence if the accumulator is zero.  Bits A1-A8 of the embedded address represent the new offset within the currently selected 256-word instruction sector, while bit A9 gives the syllable number within that word.   The "residual sector" cannot be accessed.  See also TMI.

As mentioned, the target address for the machine instruction itself had to be within the current sector, because its 8-bit address offset is embedded within the instruction.  However, the assembler would transparently work around this problem, allowing essentially any target address to be used.  For the sake of discussion, imagine an assembly language instruction,
TNZ    OINIT
in which the target location OINIT is not in the current memory sector.  The workaround procedure used by the assembler was this:
  1. Working downward from the top of the current instruction-memory sector, find an unallocated memory location.
  2. In that newly-allocated memory location, put a "HOP OINIT" instruction.
  3. Use the newly-allocated memory location as the target of the TNZ instruction.
  4. In the assembly listing, display the operator as "TNZ*" rather than "TNZ".  (But "TNZ" is nevertheless what actually appeared on the punch cards.)
Of course, this workaround preserves the intended logic, at the cost of an extra instruction word and a couple of extra machine cycles.
MPH
X
X
0
1
0
1
5
This is a multiplication instruction.  It is exactly like MPY except that the program "holds" until the multiplication is complete, so that the product is available from the accumulator or from the P-Q Register at the next instruction following MPY.  However, the result will remain in the P-Q register until the next MPH, MPY, or DIV.
AND
X
X
0
1
1
0
1
Logically ANDs the contents of the accumulator with the contents of the address embedded within the instruction and places the result in the accumulator.  Recall that A1-A8 select the offset within a 256-word sector, and A9 is the "residual bit" that selects between the current sector and the "residual sector".
ADD
X
X
0
1
1
1
1
Adds the contents of the accumulator with the contents of the address embedded within the instruction and places the result in the accumulator.  Recall that A1-A8 select the offset within a 256-word sector, and A9 is the "residual bit" that selects between the current sector and the "residual sector".

What about the carry bit?  As far as I can tell, the CPU has no provision for carry bit that's useful at the software level.  If you want to do multi-word precision arithmetic (say, 52-bit addition instead of just 26-bit addition), then you have to find some indirect, software-only way of detecting carry rather than on relying on the CPU to provide you with some easy way of handling it.  It's certainly mathematically possible to do so:  When adding two addends of the same sign using 2's-complement arithmetic, you can detect carry because the sum has the opposite sign of the addends, whereas adding two addends of opposite signs cannot result in carry anyway.  But the coding to exploit this mathematical possibility is obviously going to be cumbersome and inconvenient.  (The low-level adder circuit itself can deal with a carry bit, of course.  The adder performs additions serially, starting with the least-significant bit and moving upward to the most-significant, and at each bit-stage there's a carry bit from the previous stage to worry about.  However, the final carry bit is not accessible to software, and the carry-bit latch is cleared by any CLA instruction, making it very tough to transfer the carry-bit latch's contents from one word-addition to the next.  In theory, if you could figure out a way to do multi-precision arithmetic without using CLA, perhaps you could exploit that hidden carry bit.  But I'm having trouble seeing any way you might do it.  That could just be my failure of imagination, of course.)
TRA
X
X
1
0
0
0
1
TRA is an unconditional jump instruction, which branches to the address embedded in the instruction.  Bits A1-A8 of the embedded address represent the new offset within the currently selected 256-word instruction sector, while bit A9 gives the syllable number within that word.   The "residual sector" cannot be accessed.

Note, however, that the assembler transparently worked around the limitation that the target address had to be in the same sector.  The assembler would automatically insert a HOP instruction instead of a TRA whenever it found that it was necessary to do so.  For example, consider the instruction "TRA ETCBTC".  If the target location ETCBTC is within the current instruction sector, the assembler would indeed assemble this exact as expected, using a TRA instruction with opcode 1000.  Actually, the assembler would refuse to directly do a TRA to a target in the same instruction sector under some circumstances, presumably to help guard the programmer from easy-to-make errors.  The condition I've noticed in which this occurs is if the target address has been tagged by the assembler as being in a region with a different setting for the data module or sector, since unlike a HOP instruction, a TRA instruction doesn't alter the DM/DS settings.  Whereas if a CDS instruction (which changes the DM/DS settings in the processor itself) happens to be at the target location, it doesn't trigger a replacement by HOP.  Quite a complicated set of conditions! One wonders if the original programmers actually had much awareness at the time (or cared!) that these substitutions were being made for them.

But if the target location (ETCBTC in this example) wasn't within the current instruction sector or failed the DM/DS conditions, then the assembler would instead perform the following complicated maneuver which preserves the expected program logic, at the cost of an extra machine cycle and an extra word of memory:
  • Allocate a data word in the current data sector.  This is done by searching upward through the current data sector (or the residual sector, failing that) until an unallocated word is found.
  • Create a HOP constant for the target location ETCBTC, and store it in the newly-allocated data word.
  • Assemble a HOP instruction with the newly-allocated data word as its operand.
  • Whenever such a substitution was performed by the assembler, it noted it in the assembly listing by replacing "TRA" with "TRA*".  However, I think that "TRA" is always what would have originally been used on the punch card.
This in so far as the assembly-language source code is concerned, it seems that one may as well always use TRA rather than HOP, since TRA is more economical than HOP, and the assembler will always substitute HOP anyway whenever some limitation of TRA necessitates it.
XOR
X
X
1
0
0
1
1
Logically exclusive-ORs the contents of the accumulator with the contents of the address embedded within the instruction and places the result in the accumulator. Recall that A1-A8 select the offset within a 256-word sector, and A9 is the "residual bit" that selects between the current sector and the "residual sector".
PIO
X
X
1
0
1
0
1
Reads or writes an i/o port.    Bits A1-A9 select the source and destination of the i/o.   A table of the i/o ports vs. addresses is given in the following section.

In so far as assembly-language syntax is concerned, the operand of the instruction is always a literal octal numerical constant.
STO
X
X
1
0
1
1
1
Stores the contents of the accumulator in the word indicated by the address embedded within the instruction.  Recall that A1-A8 select the offset within a 256-word sector, and A9 is the "residual bit" that selects between the current sector and the "residual sector".  The following addresses are special, as described in the documentation of the STO instruction (see p. 2-17):
  • 0775 (octal) stores into the Product-Quotient register rather than to normal memory.
  • 0776 (octal) stores the contents of the multiplicand-divisor register (rather than the accumulator) to address 0776.
  • 0777 (octal) stores the contents of the multiplicand-divisor register (rather than the accumulator) to address 0777.

However, the description of 0776 and 0777 above is quite misleading.  For one thing, the way the multiplication and division instructions work, the inputs to those operations (multiplicand, multiplier, dividend, divisor) are all supplied in the accumulator and memory locations anyway, so why would you need an instruction to separately access the multiplicand or divisor?  The answer is that this feature has nothing at all to do with multiplication and division, but is instead crucial to storing return addresses in subroutines and interrupts!

The explanation is this:  During the process of executing any given LVDC instruction, a HOP constant for the LVDC instruction at the next successive memory address is formed.  Keep in mind that the next instruction successively in memory is not necessarily the next instruction sequentially executed in time.  Whatever the next instruction executed — unless it happens to be a multiplication or division (MPY, MPH, DIV) — the previously-generated HOP constant is temporarily shoved into multiplicand-divisor register.  Thus if the very next instruction executed is STO 776 or STO 777 (and the previous instruction was not MPY, MPH, or DIV), what ends up getting stored in location 776 or 777 is not a multiplicand or divisor, but rather the HOP constant for the memory address that numerically follows the previously executed instruction.  Which is not useful at all when instructions are simply executing sequentially, but is useful whenever there is a transfer of control because the code to which control has been transferred can use it to access its own return address ... and there is no other painless method provided in the instruction set to do so.

In other words, in a subroutine or interrupt-service routine, the very first instruction should be a STO 776 or STO 777 to save the HOP constant for the return address.  Conversely, the instruction for returning from the subroutine or interrupt is a matching HOP 776 or HOP 777 instruction.

TMI
X
X
1
1
0
0
1
This is a conditional jump instruction, which branches to the address embedded in the instruction if the accumulator is less than zero, but simply continues to the next instruction in sequence if the accumulator greater than or equal to zero.  Bits A1-A8 of the embedded address represent the new offset within the currently selected 256-word instruction sector, while bit A9 gives the syllable number within that word.   The "residual sector" cannot be accessed.  See also TNZ.

As mentioned, the target address for the machine instruction itself had to be within the current sector, because its 8-bit address offset is embedded within the instruction.  However, the assembler would transparently work around this problem, allowing essentially any target address to be used.  The workaround used by the assembler is that same as that described for the TNZ instruction above.  Instructions for which the workaround have been applied are shown on the assembly listing as "TMI*" rather than "TMI".
RSU
X
X
1
1
0
1
1
Same as SUB, except that the order of the operands in the subtraction is reversed.
CDS
or
CDSD
or
CDSS
X
0
1
1
1
0
1
Change the currently-selected 256-word data sector.  For this instruction, A9 forms a part of the instruction itself, so only A1-A8 are significant.  The partially overwrite the HOP Register as follows:



See also HOP.

In terms of assembly-language syntax, there are the following variations:
CDS     SYMBOLNAME
CDSD    DM,DS
CDSS    DM,DS
Thus CDS uses the characteristics of a variable name or a name defined with the DEQD or DEQS pseudo-ops (see below), whereas the module number and sector number are simply supplied with octal numeric literals in CDSD or CDSS.  The difference between CDSD and CDSS is that the former selects duplex memory while the later selects simplex memory.

In the usage I've seen, usage of CDSS is confined almost entirely to the context of USE DAT (see below).
SHF
0
1
1
1
1
0
1
Performs a logical shift operation on the accumulator.  For this instruction, bits A8 and A9 form a part of the instruction itself, but of the remaining bits only A1, A2, A5, and A6 are actually used, as follows:

A1
A2
A5
A6
Description of operation
0
0
0
0
Clears the accumulator
1
0
0
0
Shift one position towards in the less-significant direction
0
1
0
0
Shift two positions towards in the less-significant direction
0
0
1
0
Shift one position towards in the more-significant direction
0
0
0
1
Shift two positions towards in the more-significant direction

In terms of assembly-language syntax, I have never seen SHF itself used.  The only variations actually used are
SHL N
SHR N
where N is a literal decimal numerical constant.  However, N is not limited to just 0, 1, or 2, even those are all that SHF directly supports.  If an operand N>2 is encountered, the assembler transparently replaces it with an appropriates sequence of shift-by-2 and shift-by-1 instructions.
EXM
1
1
1
1
1
0
1
"Execute modified".  This instruction takes an instruction stored at a specified memory location, forms a modified A1-A9 field for that instruction, executes that instruction, and then continues with the next instruction following the EXM (unless the program counter has been modified).  For this instruction, A8 and A9 form a part of the instruction code, so only A1-A7 are significant.  Only 4 different target words are allowed, 0600, 0640, 0700, and 0740, and they are all in the "residual sector".  Many of the bits in A1-A7 represent various types of modifications to the embedded address at the target address rather than being address bits themselves.   Here are the interpretations of bits A1-A7 in the EXM instruction:
  • A1-A4 modify the address embedded in the target instruction as described below.
  • A5 indicates the syllable in which the target instruction resides.
  • A7,A6 selects the target address, as follows:
    • 0,0 for target address 0600
    • 0,1 for target address 0640
    • 1,0 for target address 0700
    • 1,1 for target address 0740
When the target instruction is executed, its A1-A9 bits are formed as follows:
  • A1-A2 come from A1-A2 of the EXM instruction.
  • A3 is the logical OR of the A3 of the target instruction and A3 of the EXM instruction.
  • A4 is the logical OR of the A4 of the target instruction and A4 of the EXM instruction.
  • A5-A8 come from A5-A8 of the target instruction.
The data sector used when the target instruction is executed is also changed for the duration of that one instruction, as determined by bits A1, A2, and A9 of the unmodified target instruction, as follows:

A2
A1
A9
Data Sector
0
0
0
004
0
0
1
014
0
1
0
005
0
1
1
015
1
0
0
006
1
0
1
016
1
1
0
007
1
1
1
017 ("residual" sector)

In baseball terms, this is the "infield fly rule" of the LVDC: it clearly does something, but it's hard to grasp exactly what it does.
CLA
X
X
1
1
1
1
1
Store a value to the accumulator, from the memory word at the address embedded within the instruction.   Recall that A1-A8 select the offset within a 256-word sector, and A9 is the "residual bit" that selects between the current sector and the "residual sector".

I/O Ports (For PIO Instruction)

My understanding of this is sketchy right now, so take anything I have to say with a grain of salt.

Address Field from PIO Instruction
Data Source
Data Destination
Specific I/O Ports
A9
A8
A7
A6
A5
A4
A3
A2
A1
X
0
A
A
A
A
A
0
A
LVDC Accumulator Register
LVDA Telemetry Registers
(Note:  Used to output telemetry consisting of the values of variables, typically via the TELEM macro in the LVDC source code.  For definitions of non-standard units of measurement, see the later discussion of that topic.  Page-number references are to the AS-206RAM LVDC source code or to its abridged form.)
A9 - A1 (octal)
Variable
Scale
Units
Interpretation
000
TASEC
ONTAS
B15
sec
Time elapsed from GRR or time of last orbit navigation
001
CHIZ
B0
pirad
Yaw guidance command in space-fixed coordinates
004
ACCZ

0.05 m/sec
Accelerator optisyn reading
005
CHIX
B0
pirad
Pitch guidance command in space-fixed coordinates
010
ACCX

0.05 m/sec Accelerator optisyn reading
011
CHIY
B0
pirad
Roll guidance command in space-fixed coordinates
014
ACCY

0.05 m/sec Accelerator optisyn reading
015
THETZ B0
pirad Whole yaw gimbal angle
021
THETX
B0
pirad Whole roll gimbal angle
025
THETY
B0
pirad Whole pitch gimbal angle
030
TLTBB
B15
sec
Biased time elapsed in time base
031
TB
B15
sec
Time elapsed in current time base
034
ZS
ONZN
B23
m
Component of position in space-fixed coordinates
044
XS
ONXN
B23
m
Component of position in space-fixed coordinates
050
YS
ONYN
B23
m
Component of position in space-fixed coordinates
054
GR10R
B-22
m-1 Reciprocal of total radius in space-fixed coordinates
060
GZ
B4
m/sec2 Component of gravitational acceleration
064
GX
B4
m/sec2 Component of gravitational acceleration
070
GY
B4
m/sec2 Component of gravitational acceleration
074
TBA
B15
sec
Set equal to TBB at the time of a C-band station gain or loss
075
SS


Switch selector interrupt
110
ZDS
B14
m/sec
Component of velocity in space-fixed coordinates
114
XDS
B14
m/sec Component of velocity in space-fixed coordinates
120
YDS
B14
m/sec Component of velocity in space-fixed coordinates
124
V
B14
m/sec Total velocity in space-fixed coordinates
134
ZS
B23
m
Component of position in space-fixed coordinates
144
XS
B23
m
Component of position in space-fixed coordinates
150
YS
B23
m
Component of position in space-fixed coordinates
174
ACCT
TLPAST
B25
qms
Real time clock reading
400
T1I
B10
sec
First IGM phase time-to-go
414
MC28


Mode code 28 status changes.  Refer to p. 12.
415
MC24


Mode code 24 status changes.  Refer to pp. 4-5.
420
MC27


Mode code 27 status changes.  Refer to pp. 11-12
421
MC25


Mode code 25 status changes.  Refer to p. 5.
425
DORSW


Discrete output register contents.  A change indicates a guidance failure.
431
ICR


LVDA internal control register contents.  Refer to pp. 5-6.
434
Z4
B23
m
Position in target plane coordinates
435
EMRR
TLEMR1


Error monitor register contents.  Change indicates ladder failure or redundancy disagreement.
441
INT


LLS, OBCO, TLC, or S4BCO interrupt.  Refer to p. 6.
444
X4
B23
m
Position in target plane coordinates
450
Y4
B23
m
Position in target plane coordinates
460
T3I
B10
sec
Time-to-go in S4B second burn
461
TLCHPC


TLC ("tough luck charlie") HOP constant
465
DIS.IN


Discrete inputs.  Refer to p. 6.
471
SQ3
B1

(1-ZSP2)1/2
475
TBBU


Time base backup
500
FDBK


Switch-selector feedback register, in the event of an error
501
MLCHIZ
B0
pirad
Minor loop CHIZ
504
ZDDV
B4
m/sec2 Component of vent acceleration
505
MLCHIX
B0
pirad
Minor loop CHIX
510
XDDV
B4
m/sec2 Component of vent acceleration
511
MLCHIY
B0
pirad
Minor loop CHIY
514
YDDV
B4
m/sec2 Component of vent acceleration
520
ZDDD
B4
m/sec2 Component of drag acceleration
524
XDDD
B4
m/sec2 Component of drag acceleration
530
YDDD
B4
m/sec2 Component of drag acceleration
534
ALT
B4
m
Altitude
535
TLEMR8


Minor-loop error code
544
TAUD
B10
sec
Time-to-go
561
TI
B15
sec
Time from GRR of time-base change
570
(various)


Minor-loop error code.  Refer to pp. 6 and 14.
571
ETC


End of telemetry cycle, one per computation cycle
575
BTC


Begin telemetry cycle, one per computation cycle

0
1
A A A A A 0
A
LVDC Main Memory
LVDA Telemetry Registers
1
1
A A A A A 0
A
LVDC Residual Memory
LVDA Telemetry Registers
X
0
A
A
A
A
A
1
0
LVDC Accumulator Register LVDA Output Registers
A7 - A1 (octal) Purpose
006
Mode Register
012 Discrete Output Register (Reset).  The LVDC EDD for the Saturn IB Flight Program document, section 7.3, summarizes the discrete outputs as:

016 Discrete Output Register (Set).  See PIO 012 above.
022 Internal Control Register (Set)
026 Internal Control Register (Reset)
032 Interrupt Register Reset
036 Switch Selector Register (Load)
042 Orbital Checkout
052 Switch Selector & Discrete Output Registers (Read)
062 Switch Selector Interrupt Counter
066 COD Error (Read)
072 Inhibit Interrupt
076 Minor Loop Timed Interrupt Counter
146 Ladder No. 1
152 Ladder No. 2
156 Ladder No. 3
162 Ladder No. 4
166 Ladder No. 5
0
1
A
A
A
A
A
1
0
LVDC Main Memory LVDA Output Registers
1
1
A
A
A
A
A
1
0
LVDC Residual Memory LVDA Output Registers
X
0
A
A
A
A
A
1
1
LVDA Peripheral Inputs and Errors
LVDC Accumulator
A8 - A1 (octal) Purpose
023
Error Monitor Register
043
Command Receiver or RCA-110
053
Discrete Input Spares.  Refer to the DIS1-DIS8 inputs in the table for PIO 057 below.
057
Discrete Inputs.  The LVDC EDD for the Saturn IB Flight Program document, section 7.4, summarizes the discrete inputs in the table below.  (The DIS1-DIS8 inputs in the table for PIO 053 above.)

The Command Decoder OM/D bits mentioned in the table originate in an up-data command word transmitted to the rocket from mission control.  There are two such bits in the transmitted word, but the LVDA combines them into a single bit before passing them to the LVDC — hence, OM/D bits "A" and "B" both appear at the same position in PIO 057.  A value of 1 in the OM/D bit indicates that a "mode" command word has been received (and is accessible via PIO 043), while a value of 0 indicates instead that a "data" command word has been received (in PIO 043).
067
Telemetry Scanner
077
Switch Selector
103
Real Time
107
Accelerometer Processor X
117
Accelerometer Processor Z
127
Accelerometer Processor Y
137
Interrupt Storage
X
1
A
A
A
A
A
1
1
LVDA Resolver Processor Inputs
LVDC Accumulator
A7 - A1 (octal) Purpose
203
Z Gimbal Backup.  (This is not covered by the available documentation, but seems to be implied by the comments in the source code.)
207
Spare No. 6
217
Computer COD Counter Start
223
Fine Gimbal No. 1
233
Coarse Gimbal No. 3
237
Computer COD Counter Start
243
Coarse Gimbal No. 1
247
Horizon Seeker No. 1
257
Spare No. 3
277
Spare No. 4
313
Fine Gimbal No. 4
317
Spare No. 1
323
Horizon Seeker No. 3
327
Horizon Seeker No. 2
333
Coarse Gimbal No. 4
337
Spare No. 5
343
Coarse Gimbal No. 2
353
Fine Gimbal No. 3
357
Spare No. 2
363
Fine Gimbal No. 2
367
Horizon Seeker No. 4

Subroutine Linkage

While the LVDC is executing an instruction, it is also forming the HOP constant of the next instruction in sequence—i.e., on the assumption that no branch occurs—and this HOP constant is available to the next instruction that executes.  In most cases, this means that an instruction can find out its own address (which isn't very interesting), but if the previous instruction was a branch then it means that the current instruction can determine the address of the instruction in sequence that followed the branch instruction, and thus can use this information for setting up returns from subroutines.  The instruction for fetching that HOP constant and saving it is either STO 0776 or STO 0777.  Only those special addresses work.  So a typical subroutine linkage would look like this:

    TRA     MYSUB
        ... return to here ...

        ...

MYSUB   STO     0776    # or, STO 0777
        ... do stuff ...
        HOP     0776    # or, HOP 0777

Interrupts

There are up to 12 external interrupt sources, buffered by the LVDA.  Upon any of these becoming active, the LVDA generates a master interrupt signal to the LVDC.  When the LVDC receives the master interrupt signal, assuming that interrupts have not been programmatically inhibited, the following actions occur:
  1. The LVDC automatically masks that particular interrupt from occurring again until explicitly reenabled (at step 9 below).
  2. The LVDC allows any instruction, multiplication, or division in progress to complete.
  3. The LVDC performs a HOP 0400, thus transferring control to an interrupt-service routine—or, as the original documents refer to it, an "input-output subprogram".  Recall that this does not jump to address 0400, rather it loads the HOP Register from the value stored at address 0400, and this can be used to jump to a location, set the current memory module and sector, etc.  (Early non-flight hardware seemingly used a HOP 0776 instead of HOP 0400.)
  4. The first instruction executed by the interrupt service routine must be either STO 0776 or STO 0777 to store the pre-interrupt value of the HOP Register at either address 0776 or 0777.
  5. The interrupt service routine should then save any registers or common memory locations that were going to be altered into local storage.  In almost all cases, one would suppose that the accumulator needed to be stored.  If multiplications or divisions would be used, the P-Q Register would also need to be saved, presumably with a pair of instructions like CLA 0775 followed by STO somewhere.
  6. The interrupt service routine should do a PIO from the Interrupt Storage input port to determine which of the 12 interrupt sources are active, so that it can vector to an appropriate service routine for it.
  7. The interrupt service routine should then do whatever computations it needed to do.
  8. The interrupt service routine should restore the registers it had saved (CLA somewhere followed by STO 0775 to restore the P-Q Register, if necessary, followed by a restoration of the accumulator).
  9. The interrupt service routine should do a PIO to the Interrupt Register Reset output port, with just the specific bit set corresponding to the interrupt type being processed, to reenable that specific interrupt.  The particular flavor of PIO performed should, of course, take the Data Source from memory rather than from the accumulator, since at this point the accumulator has already been restored to its pre-interrupt condition.
  10. The return from interrupt is either HOP 0776 or HOP 0777, depending on which location the HOP constant had been stored on entry to the interrupt service routine.
Interrupts can be programmatically masked or unmasked with PIO to Interrupt Inhibit.  By setting a bit in Interrupt Inhibit corresponding to the interrupt whose masking is desired, that interrupt is thereby masked.  Any combination of bits can be set, so any combination of interrupts can be masked.  To reenable the interrupt, a 0 is written to the corresponding bit in Interrupt Inhibit.

The available interrupts, and their bitwise positioning in the i/o registers mentioned above, differed for the Saturn IB rocket (Apollo 7, ASTP, Skylab) vs. the Saturn V rocket (Apollo 8-17).  The Saturn IB and Saturn V descriptions below were taken from different documents (namely, Skylab Saturn IB Flight Manual p. 6-21 and Saturn Flight Manual, SA-503 p. 7-21, respectively), and if one accounts for the differences in language between the two documents, the interrupts are almost entirely the same. I've marked the ones in red that seem different to me, as well as any other points of particular difficulty for me.

LVDC Data Word Bit Position
Description of function in Saturn IB
Description of function in Saturn V
Are these actually the same thing?
Comments
11
RCA-110A interrupt
Command LVDA/RCA-110A interrupt
Probably.
The RCA-110A is the ground-control computer.  This interrupt implies that a command word has been received via digital uplink and is ready to be processed.  See section 6.2.3 of Astrionic System Handbook, Saturn Launch Vehicles.
10
S-IB low-level sensors dry "A"
S-IC inboard engine out "A"
If interpreted as "first stage engine out", yes.
9
RCA-110A interrupt
Program re-cycle (RCA-110A) interrupt
Probably.
The RCA-110A is the ground-control computer.  The following is partly speculation, so take it with a grain of salt: I believe that this interrupt may occur when a special uplink command ("Terminate") is received.  The purpose of the "Terminate" command is to halt an operation from a previously uplinked command (see above) and to return the LVDC flight program to normal operation.  Since the "command LVDA/RCA-110A" interrupt would be disabled until that processing is completed, a separate interrupt for the "Terminate" command is needed, and that is the "Program re-cycle" interrupt.
8
S-IVB engine out "B"
S-IVB engine out "B"
Yes.

7
S-IB outboard engines cutoff "A"
S-IC propellant depletion/engine cutoff "A"
If interpreted as "first stage engine cutoff", yes.
6
Manual initiation of S-IVB engine cutoff "A"
S-II propellant depletion/engine cutoff
Both refer to the second stage, but ... don't know!

5
Guidance reference release
Guidance reference release
Yes.

4
Command decoder interrupt "A" or "B"
Command receiver interrupt
Probably.
I think this interrupt comes from the decoder that interprets uplinked data (see the two RCA-110A interrupts above), but it's unclear to me what the purpose is, or how "A" and "B" differ.
3
Simultaneous memory error
Temporary loss of control
Yes.
"Simultaneous memory error" refers to simultaneous parity errors in a single address mirrored in duplexed memory modules.  This is also known by the acronym TLC, which is related in an obvious way to the description "Temporary Loss of Control" supplied by the documentation.  However, "temporary loss of control" is quite an optimistic way of looking at it, because there is no method of recovery from it.  Far from being "temporary", the error is basically immediately catastrophic in the real world of the rocket, and therefore very permanent.  I have been told that the LVDC programmers called this the "Tough Luck Charlie" interrupt, and indeed there is a reference to this in the LVDC source code.
2
Spare
Computer interface unit interrupt
No.

1
Internal to the LVDC
Switch selector interrupt
Probably.
The switch-selector interrupt and the minor-loop interrupt are generated internally by the LVDC/LVDA.
S
Internal to the LVDC
Minor loop interrupt

Telemetry

Output telemetry from the Instrumentation Unit transmitted to mission control can be sourced from either the LVDC or the LVDA.  The LVDA spontaneously outputs telemetry on its own accord only when an error indication appears in its error-monitor register.  The normal telemetry case, however, is that the LVDC initiates output telemetry by sending data to the LVDA via a PIO CPU instruction, and then the LVDA handles subsequent details of actually transmitting the data.  In both cases, the output is in the form of 10-bit arrays, though the output packet logically consists of 4 such successive 10-bit arrays: i.e., of 40 bits. 

However, the specifics differ depend somewhat on the source.  We'll confine our description to data sourced by the LVDC.  As mentioned above, telemetering an output 40-bit packet from the LVDC's perspective is done by means of the CPU's PIO instruction, for which a variety of i/o addresses are reserved for telemetry operations.  Of the 40 bits:
Different documentation differs in describing the arrangement of these various bits within the 40-bit packet, and does not fully explain the transmission order of the bits, so I'll refrain from discussing that topic here.

A 40-bit packet requires 4.17 milliseconds to transmit, and thus the LVDC needs to space out its telemetry requests by at least this amount of time, or else subsequent telemetry request it outputs to the LVDA will overwrite and destroy prior requests.

As far as the numerical specifics of the tag values used in the 40-bit packets, this is TBD.

Up-data

(A lot of information in this section is abstracted from the Astrionics System Handbook, chapter 6, "Radio Command Systems".)

The term up-data refers to commands transmitted from mission control to the LVDC/LVDA.

As transmitted, the standard command-word format consists of 35 bits:

The latter two sets of bits are interspersed within the message, and thus are not transmitted in the specific order shown above.

However, the as-transmitted format of the data isn't really very relevant to how the LVDC and its software relate to the up-data, since only a portion of the transmitted bits reach the LVDC software — specifically only some of the bits from the final group of 18 — and even then they don't always reach the LVDC in the exact form they are transmitted.  Thus, let's narrow our discussion of the up-data to just the LVDC's perspective.  The 18 control&data bits of the message are further categorized as:

2 "OM/D" or orbital mode/data (OM/D) bits.  (These are depicted in the image above, presumably mistakenly, as DM/D bits.)  The LVDA combines this pair of bits (in some manner unknown to me), and thus the LVDC receives a single OM/D bit.  The LVDC recognizes two types of commands, mode commands words and data command words, and the type of the command word is determined by the OM/D bits.  

Similarly, there are two transmitted "interrupt bits" (see the image above).  These cause an interrupt to occur in the LVDC, which I believe is designated in the interrupt table given earlier as bit-position 4, Command Receiver Interrupt.

Finally, the 14 remaining bits actually represent just 7 bits of actual information, since each bit appears both in its normal form and in its logically-complemented form for the purpose of error-detection.

Refer to section 6.2 of the Astrionics System Handbook for more detail, but the LVDC software accesses the received command using the following general steps:

The command word read using PIO 043 has the format shown in the illustration to the right.  As mentioned above, there are 7 actual data bits, but they appear twice each:  Once "normally", and once inverted.  Besides that, there is a "sequence bit" which also appears normally (bit 8) and inverted (bit 1).  This bet helps to make sure the command words have been received in an appropriate order.  The sequence bit is 0 for the mode command word, then 1 for the first data command word (if any), and then it just toggles between 0 and 1 for each subsequent data command word received.  When the next mode command word is received, the sequence bit goes back to 0 and the pattern repeats.


LVDC Assembly Language

Unfortunately, there is no surviving manual or other reference describing the syntax of LVDC assembly language, nor the pseudo-ops available in it, nor any source code for the original LVDC assembler. 

This section is a compilation of my own inferences about those things from evaluating the available LVDC source code and from writing my own LVDC assembler.  The description is intended to be authoritative only in the sense that it describes how my own assembler (yaASM.py), which does produce an assembled core-rope image identical to that of the original assembler, handles the code.  However, my description shouldn't be taken as authoritative in any larger sense.  For example, when I say below that the assembler has an initial "preprocessor pass" that performs certain processing, I mean that yaASM.py has such a pass; I imagine that the original assembler did as well, but cannot guarantee it.

You'll probably need to refer to the previous discussion of the CPU instructions from time to time, in order to follow the discussion.

Basic Factoids

Empty lines:  Are ignored by the assembler.

Code comments (original Project Apollo):  Full-line comments seem to be prefixed by the character '*' in column 1.  Anything left over at the end of a line after the beginning of a line has been correctly parsed seems to be treated as a comment, and doesn't require any '*' prefix.

Code comments (modern Virtual AGC Project):  Comments I've added that were not present in the original listing are all full-line comments but with a '#' character in column 1.

Character set:  Upper-case alphabetic, numeric, and some punctuation.  This isn't really any great surprise, of course, if you reflect that all of the source code was supplied on computer punch cards to the IBM/360 mainframe computer which ran the assembler program, and that the character sets supported by keypunch machines did not include lower-case characters anyway.  Douglas Jones has written a fun exploration of punch cards, if you're interested in that kind of thing.  (By the way, I've heard anecdotally that the executable program which was output by the assembly of the LVDC software was punched into a long mylar tape, perhaps 1.5" wide, which was then transported by the coders themselves to Marshall Space Flight Center, where the mylar tape was put into a tape reader which transferred the program into the ferrite-core memory of the LVDC itself.  This was possible because, as you may recall from above, all of the ferrite cores in the LVDC were both readable and writable.  This contrasts to the AGC, in which the program was stored in read-only memory which had to be manufactured rather than simply written to.  So installing the software in the LVDC was a far less painful, less expensive, much quicker process than installing software in the AGC.  In the case of the AS-206RAM program listing, the assembler helpfully reports that the punch tape is 432 feet long.)

Symbols (names of variables, constants, subroutines, ...):  6 characters or less, alphanumeric, with the leading character alphabetic.  They may also contain the character ".", though not in the leading position.

Literal numerical constants
:  Decimal literals are as you might expect from working with AGC code.  They are just regular decimal numbers, possibly with a suffixed exponent "En" for powers of 10, or a suffixed "Bn" for binary scaling, or both.  Suffixed exponents and scales, if both are present, can be in either order.

As far as octal literals are concerned, some contexts (such as the OCT pseudo-op or the PIO instruction) specifically require them, in which case there's no special syntax to distinguish them from decimal numbers, other than the fact that they're not associated with the digits '8' or '9', with decimal points, nor with exponents.  In some usages there is ambiguity, which case the octal literal is prefixed by 'O' (upper-case alphabetical character, not a zero).

Regarding conversion of literal numerical constants to bit patterns actually stored within 26-bit memory words, there seem to be three basic patterns:

Units of angular measurement:  For most internal purposes, the source code typically measures angles in a unit called a pirad.  I can find no reference to any unit by this name outside of the LVDC source code, nor does the LVDC source code choose to define it in the program comments.  However, from the usage, it seems pretty clear that

1 pirad = 180° = π radians
And then there are ladder units.  They are undefined, of course, but I suspect this is the form required for outputting angular commands to external hardware:
1° = 1/0.06 ladder units
There are also references to angles measured in 2016 fine units, again undefined.  Apparently, the "fine" refers to "fine resolvers", and thus is likely the form in which the angular data is delivered to the LVDC from the resolvers.  At any rate, it appears that
1° = 2016/5.625 fine units
Finally, there are references to backup units, which are (you guessed it!) undefined.  It appears that
1° = 2016/180 backup units
Units of time:  The source code sometimes refers to a unit of time measurement it calls qms, but does not define.  I suspect this is the unit of measurement in which the real-time clock delivers data to the LVDC.  Apparently,
1 ms = 1/0.24609375 qms
1 ms ≈  4.063492 qms
In other words, "qms" probably stands for "quarter millisecond".
Format of a non-comment line

By the way, these observations about columnar alignment don't relate to the new assembler (yaASM.py), which does not enforce or use the columnar alignment in any way, other than to recognize that column 1 is special.  I don't know if the original assembler actually cared about the columnar alignment, or whether the alignment I've observed is simply a convention.

Preprocessor Pass

The LVDC assembler is a macro assembler, meaning that the language it processes has a variety of constructs intended to make coding easier and more manageable but which aren't directly related to the internal characteristics of the LVDC CPU.  These constructs are all resolved and removed from the code by a dedicated preprocessor pass prior to any assembly of actual LVDC instructions or allocation of LVDC memory.  The various preprocessor constructs that appear in LVDC code are described in this section.

The preprocessor itself operates in a single pass, and therefore any symbols or macros it uses must have been defined prior in the source code to such use.

The CALL macroCALL is a macro hard-coded into the assembler itself.  By a "macro", I mean that superficially it is used in source code as if it were a CPU instruction, but it is not a CPU instruction.  Rather, it is a shorthand for a sequence of several CPU instructions.  A single CALL is expanded by the preprocessor into the appropriate stream of true CPU instructions.  When I say it is "hard-coded" into the assembler, I mean that the language also includes ways for the programmer to define his own custom macros within the LVDC program if he wants, but that there are no such definitions for the CALL macro in the LVDC source code.

It is probably hard-coded in the assembler rather than having a soft definition within the source code because there are two distinct variations of it.:  There are both 2-argument and 2-argument variants of the macro.  The 2-argument version is used for calling a subroutine having a single input argument.  The macro is invoked by a source-code line of the form
CALL ARG1,ARG2
which the preprocessor replaces by a pair of actual instructions,
CLA ARG2
HOP ARG1
All three lines appear in assembly listings, but the CALL is treated as a comment and the other two have a '+' character printed next to them to show that they're there due to the expansion of the macro.

The 3-argument version,
CALL ARG1,ARG2,ARG3
instead expands as
CLA ARG3
STO 775
CLA ARG2
HOP ARG1
and thus calls a subroutine with two input arguments.

General macro-definition facility:  As I said above, the macro facility is general-purpose, and custom macros can also be defined within the source code.  The syntax used for defining a macro is as follows:
NAME    MACRO    ARG1,ARG2,...
        ... code using the symbols ARG1, ARG2, and so on ...
        ENDMAC
Once defined, NAME can be used to invoke the associated macro.

Note that in the definition of the macro, the strings ARG1, ARG2, and so on are literal.  You can't define a macro with arbitrary arguments names like (say) x, y, and z.  

As an example, consider the 2-argument the CALL macro described earlier.  It is hard-coded into the assembler, and therefore doesn't require a definition in the code, but if it weren't hard-coded it could have been giving the following soft definition:
CALL    MACRO    ARG1,ARG2
        CLA      ARG2
        HOP      ARG1
        ENDMAC
Pseudo variables:  "Pseudo variables" are named numeric constants known only by the preprocessor.  Any usages of such pseudo variables are replaced by numeric literals by the preprocessor, and thus none of them remain in the code by the time the actual assembly process begins.  This implies that the namespace for pseudo variables is distinct from that for left-hand symbols in general, so a pseudo variable can have the same name as a block of code or a data variable in memory without overlap.

Pseudo variables are defined by the EQU pseudo-op, with a syntax like the following:
NAME    EQU    (EXPRESSION)
In the operand here, the parentheses are literal and must always be present.  EXPRESSION is an arithmetical expression involving numeric literals, the operations + - * /, parentheses, and other (previously-defined) pseudo variables.  For example,
OMEGA   EQU    (.72921141E-4)
RWCP    EQU    (OMEGA*6373377*.87993)
In general, parenthesized expressions involving pseudo-variables like this can appear anywhere in LVDC source code, and is replaced by the preprocessor with the numeric literals.  Except for appearing as left-hand symbols in EQU statements, pseudo variables appear only with such parenthesized expressions, or in tests for conditional assembly (see below).

In most usages, an (EXPRESSION) appearing in an assembly language operand can be suffixed with an optional binary scaling factor
(EXPRESSION)Bn
The optional scaling factor doesn't really make sense if it were used in the EQU statements defining the pseudo-variables, since the purpose of the scaling factor is really a relationship between the logical value of the number and the physically-pragmatic pattern of bits stored in memory.  Nevertheless, the assembler allows any expression to be thusly suffixed by a scaling factor, even in the EQU statement itself.

Conditional assembly
:  Code can be conditionally retained or discarded by the preprocessor the basis of a test, with the syntax

IF    PSEUDOVARIABLE=(EXPRESSION)
... code ...
ENDIF

If the value of the specified pseudo variable is equal the evaluated expression, then enclosed code is retained by the preprocessor, and is thus eventually assembled.  If not, then the enclosed code is discarded.

Assembly Pass

(Technically, what I'm calling the "assembly pass" here is really implemented in the new assembler, yaASM.py, in two successive passes, known the "discovery pass" and the "assembly pass".  The former associates all program labels and variable names with physical addresses, while the latter performs the actual assembly using the now-resolved addresses.  That detail is totally irrelevant and transparent to the user, but would be necessary information to anybody modifying yaASM.py itself.)

Instruction operands:  Operand formats differ for some CPU instruction types, but most of them require a variable (a word in data memory), and conform to a pattern in which there are several allowed variants for specifying the operand:

  1. A literal 3-digit octal number (such as 777 or 776), which is an offset into the DM/DS (Data Module / Data Sector) defined by the current contents of the HOP register.
  2. The symbolic name of a variable or constant defined elsewhere in the program.  The variable/constant has to be in the current DM/DS, so if the referenced symbol doesn't happen to be located in that particular module or sector, it could be a problem.
  3. An arithmetical expression involving symbolic names, such as CBTAB+1 or CBTAB+(4*CBSTNO+1).  For a sufficiently complex expression, since as the latter one in the preceding sentence, the raw form of the instruction will appear in the assembly listing as if it were a comment (like a CALL line), and the fully resolved expression will appear marked with a "+" character as if it were a macro expansion ... which it probably is.  For example, if (4*CBSTNO+1) resolves to 57, then the expanded instruction would be CBTAB+57.  Note that such expressions, depending on what they contain, may resolve either to an address in memory, or else to a constant value.  In the latter case, the numerical constant is treated like the constants described in the following item.
  4. The character =, prefixed to a literal numerical constant.  Here the assembler allocates an unnamed variable in the current DM/DS, stores the value of the constant at that variable, and then uses the location of the new-created variable as the operand.  In other words, not all program variables are explicitly defined by pseudo-ops ... some of them are transparently allocated in as needed by the assembler itself in memory locations not previously used for anything..  Each numerically-unique constant is stored only once as a variable and is reused everywhere that requires it.  For example, in the AS-206RAM LVDC assembly listing, =1B18 is an operand in the lines with sequence numbers 051570 and 051910, while =00000004 is used at 051950 and 053380, but they both evaluate to the same numerical value and thus reference the same memory location (2-06-076).  Note that if the literal numerical constant begins with the character 'O' (alphabetic, not digit) then the number is octal, while if the leading 'O' is not present then the number is decimal.

Except for the HOP instructions, the other CPU instructions that transfer program control target a location in the current IM/IS rather than a variable in the DM/DS, and thus require a different type of operand. Those instructions (TRA, TNZ, TMI) thus have operands in one of the following two formats:

Other exceptions:

Pseudo-ops:  The following table shows every type of pseudo-op I've encountered in the AS-206RAM program listing, and what I've been able to infer about them:

Pseudo-op
Description
BSS n
This pseudo-op simply allocates n words of memory.  They are loaded with the value 0.
DEC number
This pseudo-op allocates one word of memory, and loads the decimal number in it. 
DEQD M,S,LOC
  or
DEQS M,S,LOC
This preprocessor pseudo-op is a variant of EQU (see below).  It defines a pseudo-variable, named according to its left-hand symbol, referencing a specific fixed location in memory, specified by its module, sector, and offset, which are literal octal constants.  Since the pseudo-variable exists only in the preprocessor, it does not store anything at that location, but merely defines a symbol representing that particular memory configuration.  The symbol for the pseudo-variable created in this manner did not appear in the symbol tables produced by the original LVDC assembler, but do so in symbol tables produced by yaASM.py.  As far as I know, those pseudo-variables are used only as operands of CDS instructions (see above).

DEQD differs from DEQS in that the former specifies a duplex memory configuration whereas the latter specifies a simplex configuration.
DFW instruction1,operand1,instruction2,operand2
Assembles a constant which can subsequently be used as the operand of an EXM instruction (see earlier).  Note that EXM cannot access such a constant in-place — i.e., not at the location where the DFW pseudo-op stores it — rather, requiring that the constant be moved at runtime to one of the addresses 200, 240, 300, or 340 in residual memory.

Naively, what this pseudo-op does is to assembles two instructions (remember, each instruction assembles into one "syllable" and that two syllables comprise a single word of memory), allocate a word of memory, and store the assembled pair of instructions in it.  However, because of the way EXM uses such assembled instructions, there are a few details which differ from this simple model.  Specifically, the residual bit (A9) and least-significant bits (A2, A1) in operand1 and operand2 are modified from what you expect to include certain bits from the DS (data sector) applicable to operand1 and operand2.  The documentation for EXM should make it clear what those changes are.
DOGD DM,DS,DLOC
   or
DOGD DM,DS,
Abbreviated form of ORGDD (see below) that only modifies the assembler's current data-memory pointer, leaving the instruction-memory pointer untouched.  It does not allocate any memory.  The location parameter DLOC is treated as a suggestion rather than as a hard specification of the offset into the data-memory sector, since if the assembler finds that the requested DLOC has already been used, it will search upward through the data sector until it finds a location that hasn't already been used.  If DLOC is omitted entirely, it is taken to be 000.

Presumably there is a DOGS pseudo-op as well (differing in that it pertains to a simplex memory configuration rather than a duplex one), but I have not encountered it in actual code.
EQU (expression)
Defines a "pseudo variable" used only by the assembler's preprocessor pass.  The parentheses are literally present.  The expression is arithmetical in nature, and can involve decimal numbers, other pseudo variables, and the operations +, -, *, or /.  The lines are evaluated in a single pass, so pseudo variables used in expressions need to have been defined earlier in the source code.  For example,
PI      EQU    (3.1415927)
Note that when pseudo variables are used they are always within arithmetical expressions that are enclosed in parentheses, (expression), such as:
PI      DEC    (PI)B0
PI3     DEC    (3*PI)B0
These examples also illustrate the important point that the namespace used for these pseudo variables is distinct from the namespace used for left-hand symbols naming variables or blocks of code.  There are indeed symbols that have this double usage.  For example, in the AS-206RAM program, "GEPLON EQU (15)" is at line 006600, while "GEPLON DEC (GEPLON)B10" is at line 016820.
FORM a,b,...
This preprocessor pseudo-op defines to the assembler (without generating any actual code) the name of a macro that, when used, will pack a pattern of bit-fields into a single word-size constant.  In the FORM statement itself, the field-widths are decimal, whereas when the macro is used, the values of the fields are octal.  An example may make all this clearer:
MYPAT    FORM     2,3,4,5,6
                    .
                    .
                    .
MYCON    MYPAT    1,2,3,4,5
The first of these lines defines a macro, MYPAT, which can pack 2-bit, 3-bit, 4-bit, 5-bit, and 6-bit fields into a single 20-bit field.  Since the LVDC word-size is actually 26 bits, the unused 6 bits of the compiled constant will all be 0.  Confusingly, the way the assembler displays word-size constants, the least-significant bit is always 0 (because it's the physical position in which parity is stored), so the constant is actually aligned at bit 27 and appears as exactly 9 octal digits.  Because of this, in other words, it will really appear that there are 7 unused bits assigned the value 0.

The second of the lines shown above uses the macro.  It compiles such a constant and stores it at a the memory location MYCON.  The 2-bit field will have the (binary) value 01, the 3-bit field will have the value 010, and so on, so the actual value of MYCON, in binary, as displayed by the assembler, will be
01 010 0011 00100 000101   000000 0
or octal 243101200.

The LVDC flight program AS-206RAM defines three such macros, SS, SSFORM, and SSLAD, on page 45.
HPC SYMBOLNAME
   or
HPC SYMBOLNAME1,SYMBOLNAME2
This allocates a word of memory at the current location, and stores a HOP constant in it that's constructed from the operand. 

In the one-operand variation, the HOP constant is simply the same as that of the symbol whose name is given by the operand.

In the two-operand variation, IM, IS, S, and LOC fields of the HOP constant are taken from SYMBOLNAME1, while the DM and DS fields are taken from SYMBOLNAME2.
HPCDD arg1,arg2
   or
HPCDD IM,IS,S,LOC,DM,DS
Like HPC (see above), constructs a HOP constant and stores it at the current location.

For all I know, there may be an HPCDS variation as well, differing from HPCDD in applying to a simplex memory configuration rather than a duplex one, but I have not encountered it in practice.
MAT
Forces alignment for the next memory allocated to a 020-word (octal, or 16 decimal) boundary, and may have something to do with the succeeding words logically forming a matrix.  It does not allocate any memory.  I.e., any memory it skips past to reach the proper alignment remains unallocated.
OCT number
Allocates a word of memory and stores the given octal number there.
ORGDD IM,IS,S,LOC,DM,DS,
   or
ORGDD IM,IS,S,LOC,DM,DS,DLOC
Set the instruction-memory and the data-memory assumptions for the next code or data lines to be assembled.  The fields within the operand relate to those within the HOP constants, except while the HOP constant has a single LOC field, the assembler internally maintains separate LOC fields for instruction memory (LOC) and data memory (DLOC).

If the optional DLOC is not present, then the first previously-unused location in the select data module/sector is used.  Actually, specifying DLOC does not necessarily imply that the data location is set to DLOC, since if that location has already been used, the next unused location after that will be selected instead, and the assembler generates a warning message.

There may be an ORGDS variant as well, specifying a simplex memory configuration rather than a duplex configuration, but I have not encountered it in practice.
SYN symbol
This pseudo-op requires a left-hand symbol to precede the SYN on the line.  It tells the assembler to treat the left-hand symbol as a synonym for symbol.  This is similar in concept to EQU, which essentially creates synonyms for numerical constants.  But it differs from EQU in that it is not a part of the preprocessor, and thus can reference symbols defined later in the source code.  Further, the symbols it references can be program labels or variable names
TABLE number
This informs the assembler that the succeeding number words of memory form a table.  The operand is a decimal number.  The only use I can see for this is to make sure the assembler doesn't split the table across a memory-sector boundary.  All of the uses of TABLE I find in the AS-206RAM listing are tagged as assembler warnings.
USE INST
  or
USE DAT
These pseudo-ops alter the way the assembler places and orders succeeding items in memory.  The usual positioning and ordering is represented by USE INST, whereas I'm unsure of what USE DAT is for.  I think it may be a convenient way to pack CPU instructions when one wants to place them in the midst of an area of memory used primarily for storing variables, or may represent an alternative to the DFW pseudo-op (see above).

USE INST:
The ORGDD pseudo-op (see above) defines an origin for both "instructions" (fields IM,IS,S,LOC) and "data" (fields DM,DS,LOC2).  Normally, instructions are assembled at successive offset locations while the "syllable" (0 or 1) is kept fixed.  I.e., normally, all of the locations with syllable 0 are used up, then all of the locations with syllable 1.  The assembler uses the IM,IS,S,LOC fields from ORGDD to determine the memory area in which this happens.  When the end of the memory sector is reached, a different syllable or sector or module must be selected either by the assembler or the coder. 

Data is similarly assembled at successive locations (with pseudo-ops like DEC, OCT, or BSS), but in the memory area selected by the DM,DS,LOC2 fields from ORGDD instead.  Moreover, data is typically a full word of memory rather than a single syllable, so both syllables of each memory word are typically used for each data item.
USE DAT:
On other other hand, when USE DAT is in effect, I think only instructions are assembled, and data-allocated pseudo-ops like DEC, OCT, or BSS aren't used.  There are two changes from USE INST in the way instructions are assembled. 

Firstly, instructions are assembled in the memory area selected by the DM,DS,LOC2 fields from ORGDD.  I.e., they appear in "data" memory rather than in "instruction" memory.  But beyond that, successive instructions are not assembled into the same syllable of memory, but are instead assembled at alternating syllables: 1, 0, 1, 0, 1, 0, .....   (Notice that syllable 1 comes first in the sequence.)

I'm not sure how the CPU responds to this ordering.  The USE DAT context is often (though not always) associated with simplex memory configuration as selected by a CDSS instruction so perhaps this is really the order in which the CPU executes instructions in a simplex configuration.
VEC
Forces alignment on a 4-word boundary, and may have something to do with the succeeding words logically forming a vector.  It does not allocate any memory.  I.e., any memory it skips past to reach the proper alignment remains unallocated.

Program Structure

Here are some of my own observations and inferences, based on inspection of the AS-206RAM assembly listing.

Overall structure of the program:

Assembly warnings:  Warnings are marked with a W in the leftmost column of the offending line.  No explanation appears in the assembly listing of why any particular warning is issued.  In the AS-206RAM assembly listing, the sequence numbers at which errors and warnings are found is as follows:

012420 015020 015050 015070 015090 015110 015140 015260 015290 015310
026500 026800 026820 026840 026880 026920 026990 027020 027820 027890
027910 027980 028040 028090 029370 030230 031780 031930 032230 036880
036910 036920 036930 036950 036990 037020 037200 037490 038040 038680
038820

Sector shifts in the program flow:  Recall that each 26-bit word of memory (not including the parity bit) consists of two 13-bit syllables, referred to as syllable 0 and syllable 1.  Each CPU instruction assembles to a single 13-bit value, and thus fits precisely within a single syllable, and any word in memory can simultaneously hold two separate instructions.  When the CPU executes code in a given memory sector, it just sequences through all of the instructions in the currently-selected syllable.  When the last-available word in the current syllable is reached, execution cannot continue.  Rather than forcing the programmer to deal with this situation explicitly, the assembler steps in and transparently substitutes an extra instruction into the program flow to select a different syllable, sector, or module.  So from the programmer's standpoint, he can just write an uninterrupted block of code without even worrying about the fact that it spans several different memory sectors.  The code transparently modified by the assembler uses slightly more memory and execution time that it superficially appears to from the source code, but that usually doesn't matter. 

From the assembler's point of view, though, it's a bit more complicated.  The simplest case is in reaching the end of syllable 0 for a given memory sector.  The first (unused) location of syllable 1 of that same memory sector is accessible by a TRA instruction, so the assembler transparently inserts a TRA just before reaching the end of syllable 0.

What happens when reaching the end of syllable 1 of a memory sector is much trickier.  For one thing, various factors influence the usage of words at the ends of sectors in syllable 1, so it's a chore for the assembler just to figure out when the end of sector 1 has even been reached.  The next problem is that once the end of syllable 1 has been reached, it can't simply switch to a new syllable:  it instead has to switch to a different sector within the memory module, or perhaps even to a different memory module altogether.  That can't be done with a simple TRA instruction, and requires a more-complex HOP instruction instead.  Unlike a TRA instruction which encodes its target address within the instruction itself, a HOP instruction requires a variable containing the "HOP constant" of the target address.  Naturally, no such variable containing the desired HOP constant normally exists.  So in order to insert a HOP instruction, the assembler must first create such a variable:  it must find an unused location in the current data sector or residual sector, and stick a HOP constant into it.

Those points at which a sector change is performed due to reaching the end of the sector, regardless of whether or not the assembler inserts any HOP or TRA instructions, is marked with a * in column 1 of the assembly listing.  These transparently-inserted jumps are found at the following card-sequence numbers in the AS-206RAM listing:

044700 047910 050170 052790 054910 057850 062640 065020 067470 070680
073610 076720 079190 083030 085700 089420 093330 096170 098760 101450
104090 109850 112570 116390 120760 123850 

Assembly errors:  Errors are marked in the leftmost column of the offending line by a character that presumably indicates the type of error.  In the AS-206RAM assembly listing, the 7 such errors are found, and here are my interpretations of what they mean:

Notice that about half of the errors above occur on the (rare!) lines having no card-sequence number.  I think that for pragmatic reasons, sequence numbers would usually have been left off of the punch-cards while the code was under development.  Otherwise, they would have needed to be changed frequently, which would be a great inconvenience.  In other words, the sequence numbers were likely only added once the code had reached a releasable form.  Thus most of the errors appeared in areas of the code that were under active development, which is not terribly surprising.

Alas!  There are 7 more errors exactly like the A type error listed above which the original assembler did not even detect.  These are the pairs of lines of code at card-sequence numbers 026840, 026860, 026880, 026900, 026920, 026940, and 026960.  They're all of the form

LABEL   CLA    CONSTANT
        CLA    CONSTANT+1

Fortunately, it's easy to see how these latter 7 errors should be fixed.  As it happens, there are quite a few examples on the same page of the assembly listing that make it clear the pattern should have instead been

LABEL   CDS    CONSTANT
        CLA    CONSTANT+1
Format of the source-code portion of the assembly listing:  Consider a "normal" section of the listing, containing code, comments, etc., as opposed to report tables generated by the assembler, to which I've added some markup (in green) for explanatory purposes:
The Segment Cross Reference table:  This appears near the end of the assembly listing, and is generated by the assembler.  Here's a small sample:


What the cross reference does is list each symbol in the program, tell you where it appears in memory, and then tells you how to find all uses of that symbol in the code.  It should be pretty obvious to you that in this example, the symbol UTEMP1 is in memory module 2, sector 17, at address 174.  It may or may not be obvious that where the symbol is used is at the line SEQUENCE numbers 085370, 116640, etc.  Alas, the few pages of the assembly listing which I've elected to freely expose do not include the one which UTEMP1 is allocated, but does include several examples of where it is used.  For example, if you look at the sample code I've marked up in green just a bit above, you can see it used at SEQUENCE number 117600, just as expected.  Not all lines of code have SEQUENCE numbers, and in those cases the SEQUENCE number appearing in the table are generated by simply adding 10 to last card having an explicit SEQUENCE number.

Although I haven't shown any examples in the image above, memory locations which the assembler itself automatically allocates also appear in the segment cross reference table.  Recall that there are cases where the assembler transparently allocates memory locations to store values of constants (like the values of numerical expressions or HOP constants) which are referenced on-the-fly without being explicitly defined in the source code.  These variables are distinguished by having no symbolic name.  Instead they are referenced by their unique values rather than by symbolic name.  I.e., their unique values are used as if they were their symbolic names.  Hence what appears in the table in place of a name is the 9-octal-digit value stored at the location.  They appear in the table after all of the symbolic names.

The octal listing of the program:  This is generated by the assembler and appears at the very end of the listing.  It simply shows what appears at each memory location in the modules, as either a single 26-bit octal word or as two 13-bit syllables.  So you can see in the octal table what each instruction and each pseudo-op assembles to in octal form.  In each of the numbered columns of the table, syllable 1 of the memory word is on the left and syllable 0 is on the right.  For example, referring to the image below, in module 2, sector 00, location 0201 (or 2-00-0201 for short), syllable 1 has the value 10170 and syllable 0 has the value 00174. 

It's important to understand the alignment of the data shown in the octal listing:

This seems weird — the instruction alignment in particular! — but I suppose the rationale is that the open bit positions correspond to the positions of parity bits.  The assembler itself does not bother to compute the parity bits for you, and thus represents them all as 0.  If you take an instruction from syllable 1 and one from syllable 0 as shown in the table, overlap the least-significant octal digit from the left-hand instruction with the most-significant octal digit from the right-hand instruction, then bitwise OR them (or add them), you get the full contents of the memory word.  For example, taking the first two instructions shown below (63224 12436), the full 26-bit content of address 2-00-000, left-aligned, is:

  63224
    12436
  632252436

However, there's no doubt that the visual representation of this data is undoubtedly weird.  And, it's unclear just how useful it is ... certainly not at all, if you're trying to disassemble the instructions by eye!  I've provided a handy python script (unOP.py) that you can use to provide a simple-minded disassembly of the instructions found in the octal listing.  The script assumes that if an octal number you give it has a leading space character then it is in syllable 0, and that if it has no leading space it is in syllable l.  For example, feeding "63224" into it gives back "MPH 315", while feeding " 12436" into it gives back "CLA 124".


 

MIT Instrumentation Laboratory vs IBM Federal Systems Division

The MIT Instrumentation Laboratory (which designed the Apollo Guidance Computer) had nothing to do with the LVDC, and conversely IBM Federal Systems Division (which designed the LVDC) had nothing to do with the AGC.  However, since IBM was an important manufacturer of computers (indeed, it designed the Gemini on-board computer system), whereas MIT/IL was emphatically not, there was an understandable feeling by some that the Apollo program might better be served by an IBM on-board guidance computer in the Command Module and Lunar Module than by newly-designed computer from novice MIT/IL.

Accordingly, IBM proposed that the LVDC be used in place of the AGC in the CM and LM, and in 1963, it produced a big, two volume report (here and here) to support their proposal.  The Instrumentation Labs fired back their own critiques, shredding IBM's report to the extent possible.  And in one of those critiques, we have a few precious gems of LVDC assembly language, as written by the Instrumentation Labs personnel rather than by IBM. 

Whether it's a good LVDC program or a bad one, who knows?  It certainly seems to depart a tad from the syntax of actual IBM code; for example, what MIT calls HOPCON, IBM calls HPC.   But here's what it looked like.  The '#' characters indicate the beginnings of comments.  Those comments were added by me, and weren't present in the samples originally.

# Sum of two double-precision vectors A and B to produce  vector C.
         CLA     A
         ADD     B
         STO     C
         CLA     A + 1
         ADD     B + 1
         STO     C + 1
         CLA     A + 2
         ADD     B + 2
         STO     C + 2
         ...

# Purportedly, subroutine linkages to call functions to perform vector addition.
         CLA     ADRESA
         STO     VCAADR
         CLA     * + 2
         HOP     VCALINK
         HOPCON  * + 1
         CLA     ADRESB
         STO     VADADR
         CLA     * + 2
         HOP     VADLINK
         HOPCON  * + 1
         CLA     CADRES
         STO     VTSADR
         CLA     * + 2
         HOP     VTSLINK
         HOPCON  * + 1
         ...

# Integration during accelerated flight. If you want to see the equations being
# implemented, look at page 7 of the critique.
AVERAGEG STO     EXITHOP
         HOP     HOPSET1
AVG1     CLA     WK
         SHF     R1
         ADD     HGK/2
         ADD     VK
         MPH     H
         ADD     R
         STO     R
         MPY     R
         HOP     THISEC1
AVG4     CLA     HOPWD1
         ADD     ONE
         STO     HOPWD1
         CLA     PQ
         ADD     DOTSUM
         STO     DOTSUM
HOPWD1   HOP     HOPSET1
AVG2     CLA     DOTSUM
         STO     SQRTARG
         CLA     * + 2
         HOP     SQRTLINK
         HOPCON  * + 1
         CLA     SQRTANS
         MPY     DOTSUM
         CLA     -MUH/2
         NOOP
         NOOP
         DIV     PQ
         HOP     THISSEC2
AVG5     CLA     HOPSET1
         STO     HOPWD1
         CLA     HOPSET2
         STO     HOPWD2
         NOOP
         NOOP
         NOOP
         CLA     PQ
         STO     DOTSUM
         HOP     HOPSET2
AVG3     CLA     R
         MPY     DOTSUM
         CLA     HGK/2
         ADD     W
         ADD     V
         STO     V
         CLA     PQ
         STO     HGK/2
         ADD     V
         STO     V
         HOP     THISSEC3
AVG6     CLA     HOPWD2
         ADD     ONE
         STO     HOPWD2
         HOP     HOPSET2
HOPSET1  HOPCON  AVG1, XCOMP
         HOPCON  AVG1, YCOMP
         HOPCON  AVG1, ZCOMP
         HOPCON  AVG2, XCOMP
HOPSET2  HOPCON  AVG3, YCOMP
         HOPCON  AVG3, ZCOMP
EXITHOP  ( exit hop con )
STRTLINK HOPCON  SQRT, XCOMP
THISSEC1 HOPCON  AVG4, AVG4
THISSEC2 HOPCON  AVG5, AVG5
THISSEC3 HOPCON  AVG6, AVG6

# Compute a double-precision square root.
SQRT     STO     RETURN
         CLA     ZERO
         STO     NORMCNT
         CLA     ARG
NORMTEST AND     HIGH3
         TNZ     NORMDUN
         CLA     NORMCNT
         ADD     ONE
         STO     NORMCNT
         CLA     ARG
         SHF     L2
         STO     ARG
         TRA     NORMTEST
HIGH3    DEC     -.75
1/2      DEC     .5
SLOPELO  DEC     .4162
BIASLO   DEC     .1487
SLOPEHI  DEC     .2942
BIASHI   DEC     .2046
NORMDUN  AND     1/2
         TNZ     ARGHI
         CLA     ARG
         MPY     SLOPELO
         SHF     R1
         STO     ARG
         CLA     BIASLO
         ADD     PQ
         TRA     NEWTON
ARGHI    CLA     ARG
         MPY     SLOPEHI
         SHF     R1
         STO     ARG
         CLA     BIASHI
         ADD     PQ
NEWTON   STO     BUF
         CLA     ARG
         DIV     BUF
         ADD     ZERO
         ADD     ZERO
         ADD     ZERO
         ADD     ZERO
         ADD     ZERO
         ADD     ZERO
         ADD     ZERO
         CLA     PQ
         SHF     R1
         ADD     BUF
         STO     BUF
         CLA     ARG
         DIV     BUF
         ADD     ZERO
         ADD     ZERO
         ADD     ZERO
         ADD     ZERO
         ADD     ZERO
         ADD     ZERO
         ADD     ZERO
         CLA     BUF
         SHF     R1
         ADD     PQ
         STO     ARG
CLANORC  CLA     NORMCNT
         TNZ     POSTSQRT
         CLA     ARG
         SHF     R1
         STO     ARG
         TRA     CLANORC

# Calling sequence for SQRT (or similar for any other unary subroutine).
         CLA     X
         STO     ARG
         CLA     REHOP
         HOP     SQRTLINK
RETURN   CLA     ARG
         ...
REHOP    HOPCON  RETURN
SQRTLINK HOPCON  SQRT

yaLVDC, the LVDC CPU Emulation

The inclusion of this section is an act of pure optimism.  We don't have a working LVDC CPU emulator as of yet.



An important thing to understand, in terms of simulation, is that the LVDC was not fast enough to keep up with the real-time calculations needed to directly control the rocket.  The results would have been too stair-steppy, too choppy.  25 outputs per second isn't enough!  What happened instead is that a separate unit — the LVDA — was needed to accept the digital outputs from the LVDC and turn them into smoothly-changing controls for the rocket. And when I say smoothly-changing, I mean both smoothly in time as well as in terms of electrical properties. Thus any decent simulation would require simulation of not only the LVDC but also of the LVDA, the latter of which is currently outside the scope of what's being provided here.

An interesting aspect of the LVDC is that since it is essentially a "black box" that outputs real-time control signals in response to real-time inputs, it is possible to create a reasonably satisfactory behavioral simulation for it in the absence of the original software, if one simply knows the guidance equations relating the outputs to the inputs.  While creating behavior simulations is not an objective of the Virtual AGC project as far as the Apollo CPUs are concerned, there are people engaged in creating such simulations for the Orbiter NASSP project and elsewhere.

yaASM.py, the LVDC Cross-Assembler

The original IBM 360 based LVDC assembler used during Project Apollo itself is no longer available 50+ years later ... or at least, not available to us.  So I've written an LVDC assembler completely from scratch.  It's capable of accepting the original LVDC source code without any modifications, and producing an assembly listing similar to the original one, as well as a rope image that's 100% identical to the octal rope listing from the original printouts. 

In saying this, I suppose it's important to reiterate that while we have decent contemporaneous documentation of the LVDC instruction set, we have no contemporaneous documentation whatsoever of the format of LVDC assembly language, and specifically of the pseudo-ops or operand syntax of the language.  In other words, while the new assembler works with the LVDC language documentation as described on this web page, this web page's documentation is based entirely on what I've personally inferred from reading the specific LVDC program printout available to me.  Thus a listing of a different version of the original LVDC software, were one to become available, could easily have features which I've never seen before and which therefore are not yet accounted for in the new assembler.

With that warning out of the way, the modern LVDC assembler is called yaASM.py and is available in the Virtual AGC software repository.  Both the files yaASM.py and expression.py found there are necessary for running the assembler.  As the naming implies, the assembler is written in the Python language — specifically Python 3, and it will not work with Python 2.  Python programs such as this assembler are ready to run, as-is, and require no preparation or setup other than installation of Python itself.

(Note:  The yaASM.py program should not be confused with the yaASM program, which also appears in the repository.  yaASM is an assembler program for Gemini OBC assembly language.  The naming of these two programs is similar due to the fact that the LVDC and OBC are such similar computers.  The original intention was that yaASM would be able to handle both OBC and LVDC assembly languages.  However, yaASM was written in advance of any true samples of LVDC source code becoming available.  When true LVDC samples became available much later, the inadequacy of yaASM with respect to the language features that it would need to handle became apparent, and the idea of using yaASM for the LVDC was abandoned.)

Usage of the LVDC assembler is quite simple:
yaASM.py [OCTALS.tsv] <INPUT.lvdc >OUTPUT.lst
The assembler simply takes an input file of LVDC source code (INPUT.lvdc), and produces as output a human-readable assembly listing (OUTPUT.lst).  Additionally, it produces a file containing an octal listing of the assembled code, which is always called yaASM.tsv.  This file is produced whether or not there were fatal errors in the assembly process, so don't take the existence of the yaASM.tsv file as an indication that assembly succeeded.  yaASM.tsv is what would be used to create an octal rope, if you had an LVDC, or which would be used as input to a software-based emulator for the LVDC CPU.  The format of the file will be discussed in a moment.

There is also an optional input file (OCTALS.tsv) for the assembly process.  OCTALS.tsv, if available, is an octal listing of an LVDC rope in the same format as the output yaASM.tsv file.  If present, the assembler performs a comparison during the assembly process of the contents of the input file OCTALS.tsv against that of the eventual output file yaASM.tsv, and provides messages indicating mismatches between the two.  This can be helpful in debugging LVDC source code or in debugging the assembler itself.  Of course, it's also possible to simply compare the two files directly with tools such as diff, but it's significantly more convenient to allow yaASM.py to do the comparison instead.  For one thing, OCTALS.tsv can contain user-supplied comments; these would show up as differences from yaASM.tsv when using diff, but not when using yaASM.py.  More importantly, when mismatches are found, messages generated by yaASM.py appear along with the lines of assembly-language code that generate the associated data.  With tools similar to diff, matching the differences to the associated LVDC source code is a much more inconvenient process.

As for the format of OCTALS.tsv or yaASM.tsv, the naming is intended to indicate that the files are tab-delimited ASCII files, which is largely true.

Completely-empty memory sectors do not need to appear in the file at all.  However, if any portion of a memory sector is used, the sector appears fully in the file.  I.e., all of the lines with location fields 000, 010, ..., 370 will appear, even if most of their fields are blank.

Plea for Data

As you will have noted if you've read this far, there are some pretty serious gaps in the publicly-accessible data about the LVDC and its software.  If you know where to find any more information, please tell me about it.  Examples of some of the things that would be interesting to have include:

Homage

Well, we haven't made much progress on the homage front.   I have a handful of names, though too few yet to really form any picture as to who did what.  Hopefully be able to flesh this out somewhat as time progresses and to provide that info here.  A lot of the information on this page that doesn't come directly from the surviving documentation is largely due to conversations with Barry Silverman, who has made a significant effort to find and talk to original LVDC developers.



This page is available under the Creative Commons No Rights Reserved License
Last modified by Ronald Burkey on 2019-09-23.

Virtual AGC is hosted
              by ibiblio.org