Building a GLUI Control Panel

Read time: 16 minutes (4028 words)

We need a way to see our machine run. While we could hook things together and simply turn the machine on, that is not going to be a useful way to watch what is happening, and think our way through what is happening. In order to set up this machine to we can watch it run, we need to be able to control it at a very fine level, like one clock tick at a time. Actually, one control system step at a time would be better. What I want to do is build a control panel with buttons you can click that will tell the control unit to do one tiny step in the sequence of steps needed to complete an instruction. Ideally, we will have outputs showing on this control system where we can see that is going on. That means we need to display our data, as it moves along the wires.

We had enough power to do that in the graphics code I provided, but I found a tool that will make things easier to set up and much nicer looking.

GLUI

Originally developed at Oxford University back in 1999, GLUI is a lightweight GUI package that sits on top of GLUT (freeglut is what we are using). This package sets up a nice looking GUI, complete with several controls we can drive with our favorite pointing gadget - your Mouse!

The code is maintained on GitHub and is pretty easy to get running in your VM.

Ready. Here we go!

Add Dependencies

GLUI was available on my Mac using Homebrew, but on Linux, I needed to compile the code from source. To do that, I had to install a couple of other packages needed to build the code:

$ sudo apt-get install freeglut3-dev libxi-dev libxmu-dev

Those last two are needed to run graphics code on Linux using the X11 graphics system, which has been a part of Linux since the 1980s!

Clone GLUI

With the dependencies in place, we can clone the project:

$ cd cosc2325
$ git clone https://github.com/libglui/glui.git
$ cd glui

Building the project was simple:

$ make

See, Make is very common out there!

When the smoke cleared, there was a new file in the lib folder in this project named libglui.a. We need that file and one include file in our project folder.

New Project Folders

This new chunk of code is available as a “library” which is basically a bunch of object files wrapped up into a single file. Those object files are compiled for only one system, so it is important to make sure you do not try to use a library compiled for one system on another system.

I added the ability to build a project library file for our simulator to the Makefile system we are using. However, the original setup did not deal with libraries constructed for different systems. Since I routinely test on all three major platforms, I needed to tweak the build system to handle this new GLUI package, and be prepared to build libraries for any system. As a result, I had to tweak two files in the mk folder. Here are the new versions:

mk/cpp-project.mk
 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
CXX		:= g++
TARGET	:= $(PROJNAME)$(EXT)
LIB		:= libavrsim.a
LIBDIR	:= bin

HDRS	:= $(shell python mk/pyfind.py include .h)

USRCS	:= $(wildcard src/*.cpp)
LSRCS	:= $(shell python mk/pyfind.py lib .cpp)
ALLSRCS := $(USRCS) $(LSRCS)

UOBJS   := $(USRCS:.cpp=.o)
LOBJS   := $(LSRCS:.cpp=.o)

DEPS	:= $(USRCS:.cpp=.d) $(LSRCS:.cpp=.d)

ifeq ($(PLATFORM), Windows)
	DOBJS := $(subst /,\,$(UOBJS)) $(subst /,\,$(LOBJS))
else
	DOBJS := $(UOBJS) $(LOBJS)
endif

CXXFLAGS	:= -std=c++11 -Iinclude -MMD

.PHONY: cpp-build
cpp-build: $(TARGET) ## build C++ application

$(TARGET):	$(UOBJS) $(LIBDIR)/$(PLATFORM)/$(LIB)
	@python $(MK)/pyversion.py inc-build
	g++ -o $@ $^ $(LFLAGS)

%.o:	%.cpp
	$(CXX) -c -o $@ $< $(CXXFLAGS)

$(LIBDIR)/$(PLATFORM)/$(LIB):	$(LOBJS)
	ar rvs $@ $^
		
.PHONY:	run
run:	$(TARGET)	## launch primary build application
	$(PREFIX)$(TARGET)

.PHONY: clean
clean:	## remove all build artifacts
	$(RM) $(TARGET) $(DOBJS) $(DEPS)

.PHONY:	realclean
realclean:	## delete the library file
	$(RM) $(LIBDIR)/$(PLATFORM)/$(LIB)

-include $(DEPS)

mk/cpp-graphics.mk
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
ifeq ($(PLATFORM), Mac)
	CXXFLAGS += -I/usr/local/include
    CXXFLAGS += -Wno-deprecated-declarations
	LFLAGS += -framework OpenGL -framework GLUT
	LFLAGS += -Lbin/Mac -lGLUT -lglui
endif
ifeq ($(PLATFORM), Windows)
	CXXFLAGS += -I\tools\freeglut\include -Iinclude
	LFLAGS += -L\tools\freeglut\lib\x64 -Lbin/PC
	LFLAGS += -lfreeglut -lglui -lglu32 -lopengl32 
	LFLAGS += -Wl,--subsystem,windows
endif
ifeq ($(PLATFORM), Linux)
	CXXFLAGS += -I/usr/include -Iinclude
	LFLAGS += -Lbin/Linux -lGL -l GLU -lglut -lglui
endif

The only changes involve moving libraries into subfolders under bin, one for each platform.

The Makefile system does not build these new subfolders. You can do that as follows:

$ mkdir bin/Linux
$ mkdir bin/Mac
$ mkdir bin/PC

(Not all are needed, unless you work on these platforms).

Assuming you already have the bin/Linux folder in your project, do this:

$ cp lib/libglui.a /cosc2325/lab2-CPUfactory/bin/Linux
$ cp include/GL/glui.h /cosc2325/lab2-CPUfactory/include

Note

If you build this system on anothe rplatform, be sure to compile the GLUI library, and copy it into the correct subdirectory of bin. Currently, this is working on Linux and Mac.

Now, here is a test file you can run to see if this all works. The code looks a bit strange if you have never worked with graphics projects, but it at least proves we have a way to build a nice front-end for our simulator:

src/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
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// Copyright 2019 Roie R. Black
#include <string.h>
#include <iostream>
#include "glui.h"
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif


#define PC_INCREMENT    200
#define PC_RESET        201
#define PC_ID           300
#define INS1_ID         301
#define INS2_ID         302
#define PCN_ID          303
#define ALUOP_ID        304
#define RD_ID           305
#define RS_ID           306
#define K_ID            307
#define A_ID            308

// "live" variables accessed by graphics routines
int   last_x, last_y;
int   main_window;
int  _pc = 0;
int _counter = 0;
int _ins1 = 0;
int _ins2 = 0;
int _aluop = 0;
int _pcn = 0;
int _rd = 0;
int _rs = 0;
int _k = 0;
int _a = 0;


// This is the primary graphics object
GLUI *glui;

// control routine called when we click the mouse on a control
void control_cb(int control) {
    if (control == PC_INCREMENT) {
        _pc++;
    } else if (control == PC_RESET) {
        _pc = 0;
    }
}

// user keyboard handler
void myGlutKeyboard(unsigned char Key, int x, int y) {
  switch(Key) {
  case 27:
  case 'q':
    exit(0);
    break;
  };
  glutPostRedisplay();
}

// called when no other events need processing in the graphics loop
void myIdleFunction(void) {
    if (glutGetWindow() != main_window)
        glutSetWindow(main_window);
    _counter++;
    glui->sync_live();      // update display of "live variables"
    glutPostRedisplay();
}

// User mouse handler
void myGlutMouse(int button, int button_state, int x, int y ) {
  if ( button == GLUT_LEFT_BUTTON && button_state == GLUT_DOWN ) {
    last_x = x;
    last_y = y;
  }
}

// draw the current screen - most work is behind the scenes
void myGlutDisplay() {
  glClearColor( .9f, .9f, .9f, 1.0f );
  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  glutSwapBuffers();
}


int main(int argc, char* argv[]) {

  // basic graphics setup
  glutInit(&argc, argv);
  glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH );

  // create the top-level window and position it on the screen
  glutInitWindowPosition( 50, 50 );
  glutInitWindowSize( 300, 400 );

  // create graphics window
  main_window = glutCreateWindow( "AVRsim (v4.0.23)");

  // register the user functions
  glutDisplayFunc(myGlutDisplay);
  glutKeyboardFunc( myGlutKeyboard );
  glutMouseFunc( myGlutMouse );

  // create GUI panel subwindow in the main window
  glui = GLUI_Master.create_glui_subwindow(
        main_window, GLUI_SUBWINDOW_TOP);

  // add controls to subwindow
  new GLUI_StaticText( glui, "Controls" );
  new GLUI_Separator( glui );
    GLUI_EditText *counter_edittext =
        new GLUI_EditText(glui, "Count:", &_counter);

  new GLUI_Button( glui, "Step", PC_INCREMENT, control_cb);
  new GLUI_Button( glui, "Reset", PC_RESET, control_cb);
  new GLUI_Button( glui, "Quit", 0,(GLUI_Update_CB)exit );

  // set up machine display area
  GLUI_Panel * machine = new GLUI_Panel(glui, "Machine");
  GLUI_Panel * fetch_panel = new GLUI_Panel(machine, "Fetch");
  new GLUI_EditText(fetch_panel, "PC",
         GLUI_EDITTEXT_INT,
         &_pc, PC_ID, control_cb);
  new GLUI_EditText(fetch_panel, "Ins1",
         GLUI_EDITTEXT_INT,
         &_ins1, INS1_ID, control_cb);
  new GLUI_EditText(fetch_panel, "Ins2",
         GLUI_EDITTEXT_INT,
         &_ins2, INS2_ID, control_cb);

  GLUI_Panel * decode_panel = new GLUI_Panel(machine, "Decode");
  new GLUI_EditText(decode_panel, "ALUop",
         GLUI_EDITTEXT_INT,
         &_aluop, ALUOP_ID, control_cb);
  new GLUI_EditText(decode_panel, "PC+1",
         GLUI_EDITTEXT_INT,
         &_pcn, PCN_ID, control_cb);
  new GLUI_EditText(decode_panel, "Rd",
         GLUI_EDITTEXT_INT,
         &_rd, RD_ID, control_cb);
  new GLUI_EditText(decode_panel, "Rs",
         GLUI_EDITTEXT_INT,
         &_rs, RD_ID, control_cb);
  new GLUI_EditText(decode_panel, "K",
         GLUI_EDITTEXT_INT,
         &_k, RD_ID, control_cb);
  new GLUI_EditText(decode_panel, "A",
         GLUI_EDITTEXT_INT,
         &_a, RD_ID, control_cb);

  new GLUI_Column(machine,true);
  GLUI_Panel * execute_panel = new GLUI_Panel(machine, "Execute");
  GLUI_Panel * store_panel = new GLUI_Panel(machine, "Store");

  glui->set_main_gfx_window(main_window);
  GLUI_Master.set_glutIdleFunc(myIdleFunction);
  glutMainLoop();
  return EXIT_SUCCESS;
}

We will remove the current main.cpp file and replace it with this code as a start on our front-end code.

When you run this code, you should see this:

../../_images/glui-test.png

The counter is running showing the number of “frames displayed by the graphics loop. This gives you a feel for how often we redraw the screen. The PC window shows a program counter, which we start at zero. Clicking on the Step button should increment the PC value, clicking on Reset should return that PC to zero.

At the moment, nothing is hooked intot hsicontrol system, but that will happen soon. I will post notes showing how that is proceeding as I get things running.

Hey, this is a good start on building something we can use to watch our simulator work!