Large Scale Systems
in the
Iowa Logic Specification Language

by Douglas W. Jones


When a hardware description language is used to specify a large scale system, all of the same issues arise that traditionally dominate the design of large programs. These issues are not identical to those that dominate the physical hardware design, since they involve not only questions of modularization, but also problems of scope rules, abstraction and syntax.

design hierarchy of a digital system

Borrowing from hardware design, it is not difficult to suggest that a large system be broken into a hierarchy of subsystems. For example, we can use a motherboard to bind together the entire system. The motherboard may hold several integrated circuits and sockets for the boards holding subsystems. The subsystem boards may, in turn, hold more integrated circuits and sockets for the daughter boards holding subsubsystems.

Using the Iowa Logic Specification Language, we can represent a hierarchic system as follows:

circuit SYSTEM;

   circuit SUBSYSTEMA
      -- details omitted
   end;

   circuit SUBSYSTEMB
      -- details omitted
   end;

   circuit SUBSYSTEMC

      circuit SUBSUBSYSTEM
         -- details omitted   
      end;   

      -- details omitted
      parts
         X: SUBSUBSYSTEM;
         Y: SUBSUBSYSTEM;
         Z: SUBSUBSYSTEM;
      wires
         -- details omitted
   end;

   -- details omitted 
   parts
      A: SUBSYSTEMA;
      B: SUBSYSTEMB;
      C: SUBSYSTEMC;
   wires
      -- details omitted
end.

Multiple Source Files

The above presentation of the example large-scale system is given as a single source file, and for all but the smallest circuit desciptions, this is a bad idea. Typically, it is far more convenient to package each subcircuit as a separate source file, for example:
circuit SYSTEM;

   use subsystema;
   use subsystemb;
   use subsystemc;

   -- details omitted
   parts
      A: SUBSYSTEMA;
      B: SUBSYSTEMB;
      C: SUBSYSTEMC;
   wires
      -- details omitted
end.
Here, we have simply placed the circuit description for each subsystem in a separate source file. It is a good idea to establish a convention such as the rule that the name of each source file is the same as the name of the logic circuit it contains.

It is good practice to develop test scaffolding for each subsystem independently of the system into which it is to be incorporated, so we might develop each major subsystem in its own directory. In this case, the use directives shown above might need to include the full pathnames of files, and each directory might contain not only the subsystem but the test scaffolding for the subsystem, including a circuit designed only for testing purposes, test scripts and the expected output from running the script.

Subsystems of Subsystems

When a subsystem is constructed from even simpler subsystems, for example, when a circuit board description is based on chip descriptions, or when a register transfer subsystem is based on component register, arithmetic unit, and similar subsystems, these are frequently described in separate source files. There are two ways of organizing these files in the Iowa Logic Specification Language; these are illustrated here:

circuit SYSTEM;

   -- subcircuits used in the definition of subsystems
   use subsubsystema;

   -- subsystems
   circuit SUBSYSTEM;
      use subsubsystemb;
      parts
         A: SUBSUBSYSTEMA;
         B: SUBSYBSYSTEMB;

      -- details omitted
   end;

   -- details omitted
end
Here, the specification of the subsystem includes two subsidiary circuits, A and B. The first of these is included into the system at the global level from the file subsubsystema while the second is included locally within the body of the subsystem, from the file subsubsystemb.

Inclusion of a subsidiary circuit definitions at the global level ensures that only one copy of the text will be used, while inclusion of a subsidiary definitions in the bodies of the descriptions of subsystems may lead to the making of redundant copies of the text. Therefore, global inclusion should be used for definitions of subsidiary components that are widely used in many subsystems, while local inclusion is best restricted to components used in only one place.

Inclusion of subsidiary circuit definitions only in the context where they are used makes it very clear exactly where the definition matters, without requiring commentary indicating that some circuit is defined elsewhere. Inclusion at the global level can lead to an excessive number of global definitions; in very large systems, each of these may need commentary or other auxiliary documentation to indicate, at the global level, which subsystems use which components, and within each subsystem which components that subsystem expects to have defined at the global level. When a global subsidiary definition is actually used everywhere, these documentation problems are minimal, but when a global definition is only used in only some contexts, the documentation burden can be quite high.

Tri-State Busses

The Iowa Logic Specification Language allows use of tri-state busses, but these are considered to be a special class of component. This poses several problems:

These problems can be overcome, and in fact, the solution is moderately elegant looking. The basic rule to follow when using tri-state busses is the following: Consider a very simple bus structure containing an address bus, a data bus, a read strobe line (for reading the addressed register to the data bus) and a write strobe line (for writing the data bus to the addressed register). The bus interface for a single subsystem might look like the following:

a typical bus structure

The subsystem contains logic devices that take input from each bus line, and it contains bus drivers for the data bus. Of course, at least one subsystem, the bus master, must contain drivers for the address bus, and there may be a protocol allowing multiple masters to compete for access to the bus, but we will not consider either of these issues here.

If the backplane binding the entire system together is entirely passive, and if we ignore the question of input and output from the system, the declaration for the entire system will be as follows:

circuit backplane

   use socket;

   use subsystema;
   use subsystemb;
   use subsystemc;

   range word = 0 .. 15;

   parts
      -- bus lines
      read:  bus;
      write: bus;
      addr(word): bus;
      data(word): bus;

      -- subsystem configuration
      slotA: SOCKET( SUBSYSTEMA );
      slotB: SOCKET( SUBSYSTEMB );
      slotC: SOCKET( SUBSYSTEMC );

end.
Note that there is no wire list included in the above! This is because we will do all of the wiring between subsystems and the backplane inside the socket. If, instead of a passive backplane, we wish to use an active motherboard, the details of the motherboard itself can be given in a local wirelist.

Within the definition of the socket, we will wire the backplane to the input and output connections of the subsystems using the Iowa Logic Specification Language's iteraton facilities. Backplane bus wires will be addressed directly as global variables, taking advantage of the scope rules of the logic specification language:

circuit SOCKET( circuit subsystem );

   -- makes global references to
   --   range
   --     word = ...
   --   parts
   --     read, write, addr(word), data(word): bus;

   parts
      plugin: subsystem;

   wires
      write.out to plugin.write;
      read.out  to plugin.read;
      for i in word do
         addr(i).out       to plugin.addr(i);
         data(i).out       to plugin.datain(i);
         plugin.dataout(i) to data(i).in;
      endfor;

end.
The above example includes comments documenting the items that must be declared globally for the circuit to work. The comment convention suggested above is only one possibility, but omitting such comments in the design of a large system can make reading the system description quite confusing.

Parameterized Subcircuits

Consider the design for an addressable register to be attached to the example bus as an exercise to be used in testing the scheme we have outlined. Here is a fairly obvious design:

a register subsystem

In drawing the schematic diagram of the register, it is easy to put a notation such as =addr in the box representing the address decoder, to indicate that equality for some address is being tested. It is up to the human reader to recognize that the identifier addr is not bound to any particular value, and therefore represents something of a parameter to the design of the subcircuit.

When we specify this a subcircuit under the Iowa logic specification language, we must state such parameters explicitly, as in the following:

circuit REGISTER( integer address );
   -- an addressable register, designed to plug into a SOCKET.

   -- makes global references to
   --   range
   --     word = ...
   --   circuits
   --     reg( range word, time speed )    -- register
   --     comp( range word, time speed )   -- comparitor
   --     const( range word, integer val ) -- constant
   --     driver( range word, time speed ) -- bus driver

   inputs
      read, write, addr(word), datain(word);

   outputs
      dataout(word);

   parts
      R: reg( word, 10 * ns );
      C: comp( word, 10 * ns );
      B: driver( word, 10 * ns );
      myaddr: const( word, address );
      readgate, writegate: and(2);

   wires
      { data path }
      datain to R.in;
      R.out  to D.in;
      D.out  to dataout;

      { address decode }
      addr  to C.inA;
      myaddr.out to C.inB;
      C.out to readgate.in(1);
      C.out to writegate.in(1);

      { clock and enable distribution }
      read  to readgate.in(2);
      readgate.out to D.enable;
      write to writegate.in(2);
      writegate.out to R.strobe;
end.
Having parameterized the register with its address, we now need some way to allow this parameter to be set when the register is plugged into its socket. To do this, we modify the socket to pass this parameter:
circuit SOCKET( circuit subsystem, integer address );

   -- makes global references to
   --   range
   --     word = ...
   --   parts
   --     read, write, addr(word), data(word): bus;

   parts
      plugin: subsystem( address );

   wires
      -- details omitted (same as previous version)

end.
We can now insert multiple registers into our backplane, assigning each an address, as follows:
circuit backplane

   use socket;

   use register;

   range word = 0 .. 15;

   parts
      -- bus lines
      read:  bus;
      write: bus;
      addr(word): bus;
      data(word): bus;

      -- subsystem configuration
      slotA: SOCKET( REGISTER, 5 );
      slotB: SOCKET( REGISTER, 7 );

end.
Here, we have installed registers at address 5 and 7 in on the bus. The same approach can be used to provide other parameters to subcircuits that are designed to plug into a main circuit through such an interface. If all components that plug into the main circuit require the same parameters, this approach has no downside. If, on the other hand, some components do not require one or more of the parameters, this approach requires us to pass these parameters anyway.

Connecting Bus Masters

All of the slave devices described up to this point have read-only connections to the bus control signals and the address lines. The bus master, in contrast, must write these lines. If there are multiple masters, each master must be able to both read and write the control lines in order to arbitrate bus use.

Consider the following design for a socket that can accomodate both bus master and bus slave plugins:

integer master = 0;  -- type parameter values for socket
integer slave = 1;

circuit SOCKET( integer type, circuit subsystem, integer address );

   -- makes global references to
   --   range
   --     word = ...
   --   parts
   --     read, write, addr(word), data(word): bus;

   parts
      plugin: subsystem;

   wires
      if type = master then
         plugin.write to write.in;
         plugin.read  to read.in;
         for i in word do
            plugin.addr(i) to addr(i).out;
         endfor;
      else -- type = slave
         write.out to plugin.write;
         read.out  to plugin.read;
         for i in word do
            addr(i).out to plugin.addr(i);
         endfor;
      endif;

      for i in word do
         data(i).out       to plugin.datain(i);
         plugin.dataout(i) to data(i).in;
      endfor;

end.
Using this scheme, we can now create a full system with a main circuit such as the following:
circuit backplane

   use socket;

   use register;
   use processor;

   range word = 0 .. 15;

   parts
      -- bus lines
      read:  bus;
      write: bus;
      addr(word): bus;
      data(word): bus;

      -- subsystem configuration
      slotA: SOCKET( master, PROCESSOR, 0 );
      slotA: SOCKET( slave,  REGISTER,  5 );
      slotB: SOCKET( slave,  REGISTER,  7 );

end.