AN OBJECT-ORIENTED LOGIC SIMULATOR

Wire-wrapping and breadboarding made easy

Kenneth E. Ayers

Ken Ayers is a self-educated software engineer. He is currently employed by Industrial Data Technologies of Westerville, Ohio. His interests include computer graphics, machine intelligence, and OOP systems. He can be reached at 7825 Larchwood St., Dublin, OH 43017.


As a bench technician in a research and development laboratory, my primary job was to build prototype circuits with digital logic devices, and to make sure that the prototypes worked.

After migrating into the world of software, I saw no reason why my computer could not simulate those same digital logic circuits. When I discovered object-oriented programming, I became more and more convinced that it was both possible and practical.

The project, which I named LogicLab, was intended from the start to simulate not just logic devices, but the complete bench environment. LogicLab was required to provide the simulated equivalent of four aspects of that environment:

    1. A well-stocked parts cabinet containing standard integrated circuit (IC) devices (74LSOO, and so forth);

    2. A method to connect the pins of the ICs together (commonly called a "breadboarding system");

    3. A source of signals (clocks, pulses, switches, and so on) that could be used to simulate inputs to the circuit;

    4. A logic analyzer that I could use to monitor the activity within my circuit for debugging purposes.

At first glance, these four simulations appear simple. If implemented properly, they would indeed emulate the workbench environment. But when these simulations were expanded to the level of detail required for an effective implementation, the task seemed overwhelming.

I had previously considered tackling this project with a combination of C and assembly language, and estimated that the time required to produce a prototype in those languages would be about 8 to 12 months. When I used Smalltalk to write LogicLab, I had a working version of the project in roughly 100 hours! I spent much of that time learning to use the incredibly complete environment provided with Smalltalk/V. The environment includes more than 100 classes and some 2000 methods, and source code is provided for everything (except the compiler itself!). After building the prototype, I invested another 150 (or so) hours on the project in order to give LogicLab a major overhaul that improved both performance and the user interface.

A Glance Inside LogicLab

The heart of LogicLab -- the simulation kernel -- is shown in Listing One (page 130). The system also includes more than 20 object classes. Nearly all of these object classes correspond directly to real-life counterparts on the design bench. Some of the typical simulated devices are a toggle switch (Listing Two, page 136), a pulser switch (Listing Three, page 136), and a logic device Listing Four, page 137). Other simulated devices include buttons, switches, logic probes, input signals, a logic analyzer, and an assortment of logic devices. A list of the class hierarchy for LogicLab is given in Figure 1. Figure 2 shows a typical display produced by LogicLab's logic analyzer.

Figure 1: LogicLab's class hierarchy

  Object
    Button
       MomentaryActionButton
    ButtonPanel
    VariableMenu
    EmptyMenu

    TimeInterval
    LogicNode
    LogicProbe

       LogicComponent
          LogicDevice
             N74LS00

          LogicSignal
             ClockInput
             ConstantInput

          LogicSwitch
             PulserSwitch
             ToggleSwitch

    LogicAnalyzer
    LogicLab

LogicLab is organized into two discrete application phases. The first phase is the breadboard. In this phase, the user installs devices into the circuit and makes the connections between pins. This process is very similar to the process of wire-wrapping, and is driven by a series of pop-up menus and prompts for device names and pin numbers.

The second application phase in LogicLab is the logic analyzer. In a hardware workbench environment, the logic analyzer is used as a testing and debugging tool. The logic analyzer has a number of probes (LogicLab's logic analyzer has eight probes) that can be connected to various points in the circuit (usually to pins on ICs). While the circuit is operating, the logic analyzer records and displays the states monitored by each probe. The output is a timing diagram that illustrates the relationships between signals in the circuit. A technician uses this information to determine if the circuit is functioning properly. If the circuit is not functioning properly, this information is used to identify where a problem (a bug!) might be found.

Because LogicLab is a simulation, the process of using the logic analyzer is handled a little bit differently than in a hardware circuit. LogicLab simulates time in discrete steps. In essence, LogicLab examines all of the IC input pins and calculates the corresponding output states. After LogicLab completes the calculation, it pretends that one nanosecond (a billionth of a second) has passed, and then repeats the process.

Between nanoseconds, LogicLab performs housekeeping chores -- such as updating the timebase, recording the states at the probes, and updating the timing diagram display.

The State Connection

One object class -- the LogicNode class -- performs the majority of the work during a simulation. This class simulates the electrical connections within a circuit. Each pin of a simulated logic device (most devices have 14, 16, or more pins) is associated with a LogicNode; each input signal has a LogicNode; and the private data for each switch or push-button device includes a LogicNode.

The LogicNode class is a good example to use for demonstrating the close correspondence between physical objects and their software counterparts. Figure 3 shows the conceptual organization of pins on an IC.

A characteristic of hardware logic devices is that a change in an input signal requires a finite amount of time before the corresponding change appears at the output. This delay is known as the "propagation delay." The length of the propagation delay is determined by examining the specifications (data sheets) for a particular type of device.

In a hardware logic device, the propagation delay occurs within the logic elements (transistors, diodes, and so forth) inside the device. Because of different signal path lengths within an IC, the propagation delay at an output can often differ, based upon which input signal is present. (For example, in a typical flip-flop, the delay from the reset input to the data output is different than the delay from the clock input to the data output.)

To accommodate this phenomenon, I have modeled the propagation delay as an integral part of the electrical connection rather than as a part of the device. When a new IC object is created, each of its pins is assigned a delay value by the initialization method for that device class.

Each LogicNode maintains a record of both its internal and its external state. In this case, the "external state" is the (high or low) state that actually appears at the pin of the device. The "internal state" is the state that is present at the logical elements of the device. Each LogicNode also keeps a record of its most recent internal state. This extra state record is essential to the simulation of edge-triggered devices, such as flip-flops and counters.

In the case of LogicNodes that are associated with output pins, a change in the internal state (as the result of a logical computation) triggers a propagation delay counter. If the internal and external states are still different when the counter reaches zero, the internal state appears at the output -- that is, the internal state's value is stored in the node's external state variable. During each simulation cycle, an output state is broadcast to all other nodes to which that node is connected.

This brings us to another point. An isolated IC pin is of little use. An IC pin must be connected to other pins, input signals, or output devices in order to perform a useful function. Each of LogicLab's simulated pins maintains a list of the other LogicNodes that form its connection chain. The node's private data contains the identity of its successor and its predecessor nodes. This information allows an output pin to access all of the input pins that are affected by the output pin's signal.

Listing Five (page 138) is the source code for the output method implemented in the LogicNode class. This method detects changes in the node's internal state, times the propagation delay, and broadcasts the current external state along the node's connection chain. The output method is invoked at each simulation cycle, once per simulated nanosecond.

Putting It All Together

Ultimately, LogicLab simulates a circuit. Somewhere along the line, it must simulate logic devices. Listing Six (page 138) is the simulation method found in the class N74LS00. A hardware 74LS00 device is commonly known as a "Quad 2-Input NAND gate." This name indicates that a single IC in this device contains four logic gates. The output of each gate is determined by logically ANDing tow inputs and then negating that operation -- hence the name "NOT-AND," or "NAND."

The primary characteristic of this type of gate, which is exploited by the simulation, is that its output is low only when both inputs are high.

In LogicLab, one of the variables in each logic device is an array of LogicNodes. In Listing Six, the statement (pins at: 1) isHigh fetches the LogicNode from the first element of the device's pins array. The message isHigh is then sent to that LogicNode. In response to the isHigh message, the LogicNode returns either a true or false value, depending upon whether the node's internal state is high or low.

Most simple logic gates can be simulated in a similar manner. More complex functions, such as flip-flops and counters, can also be simulated in the same general way as long as you recognize that latched devices must remember their previous state between simulation cycles.

As a test of LogicLab's ability to simulate an actual circuit, I breadboarded the simple divide-by-three counter shown in Figure 4. The timing diagram that is expected for this circuit is shown below the schematic. Compare this timing diagram to the timing diagram in Figure 2, which was produced by LogicLab's simulation of the circuit. The primary difference between the two timing diagrams is the length of the propagation delays between inputs and outputs.

Objects Pay Big Dividends

This project demonstrated to me, beyond any doubt, the value of using an object-oriented programming system to construct a simulation. The ability to construct objects by using their physical counterparts as models meant that I was able to work within a familiar framework -- I already knew the relationships between logic devices, signals, logic probes, and analyzers. Smalltalk let me express those relationships essentially intact. Other languages would have required me to translate those relationships into a more rigid and less expressive form. Smalltalk's integrated, fully interactive development environment also assisted me at every step and let me produce the prototype with relative ease.

The only drawback to Smalltalk is its performance. Not only is Smalltalk an interpreted language, but the overhead associated with message sends inflicts a significant penalty upon run-time performance. The end result is that LogicLab requires approximately one second of real time for every ten nanoseconds of simulated time. Blazing speed is not this simulator's claim to fame! But the slow run-time performance is easily overlooked, once you've experienced firsthand the ease with which Smalltalk lets you transform ideas into functional extensions of the environment itself.

_AN OBJECT-ORIENTED LOGIC SIMULATOR_ by Kenneth E. Ayers [LISTING ONE]



Object subclass: #LogicLab
  instanceVariableNames:
    'devices signals switches clashes changed topPane analyzer breadboard
                                               listSelector currentComponent '
  classVariableNames: ''
  poolDictionaries:
    'FunctionKeys CharacterConstants ' !

!LogicLab class methods !
description
         "Answer a String describing the
         application and version."
      ^'LogicLab (Version 1.0 -- 06/26/88)'.!
new
        "Answer an initialized LogicLab application."
    | logicLab |
    logicLab := super new.
    logicLab initialize.
    ^logicLab.! !

!LogicLab methods !
addComponent: aComponent
           "Add aComponent to the circuit description.
         If there is an error, answer nil;  otherwise
         answer aComponent."
    | name |
       name := aComponent name.
    name size == 0
         ifTrue: [
           "
                User is installing -- get a name.
            "
         name := self getNewName.
          name isNil
            ifTrue: [^nil].
          aComponent name: name]
        ifFalse: [
           "
                A name has been supplied -- this implies
                that the component is being installed from
                a file.  Need to check for a clash with
                an existing name.
            "
          ((self componentNamed: name) isNil)
          ifFalse: [
                "
                        Had a name clash -- get a synonym
                        from the user and stash both of them
                        away in the clashes table.  Then
                        rename the component.
                    "
                 name := self getNewName.
                name isNil
               ifTrue: [^nil].
              clashes
                 at: aComponent name
                    put: name.
              aComponent name: name]].
    changed := true.
    aComponent isDevice
           ifTrue:  [devices add: aComponent]
     ifFalse: [
           aComponent isSignal
           ifTrue:  [signals add: aComponent]
             ifFalse: [
                switches add: aComponent.
                analyzer isNil
                   ifFalse: [analyzer addSwitch: aComponent]]].
    ^aComponent.!
allNames
      "Answer an array of all of the
         names of installed components."
    ^((self deviceNames), (self signalNames), (self switchNames)).!
analyzer: aModel
       "Set the LogicAnalyzer Application model
         to aModel."
       analyzer := aModel.!
breadboardList
         "Answer an array of strings according to the
         current list selector."
    listSelector isNil
           ifTrue: [listSelector := #listDevices].
      ^(self perform: listSelector).!
breadboardMenu
           "Private -- answer the menu that processes
         breadboard functions."
    MenuPosition := Cursor position.
    ^(Menu
          labels: ('Load\Save\Erase\List\',
                 'Install\Connect\Remove\Disconnect\',
               'Simulate\',
            'Quit') withCrs
      lines: #(4 8 9)
     selectors: #(load     save     erase   list
               install  connect  remove  disconnect
                run
                quit)).!
changed
     "Answer true if the circuit has changed."
    ^changed.!
changed: aBoolean
     "Set the circuit-changed flag to aBoolean."
    changed := aBoolean.!
close
      "Close the LogicLab breadboarding window."
    topPane dispatcher deactivateWindow closeWindow.!
closeIt
        "Close the breadboard application window."
    self close.!
componentNamed: aName
           "Answer the component (device, signal, or switch)
         whose name is aName.  If no component can be found
         answer nil."
    | realName |
    realName := aName.
      clashes isNil
           ifFalse: [
         (clashes includesKey: aName)
          ifTrue:  [realName := clashes at: aName]].
    devices do: [:aDevice|
         (aDevice name = realName)
           ifTrue: [^aDevice]].
      signals do: [:aSignal|
        (aSignal name = realName)
         ifTrue: [^aSignal]].
    switches do: [:aSwitch|
        (aSwitch name = realName)
          ifTrue: [^aSwitch]].
     ^nil.!
componentTypeMenu: selectorArray
           "Answer a user-selected action for a
         component type."
    ^((Menu
           labels: 'Device\Signal\Switch' withCrs
         lines: #()
           selectors: selectorArray) popUpAt: MenuPosition).!
connect
        "Make a user-specified connection."
    | from to |
    from := self getNode.
    from isNil
     ifTrue: [^nil].
    to := self getNode.
       to isNil
       ifTrue: [^nil].
    from connect: to.
       changed := true.
    currentComponent := from model.
      listSelector := #listComponentConnections.
    breadboard update.!
description
          "Answer a string with a description of the receiver."
    ^(self class description).!
deviceNames
         "Answer a collection of all of the
         names of installed devices."
    | list |
    list := OrderedCollection new: (devices size).
    devices  do: [:aDevice| list add: aDevice name].
      ^list.!
devices
        "Answer the list of installed devices."
       ^devices.!
disconnect
           "Remove a user-specified connection."
    | node |
    node := self getNode.
    node isNil
           ifTrue: [^nil].
      node disconnect.
    changed := true.
      currentComponent := node model.
    listSelector := #listComponentConnections.
       breadboard update.!
erase
       "After user-verification, erase
         the circuit description."
    Cursor offset: MenuPosition.
    (self verify: 'Erase circuit description?')
       ifFalse: [^nil].
    self eraseCircuit.
    listSelector := #listDevices.
    changed := true.
      breadboard update.!
eraseCircuit
     "Erase the circuit description."
    devices  do: [:aDevice|
        self removeComponent: aDevice].
    signals  do: [:aSignal|
          self removeComponent: aSignal].
     switches do: [:aSwitch|
        self removeComponent: aSwitch].
       self initialize.!
getExistingComponent
        "Answer a user-specified component."
    | name component reply list |
     name := self getName.
      name isNil
        ifTrue: [^nil].
    component := self componentNamed: name.
      component isNil
     ifFalse: [^component].
       Cursor offset: MenuPosition.
       (Menu message:
         (name, ' not installed -- select from list?')) isNil
     ifTrue: [^nil].
    Cursor offset: MenuPosition.
    reply := self componentTypeMenu:
                 #(deviceNames signalNames switchNames).
       Cursor offset: MenuPosition.
       reply isNil
          ifTrue: [^nil].
     list := self perform: reply.
     (list size == 0)
     ifTrue: [
          Menu message: 'None installed'.
          Cursor offset: MenuPosition.
          ^nil].
       name := VariableMenu selectFrom: list.
    name isNil
           ifTrue: [^nil].
      name := list at: name.
    ^(self componentNamed: name).!
getExistingName
     "Answer a user-specified name of
         an existing component."
    | component |
    component := self getExistingComponent.
     component isNil
        ifTrue: [^nil].
       ^(component name).!
getFile
         "Answer a FileStream for a
         user-specified filename."
    | name |
     name := self getFilename.
    name isNil
        ifTrue: [^nil].
       ^(File pathName: name).!
getFilename
          "Answer a user-specified filename."
    | name |
     Cursor offset: MenuPosition.
     name :=
        Prompter
         prompt: 'Enter filename'
            default: ''.
    Cursor offset: MenuPosition.
    ^name.!
getName
          "Answer a user-specified name."
     | name |
    Cursor offset: MenuPosition.
    name :=
        Prompter
            prompt: 'Enter component name'
           default: ''.
      Cursor offset: MenuPosition.
      ^name.!
getNewName
       "Answer a user-specified name for
         a new component."
      | name |
    Cursor offset: MenuPosition.
    name :=
         Prompter
          prompt: 'Enter name for new component'
            default: ''.
       Cursor offset: MenuPosition.
       name isNil
         ifTrue: [^nil].
    [(self componentNamed: name) isNil]
       whileFalse: [
         name :=
            Prompter
                prompt: 'Name exists -- enter NEW name'
                default: name.
         Cursor offset: MenuPosition.
         name isNil
               ifTrue: [^nil]].
      ^name.!
getNode
        "Answer a user-specified LogicNode."
    | component |
     component := self getExistingComponent.
    component isNil
       ifTrue: [^nil].
    ^(component getNode).!
initialize
          "Private -- initialize a new
         LogicLab application."
    devices  := OrderedCollection new.
       signals  := OrderedCollection new.
     switches := OrderedCollection new.
    changed := true.!
install
           "Install a user-specified component."
    | component |
     component := LogicComponent install.
     component isNil
        ifTrue: [^nil].
       self addComponent: component.
    listSelector := self listSelectorFor: component.
    breadboard update.
    ^component.!
installClassFrom: aStream
       "Install a LogicComponent subclass
         whose name is the next word on aStream."
      | className |
       className := aStream nextWord.
    (Smalltalk includesKey: className asSymbol)
        ifFalse: [
          self error: ('Class: ', className, ' not installed')].!
installComponentFrom: aStream
      "Install a LogicComponent instance
         whose name is the next word on aStream."
     | className class component |
      className := aStream nextWord.
    class := LogicComponent classNamed: className.
    class isNil
     ifTrue: [
          self error: ('Unknown class: ', className).
         ^nil].
    component := class new installFrom: aStream.
    component isNil
          ifTrue: [^nil].
     ^(self addComponent: component).!
installConnectionFrom: aStream
        "Install a connection from aStream."
    | fromName from toName to fromNode toNode |
    fromName := aStream nextWord.
    from := self componentNamed: fromName.
      from isNil
        ifTrue: [
          self error: ('Unknown component: ', fromName).
            ^nil].
    fromNode := from getNodeFrom: aStream.
    fromNode isNil
         ifTrue: [
           self error: ('Unknown node on: ', fromName).
           ^nil].
    toName := aStream nextWord.
       to := self componentNamed: toName.
     to isNil
     ifTrue: [
          self error: ('Unknown component: ', toName).
          ^nil].
    toNode := to getNodeFrom: aStream.
    toNode isNil
      ifTrue: [
           self error: ('Unknown node on: ', toName).
         ^nil].
    ^(fromNode connect: toNode).!
installFrom: aStream
        "Load a circuit from the description
         on aStream."
    | keyWord |
    clashes := Dictionary new.
      [(aStream atEnd)
      or: [(keyWord := aStream nextWord) isNil]]
        whileFalse: [
         keyWord = 'LOAD'
              ifTrue:  [
                 self installClassFrom: aStream]
           ifFalse: [
                 keyWord = 'INSTALL'
                 ifTrue:  [
                    self installComponentFrom: aStream]
                  ifFalse: [
                     keyWord = 'CONNECT'
                        ifTrue:  [
                           self installConnectionFrom: aStream]
                          ifFalse: [
                        self error:
                            ('Unknown command: ',
                              keyWord)]]]].
      clashes release.
    clashes := nil.!
list
      "Process a user-specified list request."
    | selection |
    selection :=
       (Menu
         labels: ('Components\Connections\',
                  'Circuit Description') withCrs
          lines: #()
         selectors: #(listComponents
                      listConnections
                      listCircuit))
         popUpAt: MenuPosition.
    selection isNil
          ifTrue: [^nil].
     listSelector := selection.
    breadboard update.!
listCircuit
         "Answer a collection of strings with
         the circuit description."
    | name stream list |
    CursorManager execute change.
    name := 'logiclab.tmp'.
       stream := File pathName: name.
    list := OrderedCollection new.
    stream
         nextPutAll: '****  Circuit Description  ****';
       cr;
          cr.
    self storeOn: stream.
    stream flush.
    stream position: 0.
    [stream atEnd]
        whileFalse: [list add: stream nextLine].
    stream close.
     File remove: name.
    CursorManager normal change.
    ^list.!
listComponentConnections
          "Answer a collection of strings listing
         the connection chain(s) for the
         'currentComponent'."
    currentComponent isNil
        ifTrue:  [^#()]
       ifFalse: [
          ^(#('****  Connection List  ****' ' '),
              currentComponent connectionList)].!
listComponents
         "Answer a collection of strings containing
         a list of installed components."
    | selection |
    selection :=
     self componentTypeMenu:
             #(listDevices listSignals listSwitches).
    selection isNil
       ifTrue: [^#()].
    ^(self perform: selection).!
listConnections
     "Answer a collection of strings listing
         the connection chain(s) for a
         user-specified component."
    | component |
    component := self getExistingComponent.
     component isNil
        ifTrue: [^#()].
       currentComponent := component.
    ^self listComponentConnections.!
listContaining: aComponent
           "Answer the list (devices, signals, or switches)
         that includes aComponent."
    (devices includes: aComponent)
        ifTrue: [^devices].
       (signals includes: aComponent)
     ifTrue: [^signals].
    ^switches.!
listDevices
          "Answer a collection of strings containing
         a list of all the installed devices."
      | size list |
       size := devices size.
    size == 0
         ifTrue: [^#('No devices installed')].
    size := size + 1.
       list := OrderedCollection new: size.
       list add: 'DEVICES'.
       devices do: [:aDevice| list add: ('  ', (aDevice identify))].
    ^list.!
listSelectorFor: aComponent
          "Answer the list selector method used
         to produce the list for aComponent's type."
       aComponent isDevice
          ifTrue: [^#listDevices].
      aComponent isSignal
         ifTrue: [^#listSignals].
     ^#listSwitches.!
listSignals
        "Answer a collection of strings containing
         a list of all the installed input signals."
    | size list |
    size := signals size.
    size == 0
     ifTrue: [^#('No signals installed')].
      size := size + 1.
    list := OrderedCollection new: size.
    list add: 'SIGNALS'.
    signals do: [:aSignal| list add: ('  ', (aSignal identify))].
    ^list.!
listSwitches
       "Answer a collection of strings containing
         a list of all the installed swithces."
    | size list |
     size := switches size.
       size == 0
        ifTrue: [^#('No switches installed')].
    size := size + 1.
       list := OrderedCollection new: size.
       list add: 'SWITHCES'.
    switches do: [:aSwitch| list add: ('  ', (aSwitch identify))].
    ^list.!
load
     "Load a circuit description from
         a user-specified file."
    | file |
     file := self getFile.
      file isNil
        ifTrue: [^nil].
    self installFrom: file.
      listSelector := #listDevices.
       breadboard update.!
noDelay
         "Setup all components to ignore
         propagation delays."
    signals  do: [:signal| signal noDelay].
    switches do: [:switch| switch noDelay].
       devices  do: [:device| device noDelay].!
noMenu
     "Private -- answer an empty menu."
    ^(EmptyMenu new).!
open
         "Open the Breadboard and Analyzer windows."
    | size position |
     size := (Display boundingBox extent * 3) // 4.
       position := Display boundingBox center - (size // 2).
    topPane :=
          TopPane new
         label: ((self class description),
                    ' -- Breadboard');
         model: self;
         menu: #noMenu;
          yourself.
       topPane addSubpane:
          (breadboard := ListPane new
         name: #breadboardList;
           model: self;
           menu: #breadboardMenu;
          change: #doNothing:;
          framingRatio: (0 @ 0 extent: 1 @ 1)).
      topPane reframe: (position extent: size).
    topPane dispatcher openWindow scheduleWindow.!
quit
     "Quit this LogicLab."
      (self verify: 'Quit this LogicLab?')
          ifFalse: [^nil].
      self eraseCircuit.
    signals := switches := devices := nil.
      analyzer isNil
        ifFalse: [
          analyzer closeWindow.
           analyzer := nil].
    breadboard dispatcher deactivateWindow closeWindow.
       Scheduler systemDispatcher redraw.
     Scheduler resume.!
remove
     "Remove a user-specified component."
     | component |
      component := self getExistingComponent.
    component isNil
        ifTrue: [^nil].
    changed := true.
       listSelector := self listSelectorFor: component.
    self removeComponent: component.
       breadboard update.!
removeComponent: aComponent
     "Remove aComponent from the circuit."
      analyzer isNil
        ifFalse: [analyzer removeComponent: aComponent].
    (self listContaining: aComponent) remove: aComponent.
    aComponent remove.!
reset
         "Reset all components."
    signals     do: [:signal| signal reset].
     switches do: [:switch| switch reset].
      devices  do: [:device| device reset].!
restoreDelay
        "Setup all components to use
         propagation delays."
     signals  do: [:signal| signal restoreDelay].
     switches do: [:switch| switch restoreDelay].
     devices  do: [:device| device restoreDelay].!
resume
        "Resume the breadboarding application
         after running the simulation."
    Cursor offset: breadboard frame center.
    topPane dispatcher scheduleWindow.!
run
         "Invoke the LogicAnalyzer to run the simulation."
      analyzer isNil
        ifTrue: [
         analyzer := LogicAnalyzer new.
           analyzer openOn: self]
         ifFalse: [analyzer activate].!
save
           "Store the circuit description in
         a user-specified file."
    | file |
    file := self getFile.
     file isNil
       ifTrue: [^nil].
    CursorManager execute change.
    self storeOn: file.
    file
      flush;
        close.
      CursorManager normal change.!
selectName
     "Answer a user-selected name from a list
         of the names of installed components."
    | names index |
    names := self allNames.
      (names size == 0)
       ifTrue: [^nil].
    index := VariableMenu selectFrom: names.
      index isNil
         ifTrue: [^nil].
    ^(names at: index).!
signalNames
           "Answer a collection of all of the
         names of installed signals."
      | list |
    list := OrderedCollection new: (signals size).
    signals     do: [:aSignal| list add: aSignal name].
    ^list.!
signals
      "Answer the list of installed signals."
    ^signals.!
simulate
           "Simulate one pseudo-time interval."
    signals  do: [:signal| signal simulate].
       switches do: [:switch| switch simulate].
    devices  do: [:device| device simulate].!
storeClassesOn: aStream
           "Write a record of each component class
         used by the circuit on aStream."
       | classes |
      classes := Set new.
     devices  do: [:aDevice| classes add: aDevice class].
     signals  do: [:aSignal| classes add: aSignal class].
     switches do: [:aSwitch| classes add: aSwitch class].
     classes do: [:aClass|
          aStream
          nextPutAll: ('LOAD ', (aClass name));
           cr].!
storeComponentsFrom: aCollection on: aStream
       "Write a record of each logic component from
         aCollection installed in the circuit on aStream."
    aCollection do: [:aComponent|
      aStream nextPutAll: 'INSTALL '.
     aComponent storeOn: aStream.
         aStream cr].!
storeConnectionsOn: aStream
         "Write a record of each connection
         in the circuit on aStream."
    devices  do: [:aDevice| aDevice storeConnectionsOn: aStream].
    signals     do: [:aSignal| aSignal storeConnectionsOn: aStream].
     switches do: [:aSwitch| aSwitch storeConnectionsOn: aStream].
      devices  do: [:aDevice| aDevice unMark].
    signals  do: [:aSignal| aSignal unMark].
      switches do: [:aSwitch| aSwitch unMark].!
storeDevicesOn: aStream
      "Write a record of each logic device
         installed in the circuit on aStream."
    self storeComponentsFrom: devices on: aStream.!
storeOn: aStream
           "Write a description of the circuit on
         aStream in a form that can be recovered
         by the 'installOn:' method."
    self
      storeClassesOn: aStream;
      storeDevicesOn: aStream;
      storeSignalsOn: aStream;
      storeSwitchesOn: aStream;
       storeConnectionsOn: aStream.!
storeSignalsOn: aStream
           "Write a record of each logic signal
         installed in the circuit on aStream."
    self storeComponentsFrom: signals on: aStream.!
storeSwitchesOn: aStream
        "Write a record of each logic switch
         installed in the circuit on aStream."
      self storeComponentsFrom: switches on: aStream.!
switches
      "Answer the list of installed switches."
    ^switches.!
switchNames
        "Answer a collection of all of the
         names of installed swithces."
    | list |
    list := OrderedCollection new: (switches size).
    switches     do: [:aSwitch| list add: aSwitch name].
    ^list.!
verify: aPrompt
      "Ask the user to verify some condition."
    Cursor offset: MenuPosition.
    ^((Menu message: aPrompt) notNil).! !






[LISTING TWO]


LogicSwitch subclass: #ToggleSwitch
  instanceVariableNames: ''
  classVariableNames: ''
  poolDictionaries: '' !

!ToggleSwitch class methods !

type
        "Answer a string with the receiver's type."
    ^'Toggle Switch'.! !
!ToggleSwitch methods !
identify
        "Answer a string identifying the receiver."
    ^((self name),
        ' (', (self type), ')').!
push: aButton
        "Simulate pushing a toggle switch by
         inverting its state."
    node invert.
    node isHigh
        ifTrue:  [aButton lampOn]
        ifFalse: [aButton lampOff].
    (model isNil or: [changeSelector isNil])
        ifFalse: [model perform: changeSelector].!
reset
        "Reset the receiver."
    button isNil
        ifFalse: [
            node isHigh
                ifTrue:  [button lampOn]
                ifFalse: [button lampOff]].!
simulate
        "Simulate a toggle switch."
    node output.! !






[LISTING THREE]


LogicSwitch subclass: #PulserSwitch
  instanceVariableNames:
    'rest time timer '
  classVariableNames: ''
  poolDictionaries: '' !

!PulserSwitch class methods !

type
        "Answer a string with the receiver's type."
    ^'Pulser'.! !
!PulserSwitch methods !
identify
        "Answer a string identifying the receiver."
    ^((self name),
        ' (', (self type), ' -- ',
            (LogicNode
                statePrintString: (LogicNode not: rest)), ': ',
            (TimeInterval timePrintString: time), ')').!
initialize
        "Initialize a new PulserSwitch."
    super initialize.
    rest := false.
    time := timer := 0.!

install
        "Answer the receiver with user-specified
         rest state and pulse time."
    rest := LogicNode getState.     "User will select pulse state"
    rest isNil
        ifTrue: [^super release].
    rest := LogicNode not: rest.
    time := TimeInterval getTimeFor: 'pulse'.
    time isNil
        ifTrue: [^super release].
    ^self.!
installFrom: aStream
        "Answer a new PulserSwitch initialized with
         parameters read from aStream."
    super installFrom: aStream.
    rest := LogicNode stateNamed: aStream nextWord.
    node state: rest.
    time  := aStream nextWord asInteger.
    ^self.!
push: aButton
        "Simulate pushing a Pulser Switch."
    timer == 0
        ifTrue: [node state: (LogicNode not: rest)].
    timer := time.
    node isHigh
        ifTrue:  [aButton lampOn]
        ifFalse: [aButton lampOff].
    (model isNil or: [changeSelector isNil])
        ifFalse: [model perform: changeSelector].!
reset
        "Reset the receiver's state to its resting
         state and its timer to zero."
    node state: rest.
    timer := 0.
    button isNil
        ifFalse: [
            node isHigh
                ifTrue:  [button lampOn]
                ifFalse: [button lampOff]].!
simulate
        "Simulate a Pulser Switch."
    timer == 0
        ifTrue: [
            node state: rest.
            button isNil
                ifFalse: [
                    node isHigh
                        ifTrue:  [button lampOn]
                        ifFalse: [button lampOff]]]
        ifFalse: [timer := timer - 1].
    node output.!
storeOn: aStream
        "Store a record of the receiver on aStream."
    super storeOn: aStream.
    aStream
        nextPutAll: (' ',
                        (LogicNode statePrintString: rest), ' ',
                        (time printString)).! !





[LISTING FOUR]


LogicDevice subclass: #N74LS00
  instanceVariableNames: ''
  classVariableNames: ''
  poolDictionaries: '' !

!N74LS00 class methods !
description
        "Answer a string with a description
         of the receiver's function."
    ^'Quad 2-input NAND gate'.!
type
        "Answer a string with the receiver's type."
    ^'74LS00'.! !
!N74LS00 methods !
initialize
        "Private -- initialize the propagation delays
         for a new 74LS00 LogicDevice."
    super
        initialize;
        initializeDelays:
            #(  5  5 10    5  5 10    0
               10  5  5   10  5  5    0 ).!
simulate
        "Simulate a 74LS00 device."
    ((pins at: 1) isHigh and: [(pins at: 2) isHigh])
        ifTrue:  [(pins at: 3) output: false]
        ifFalse: [(pins at: 3) output: true].
    ((pins at: 4) isHigh and: [(pins at: 5) isHigh])
        ifTrue:  [(pins at: 6) output: false]
        ifFalse: [(pins at: 6) output: true].
    ((pins at: 10) isHigh and: [(pins at: 9) isHigh])
        ifTrue:  [(pins at: 8) output: false]
        ifFalse: [(pins at: 8) output: true].
    ((pins at: 13) isHigh and: [(pins at: 12) isHigh])
        ifTrue:  [(pins at: 11) output: false]
        ifFalse: [(pins at: 11) output: true].! !






[LISTING FIVE]


output: aState

        "Generate aState as an output from the node."

    old := int.
    int := aState.
    int == ext
        ifTrue: [
            "State is stable"
            timer := 0.
            ^self outputToConnections].
    "State has changed"
    timer == 0
        ifTrue: [
            "No delay in progress -- initiate prop delay"
            delay == 0
                ifTrue: [
                    "No delay -- just change state"
                    ext := int]
                ifFalse: [
                    "Arm delay timer"
                    timer := delay].
            ^self outputToConnections].
    "Propagation delay in progress"
    timer := timer - 1.
    timer == 0
        ifTrue: [
            "Timer has expired -- update state"
            ext := int].
    self outputToConnections.





[LISTING SIX]


simulate

        "Simulate a 74LS00 device."

    ((pins at: 1) isHigh and: [(pins at: 2) isHigh])
        ifTrue:  [(pins at: 3) output: false]
        ifFalse: [(pins at: 3) output: true].
    ((pins at: 4) isHigh and: [(pins at: 5) isHigh])
        ifTrue:  [(pins at: 6) output: false]
        ifFalse: [(pins at: 6) output: true].
    ((pins at: 10) isHigh and: [(pins at: 9) isHigh])
        ifTrue:  [(pins at: 8) output: false]
        ifFalse: [(pins at: 8) output: true].
    ((pins at: 13) isHigh and: [(pins at: 12) isHigh])
        ifTrue:  [(pins at: 11) output: false]
        ifFalse: [(pins at: 11) output: true].







Copyright © 1989, Dr. Dobb's Journal