Animation and the Machine

Read time: 34 minutes (8518 words)

We have a machine that operates in a loop, working through the four steps to process one instruction. We will want to run that cycle continuously (or until we want it to stop) and watch what our machine does. If we are interested in the output of that machine, speed may be important. If we want to verify that it is operating correctly, we may want to step it very slowly so we can examine the machine in detail.

The animation engine we are given in the graphics system is capable of running at a controlled rate. Normal animation applications decide on a frame rate, which determines how often we will update the graphics display to make things move. In the demonstration code, we have set the frame rate at 80 frames per second.

That means two routines will be called every frame: one to draw the screen, and one to update data used to do that drawing. While all of that is going on, we have routines that will check for mouse and keyboard events.

Our task is to figure out exactly what we want to see on the screen while this machine is running, then come up with a suitable way to make that happen.

Merging the Loops

The animation loop and the machine loop need to be synchronized while we are animating things, so the display can be controlled easily. That means we should make either the display or animate functions take control of the machine loop. Traditionally, the place to put the calls to do that step is in the animate function, and leave the display method alone, just having it draw a single screen.

Single Stepping

The easiest way to make this happen is to design our machine so it performs one step at a time, then plug a call to that one-step method into the animate function so it happens over and over.

This will work well for other reasons. When debugging programs, it is common to use a debugger that can “single step” your program, stopping each step so you can examine variables in memory at that moment. We might want to do something similar with our machine, only we will be examining signals moving around as the machine runs.

Full Speed Ahead!

If we want our simulator to run as fast as it can, we might need to give up the animation, and just let is run!. That mode of operation can be done by wrapping up our single step logic in a loop that is not slowed down to accommodate a specific “frame rate”.

Switching Modes

Another feature of modern debuggers is the ability to set “breakpoints”, which are points in your code where you want the machine to stop, giving you control and the ability to examine the world inside the machine. The code runs full speed until it reaches that breakpoint location, then it halts and gives control back to the user.

In our animation system, we have the tools to make buttons we can use to start and stop the machine. What we do when it is stopped is something we need to figure out.

Stages or Cycles

Since we are watching the machine stage by stage, looking at how it operates, it makes more sense to let the animation loop run through the stages, not through full instruction cycles. We might want to stop the machine in the middle of processing one instruction.

In the example code shown in class, we saw a display cycle through the four step process, lighting up a virtual LED as it processed each cycle. What we need to do it tie that code to our simulator code, and add a few buttons we can click to control the action!

Step Logic

Let’s assume we are going to set up a control system for our machine that can step on stage at a time. Here is the basic logic we need to use this scheme:

main.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>

long frame_count = 0;
long stage_count = 0;
int stage;
bool running = true;
bool guienables = true;

void single_step(int stage) {
    switch (stage) {
        case 0: fetch.tick(); break;
        case 1: decode.tick(); break;
        case 2: execute.tick(); break
        case 3: store.tick(); break;
        default: std::cout << "should not happen" << std::endl;
    }
}


void animate(void) {
    if (runnng) {
        stage = stage_count % 4;    // cycles from 0-3
        single_step(stage);
        stage_count++
    }
    frame_count++;
}

int main(void) {


    if (guienabled) {
        // this happens inside glutMainLoop
	    while (true) {		// simulate the animation loop
            animate();
        }
    } else {
        while (true) {
            // run full speed
            stage = stage_count % 4;
            single_step(stage);
            stage_count ++;
        }
    }
}

Note that we are using global variables to make this work. This is not the most popular thing to do, but it simplifies the code, which would need to pass those variables around as references if we did not do this.

Here, we are using the frame_count to track the number of calls to the animate routine. We cannot use that variable directly if we want to be able to “pause” our machine, so we need a second variable, stage_count, that tracks the number of stages we have worked through. If the machine is stopped, we will not update that stage_count counter, while the frame_count variable will continue to increase. (Based on your homework, you know how long it will take to reach the biggest unsigned number you can reach with a 64-bit integer!)

Throttle Control

While this logic will work, it still locks us into the fixed frame rate (divided by 4) for stepping. This is probably too fast for observing the action in a single instruction.

Can we “throttle” the animation, meaning provide a way to make it slower, until it runs at some speed we like?

If we set up another integer variable, whose value ranges from 0 to some high number, we can use this to delay the calls to the switch statement that makes things happen. Here is the idea:

main.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>

long frame_count = 0;
long stage_count = 0;
int stage;
int delay = 0;   // no delay added
int count = 0;      // tracks calls
bool running = true;
bool guienables = true;

void single_step(int stage) {
    switch (stage) {
        case 0: fetch.tick(); break;
        case 1: decode.tick(); break;
        case 2: execute.tick(); break
        case 3: store.tick(); break;
        default: std::cout << "should not happen" << std::endl;
    }
}


void animate(void) {
    if (runnng) {
        if (count == delay) {
            stage = stage_count % 4;    // cycles from 0-3
            single_step(stage);
            stage_count++
            count = 0;
        }
        count++;
    }
    frame_count++;
}

int main(void) {


    if (guienabled) {
        // this happens inside glutMainLoop
	    while (true) {		// simulate the animation loop
            animate();
        }
    } else {
        while (true) {
            // run full speed
            stage = stage_count % 4;
            single_step(stage);
            stage_count ++;
        }
    }
}

The graphics engine will call animate at our defined frame rate. We will not modify that at all. Inside of our aniate function, we set up another counter that will track how many times animate has been called. The idea is to let some of those calls skip running our simulator step scheme. If we call the step function every other call, our simulator logic will run at half speed. If we skip two calls, it will run at one third speed, and so on.

We do this by picking some control number that we will let the counter reach before we call our simulator step function. When we reach that number, we call our simulator function, then reset the counter so the process begins again. If we let the user control that maximum counter number, we have a throttle we can use to control our simulation! Neat!

Note

Well, neat if we can get that user control figured out. We do have enough capability to do that, all we need is some cretive controls!)

Note

We will use this scheme in later lab projects.

Adding this to the Code

What do we need to do to incorporate these ideas into our simulator?

First, we need to set up a control system that can run one stage at a time. The basic code was shown in the examples above. We have shown basically how we will do this in the previous lecture, but how we will control the entire application is another topic.

WHat we need to do is create a basic control diagram, showing what happens and who gets notified to make things run the way we want.

Ideally, we keep each class in this system focused on one part of the application, and do not mingle responsibilities where they do not belong.

Here is our program structure:

Main.cpp

This code is responsible for getting options from the user, creating the simulator object, ans launching the graphics system, if needed. Other than that, it simply waits for things to die, then it exits.

AVRsim.cpp

This class will be our simulator management object. It is responsible for assembling our machine, loading the program, and stepping one stage at a time through a simulation. It will not run the complete simulation. This object will not be involved with any graphics actions. Other parts of the program can ask the AVRsim object questions, or sent it new settings, but the internal operation of this object is private. Once the machine has been assembles, it will run as designed, and we can watch the action by asking for data using the AVRsim methods.

gui.cpp

This code is our graphical interface. Due to limitations of our graphics code, this will not be a class, just a set of functions we can set up to deal with graphics issues.

The code in this module needs to react to user inputs, and send commands to the AVRsim object to actually step through our simulation.

machine.cpp

Again, this code is just a set of functions that create the graphical user interface. This code is responsible for laying out the simulation screen, and placing the controls where we want them. The methods in this file will ba called from the GUI layer as needed to figure out what the user has done to alter operation of the application.

Here is a diagram, showing how these parts interact:

Note

C++ class objects are shown in green, non-class code is in red. Green is good, red is bad, but that is how things need to work for now. (I really need to update all that code!)

For the graphics interface, things are going to work better. We will create a set of controls we can use while the simulation is running. Here is a sample set:

  • Run - turn the simulator loose and let it run the program
  • Pause - stop the simulation and wait for user commands
  • STep - if the simulator is paused, step one stage and stop
  • Exit - quit the program
  • Increase/decrease simulator delay to control the speed
  • Reset - restart the program at the first instruction

If we get this far, we will have a nice program. The issue we face is figuring our how to get these controls into the simulator object and control the action.