In Part 2, we went from using Icarus in Part 1 to using Verilator to run our test bench. In itself, in can be useful but the major benefit is that that test bench can be easily used by other C++ code and can utilize other libraries fairly easily. In this example we'll throw together a simple test framework that will visualize input and outputs to the ALU.

Starting with the test bench from the previous section, my_not_test.cpp:

#include <fstream>
#include "Vmy_not.h"
using namespace std;

ofstream dump("my_not_test.out");
Vmy_not top;

void evalDump()
{
    top.eval();
    dump << "|   " << (int)top.in << "   |   " << (int)top.out << "   |" << endl;
}

int main()
{
    dump << "|  in   |  out  |" << endl;
    top.in = 0;
    evalDump();
    top.in = 1;
    evalDump();
    return 0;
}

my_alu_test_sdl.cpp:

//include the SDL and font headers
#ifdef WIN32
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#else
#include <SDL.h>
#include <SDL_ttf.h>
#endif

#include <memory>
#include <vector>
#include "Vmy_alu.h" //the ALU Verilog to C++ header
using namespace std;

Vmy_alu top; //ALU implementation

//SDL renderer and single font (leaving global for simplicity)
SDL_Renderer *renderer;
TTF_Font *font;

//parent class Vis will be inherited by our 16 bit and 1 bit values,
//it sets up common items click the text rendering and interface for
//draw and click
class Vis {
public:
    Vis(int x, int y)
    {
        //set starting position of rect, the size is unknown at this time
        m_rect = { x, y, 0, 0};
    }
    virtual void draw()
    {
        //if there's a texture set, draw it
        if(textTexture) {
            SDL_Rect dest = m_rect;
            //get the text size
            SDL_QueryTexture(textTexture, NULL, NULL, &dest.w, &dest.h);
            //offset of -5, -10 from the Vis item itself
            dest.x -= dest.w + 5;
            dest.y -= 10;
            //draw the text to the renderer surface
            SDL_RenderCopy(renderer, textTexture, nullptr, &dest);
        }
    }
    //interface for mouse
    virtual bool click(int x, int y) = 0;
    void setText(const char *str)
    {
        //if there's already a texture, remove it
        if(textTexture) {
            SDL_DestroyTexture(textTexture);
            textTexture = nullptr;
        }
        //white font
        SDL_Color color = {255, 255, 255, 255};
        //create the surface of the font in host space
        SDL_Surface *textSurface = TTF_RenderText_Solid(font, str, color);
        //create the texture of the font in video space
        textTexture = SDL_CreateTextureFromSurface(renderer, textSurface);
        //should probably free the textSurface?
        //SDL_FreeSurface(textSurface);
    }
protected:
    //stores our Vis elements location and size (not including message)
    SDL_Rect m_rect;
    SDL_Texture *textTexture = nullptr;
    //some defaults
    static int box_size, box_space, group_space;
};

int Vis::box_size = 20; //size of each bit
int Vis::box_space = 5; //space between each bit
int Vis::group_space = 10; //space between each 4-bit group (simple for hex)

//Vis16 is the 16bit visualization element
class Vis16 : public Vis {
public:
    Vis16(int x, int y, SData &value) : Vis(x, y), m_value(value) { }
    void draw()
    {
        Vis::draw(); //handles text rendering

        //iterate through each group of 4-bits
        int ipos = 0;
        int c = 0;
        for (int g = 0; g < 4; g++) {
            //iterate through each bit in the group
            for (int i = 0; i < 4; i++) {
                SDL_Rect rect = { ipos + m_rect.x, m_rect.y, box_size, box_size };

                //each bit value in the value itself (reverses bit order visually)
                //and sets color to white or black accordingly
                if (m_value & 1 << (15 - c))
                    SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
                else
                    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);

                //display bit
                SDL_RenderFillRect(renderer, &rect);
                ipos += box_size + box_space;
                c++;
            }
            //increase position by group spacing
            ipos += group_space;
        }
    }
    bool click(int x, int y)
    {
        //mouse click position
        SDL_Point p = { x, y };

        int ipos = 0;
        int c = 0;
        //iterate each group
        for (int g = 0; g < 4; g++) {
            //iterate each bit
            for (int i = 0; i < 4; i++) {
                SDL_Rect rect = { ipos + m_rect.x, m_rect.y, box_size, box_size };

                //checks for mouse point in bit's rectangle
                if (SDL_PointInRect(&p, &rect)) {
                    m_value ^= 1 << (15 - c);
                    return true; //hit
                }
                ipos += box_size + box_space;
                c++;
            }
            ipos += group_space;
        }

        return false; //no hit
    }
private:
    SData &m_value; //16bit value in model
};

//Vis1 is the 1bit visualization element
class Vis1 : public Vis {
public:
    Vis1(int x, int y, CData &value) : Vis(x, y), m_value(value) { }
    void draw()
    {
        Vis::draw();
        SDL_Rect rect = { m_rect.x, m_rect.y, box_size, box_size };
        if (m_value)
            SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
        else
            SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderFillRect(renderer, &rect);
    }
    bool click(int x, int y)
    {
        SDL_Rect rect = { m_rect.x, m_rect.y, box_size, box_size };
        SDL_Point p = { x, y };
        if (SDL_PointInRect(&p, &rect)) {
            m_value = !m_value;
            return true;
        }
        return false;        
    }
private:
    CData &m_value; //1bit value in model (stored as 8bit in memory)
};

vector<shared_ptr<Vis>> viss;

void handleMouse(int x, int y)
{
    //iterate through each visual element and handle mouse click
    for (auto vis : viss)
        if (vis->click(x, y))
            return;
}

int handleInput()
{
    SDL_Event event;
    //event handling, check for close window, escape key and mouse clicks
    //return -1 when exit requested
    while (SDL_PollEvent(&event)) {
        switch (event.type) {
        case SDL_QUIT:
            return -1;
        case SDL_KEYDOWN:
            if (event.key.keysym.sym == SDLK_ESCAPE)
                return -1;
        case SDL_MOUSEBUTTONDOWN:
            handleMouse(event.button.x, event.button.y);
            break;
        }
    }

    return 0;
}

void initVideo()
{
    //setup SDL with title, 640x480, and load font
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window *window = SDL_CreateWindow("ALU Test - SDL", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, 0);
    renderer = SDL_CreateRenderer(window, -1, 0);
    TTF_Init();
    font = TTF_OpenFont("FreeMonoBold.ttf", 36);
    assert(font);
}

void draw()
{
    //clear screen, draw each element, then flip the buffer
    SDL_SetRenderDrawColor(renderer, 100, 100, 100, 255);
    SDL_RenderClear(renderer);

    for (auto vis : viss)
        vis->draw();

    SDL_RenderPresent(renderer);
}

//simple wrapper for adding 16bit visual elements to our viss vector
shared_ptr<Vis16> add(int x, int y, SData &v, const char *str = nullptr)
{
    //use a shared pointer, set the text, and add it to the vector
    shared_ptr<Vis16> s = make_shared<Vis16>(x, y, v);
    if(str)
        s->setText(str);
    viss.push_back(s);
    return s;
}

//simple wrapper for adding 1bit visual elements to our viss vector
shared_ptr<Vis1> add(int x, int y, CData &v, const char *str = nullptr)
{
    shared_ptr<Vis1> s = make_shared<Vis1>(x, y, v);
    if (str)
        s->setText(str);
    viss.push_back(s);
    return s;
}

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

    //set up where we'll start drawing the inputs
    int xstart = 110;
    int ystart = 80;

    //set where to draw the outputs
    int ystart2 = ystart+220;

    //the current x position
    int posx = xstart;

    //how much to increment between the elements
    int xinc = 81;
    int yinc = 40;

    //create and place the input visual elements
    add(xstart, ystart, top.x, "x");
    add(xstart, ystart+ yinc, top.y, "y");
    add(posx, ystart+yinc*2, top.zx, "zx");
    add(posx += xinc, ystart + yinc * 2, top.nx, "nx");
    add(posx += xinc, ystart + yinc * 2, top.zy, "zy");
    add(posx += xinc, ystart + yinc * 2, top.ny, "ny");
    add(posx += xinc, ystart + yinc * 2, top.f, "f");
    add(posx += xinc, ystart + yinc * 2, top.no, "no");

    //create and place the output visual elements
    add(xstart, ystart2, top.out, "out");
    add(xstart, ystart2+yinc, top.zr, "zr");
    add(xstart + xinc, ystart2+yinc, top.ng, "ng");

    //main loop
    do {
        top.eval(); //preform model update based on inputs
        draw();
    }
    //run until exit requested
    while (handleInput() >= 0);

    return 0;
}

Next we'll compile the Verilog to C++ compile.sh:

verilator -Wall --cc my_alu.v

Then build the test bench build_sdl.sh:

g++ `sdl2-config --cflags --libs` -lSDL2_ttf -Iobj_dir -I/usr/local/share/verilator/include -std=c++11 /usr/local/share/verilator/include/verilated.cpp my_alu_test_sdl.cpp obj_dir/Vmy_alu.cpp obj_dir/Vmy_alu__Syms.cpp -o my_alu_test_sdl

This should work in Linux and OSX as is. For Windows you can just run the compile manually or rename the compile.sh to compile.cmd and run. For the build itself, I've attached .sln and .vcxproj files for Visual Studio 2017.

First screen shot is the initial values. If all the inputs are set to zero, we should just see the zero flag (zr) showing true, everything else should be false. If zr is showing false then our model hasn't been wired up correctly.

Let's test it out. Set x to 0x00ff (255), so first group to zero, 2nd group to zero, 3rd group to f (16) so all for on, and the 4th group the same as the 3rd. Then set function (f) to true, so that the ALU will perform an add operation. We should see that output should show 0x0100 (256). zr and ng should show false.

Now set the function (f) back to false, and we see that the ALU has performed and And operation, basically just the last bit in out will be true.

For the next section, I'll probably add information to load values into our memory module, then examine the different memory area contents. I haven't narrowed down the interface quite yet, but I'm thinking of something like a PDP-11 front panel interface (bit flip interface), or a Kim-1 (hex and lcd interface), or a Woz Monitor (text debugger interface). The PDP-11 probably being fairly similar to our current interface.