This session will be all about an extended example program dealing with Discrete Event Simulation of digital circuits. This example will show interesting ways how one can combine assignments and higher order functions. Our task will be to construct a digital circuit simulator. There are number of things that we need to do. We need to define a way to specify digital circuits. We need to define a way to how to run simulations and then we have to put everything together so that the circuits can actually drive the simulator. This is also a great example that shows how to build programs that do discrete event simulation. So we start with a small description language for digital circuits. A digital circuit is composed of wires and the functional components. Wires, transport signals and components transform signals. We represent signals using booleans true and false. So it's a digital circuit simulator and analog one. A signal is either true or false but not something in between. They're based components are also called gates. They are the inverter whose output is the inverse of its input. The AND Gate whose output is the conjunction, logical end of its inputs and the OR Gate whose output is a disjunction, logical or of its inputs. Once we have these three, we can construct the other components by combining the based components. one thing important for the simulation is that the components have a reaction time or a delay. That means their outputs don't change immediately after a change in their inputs. Logical gates are usually drawn with standard symbols. So this would be an AND Gate, this would be an OR Gate and this would be an inverter. We can use the basic gates to build more complicated structures. For instance, here we have created a half adder, the half adder takes two Inputs A&B. And then it has two outputs the sum and to carry. The carry is said if both a and b are set, so we have a logical AND Gate that goes into the carry output. The sum is said if either A or B is set, we have an OR Gate here and the carry is not set. So we have an inverter here. So that one is said if the carrier's falls and here we have an AND Gates or the sum is true only if either a or b is true and carry is false. Once we have to find a circuit like a half adder, we can use it in turn to define more complicated circuits. For instance, we can use to half adders to define a full adder. So what we do here is we first have a half adder that ads B and C producing a carry here and some here. We take the some and add a with another half adder. So the final sum is the sum of the whole full adder and the output carry is then the logical or of the carriers of the two half adders that you see here. So our next question is how do we put that in code? What we have to do is essentially find code representations for these three basic circuit diagrams so that they can be composed to give you half adults, full adders and any other components you might wish to construct. So our basic language then consists of the three gates here plus the wires. So a wire is needed to connect gates. And here it should be noted that a wire doesn't necessarily have to ends essentially any network that has the same current electrically counts as a wire. So for instance, the a wire would be not just this line here but it would also that would be part of the a wire and the c wire would consequently the the whole network that you see here. So these color things are wires and then we have gates that connect the wires, that's the basic situation. You see also that these circuits are not necessarily hierarchical. There potentially complex networks of components connected by wires. The way we're going to represent that is that we are going to have a class for wires and then we have a model where essentially we drop a gate a component onto a circuit board connecting it with the wires that are there. So essentially our idea to construct the circuit is based on side effects. You can think of it as essentially you have a circuit board, you have some wires and then essentially you put the gates as an action onto the spot and connect them with the virus. That's the mental model that we are going to pursue. So to start we need a class wire to model wires. So wires can then be constructed as follows. You can say A equals a new wire, B is a new wire, C is a new wire or as color allows you to shorten that. You can just write val abc equals wire. That means each of these three names will get the same right hand side namely create a new wire. And then we need functions that create the base components as a side effect. We dropped them on the board. So inverter says place an inverter between the input wire here and the output wire there. AND Gate say says place an AND Gate between the two inputs here and the output there. And OR Gates says create an OR Gate with those two inputs and the output. All three methods have unit as the result type. That means they act only as a side effect by essentially connecting themselves to the wires. They don't return anything interesting. Starting with these basic elements, wires and three kinds of gates. We can construct more complex components. For instance, here's how we can define a half adder. So it says we have four wires a, b, s and c. We create some internal wires which we're going to use later. We place an or gate between a, b and d. So that would be an OR Gate here. That would be d. We place an AND Gate between a, b and c. So that would be my AND Gate here. And that would be c. And connect that already. He plays an inverter between c and e. So that would be e now and we place an AND Gate between e and s, okay? And that's an AND Gate not an OR Gate, okay? And that's my half adder. So I define a method which means I draw a box around it and now I can use this thing as its separate components. So now I can start to drop half adders on my circuit board and connect them to further wires. So here's how you would do a full adder. It takes three input wires to output wires. Has three internal wires. And you place to half adders on the board connected as you see here. And then OR Gates for the two output carries. That gives you the final c out signal. I invite you to do that yourself for just following these instructions and verifying that you will get the full era that we've seen some slides ago. So here's an exercise for you. What logical function does this program described? There's a function f mystery function that takes to input wires a and b. And has an output wires c, and here are its internal workings. what's the logical function that gives, is it one of these six that you see here below? So one way to solve this puzzle is to look at these gates as describing logical formulas, for instance this inverter here would define the signal d to be naught a similarly e equals naught b. And f is a and e so fs is a and naught b, g his b and naught a, and the final results c is f or g. So the final result is true, if either is true in these falls or b is true and a is false, which means to say that the final result is true if a and b are different signals. So that's the answer to our pastor. Okay, let's proceed to an implementation of all this. So the class wire and the functions inverter andGate and orGate represent a small description language for digital circuits. We now need an implementation of this class and these methods to allow us to simulate circuits. So to be able to do this, we first have to clarify how do we do simulation? What interface do we have to run a simulation? So what we do next is develop a simple API for discrete event simulation. So a discrete events simulator performs actions that are specified by the user to be performed at a given moment and an action is a function that doesn't take any parameters and which returns unit. So an action is really just something that lives for the action, the side effect that it performs and the time when an action is performed to simulated, it has nothing to do with the actual time. So there's an internal timekeeping unit that essentially advances the time as a simulation progresses. So the way we'll set it up is that a concrete simulation will be inside an object that inherits from the trade simulation. And the trade simulation has the following signature of methods and members that we can use in our simulation object. So first there's the current time which is the simulated time. So that's the current time in our simulation gender is a method after delay that registers an action to be performed after a certain delay relative to the current time. So we say after delay and units run this block of statements. So what this does is it essentially stores this block to be run once the simulation has reached the time that's specified by current time plus delay. But after delay doesn't run the simulation by itself, that's done by a separate method called run, so run since she says, well, the actions that are now stored for the simulation, they should be performed at this point before going into details. I want to show you the outline of the different components that we are going to assemble. So at the very top is a simulation trade and that will be inherited by essentially our structure that defines the basic gates, gates need simulation because they will in the end them that will be simulated. And then from gates we will have another trade called circuits that contains things like half adders, full adders or other circuits that a user might want to define. So here we're sort of up here, that's essentially the system provides a simulation package and it provides the basic gates. When we define circuits, then maybe some of them are provided by the system and others could be provided by the user. So we could also have several classes here that essentially defined different libraries of circuits. In our example, we just need a single one. And then finally, we will have an object call it, call it simulator, which is the concrete simulation. So the concrete simulation essentially defines a test circuit that we now want to run and simulate. So we have already shown the circuits layer when we defined half ADDers and full ADDers. So that showed how to define these circuits. What we still need to do is we need to fill in the blanks for the gates class, what do inverter and and kate or get actually do and how it's why are defined. And we have to fill in the simulation class to implement the api that we have defined. So let's turn next to gates and in gates let's turn to while So wire must support three basic operations. The simulation might want to know what is the current signal on the wire, is it true or false? It might want to set the signal of a wire as an action. So that would modify the value of the signal transported by the wire. And the third basic operation add action allows the simulation to customize what should happen when the signal of a wire changes. We can add an action, we can attach that to the actions of a wire. And all of these attached actions are executed at each change of the transported signal. So basically we register an action to say perform this action when the signal of the wire changes. So here's an implementation of class wire, internally a wire would have a signal value initially it's false or no current and that's private, so you can't access it from the outside. And the other piece of state in a wire is the list of actions that are currently attached to the wire, initially that's the empty list. Now here the three methods get signals simply returns the current signal value. So the set signal operation sets the current signal value to the signal s and it also executes all the stored actions if the signal value changes. So this line here, it's maybe a bit cryptic, so let's analyze what this is. It doesn't for each and all the actions and what does it do? Well, it calls the action with the empty parameter list, so that's what that does. You could write this also a bit more expensive to save for a taken from actions, do execute the action aid, that's probably a bit clearer. The last operation add action simply adds the given action to the list of actions on the front and then it immediately calls the action. So why is that? Well, you can think of the circuit initially to be in an undefined state. So when I said signal value equals falls, I really should have said undefined. So once we have an action essentially, we immediately executed to essentially force the signal to be defined namely to be the output value of that action. So once we have wires, we can now proceed to define the basic gates. So let's start with inverter, we implement an inverter by installing an action on its input wire. So we have here input, add action, invert action. And what does invert action do? Well, it will sample the signal on the input wire and set the output to be the negation the knot of the input signal. That's what inversion means, but it will do so not immediately, but only after the inverted delay that we still have to specify. So to summarize, we've said before that in placing an inverter on the board essentially produces a side effect. What the side effect is to add the inward action to the set of actions on the input wire. If you now look at andgate, it's implemented in a very similar way. So, again, the andgate function here as a side effect. And the side effect is to add the andAction on its two input wires in one and in two. So what is andaction? Well, we get the signal from the two input wires here and here. And then we set the output signal to be the end of the two input signals and to do so only after a delay, namely, and gate delay. So, this action will be registered in the simulation to be take place at a point in time andgate delay from the current time that is now. And the Orgate is now implemented quite analogously. So, again, it adds an action to its input wires, that's the or action. And the or action that simply would take the disjunction, the or of the two input signals and do so after Orgate delay. So, here's a question to you to see whether you follow. What would happen if we compute in1sig and in2sig directly inside the after delay. As you see here. So, I don't bother to define them as two vowels in front. I Just essentially in line them here and here. As you see that. Would that give us the same behavior or would the behavior be different and in this case or get to would not model the or gate faithfully. And the answer is of course that would be something different. So, here we get the signal at the current simulated time. Let's just to get signal and then we wait orgatedelays and then we set the output signal. Whereas in the modified program we again right orgatedelay units, but we set the output signal to the disjunction of the input signals at this point in the future. And of course at this point in the future, something else might already have happened. So this is not a faithful model of an orchid. So, let's see where we are in the worksheet. So, I have here my simulation trait which is still empty. I have my gate strait which is again empty. I just have the class wire and the three methods. And I have the delays that are also defined here. And finally, I have circuits and they have already added half adder and full adder. I just need the interface of the gates class and that interfaces provided just the implementation that is still missing. So, what we've done then is we have implemented the class wire as you see here. We have implemented in virtue as you see here and we have implemented and gate and organs. So, what we have to do next is flesh out the simulation trade. So, the idea is to keep in every instance of the simulation trade, an agenda of actions to perform. An agenda is simply a list of events and each event is composed of an action and the time when the action must be executed, must be produced. The time is simulated time as we said before. The agenda is sort it in such a way that the actions to be performed first are in the beginning of the agenda. So, agenda is a list of events and here's our agenda which is initially nil. There's also a private variable call it current time or court time that contains the current simulation time. An application of the after delay method with some delay in some block given inserts the task event to be produced at current time plus delay to consist of the actions in block into the agenda at the right position. So, here's after delay, we produce the right event here. And here is inserted into the agenda. Insertion function is straightforward. So, that's essentially just what we did when we did sorting as well. So, which we just go through the agenda if the time of the first item in the agenda is less or equal to the time that the item that we want to insert and we insert in the tail of the agenda. And otherwise we put the item at the top of the agenda and follow it with the previous agenda. So, once we have an agenda we need to execute it. That's done in an event handling loop. The event, handling loops, remove successive elements from the agenda and performs the associated actions. So, here's an implementation of the event loop, It does a para match on agenda. As long as the agenda is non empty, it executes the first item on the agenda and it recursively calls itself. If the agenda is empty, it terminates. What does it mean to execute the first item of the agenda while we strip off the item from the agenda. So, the agenda now becomes the rest here. It sets the current time to the time stored in the first entry and it performs the action of the first event at this time, at the simulated time. Quick check whether the loop function is tail recursive. Yes, indeed, it calls itself as last action. That's important because of course the agendas for simulations might become quite long. So now, finally the run method, run method simply calls loop after it prints essentially a header. It installs the first action after delay zero that says simulation has started and here's the current time. So, it puts that at the front of the agenda and executes loop. One question for you, does every simulation terminate after the finite number of steps? At first glance, it might seem so, since loop just goes through the agenda left to right until the agenda is natural. However, remember that actions that are performed here can install further actions in the agenda through after delay. And that means that in fact we might never finish the simulation because every action will install one or more further actions in the agenda. And the agenda could even grow without bounds if every action installs more than one action into the agenda, so time to try it out. But before we can launch the simulation, we still need a way to examine the changes of the signals on the wires. So, far it's a black box. Something happens, but we don't know what. So, to this end we define another function called probe. So probe is essentially you have a wire, And then you have a pair of pliers and very bad at drawing. And then an old fashioned oscilloscope was something like that. That would tell you what goes on in these wires. So, that's what the probe is. So, a probe gets attached to this wire here and it has a name because we are actually going to print out the signal of the wire just rather than showing it as a curve like here. So, what we want to print is the probe action says print the name of the wire, print the current time and print the current value of that wire. And every time the signal on the wire changes. This probe action will be executed because we have added probe action as an action to that wire. I have added all these implementations to the worksheet. His probe. So, that's the last thing I've added here. So, it's time to set up a simulation. To set up a simulation, we define an object sim. And that extends essentially the circuits that we want to simulate [SOUND] And we get an error and says, okay, so three things that I haven't defined yet. Indeed, those were the methods InvertedDelay and GateDelay and OrGateDelay. I could define them right here, but that wouldn't be very systematic because when we define a library of gates then we don't really want to fix these delays at this point. The delays of these gates is technology dependent with a new generation of silicon, it might be different. So we want to define them somewhere else, further towards the actual simulation class. So what we do instead is we [SOUND] Create a separate trait for the delays, which you see here essentially our simulation objects extend circuits and delays. So it gets the circuits from one part and the delays from another. And here are the delays that I have to find just to do an example. So, if I look at my class diagram again, I have the classes simulation, gate circuits and my concrete simulation and the concrete simulation now also inherits from a trade that fixes the technology dependent delays. So here's a sample of simulation that we're going to do in the worksheet. We define four wires and place some probes so to input wires, a sum in a carry wire. And we want to place the probes on the sum and carry next we want to define a half adder using these wires. So he plays a half adder between Input one input to some and carry. Now we want to give the value true to input one and launch the simulation. So let's set up the simulation like this. I have the wires. I have two probes and the halfAdder. In order to do this without pre fixing. I've just imported the simulation objects all that way have access to everything that's defined in here. So we get the initial values of sum and carry which are both zero. Now to do something let's change the symbol of one of the inputs [SOUND] And run the simulation. So it's a simulation started and not more. But if you hover over it then we will see okay. The some probe gave us at time eight a new value troops after eight simulated units. Some signal went to truth. What we could do now is we could also set a signal for input two and run again. And what we see now is that the carry and some signals have changed the carrier signal at assimilated time 11 became true and some signal at simulated times 16 became false again. That just shows that the basic of simulations work. As expected. I invite you to define more circuits and have more simulation runs to play with it. So in fact logically speaking, we wouldn't have needed three gates since for instance, the organ can be defined in terms of and or in the so an alternative for the or gate would be to define it as a circuit that would then correspond to this circuit here, where we first put an inverter here and then inverter there and then we have an and Gate of the negated symbols and then we put a final inverter on the result wire. That's of course just a consequence of the formula that a or b is the same as not a and not b. So that's the circuit that we have drawn here. So a question to you, what would change in the circuit simulation if the implementation of orGate out that you have just seen was used for or would it be nothing the two simulations behave the same or what the simulations produce the same events but they indicated times are different. Would the times be different and or get out made us might also produce additional events or would the two simulations produce different events are together? What do you think? So, clearly the timings would be different in general, If you take our current example values than in better delay would be two and andGate delay would be three. So you would get a total delay of seven. Where's an or gate delay in our example, values head of delay of five. So that would give you different times. And you might think that's it, so it will be number two. But if you have actually tried this in the worksheet and you will actually see something else, the times are different and or get out might also produce additional events [SOUND] Or to demonstrate. I have plugged in the new version of the organ in the worksheet and now we have the run here where we get for the first run at time, number five, the value is true and then at 10 the values first false and then true again. So that looks at first really mysterious to explain the mystery. Let's have another look at this diagram again. So what we have here is essentially not a single event as in the or gate where essentially we have a single action to be produced, but we have multiple actions. We have to to invert actions and the end action and then you finally invert actions. So we get many more actions in our agenda that also will be executed sometimes at the same time. And when you have several items in the agenda that are executed at the same time, you can essentially get into leavings, you can have some things that happen at some moment before something else happened. And that was essentially here. What we observed that at time 10 you got one item in the agenda that just decided that the signal should be false. And at the same time because another item that put the signal back to true. So essentially, it's the fact that you have instead of a single item in the agenda, you have multiple items which are not executed atomically as a whole. They get interspersed with each other. And that causes this flattering behavior where you see events that you didn't see before. So to summarize state and assignments make our mental model of computation more complicated in particular because we lose referential transparency. On the other hand, assignments allow us to formulate certain programs in an elegant way. An example that we saw was this great event simulation here, a system is represented by a mutable list of actions and the effect of actions when they're called are to change the state of objects and also to install other actions to be executed in the future. As always, the choice between function and imperative programming must be made depending on the situation. The digital circuit simulation was a good example for a mixture of functional and imperative programming for essential two reasons. One reason is that the non hierarchical nature of circuit networks lends itself well to an imperative formulation where we essentially place gates and and circuits between wires. The other reason is that we simulated a real world system where things change with a system where the internal state also changes. So this is quite natural.