_AN OBJECT-ORIENTED LOGIC SIMULATOR_ by Kenneth E. Ayers
[LISTINÇ 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).! !
[LISTINÇ 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.! !
[LISTINÇ 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)).! !
[LISTINÇ 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].! !
[LISTINÇ 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.
[LISTINÇ 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].