Skip to content

JC-LL/ruby_rtl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

drawing

RubyRTL : Ruby-on-gates !

WARNING : Work-in-progess !

RubyRTL is an experimental Ruby DSL that aims at :

  • Describing Digital circuits in Ruby, at the RTL level
  • Generating synthesizable VHDL for FPGAs or ASICs

This approach is quite similar to Python Migen. We recommand having a look at the very successful Litex IP library approach, relying on Migen. Our paper talks about Litex and Migen.

We believe however that Ruby metaprogramming capabilities probably best suits the idea of creating such DSL.

How to install ?

The recommanded version of RubyRTL is uploaded on RubyGems, so that can simply be installed on a Linux box, by typing (use of rvm recommended):

  • gem install ruby_rtl

How does it look ?

Let's build a introductory-level digital system : a ripple-carry adder. Using RubyRTL, we can elaborate much more complex circuits than this simple adders. At the register-transfer level, much more complex functions operating on complex data structures, are possible : imagine a video macroblocks on which several filters are applied, in a single clock cycle, or a processor pipline etc.

For the moment, let's build this adder, in a progessive manner !

Basic signal assignments : example of a 1-bit half adder circuit

We start by the "Hello World" of Digital Design : the Half adder. We recall that it built from 2 basic gates. That "block" can be then used in a hierarchical manner to build a 1-bit full-adder and then a classical adder operating on integers. See wikipedia if needed. This bottom-up approach is representative of Digital System Design : we can elaborate complex functions with a clever composition of such components, either hierarchically or using the intrinsic parallelism of digital circuit, or both.

  require_relative 'ruby_rtl'

  include RubyRTL

  class HalfAdder < Circuit
    def initialize
      input  :a,:b
      output :sum
      output :cout

      assign(sum  <= a ^ b) #xor
      assign(cout <= a & b) #cout
    end
  end

  ha=HalfAdder.new

  compiler=Compiler.new
  compiler.compile ha # VHDL generated !

The generated code is then :

-- automatically generated by RubyRTL
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

library ruby_rtl;
use ruby_rtl.ruby_rtl_package.all;

library halfadder_lib;
use halfadder_lib.halfadder_package.all;

entity halfadder_c is
  port (
    a : in  std_logic;
    b : in  std_logic;
    sum : out std_logic;
    cout : out std_logic);
end halfadder_c;

architecture rtl of halfadder_c is
begin

  sum <= (a xor b);
  cout <= (a and b);

end rtl;

Hierarchical composition : example of 1-bit full adder

Here, we reuse 1-but half adder, to elaborate a 1-bit full adder. It now has an input carry and an output carry. The example show how to call such hierarchal components and glue them together.

require 'ruby_rtl'

include RubyRTL # module now visible

require_relative 'half_adder' # preceding circuit

class FullAdder < Circuit
  def initialize
    input  :a,:b,:cin
    output :sum,:cout

    component :ha1 => HalfAdder     # class...
    component :ha2 => HalfAdder.new # or ...obj

    assign(ha1.a <= a )
    assign(ha1.b <= b )
    assign(ha2.a <= cin)
    assign(ha2.b <= ha1.sum)
    assign(sum   <= ha1.sum)
    assign(cout  <= ha1.cout | ha1.cout)

  end
end

Genericity : example of a word-level adder

Here comes the most exiting parts of RubyRTL. We can rely on Ruby host itself, to describe the glue between components. This glue may rely on many parameters (or "generics" in the VHDL world). Ruby host also allows you to make regular computations required for the configuration of your design, which can be cumbersome in classical HDLs.

Let's build a n-bits adder using previous components ! generic adder

require_relative 'ruby_rtl'
require_relative 'full_adder' #preceding circuit

include RubyRTL

class Adder < Circuit

  def initialize nbits
    input  :a    => nbits
    input  :b    => nbits
    output :sum  => nbits
    output :cout

    # create  components
    adders=[]
    for i in 0..nbits-1
      adders << component("fa_#{i}" => FullAdder)
    end

    # connect everything
    for i in 0..nbits-1
      assign(adders[i].a <= a[i])
      assign(adders[i].b <= b[i])
      if i==0
        #assign(adders[0].cin <= Bit(0)) # no carry in for FA_0
        assign(adders[0].cin <= 0)       # even better.
      else
        assign(adders[i].cin <= adders[i-1].cout)
      end
      # final sum
      assign(sum[i]        <= adders[i].sum)
    end
  end
end

Behavioral statements : counter

All previous examples were structural. Hardware descriptions languages such as VHDL and Verilog also allows for so called behavioral descriptions (please note that this naming is historical, and still found in course books. This is not to be cofounded with modern "High-level synthesis" also called behavioral synthesis, that is at a higher abstraction layer than RTL). Here our DSL allows more basically to resort to statements like :

  • If..Elsif...Else
  • Case...When ...

RubyRTL introduces these DSL keywords, that require upcase (in order to avoid collision with regular Ruby host keywords).

As for VHDL or Verilog, such RubyRTL statements are also synthesizable on hardware, if used correctly.

The important remark is about clocks and resets. RubyRTL recognizes (via sequential keyword) that your design requires D (edge-triggered) flip-flops : their clocking is considered implicit. By default, RubyRTL works on a single clock and generates synchronous and asynchronous reset. This may be modified in future versions.

class Counter < Circuit
  def initialize
    input  :do_count
    output :count => :byte

    sequential(:strange_counting){
      If(do_count==1){
        If(count==255){
          assign(count <= 0)
        }
        Elsif(count==42){
          assign(count <= count + 42)
        }
        Else{
          assign(count <= count + 1)
        }
      }
    }
  end
end

Finite state machines (FSM)

Finite state machines are essential in Digital System Design. However, VHDL and Verilog do not provide instrinsic keywords for them. Here, RubyRTL simplified the coding by providing such keywords.

class FSM1 < Circuit
  def initialize
    input :go,:b
    output :f => :bv2

    fsm(:simple){

      assign(f <= 0)

      state(:s0){
        assign(f <= 1)
        If(go==1){
          next_state :s1
        }
      }

      state(:s1){
        assign(f <= 2)
        next_state :s2
      }

      state(:s2){
        assign(f <= 3)
        next_state :s0
      }
    }
  end
end

How does this DSL works ?

RubyRTL is an internal DSL. We can see it as a new language, embedded in Ruby syntax. It benefits from Ruby directly. However, such embedding needs a cautious resort to metaprogramming and introspection.

More to come here. Stay tuned !

Please cite RubyRTL !

  • RubyRTL was published at OSDA DATE'21 satellite workshop.
  • The pdf paper is here.
  • A poster is also provided here.
  • The bibtex entry is here -->

Contact

Don't hesitate to drop me a mail if you like RubyRTL, or found a bug etc. I will try to do my best to consolidate, maintain and enhance RubyRTL.

jean-christophe.le_lann at ensta-bretagne.fr

Releases

No releases published

Packages

No packages published