Start by defining a Python function that we want to compute.


In [1]:
def f(a, b, c):
    return (a & b) ^ c

Generate a circuit that computes this function. To implement the logical operations we use standard verilog gates, which are available in mantle.verilog.gates.


In [2]:
import magma as m
import mantle

class VerilatorExample(m.Circuit):
    io = m.IO(a=m.In(m.Bit), b=m.In(m.Bit), c=m.In(m.Bit), d=m.Out(m.Bit))
    io.d <= f(io.a, io.b, io.c)

m.compile("build/VerilatorExample", VerilatorExample, "coreir-verilog", inline=True)
%cat build/VerilatorExample.v


/opt/homebrew/lib/python3.7/site-packages/pysmt/walkers/generic.py:43: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3,and in 3.9 it will stop working
  if len(nodetypes) == 1 and isinstance(nodetypes[0], collections.Iterable):
/opt/homebrew/lib/python3.7/site-packages/ast_tools/immutable_ast.py:10: UserWarning: /opt/homebrew/lib/python3.7/site-packages/ast_tools/immutable_ast.py generated for (3, 6)does not match system version (3, 7)
  warnings.warn(f"{__file__} generated for (3, 6)"
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'definition' class method syntax is deprecated, use inline definition syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'definition' class method syntax is deprecated, use inline definition syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'definition' class method syntax is deprecated, use inline definition syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'definition' class method syntax is deprecated, use inline definition syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'definition' class method syntax is deprecated, use inline definition syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'definition' class method syntax is deprecated, use inline definition syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
WARNING:magma:'IO = [...]' syntax is deprecated, use 'io = IO(...)' syntax instead
module VerilatorExample (
    input a,
    input b,
    input c,
    output d
);
assign d = (a & b) ^ c;
endmodule

Next, generate a verilator test harness in C++ for the circuit. The test vectors are generated using the python function f. The verilator test bench compares the output of the simulator to those test vectors.


In [3]:
from itertools import product
from fault import Tester

tester = Tester(VerilatorExample)
for a, b, c in product([0, 1], [0, 1], [0, 1]):
    tester.poke(VerilatorExample.a, a)
    tester.poke(VerilatorExample.b, b)
    tester.poke(VerilatorExample.c, c)
    tester.eval()
    tester.expect(VerilatorExample.d, f(a, b, c))
tester.print("done!!")
tester.compile_and_run("verilator", directory="build")
%cat build/VerilatorExample_driver.cpp


Failed to import libraries for results parsing.  Capabilities may be limited.
#include "VVerilatorExample.h"
#include "verilated.h"
#include <iostream>
#include <fstream>
#include <verilated_vcd_c.h>
#include <sys/types.h>
#include <sys/stat.h>

// Based on https://www.veripool.org/projects/verilator/wiki/Manual-verilator#CONNECTING-TO-C
vluint64_t main_time = 0;       // Current simulation time
// This is a 64-bit integer to reduce wrap over issues and
// allow modulus.  You can also use a double, if you wish.

double sc_time_stamp () {       // Called by $time in Verilog
    return main_time;           // converts to double, to match
                                // what SystemC does
}

// function to write_coverage
#ifdef _VERILATED_COV_H_
void write_coverage() {
     VerilatedCov::write("logs/coverage.dat");
}

#endif

#if VM_TRACE
VerilatedVcdC* tracer;
#endif

void my_assert(
    unsigned int got,
    unsigned int expected,
    int i,
    const char* port) {
  if (got != expected) {
    std::cerr << std::endl;  // end the current line
    std::cerr << "Got      : 0x" << std::hex << got << std::endl;
    std::cerr << "Expected : 0x" << std::hex << expected << std::endl;
    std::cerr << "i        : " << std::dec << i << std::endl;
    std::cerr << "Port     : " << port << std::endl;
#if VM_TRACE
    // Dump one more timestep so we see the current values
    main_time++;
    tracer->dump(main_time);
    tracer->close();
#endif
    
    exit(1);
  }
}

int main(int argc, char **argv) {
  Verilated::commandArgs(argc, argv);
  VVerilatorExample* top = new VVerilatorExample;
  
#if VM_TRACE
  Verilated::traceEverOn(true);
  tracer = new VerilatedVcdC;
  top->trace(tracer, 99);
  mkdir("logs", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
  tracer->open("logs/VerilatorExample.vcd");
#endif

  top->a = 0;
  top->b = 0;
  top->c = 0;
  top->eval();
  main_time++;
  #if VM_TRACE
  tracer->dump(main_time);
  #endif
  my_assert(top->d, 0 & 1, 4, "VerilatorExample.d");
  top->a = 0;
  top->b = 0;
  top->c = 1;
  top->eval();
  main_time++;
  #if VM_TRACE
  tracer->dump(main_time);
  #endif
  my_assert(top->d, 1 & 1, 9, "VerilatorExample.d");
  top->a = 0;
  top->b = 1;
  top->c = 0;
  top->eval();
  main_time++;
  #if VM_TRACE
  tracer->dump(main_time);
  #endif
  my_assert(top->d, 0 & 1, 14, "VerilatorExample.d");
  top->a = 0;
  top->b = 1;
  top->c = 1;
  top->eval();
  main_time++;
  #if VM_TRACE
  tracer->dump(main_time);
  #endif
  my_assert(top->d, 1 & 1, 19, "VerilatorExample.d");
  top->a = 1;
  top->b = 0;
  top->c = 0;
  top->eval();
  main_time++;
  #if VM_TRACE
  tracer->dump(main_time);
  #endif
  my_assert(top->d, 0 & 1, 24, "VerilatorExample.d");
  top->a = 1;
  top->b = 0;
  top->c = 1;
  top->eval();
  main_time++;
  #if VM_TRACE
  tracer->dump(main_time);
  #endif
  my_assert(top->d, 1 & 1, 29, "VerilatorExample.d");
  top->a = 1;
  top->b = 1;
  top->c = 0;
  top->eval();
  main_time++;
  #if VM_TRACE
  tracer->dump(main_time);
  #endif
  my_assert(top->d, 1 & 1, 34, "VerilatorExample.d");
  top->a = 1;
  top->b = 1;
  top->c = 1;
  top->eval();
  main_time++;
  #if VM_TRACE
  tracer->dump(main_time);
  #endif
  my_assert(top->d, 0 & 1, 39, "VerilatorExample.d");
  printf("done!!");


#if VM_TRACE
  tracer->close();
#endif
  

#ifdef _VERILATED_COV_H_
    write_coverage();
#endif

}

Using fault, we can use the same tester and (with the same testbench inputs/expectations) and use a different backend, like the python simulator.


In [4]:
tester.compile_and_run("python")


done!!