RHDL (Ruby Hardware Description Language) is an HDL based on the Ruby programming language. My idea in developing RHDL was to build an HDL on an object oriented programming language to allow HDL features ( concurrent processes, signals, parallelism etc.) in addition to features which come with a modern, object oriented, agile programming language like Ruby (www.ruby-lang.org). The intent is to allow more than just simulation, but also verification and testbench creation features. Ultimately, I would like to be able to translate RHDL to VHDL and/or Verilog. The fact that RHDL is based on Ruby allows modeling at a higher level of abstraction than is possible with VHDL or Verilog.
RHDL users don't need to know much Ruby - this is intentional, I didn't want potential users to have to know Ruby in order to benefit from RHDL. However, RHDL becomes a more powerful tool if the user takes a little time to learn some Ruby (an excellent tutorial and reference book on Ruby is: "Programming Ruby: The Pragmatic Programmer's Guide" by Thomas and Hunt.)
RHDL is not stricly speaking a new language. It is a set of modules (code libraries) that allow Ruby to look like an HDL. Ruby has a concept called code blocks; these blocks are made into lexically scoped closures in order to define a domain specific language like RHDL without having to write a seperate parser.
Comments start with a '#'. The rest of the line following is a comment.
delimited by ''
example '100101'
In most cases string literals are automatically converted to Bit or BitVector values when it makes sense (see below).
A Bit type can take on the following values: 1,0,X,Z
creating a Bit object: b = Bit('0') or b=Bit(0)
NOTE: the Bit constructor can take either a string literal or an integer value of 1 or 0.
OR '+,|': a + b -> 1, a | b -> 1 (NOTE: '->' means 'results in', it is not an operator)
AND '*,&': a * b -> 0, a & b -> 0
XOR '^': a ^ b -> 1
Inversion '~': ~a -> 0
A BitVector is an array of values of type Bit with some extra functionality for math.
creating a BitVector object: bv = BitVector('1001',4) or bv = BitVector(9,4) (the two are equivalent). Note that the second argument to the BitVector constructor indicates the number of bits in the vector.
Logical operators (|,&,^,~)
Math operators (*(multiplication), /(division), +(addition), -(subtraction))
Note: Math operators on BitVectors are (mostly) polymorphic unlike in VHDL. For example, the following are equivilent (given that bv=BitVector('1001',4)): bv=bv+2 and bv=bv+'10'.
concatenation: bv3 = bv1.cat bv2
An enumerated type for representing a collection of states (for use in creating state machines)
Example: washStates = EnumType(:start, :wash, :rinse, :spin, :stop)
See state machine example for an example of using EnumType.
inspect and value methods return current state of washStates.
first state in enumeration automatically becomes the 'reset' state (ie. in the example above the initial state is :start)
NOTE: the ':' prefixed to a string a characters (as with :start) creates what is known in Ruby as a Symbol - basically it is an immutable string.
While Bit and BitVector are special to RHDL, users can also use other types native to Ruby: Integer, String (ie. 'This is a string'), Range (ie. 0..5), Symbol (ie. :start) and others (TODO: add more types here). In addition, users can define their own types using Ruby's 'class' definitions.
Signals in RHDL are very similar to signals in VHDL. Signals have a history (and more importantly, a future) and can contain values of various types.
Example to create a Signal with a Bit type: s=Signal(Bit('0')) (NOTE: this is roughly equivalent to the VHDL statement: signal s:BIT := '0';)
Example of creating a Signal with a BitVector type: s=Signal(BitVector('1001',4)) (NOTE: this is roughly equivalent to the VHDL statement: signal s:BIT_VECTOR(0 to 3):='1001'; )
It is essential to understand that '=' is not to be used for signal assignment. The reason being that Ruby uses '=' for assigning object references to variables. Use either 'assign' or '<<' to assign new values to signals.
bv.assign (bv + 1)
bv << ( bv + 1 ) (these are equivalent assignments)
bv << '0110' #assign a constant bit string
You can specify that values be assigned to signals after a specified amount of ttime by usign the assign_at method:
bv.assign_at(5) { bv + 1 }
which means that the value bv+1 will be assigned to bv five
timesteps after the current one.
TODO: define Ports
Note: the role of Ports in RHDL is 'evolving'. At this point, I don't believe they are necessary except for two uses:
As a hint for translation to either VHDL or Verilog (otherwise there is no way to tell if a signal is an input or an output).
As a means to access values of variables/signals which are not visible at the top level of the design.
define_behavior takes a block of code (surrounded by '{}') in which all behavioral code for the design is placed. (NOTE: a completely structural design doesn't need to have a define_behavior declaration).
It is essential that the '{' be on the same line as 'define_behavior'. The reason for this is that define_behavior is a method and the block of code surrounded by '{}' is an argument to the define_behavior method, which means the following would be illegal:
define_behavior
{
#this will result in an error!
}Very similar to the 'process' statement in VHDL or the 'always' statement in Verilog. The process statement can take a list of sensitive signals. The block following the process declaration is executed when any of the signals in the sensitivity list changes. Example:
process(clk){
puts "clk changed: clk"
} will print "clk changed: 1|0" whenever the clk signal changes. NOTE: process statements should only be used within define_behavior blocks.
Again, as with the define_behavior declaration, the '{' must appear on the same line as the 'process'.
Used to suspend a process until some condition is met. Example:
process() {
wait { $simTime == 1000 }
#other stuff
}In this example the process will be initiated, but it will be suspended until $simTime is equal to 1000. NOTE: $simTime is a globally available variable that contains the current simulation time (or the number of steps that have been run so far).
NOTE: You can use wait statements in a process with a sensitivity list, but the results may not be what you expect. It is probably best to use wait statements only in a process without a sensitivity list but this is not enforced by RHDL.
Used to suspend a process for a time. Example:
clock=Signal(Bit(0))
define_behavior {
process() {
wait_for 4
clock << clock.inv
wait_for 3
clock << clock.inv
}
}This example creates a repeating clock signal which starts out low and remains low for four time steps and then goes high and remains high for three time steps and repeats.
A design in RHDL is similar to an entity/architecture pair in VHDL. An RHDL design contains either a structural or behavioral design description. RHDL designs are classes in Ruby – in other words they define a template for defining objects. RHDL designs can be included in other RHDL designs thus implementing hierarchy.
Here's an example of a simple RHDL design that defines a simple OR gate:
require 'RHDL'
class My_Or < RHDL::Design
include RHDL
def initialize(a,b,a_or_b)
super()
define_behavior {
a_or_b << (a | b)
}
end
endThe name of this design class is My_Or. The the '< RHDL::Design' indicates that this class inherits from a Ruby module called RHDL::Design; this module is actually where statements like define_behavior, process and wait are actually defined. The next line 'include RHDL' simply makes those methods available to your design. The next line which starts 'def initialize' is the constructor for My_Or objects. It is called whenever you instantiate a My_Or object (such as: My_Or.new(aa,bb,output) ). The constructor takes signal arguments - in this case: a,b,a_or_b ; a and b are inputs to the design, while a_or_b is an output. The next line 'super()' is required so that RHDL designs can be correctly constructed; you can think of this as some wizardry that is peeking out from behind the curtain (it's a Ruby requirement that couldn't quite be hidden from view, just remember that you always need to make a call to super in your design's constructor because you really want your design to be super!). After this the behavior of the design is described in a define_behavior block; in this case a is Or'ed with b.
Now to use the My_Or class/design that you've defined:
include RHDL
aa = Signal(Bit('0'))
bb = Signal(Bit('0'))
output = Signal(Bit()) #initialized to 'X'
myOr = My_Or.new(aa,bb,output)The include RHDL tells Ruby to allow calls to functions (methods) defined in the RHDL module to be called in the current scope. We then set up two input signals (aa and bb) and an output signal ( output), instantiate a My_Or object and pass in the signals.
Simulation in RHDL is done by including the Simulator module and calling the step method which is defined in that module (NOTE: this is subject to change, I'll probably be changing the name of this to the Simulator module). Here's how the design is simulated:
include Simulator
step { puts "aa=#{aa}, bb=#{bb}, output=#{output}"}
aa << '1'
step
bb << '1'
step
aa << '0'
stepThe output from running this is:
step #0: aa=0, bb=0, output=X step #1: aa=0, bb=0, output=0 step #2: aa=1, bb=0, output=1 step #3: aa=1, bb=1, output=1 step #4: aa=0, bb=1, output=1
The step method in the Simulator module is used to step the simulation to the next time interval. Notice that step can take an optional block of code which is executed at each time step. You do not need to specify this block of code for each step as it will be 'remembered' until a new block is given. In this example the first step takes a block which prints out the values of aa, bb and output. The step method takes care of printing the 'step #x:' in front of each line of output (NOTE: the line which starts with 'step #0:' is the output at $simTime==0 prior to any simulation steps, it represents the initial values of the signals specified.)
By now you've noticed that there is no type specification on the signals passed into/out of the My_Or design. This is primarily because Ruby is what is known as a dynamically typed language as opposed to a statically typed language like C or Java (or VHDL or Verilog) where the types of all variables must be declared prior to their use in the program. The main advantage in this case is that you can reuse your My_Or design with different types of signals, for example, if you want to OR four-bit BitVectors:
include RHDL
aa = Signal(BitVector('0000',4))
bb = Signal(Bit('0000',4))
output = Signal(BitVector('XXXX',4))
myOr = My_Or.new(aa,bb,output)As long as the underlying types of the signals being passed in support an OR operator ( | ) it just works. No need to rewrite your My_Or design to accommodate different types. This greatly improves the chances that you'll be able to reuse your design code.
...More to come... for now do check out the examples.