A project I have.

A have used VHDL for a while, and starts to be pretty familiar with the language. Because I as well have skills in C++, I get irritated of VHDL, it's so much yada yada in it. Unnecessary many words and lines are offered for the syntax of doing things, causing less (effective) code can be displayed at the same time (but the same amount of words to look at for the eye). Annoying.

Why can't there be a more C++-similar HDL language? All the time when I code VHDL, I see how all the things I do could be better with some C++-syntax.

I don't "speak" Verilog or SystemC very good, but Verilog isn't very like C/C++, strictly speaking, and System-C isn't a HDL, it's a HDL-wannabe-library to C++. I really have to learn SystemC one day.

So, I decided to give C-like HDL (CHDL) a thought. I have never created a language before, so I have no clue how to do this.

This is just a hobby project, but if someone whould like to contribute, please do so.

Some thoughts:

  • What do I want the language to look like? C++ of course. Subculture... VHDL, Verilog, SystemC or something else?
  • Preprocessor? The same as C++, with include, defines and so on.
  • Building blocks and so on? Classes and functions...
  • How to handle processes? Should there be processes? Well, if there should be, just call them process(...) { ... }
  • If there's not processes, how to handle variables vs (synchronous vs asynchronous) signals? keywords, perhaps... "asynch int bus;", "synch int bus", "[variable ]int bus", for instance...
  • Should the language be optimized for hight or low level HDL? Simulation or orientation? Well... C++ is optimized for "high" level programming, but is easy to write as low-level. How to do the same here?
  • How to handle pins? in vs out signals in modules? references used for outgoing signals? std_logic inc(const std_logic in_data[]; std_logic& out_data[]) // return carry-out ...? reserved words in resp. out?
  • How to handle top module? void main(...), or class main{...}?
  • Inherit inheritance from C++ (for instance a DFF with enable-signal, that inherit the usual DFF)?

With some luck, this could be implemented so that a project can be both compiled and simulated and synthesist to hardware using MAKE-macros...

Example of code

edit

Lets find out what to do by writing some examples in the CHDL. Start with a simple xor gate for a fpga, with pin assignments and everything.

The gate is written in two variants of CHDL and one in VHDL. One CHDL is designed "from scratch", and one is a pretty sharp modification of VHDL. In the first CHDL variant I'm using a class as top module, and declare it. Nice and dynamic - choose between class or a function as top module.

CHDL ("from scratch") CHDL ("from VHDL") VHDL
#include <some_libraries>

using namespace std_logic::misc;

//define main object/function
#define MAIN class my_xor_device
//defines the pins to the device;
//can preferable be done in an h-file somewhere.
#define PIN_A ad5
#define PIN_B br22
#define PIN_X a5

class my_xor_device {
  // in is a asynchronous signal if nothing else is said
  in std_logic a, b;
  // out is asynch if nothing else is declared
  out asynch std_logic x;
};

my_xor_device::asynch() {  // all asynchronous assignments
  x = a xor b;
}
#include ...
using namespace std_logic::misc

//move out pin declaration
#define PIN_A ad5
#define PIN_B br22
#define PIN_X a5

//corresponding entity
class my_xor_device {
  in std_logic a, b;
  out std_logic x;
};

//corresponding architecture
//void void => undeclared function operating on class members.
void my_xor_device::void() {
  x = a xor b;
}
library ieee;
use ...

entity my_xor_device is
  port(a, b : in std_logic;
       x    : out std_logic);

--defines the pinns to the device;
  attribute pin_name : string;
  attribute pin_name of A : signal is "ad5"
  attribute pin_name of B : signal is "br22"
  attribute pin_name of X : signal is "a5"
end my_xor_device;

architecture foo of my_xor_device is
  x <= a xor b;
end foo;

Discussion about the "from VHDL" CHDL:
Instead of having an architecture, like in VHDL, there is an undeclared function to all classes, that is called void, takes no arguments and returns nothing, that can operate on class members. If user want to have more such functions (s)he is free to declare them (this is a very RTL level of coding), e.g:

class vector_xor {
  in std_logic x[16], clk;
  out std_logic c_res;      // combinatorial output
  out dff<std_logic> s_res; // synchronous output
  void calc_res();
 private:
  static std_logic vec_xor(std_logic x[]); //overides class member x.
}

void vector_xor::calc_res() {
  s_res.clk = clk;    // assign clock input
  s_res = vec_xor(x); // default assignment = .d member of dff
  c_res = s_res;      // default read port = .q member of dff
  //the result is what is left when leaving function.
}

std_logic vector_xor::vec_xor(std_logic x[]) {
  std_logic res = '0';
  for(int i in x.range) // I must admit that VHDLs array handler is convenient.
    res = res xor x[i];
  return res;
}

All functions returning void and taking no arguments are called active functions, they have the right to change value on the class members.

An std_logic(*) friendly definition of the dff<> used above could be:

template<typename T>
class dff<T> : T { // inherit from T. If T is a scalar?
  dff(std_logic clk=void, rst='1', en='1')
   : clk(clk), reset(rst), enable(en) {} // constructor connecting pins

  in std_logic clk, reset='1', enable='1'; // default input if not assigned
  in T d;
  out T q;

  //assign to d-input
  dff<T> operator=(T rhs) {d = rhs; return me;} // me = *this
  //read from q-output
  operator T() {return q}
}

void dff<T>::void() {
  if(reset == '0')
    q = T();
  else if(enable == '1' and clk.rising_edge)
    q = d;
}

(* "std_logic friendly" because the clock, reset and enable is explicitly used as std_logic)

The simulation procedure should be similar to VHDL's, I suppose:

  1. Find out which classes/global functions are affected by last change (that is all during first iteration)
  2. All active functions in each affected class should be run (in parallell). Two different functions may not set common variable (unless there is a resolution function).
    1. All used/affected variables are copied into own copies for each function, where they are updated (with it's eventual delay).
  3. When all functions is done, copy the output of the functions to an signal-update-table (the VHDL simulator does so), including the signal delays.
  4. Find the next events to happen (immediate or after a an delay) from the signal-update-table. The time until then = dt = min(deltas in table)
  5. Update simulator time += dt. Decrease all delays in table. Pic out all with resulting delay 0. Start over with those signals (and their affected classes/functions)

To be continued...