Structure of the Verilog simulation block

Overall Code Structure

Posted by Bowen on May 29, 2019

This is a report about the overall structure of my GSoC project, cycle-accurate Verilog simulation integration.


Though some of the details have been discussed in my proposal, I have some new insights on this task.

I create a block named verilog_ii, this is a sync block that takes in unsigned integer and output unsigned integer.

I plan to do most of the work in the block’s constructor. There are several class I will create and include in the block, namely Shell_cmd(the class that call bash command), Shared_lib(the class that handle the shred library), Verilog_data(the class that store the port map and other input/ouput data), etc.

It will be better to demonstrate this by codes directly. So I will show some of the codes here. You can find the repository here

The verilog_ii_impl.h and verilog_ii_impl.cc are as follow. You can find it here and here.

verilog_ii_impl.h:

#ifndef INCLUDED_VERILOG_VERILOG_II_IMPL_H
#define INCLUDED_VERILOG_VERILOG_II_IMPL_H

#include <verilog/verilog_ii.h>
#include <string>

#define SLASH "/"

namespace gr {
  namespace verilog {

    typedef unsigned int ITYPE;
    typedef unsigned int OTYPE;

    class verilog_ii_impl : public verilog_ii
    {
     private:
      /* gr::verilog::verilog_ii private member variables  */
      
      // The path and name of user's verilog module
      // Construct by (const char *filename)
      std::string verilog_module_path;
      std::string verilog_module_name;
      
      // The path of makefile template
      std::string makefile_template_path;

      // The path of cpp template 
      std::string cpp_template_path;

      // The class that call the shell command make
      // Use sh_make.cmd(std::string cmd) to call bash
      // Shell_cmd sh_make;

      // The class that store the verilog module data
      // Including port map and input/output data
      // Use verilog_data.set_input( (ITYPE) in[i] ) to set input data
      // Usr verilog_data.set_output( (OTYPE) out[i] ) to set output data
      Verilog_data verilog_data;

      // The class that control the shared library
      // Use verilog_module_so.load_lib(std::string verilog_module_path, std::string lib_name) to load library
      // Use verilog_module_so.find_func(std::string func_name) to get the function
      // Use verilog_module_so.release_lib(std::string string lib_name) to release the library
      Shared_lib verilog_module_so;

      // typedef void (*Simulation_func)(const verilog_data.get_input_addr(), verilog_data.get_output_addr());
      Simulation_func sim;
      
      /* gr::verilog::verilog_ii private member variables  */



      /* gr::verilog::verilog_ii private member functions */

      /* Construct routine */
      // The function that call Verilator (Makefile) to generate the cpp code
      bool generate_verilator_file() throw(std::runtime_error);

      // Parse the Verilator generate file and extract the port map
      // The port map should be stored in Veriloag_data verilog_data
      bool init_port_map() throw(std::logic_error);

      // The function that call g++ (Makefile) to generate the shared library
      // There might be some modifications on the tempalte cpp interface file
      bool generate_so() throw(std::runtime_error);

      // The function that load the shared library that generated above
      // with the Shared_lib verilog_module_so
      bool load_lib() throw(std::runtime_error);
      /* Construct routine */

      /* gr::verilog::verilog_ii private member functions */

     public:
      verilog_ii_impl(const char *filename);
      ~verilog_ii_impl();

      // Where all the action really happens
      int work(int noutput_items,
         gr_vector_const_void_star &input_items,
         gr_vector_void_star &output_items);
    };

  } // namespace verilog
} // namespace gr

#endif /* INCLUDED_VERILOG_VERILOG_II_IMPL_H */

verilog_ii_impl.cc:

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gnuradio/io_signature.h>
#include "verilog_ii_impl.h"

namespace gr {
  namespace verilog {

    verilog_ii::sptr
    verilog_ii::make(const char *filename)
    {
      return gnuradio::get_initial_sptr
        (new verilog_ii_impl(filename));
    }

    /*
     * The private constructor
     */
    verilog_ii_impl::verilog_ii_impl(const char *filename)
      : gr::sync_block("verilog_ii",
              gr::io_signature::make(1, 1, sizeof(ITYPE)),
              gr::io_signature::make(1, 1, sizeof(OTYPE)))
    {
      /* Get module_name and module_path */
      std::string filename_temp(filename);
      std::size_t filename_pos = filename_temp.rfind(SLASH);
      if (std::string::npos == filename_pos) {
        /* TODO: raise_error() */
      }
      this->verilog_module_name = filename_temp.substr(filename_pos + 1);
      this->verilog_module_path = filename_temp.substr(0, filename_pos + 1);

      /* Initialize makefile_template_path and cpp_template_path */
      // TODO: #define MAKEFILE_TEMPLATE_PATH "$(prefix)/gnuradio/gr-verilog/lib"
      this->makefile_template_path = MAKEFILE_TEMPLATE_PATH;
      // TODO: #define CPP_TEMPLATE_PATH "$(prefix)/gnuradio/gr-verilog/lib"
      this->cpp_template_path = CPP_TEMPLATE_PATH;

      /* Call Verilator (Makefile) to generate the cpp code */
      // There will be a Shell_cmd object created in the function to
      // run configure.sh
      // configure.sh will copy the makefile template and modify it
      // for the verilog module
      // the Shell_cmd will run make at the verilog module path
      try {
        this->generate_verilator_file();
      } catch (...) {
        /* TODO: Handle the error */
      }
      

      /* Initialize port_map that stored in verilator_data */
      // The port map is the structure of input/output ports of
      // verilog module
      // Verilog_data should have a method to parse the code(.cpp/.v) and
      // extract the port structure of verilog module.
      try {
        this->init_port_map();
      } catch (...) {
        /* TODO: Handle the error */
      }
      

      /* Generate shared library and initialize verilog_module_so */
      // There will be a Shell_cmd object created in the function to
      // run make at the verilog module path
      try {
        this->generate_so();
      } catch (...) {
        /* TODO: Handle the error */
      }
      
      /* Load the shared library that generated above */
      // The function should also initialize the external veriable
      // in the shred library
      // Call verilog_module_so.find_func(VERILOG_INIT_FUNC)
      // Call verilog_module_so.find_func(VERILOG_RESET_FUNC)
      // shared library function reset() should be defalt generated
      // There would be a function called general_sim()
      // general_sim() could accept input and output of all port with
      // the help of port map stored in the verilog_data
      try {
        this->load_lib();
      } catch (...) {
        /* TODO: Handle the error */
      }

      /* Initialize sim */
      this->sim = NULL;
    }

    /*
     * Our virtual destructor.
     */
    verilog_ii_impl::~verilog_ii_impl()
    {
    }

    int
    verilog_ii_impl::work(int noutput_items,
        gr_vector_const_void_star &input_items,
        gr_vector_void_star &output_items)
    {
      const ITYPE *in = (const ITYPE *) input_items[0];
      OTYPE *out = (OTYPE *) output_items[0];
      if (NULL == this->sim)
      {
        // TODO: #define VERILOG_SIMULATION_FUNC "$(the_name_of_the_function)"
        try {
          // Find the function of simulation in the shared library
          sim = verilog_module_so.find_func(VERILOG_SIMULATION_FUNC);
        } catch (...) {
          /* TODO: Handle the error */
        }
      }

      // Do <+signal processing+>
      for (unsigned int i = 0; i < noutput_items; i++)
      {
        this->verilog_data.set_input(in[i]);
        this->verilog_data.set_output(out[i]);
        this->verilog_data.convert();
        sim(verilog_data.get_input_addr(), verilog_data.get_output_addr());
      }

      // Tell runtime system how many output items we produced.
      return noutput_items;
    }

  } /* namespace verilog */
} /* namespace gr */

There still left the some issues, I will cover them in the weekly report this weekend.