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:
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.
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.
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.
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.
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.
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]
Copyright © 1989, Dr. Dobb's Journal
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].