HDLRuby 3.2.0 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.html +2330 -2670
- data/README.md +391 -101
- data/ext/hruby_sim/hruby_rcsim_build.c +400 -3
- data/ext/hruby_sim/hruby_sim.h +2 -1
- data/ext/hruby_sim/hruby_sim_calc.c +1 -1
- data/ext/hruby_sim/hruby_sim_core.c +15 -5
- data/ext/hruby_sim/hruby_sim_tree_calc.c +1 -1
- data/lib/HDLRuby/hdr_samples/c_program/echo.c +33 -0
- data/lib/HDLRuby/hdr_samples/ruby_program/echo.rb +9 -0
- data/lib/HDLRuby/hdr_samples/ruby_program/stdrw.rb +6 -0
- data/lib/HDLRuby/hdr_samples/ruby_program/sw_cpu_terminal.rb +614 -0
- data/lib/HDLRuby/hdr_samples/ruby_program/sw_inc_mem.rb +32 -0
- data/lib/HDLRuby/hdr_samples/ruby_program/sw_log.rb +33 -0
- data/lib/HDLRuby/hdr_samples/with_board.rb +63 -0
- data/lib/HDLRuby/hdr_samples/with_clocks.rb +42 -0
- data/lib/HDLRuby/hdr_samples/with_of.rb +1 -1
- data/lib/HDLRuby/hdr_samples/with_program_c.rb +28 -0
- data/lib/HDLRuby/hdr_samples/with_program_ruby.rb +28 -0
- data/lib/HDLRuby/hdr_samples/with_program_ruby_cpu.rb +234 -0
- data/lib/HDLRuby/hdr_samples/with_program_ruby_io.rb +23 -0
- data/lib/HDLRuby/hdr_samples/with_program_ruby_mem.rb +58 -0
- data/lib/HDLRuby/hdr_samples/with_program_ruby_threads.rb +56 -0
- data/lib/HDLRuby/hdr_samples/with_sequencer_func.rb +2 -4
- data/lib/HDLRuby/hdrcc.rb +60 -21
- data/lib/HDLRuby/hruby_error.rb +13 -0
- data/lib/HDLRuby/hruby_high.rb +50 -7
- data/lib/HDLRuby/hruby_low.rb +74 -30
- data/lib/HDLRuby/hruby_rcsim.rb +89 -5
- data/lib/HDLRuby/std/clocks.rb +118 -50
- data/lib/HDLRuby/std/std.rb +5 -0
- data/lib/HDLRuby/ui/hruby_board.rb +1079 -0
- data/lib/HDLRuby/version.rb +1 -1
- data/lib/c/Rakefile +8 -0
- data/lib/c/cHDL.h +12 -0
- data/lib/c/extconf.rb +7 -0
- data/lib/rubyHDL.rb +33 -0
- data/tuto/gui_accum.png +0 -0
- data/tuto/gui_board.png +0 -0
- data/tuto/tutorial_sw.html +2263 -1890
- data/tuto/tutorial_sw.md +957 -62
- metadata +24 -5
- data/README.pdf +0 -0
- data/tuto/tutorial_sw.pdf +0 -0
data/tuto/tutorial_sw.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# HDLRuby Tutorial for
|
1
|
+
# HDLRuby Tutorial for software people (*and hardware people too*)
|
2
2
|
|
3
3
|
In this tutorial, you will learn the basics about the description of digital circuits using HDLRuby from the software point of view. In detail you will learn:
|
4
4
|
|
@@ -16,6 +16,10 @@ Then, the following section will introduce advanced concepts about hardware desi
|
|
16
16
|
|
17
17
|
6. [Not enough? What about genericity, object orientation, metaprogramming, and reflection?](#6-not-enough-what-about-genericity-object-orientation-metaprogramming-and-reflection)
|
18
18
|
|
19
|
+
7. [How to mix hardware and software.](#7-how-to-mix-hardware-and-softwware)
|
20
|
+
|
21
|
+
8. [How to interact with the simulator.](#8-how-to-interact-with-the-simulator)
|
22
|
+
|
19
23
|
Within these topics, you will also have an explanation of how the following high-level concepts can be used in HDLRuby:
|
20
24
|
|
21
25
|
* Object-oriented programming
|
@@ -68,7 +72,7 @@ In hardware, the design loop is more like as follows:
|
|
68
72
|
</p>
|
69
73
|
|
70
74
|
|
71
|
-
At first, an HDL has the same look and feel compared to classical programming languages like C or Java: it includes expressions, control statements, and kinds of variables. However, the
|
75
|
+
At first, an HDL has the same look and feel compared to classical programming languages like C or Java: it includes expressions, control statements, and kinds of variables. However, the underlying model of computation is quite different, especially because circuits are inherently parallel devices. This will be explained progressively during this tutorial, but for now, it is enough to remember the following:
|
72
76
|
|
73
77
|
* HDL are used for describing digital circuits and the most common ones are Verilog HDL and VHDL.
|
74
78
|
|
@@ -79,7 +83,7 @@ At first, an HDL has the same look and feel compared to classical programming la
|
|
79
83
|
|
80
84
|
#### And what about HDLRuby?
|
81
85
|
|
82
|
-
Well, HDLRuby is an HDL for describing digital circuits like Verilog HDL or VHDL but
|
86
|
+
Well, HDLRuby is an HDL for describing digital circuits like Verilog HDL or VHDL but aims at being more flexible and productive than them by relying on many concepts inherited from the Ruby programming language. So everything said earlier about HDL applies to HDLRuby, but we try to make it much easier for the designers. Furthermore, HDLRuby includes constructs for describing and simulating software modules so that you can design a full hardware-software device using the same framework (there is a fancy word for that: hardware-software co-design).
|
83
87
|
|
84
88
|
|
85
89
|
### 1.2. Installing HDLRuby
|
@@ -130,7 +134,7 @@ hdrcc <options> <input file> <output directory>
|
|
130
134
|
|
131
135
|
Where `options` specifies the action to be performed, `input file` specifies the input HDLRuby file, and `output directory` specifies the directory where the command results will be saved. As a general rule, when an input file is specified, an output directory must also be specified.
|
132
136
|
|
133
|
-
Several actions are possible using `hdrcc`, the main ones being the
|
137
|
+
Several actions are possible using `hdrcc`, the main ones being the following:
|
134
138
|
|
135
139
|
* Simulate a circuit description:
|
136
140
|
|
@@ -183,7 +187,7 @@ Ports are not simple entry points though, because they also have a data type and
|
|
183
187
|
|
184
188
|
#### That's all very well, but when do I write HDLRuby code?
|
185
189
|
|
186
|
-
With that in mind, declaring a circuit consists
|
190
|
+
With that in mind, declaring a circuit consists of specifying its name and its ports. In HDLRuby this is done as follows:
|
187
191
|
|
188
192
|
```ruby
|
189
193
|
system :my_circuit do
|
@@ -262,7 +266,7 @@ end
|
|
262
266
|
|
263
267
|
It looks somewhat similar to the code you have just written. This is because it is the internal representation (IR) of your circuit in HDLRuby. You can see that the name of the circuit changed to some weird character string and that the data types also changed. The weird string is for avoiding name clashes, so you do not need to be concerned about it. The data types are low-level representations of the same data types that were used in the initial file. Still, this low-level representation is very close to the original one, but that will be less and less the case as the features are added to the circuit.
|
264
268
|
|
265
|
-
Now, out of curiosity, how will look the equivalent Verilog HDL code?
|
269
|
+
Now, out of curiosity, how will look the equivalent Verilog HDL code? To check that just type the following command:
|
266
270
|
|
267
271
|
```bash
|
268
272
|
hdrcc --verilog my_circuit.rb work
|
@@ -307,13 +311,13 @@ Unfortunately, there is no more smiling face. This is because Verilog HDL only s
|
|
307
311
|
|
308
312
|
### 2.2. How to reuse a circuit already declared
|
309
313
|
|
310
|
-
Like with functions in software, a circuit is often used as part of one or several larger circuits. Contrary to the software, however, the circuit must be physically copied
|
314
|
+
Like with functions in software, a circuit is often used as part of one or several larger circuits. Contrary to the software, however, the circuit must be physically copied to be reused. This copy is called an *instance* and the act of copying an *instantiation*. In HDLRuby, an instantiation is done as follows:
|
311
315
|
|
312
316
|
```ruby
|
313
317
|
<circuit name>(:<copy name>)
|
314
318
|
```
|
315
319
|
|
316
|
-
For example, if you
|
320
|
+
For example, if you want to use copies of the previously defined circuit `my_circuit` in a new circuit called `another_circuit` you can do as follows:
|
317
321
|
|
318
322
|
```ruby
|
319
323
|
system :another_circuit do
|
@@ -377,7 +381,7 @@ Again, we can see similarities between the resulting Verilog HDL code and the or
|
|
377
381
|
> __But why two of them?__ I would like to answer that this is because of a limitation of Verilog HDL, but this is not the case. It is because HDLRuby's instantiation mechanism is very different from the Verilog HDL (and the VHDL) one, so for the moment, and only for easing the coding work of the HDLRuby compiler, one description of `my_circuit` is generated per instance.
|
378
382
|
|
379
383
|
|
380
|
-
Copying a circuit is easy, but it achieves no purpose if the copied circuit is not in a relationship with its environment. It is where the ports become useful: they are the communication points between a circuit and its outside world. Concretely, to interact with a circuit, its ports must be connected to something that will interact with them. How this interaction
|
384
|
+
Copying a circuit is easy, but it achieves no purpose if the copied circuit is not in a relationship with its environment. It is where the ports become useful: they are the communication points between a circuit and its outside world. Concretely, to interact with a circuit, its ports must be connected to something that will interact with them. How this interaction works is a story for the other sections of this tutorial. For now, let us focus on connections: in HDLRuby this is done using the assignment operator `<=` as follows:
|
381
385
|
|
382
386
|
* For an input port of the current circuit:
|
383
387
|
```ruby
|
@@ -546,9 +550,9 @@ module _v0_3( clk, rst, addr, ce0, ce1, data_in, data_out );
|
|
546
550
|
endmodule
|
547
551
|
```
|
548
552
|
|
549
|
-
The code is starting to get complicated and
|
553
|
+
The code is starting to get complicated and seems to be much different from the HDLRuby description than before. This is because this time, real syntactic limitations of Verilog HDL compared to HDLRuby have to be bypassed. Here, the limitation is that while in HDLRuby, ports can be connected wherever we want, in Verilog HDL, this must be done only while instantiating.
|
550
554
|
|
551
|
-
In fact, in HDLRuby too you can do the connection while instantiating, this is even recommended for better readability of the code. There are two ways to do so: by position (like for the arguments of a function call) or by name. Let us see both by editing again `another_circuit.rb`: please just replace both instantiation lines with the
|
555
|
+
In fact, in HDLRuby too you can do the connection while instantiating, this is even recommended for better readability of the code. There are two ways to do so: by position (like for the arguments of a function call) or by name. Let us see both by editing again `another_circuit.rb`: please just replace both instantiation lines with the following:
|
552
556
|
|
553
557
|
```ruby
|
554
558
|
my_circuit(:my_circuit0).(clk,rst,addr,ce0,data_in,data_out)
|
@@ -593,7 +597,7 @@ In software, handling values looks straightforward enough: they are computed wit
|
|
593
597
|
|
594
598
|
* The wires are abstractions of physical wires or sets of wires that transmit data to the hardware component they are connected to. A wire cannot hold a value: if a component drives a value to a wire, this wire takes this value, and it will last as soon as this drive stops.
|
595
599
|
|
596
|
-
* The storage components are abstractions of registers or memories that can hold values. Depending on how they are described, the value they hold can be changed on specific events like the rising edge of a clock. Hence, the
|
600
|
+
* The storage components are abstractions of registers or memories that can hold values. Depending on how they are described, the value they hold can be changed on specific events like the rising edge of a clock. Hence, the storage components are closer to the software variable than the wires are.
|
597
601
|
|
598
602
|
With HDL like Verilog HDL, there is a real distinction between wires and storage components. However, with other HDL like VHDL, or here with HDLRuby, both are abstracted by a single concept: the signal. Specifically, when a signal is only driven on a given event, it becomes a storage element, otherwise, it will remain a wire.
|
599
603
|
|
@@ -622,7 +626,7 @@ For now, we only used positive integer values, e.g., `[8].inner` declares an 8-b
|
|
622
626
|
|
623
627
|
Where `base` is another data type and `range` describes the range of indexes used for accessing its elements individually. In addition, there are three root data types used for building all the other ones:
|
624
628
|
|
625
|
-
* `bit`: for boolean
|
629
|
+
* `bit`: for boolean or 1-bit unsigned values, i.e., the only possible values are 0 and 1.
|
626
630
|
|
627
631
|
* `signed`: for 1-bit signed values in 2's complement, i.e., the only possible values are 0 and -1.
|
628
632
|
|
@@ -792,9 +796,9 @@ system :the_counter do
|
|
792
796
|
end
|
793
797
|
```
|
794
798
|
|
795
|
-
The first line of the body of the counter looks like the connection of ports we described [previously](#circuit-use). However it is inside the body of a sequencer and will therefore be *executed* by it, that is to say, before this statement is executed, `count` may not be 0. More generally:
|
799
|
+
The first line of the body of the counter looks like the connection of ports we described [previously](#circuit-use). However, it is inside the body of a sequencer and will therefore be *executed* by it, that is to say, before this statement is executed, `count` may not be 0. More generally:
|
796
800
|
|
797
|
-
* Every assignment statement outside sequencers (and later processes) is a connection: the signal will *always* and *continuously* have the value that is assigned to
|
801
|
+
* Every assignment statement outside sequencers (and later processes) is a connection: the signal will *always* and *continuously* have the value that is assigned to it.
|
798
802
|
|
799
803
|
* The other assignment statements, e.g., the ones inside a sequencer, are called *transmission* in HDLRuby and happen only when "executed."
|
800
804
|
|
@@ -814,7 +818,7 @@ If everything was all right the following text will appear:
|
|
814
818
|
__:T:0:::2: 000
|
815
819
|
```
|
816
820
|
|
817
|
-
It indicates that a signal named `__: T:0:::2` has been initialized to 0 (000), and that's all... Why is that, and why only that? First, this signal with a strange name has been generated by HDLRuby for the internal processing of the sequencer and is required to be initialized to 0. So, ok, the simulation did some initialization, but it did not seem to do any execution. This is because we did not give any description of the physical environment of the circuit, and especially, we did not say that the clock and the start signal need to change value. For describing the behavior of the environment of a circuit, we use a construct called in HDLRuby the *timed process*. They are somewhat close to sequencers but are not controlled by a clock signal but by physical time. So let us add the following code just after the sequencer (but still inside the module `the_counter`:
|
821
|
+
It indicates that a signal named `__: T:0:::2` has been initialized to 0 (000), and that's all... Why is that, and why only that? First, this signal with a strange name has been generated by HDLRuby for the internal processing of the sequencer and is required to be initialized to 0. So, ok, the simulation did some initialization, but it did not seem to do any execution. This is because we did not give any description of the physical environment of the circuit, and especially, we did not say that the clock and the start signal need to change value. For describing the behavior of the environment of a circuit, we use a construct called, in HDLRuby, the *timed process*. They are somewhat close to sequencers but are not controlled by a clock signal but by physical time. So let us add the following code just after the sequencer (but still inside the module `the_counter`:
|
818
822
|
|
819
823
|
```ruby
|
820
824
|
timed do
|
@@ -925,11 +929,13 @@ Indeed, the text output of the simulator is hard to read, and therefore we highl
|
|
925
929
|
hdrcc --sim --vcd the_counter.rb the_counter
|
926
930
|
```
|
927
931
|
|
928
|
-
The new option `--vcd` makes the simulator produce a *Value Change Dump* file (VCD) that can be visualized graphically by many HW design tools. If you do not have any such tool you can get [GTKWave](https://gtkwave.sourceforge.net/) which is under GNU General Public License v2.0.
|
932
|
+
The new option `--vcd` makes the simulator produce a *Value Change Dump* file (VCD) that can be visualized graphically by many HW design tools. If you do not have any such tool you can get [GTKWave](https://gtkwave.sourceforge.net/) which is under GNU General Public License v2.0, or [HTMLWave](https://github.com/civol/htmlwave) by the author of HDLRuby (link of interest!) and which is under the MIT License (also available as web application at this [link](https://civol.github.io/htmlwave/htmlwave.html).)
|
929
933
|
|
930
934
|
The resulting vcd file can be found in the `the_counter` directory with the name `hruby_simulator.vcd`. If you open it and select the `clk`, `start`, and `counter` signals you will see something like the following picture:
|
931
935
|
|
932
|
-
|
936
|
+
<p align="center">
|
937
|
+
<img data-name="hruby_simulator.vcd" src="the_counter_vcd.png" width="80%">
|
938
|
+
</p>
|
933
939
|
|
934
940
|
Such a graph is called a time chart and displays the evolution of signals with time on the X-axis. You can see that `clk` is indeed alternating from 0 to 1, and that `start` is set to 1 for one clock cycle only. After this later signal becomes 0 again, the sequencer starts. This can be verified by looking at the value of `counter`: at first, it is undefined, that it is set to 0, then increased by 1 at each clock cycle until it reaches 3.
|
935
941
|
|
@@ -948,7 +954,7 @@ Hum, the algorithm part is quite limited, and the timed processes seem to be rea
|
|
948
954
|
|
949
955
|
#### 3.2.2. How to write a structured programming algorithm in a sequencer
|
950
956
|
|
951
|
-
Now, when we speak about algorithms, we often think about software constructs like `if` for conditional executions and `for` for loops. Unfortunately, in HW design this is usually not that simple at all... But for the HDLRuby sequencers, it is indeed that simple: all these control statements are supported without any limitation. The only thing you have to be careful about is their name: to avoid confusion with the Ruby language, their names are all prefixed with an `s` (for *sequencer*), e.g
|
957
|
+
Now, when we speak about algorithms, we often think about software constructs like `if` for conditional executions and `for` for loops. Unfortunately, in HW design this is usually not that simple at all... But for the HDLRuby sequencers, it is indeed that simple: all these control statements are supported without any limitation. The only thing you have to be careful about is their name: to avoid confusion with the Ruby language, their names are all prefixed with an `s` (for *sequencer*), e.g., you have to use `sif` for doing an *if*.
|
952
958
|
|
953
959
|
In detail here is a list of the control statements you can use within a sequencer:
|
954
960
|
|
@@ -1011,13 +1017,13 @@ system :fact do
|
|
1011
1017
|
end
|
1012
1018
|
```
|
1013
1019
|
|
1014
|
-
The code is more complex
|
1020
|
+
The code is more complex than what we have seen up to now, so let us study it progressively:
|
1015
1021
|
|
1016
1022
|
* The circuit is named `fact` (for factorial).
|
1017
1023
|
|
1018
1024
|
* It has four inputs:
|
1019
1025
|
|
1020
|
-
- `clk` and `start`: the signals that
|
1026
|
+
- `clk` and `start`: the signals that control the execution of the sequencer (nothing new here).
|
1021
1027
|
|
1022
1028
|
- `req`: the signal that will ask for a factorial computation (*req* stands for require).
|
1023
1029
|
|
@@ -1080,7 +1086,7 @@ system :fact_bench do
|
|
1080
1086
|
end
|
1081
1087
|
```
|
1082
1088
|
|
1083
|
-
This module introduces the `repeat` construct. It can be used within a timed process only and
|
1089
|
+
This module introduces the `repeat` construct. It can be used within a timed process only and has the following syntax:
|
1084
1090
|
|
1085
1091
|
```ruby
|
1086
1092
|
repeat(<number>) <block>
|
@@ -1100,7 +1106,7 @@ clk <= 1
|
|
1100
1106
|
```
|
1101
1107
|
|
1102
1108
|
> __IMPORTANT__: as said when presenting HDLRuby, this language is implemented on top of the Ruby language, and is fully compatible with it. For instance, you can write any Ruby code within HDLRuby constructs (e.g., `def`), and you can write HDLRuby code within Ruby constructs. However, there is an important difference: Ruby code is executed at compile time (i.e., when hdrcc runs) and does not produce any hardware, whereas HDLRuby code is the description of the hardware that will be produced and will be then executed either through simulation or after production physically.
|
1103
|
-
Then, what calling `clk!`
|
1109
|
+
Then, what calling `clk!` does is paste the HDLRuby code in place. Here it is used to shorten the code: instead of each time setting the clock to 0, advancing time, then setting it to 1 again, writing `clk!` is enough to obtain the same result.
|
1104
1110
|
It is from this capability to mix Ruby and HDLRuby that comes the *meta programmability* of HDLRuby.
|
1105
1111
|
|
1106
1112
|
Finally, when you simulate with the following command:
|
@@ -1111,7 +1117,9 @@ hdrcc --sim --vcd fact.rb fact
|
|
1111
1117
|
|
1112
1118
|
You should obtain the following kind of resulting VCD file:
|
1113
1119
|
|
1114
|
-
|
1120
|
+
<p align="center">
|
1121
|
+
<img data-name="hruby_simulator.vcd" src="fact_vcd.png" width="80%">
|
1122
|
+
</p>
|
1115
1123
|
|
1116
1124
|
|
1117
1125
|
#### But in structured programming, it is better to use local variables!
|
@@ -1145,7 +1153,7 @@ system :fact do
|
|
1145
1153
|
end
|
1146
1154
|
```
|
1147
1155
|
|
1148
|
-
You can simulate it again, and you should obtain
|
1156
|
+
You can simulate it again, and you should obtain the same result. However, if you try to access `res` or `val` outside the main loop, then an error will be raised.
|
1149
1157
|
|
1150
1158
|
|
1151
1159
|
#### Now about `sfor`
|
@@ -1240,7 +1248,9 @@ hdrcc --sim --vcd serializer.rb serializer
|
|
1240
1248
|
|
1241
1249
|
You should obtain the following time chart:
|
1242
1250
|
|
1243
|
-
|
1251
|
+
<p align="center">
|
1252
|
+
<img data-name="hruby_simulator.vcd" src="serializer_vcd.png" width="80%">
|
1253
|
+
</p>
|
1244
1254
|
|
1245
1255
|
|
1246
1256
|
|
@@ -1288,7 +1298,7 @@ There is a lot to unpack from this small example:
|
|
1288
1298
|
end
|
1289
1299
|
```
|
1290
1300
|
|
1291
|
-
In the code above, `size` is the forced size of the stack, and `error_handler` is a block of code that will be executed when a stack overflow occurs. Both arguments are optional, but if the error handler is provided, then the size must also be provided. For example, the code of the factorial can be rewritten as follows
|
1301
|
+
In the code above, `size` is the forced size of the stack, and `error_handler` is a block of code that will be executed when a stack overflow occurs. Both arguments are optional, but if the error handler is provided, then the size must also be provided. For example, the code of the factorial can be rewritten as follows to force the stack to support 64 recursions:
|
1292
1302
|
|
1293
1303
|
```ruby
|
1294
1304
|
sdef(:fact,64) do |n|
|
@@ -1381,7 +1391,7 @@ So, enumerators iterate over enumerable, but what is it? In HDLRuby, an enumerab
|
|
1381
1391
|
|
1382
1392
|
#### Let us build HDLRuby enumerators.
|
1383
1393
|
|
1384
|
-
Like Ruby builds enumerators using the `each` method and its derivates (`each_with_index` and so on,) HDLRuby uses the `seach` method to build its enumerator. For example, an enumerator over the bits of signal `sig` will be built as follows: `sig.seach`, and if you want an enumerator with index, just like Ruby: `sig.seach_with_index`, and so on. Then, an enumerator can be executed when
|
1394
|
+
Like Ruby builds enumerators using the `each` method and its derivates (`each_with_index` and so on,) HDLRuby uses the `seach` method to build its enumerator. For example, an enumerator over the bits of signal `sig` will be built as follows: `sig.seach`, and if you want an enumerator with index, just like Ruby: `sig.seach_with_index`, and so on. Then, an enumerator can be executed when created by providing the block that will be applied on each element like in ruby, or executed later using the `seach` method again. For example, the following sequencer code first sums the bits of signal sig at once, then does it again later with another enumerator previously stored in the Ruby variable `another_enumerator`:
|
1385
1395
|
|
1386
1396
|
```ruby
|
1387
1397
|
input :clk, :start
|
@@ -1419,9 +1429,9 @@ That's all that we will explain here, the remaining is exactly like Ruby. Moreov
|
|
1419
1429
|
|
1420
1430
|
- You may have noticed that the initial value of reduce is set to a 4-bit 0 (`_h0`, since a hexadecimal digit is 4-bit). If this value is not set, the data type of the elements will be used for the additions, in this case, 1-bit (and a 1-bit addition is actually an exclusive or).
|
1421
1431
|
|
1422
|
-
- While the count of the bits is a single-line statement, it is executed like a usual sequencer loop and therefore requires 8 clock cycles
|
1432
|
+
- While the count of the bits is a single-line statement, it is executed like a usual sequencer loop and therefore requires 8 clock cycles to complete.
|
1423
1433
|
|
1424
|
-
* Fill array `ar` from input `sin` one byte per cycle then sort it and
|
1434
|
+
* Fill array `ar` from input `sin` one byte per cycle then sort it and output its content one byte per cycle.
|
1425
1435
|
|
1426
1436
|
```ruby
|
1427
1437
|
input :clk,:start
|
@@ -1437,7 +1447,7 @@ That's all that we will explain here, the remaining is exactly like Ruby. Moreov
|
|
1437
1447
|
end
|
1438
1448
|
```
|
1439
1449
|
|
1440
|
-
In this example, `16.stimes` generates an enumerator over the `0..7` range, and is a way to build an enumerator from an integer value. In addition, please notice the use of the Ruby variable `res` for
|
1450
|
+
In this example, `16.stimes` generates an enumerator over the `0..7` range, and is a way to build an enumerator from an integer value. In addition, please notice the use of the Ruby variable `res` for accessing the signal containing the sort result.
|
1441
1451
|
|
1442
1452
|
* Apply a 4-point FIR filter over an array obtained from input signal `sin` with 0-padding at the beginning and output the result to `sout`
|
1443
1453
|
|
@@ -1541,7 +1551,7 @@ There is a simple rule to follow in hardware design to avoid any trouble when ac
|
|
1541
1551
|
|
1542
1552
|
This rule is not absolute as you will see in the [hard way](#the-hard-way-arbitrating-between-writes-to-signals) to access signals, but it has the advantage of not requiring any additional hardware construct to be implemented. Hence, we do recommend following this rule as much as possible.
|
1543
1553
|
|
1544
|
-
Let us see an example for understanding how several sequencers can interact while following this rule. First,
|
1554
|
+
Let us see an example for understanding how several sequencers can interact while following this rule. First, let us consider a sequencer that increases periodically a value and sends it to another sequencer that will count the number of bits of this value and tell the first one to proceed with the increase:
|
1545
1555
|
|
1546
1556
|
```ruby
|
1547
1557
|
system :bit_pong do
|
@@ -1600,7 +1610,9 @@ hdrcc --sim --vcd bit_pong.rb bit_pong
|
|
1600
1610
|
|
1601
1611
|
You will obtain the following kind of time chart:
|
1602
1612
|
|
1603
|
-
|
1613
|
+
<p align="center">
|
1614
|
+
<img data-name="hruby_simulator.vcd" src="bit_pong_vcd.png" width="80%">
|
1615
|
+
</p>
|
1604
1616
|
|
1605
1617
|
You may notice a detail that is crucial in hardware: the increase of `value` by the first sequencer starts not when `ack` becomes 1, but the next clock cycle. This is the second important rule in hardware design:
|
1606
1618
|
|
@@ -1709,7 +1721,9 @@ end
|
|
1709
1721
|
|
1710
1722
|
Now, you should be used to it, so please try to simulate the code above. If you look at the VCD file in detail you will see a lot of signals but not any signal called `pingpong`. This is because shared signals hide underlining hardware that is exposed at simulation. Usually, what is relevant is the output value of the shared signal, which is called `<shared signal name>_out$<number>`. For our `pingpong` it is `pingpong_out$2`. Hence, you should get the following graph:
|
1711
1723
|
|
1712
|
-
|
1724
|
+
<p align="center">
|
1725
|
+
<img data-name="hruby_simulator.vcd" src="pingpong0_vcd.png" width="80%">
|
1726
|
+
</p>
|
1713
1727
|
|
1714
1728
|
Well, this is not an interesting result: `pingpong` is always 1, what about the second sequencer? There are two reasons for this:
|
1715
1729
|
|
@@ -1761,7 +1775,9 @@ end
|
|
1761
1775
|
|
1762
1776
|
And the simulation result should be:
|
1763
1777
|
|
1764
|
-
|
1778
|
+
<p align="center">
|
1779
|
+
<img data-name="hruby_simulator.vcd" src="pingpong1_vcd.png" width="80%">
|
1780
|
+
</p>
|
1765
1781
|
|
1766
1782
|
Still, it may be annoying that some writes of a sequencer can be ignored. In such a case, you can use a blocking version of the arbiter called the `monitor`. This module is used like the arbiter, but when write access is required, the sequencer will be blocked until the access is granted. Hence to avoid confusion with the arbiters' syntax, requiring access to a monitor is done by the `lock` method, and releasing it is done by the `unlock` one. For example, with the following code, the expected pingpong exchange will happen even when both sequencers try to write at the same time:
|
1767
1783
|
|
@@ -1802,9 +1818,11 @@ system :pingpong do
|
|
1802
1818
|
end
|
1803
1819
|
```
|
1804
1820
|
|
1805
|
-
As seen in the example, since the
|
1821
|
+
As seen in the example, since the monitor locks processes, no `step` is required, and the simulation result should be:
|
1806
1822
|
|
1807
|
-
|
1823
|
+
<p align="center">
|
1824
|
+
<img data-name="hruby_simulator.vcd" src="pingpong2_vcd.png" width="80%">
|
1825
|
+
</p>
|
1808
1826
|
|
1809
1827
|
> __WARNING__: while with an arbiter it was of no importance, with a monitor the lock must be made *after* the shared signal is written, otherwise this value will be taken into account one cycle later.
|
1810
1828
|
|
@@ -1821,7 +1839,7 @@ end
|
|
1821
1839
|
|
1822
1840
|
In the code above, the sequencer checks if it has access by comparing the shared signal selection with its priority (obtained by `arb.cur_priority`).
|
1823
1841
|
|
1824
|
-
#### I do not like the priority rule of the arbiter
|
1842
|
+
#### I do not like the priority rule of the arbiter/monitor
|
1825
1843
|
|
1826
1844
|
Indeed, sometimes we need to define our priority rules. This can be done when instantiating an arbiter or a monitor in two possible fashions:
|
1827
1845
|
|
@@ -1901,7 +1919,7 @@ sequencer(clk,start) do
|
|
1901
1919
|
z <= u + v + d
|
1902
1920
|
```
|
1903
1921
|
|
1904
|
-
... This time, `z` requires the value of `d`, but both circuits will again be selected at the same time. However, the output of the first one is connected to the third input of the second one: since they are combinatorial, waiting a little bit is enough
|
1922
|
+
... This time, `z` requires the value of `d`, but both circuits will again be selected at the same time. However, the output of the first one is connected to the third input of the second one: since they are combinatorial, waiting a little bit is enough to obtain the right `d` input for computing `z`.
|
1905
1923
|
|
1906
1924
|
> __WARNING__: in hardware design with HDLRuby (and with all similar languages like Verilog VHDL or VHDL,) it is assumed that a clock is slow enough for the relevant combinatorial circuits to complete computation before the next cycle. If this is not the case, the resulting circuits will not function properly. Fortunately, the synthesis frameworks usually provide tools for verifying these timings.
|
1907
1925
|
|
@@ -1984,7 +2002,9 @@ end
|
|
1984
2002
|
|
1985
2003
|
The simulation result should be:
|
1986
2004
|
|
1987
|
-
|
2005
|
+
<p align="center">
|
2006
|
+
<img data-name="hruby_simulator.vcd" src="maxxer_vcd.png" width="80%">
|
2007
|
+
</p>
|
1988
2008
|
|
1989
2009
|
As promised, the max is obtained at the first cycle of the sequencer execution!
|
1990
2010
|
|
@@ -2061,19 +2081,19 @@ Why parallel dataflow computations are indeed faster than sequential ones, in th
|
|
2061
2081
|
|
2062
2082
|
* Second, parallel implementation of algorithms requires much more chip area than sequential ones. This is expensive, and there are some physical limitations with the size of a chip.
|
2063
2083
|
|
2064
|
-
* Third, while faster in theory, parallel implementations may end slower than sequential ones in practice. This is because, the
|
2084
|
+
* Third, while faster in theory, parallel implementations may end slower than sequential ones in practice. This is because, the larger a combinatorial circuit is, the longer its delays are.
|
2065
2085
|
|
2066
2086
|
|
2067
2087
|
Now, the big question is: how do we know which part of our circuit would better be parallel?
|
2068
2088
|
|
2069
2089
|
> Unfortunately, the best answer is the designer's experience.
|
2070
2090
|
|
2071
|
-
Indeed, some design tools can
|
2091
|
+
Indeed, some design tools can decide for you, but the solution they give may not match your expectations. For HDLRuby, the idea is to let the designer decide but help him with as easy to use as possible sequential and parallel constructs.
|
2072
2092
|
|
2073
2093
|
|
2074
2094
|
#### That's all for this section!
|
2075
2095
|
|
2076
|
-
That was short this time because almost all
|
2096
|
+
That was short this time because almost all had been already said in the previous sessions. But now it is time to go past the sequencers and dive into real RTL design.
|
2077
2097
|
|
2078
2098
|
|
2079
2099
|
|
@@ -2098,7 +2118,7 @@ Why sequencers are easy to use with a software mindset, they are implemented on
|
|
2098
2118
|
|
2099
2119
|
First, we must make things clear:
|
2100
2120
|
|
2101
|
-
> Processes in hardware
|
2121
|
+
> Processes in hardware have very little (nothing?) to do with any kind of software process.
|
2102
2122
|
|
2103
2123
|
In hardware, a process is a list of data flow statements that are activated (we would say *executed* if they were software instructions) on a common condition.
|
2104
2124
|
|
@@ -2189,7 +2209,7 @@ end
|
|
2189
2209
|
|
2190
2210
|
### 5.3. What are the combinatorial processes?
|
2191
2211
|
|
2192
|
-
These processes are declared with a `seq` or `par` keywords with a list of signals as activation conditions. They have activated each time one or more signals of their activation condition changed value. For example, the following process will only be activated when `x` or `y` changes value, but not when `z`, `u` or `v` does:
|
2212
|
+
These processes are declared with a `seq` or a `par` keywords with a list of signals as activation conditions. They have activated each time one or more signals of their activation condition changed value. For example, the following process will only be activated when `x` or `y` changes value, but not when `z`, `u` or `v` does:
|
2193
2213
|
|
2194
2214
|
```ruby
|
2195
2215
|
par(x,y) do
|
@@ -2247,7 +2267,9 @@ end
|
|
2247
2267
|
|
2248
2268
|
The simulation result should be:
|
2249
2269
|
|
2250
|
-
|
2270
|
+
<p align="center">
|
2271
|
+
<img data-name="hruby_simulator.vcd" src="checksum_vcd.png" width="80%">
|
2272
|
+
</p>
|
2251
2273
|
|
2252
2274
|
That is to say that the checksum of `x` is `04` appended at the end of `z`.
|
2253
2275
|
|
@@ -2312,7 +2334,9 @@ end
|
|
2312
2334
|
|
2313
2335
|
The result should be:
|
2314
2336
|
|
2315
|
-
|
2337
|
+
<p align="center">
|
2338
|
+
<img data-name="hruby_simulator.vcd" src="clock_counter_vcd.png" width="80%">
|
2339
|
+
</p>
|
2316
2340
|
|
2317
2341
|
Did you get what the `clock_counter` circuit does? Yes, it simply counts the number of the positive edge of the signal `clk` when `run` is 1: signal `count` is initialized at 0 when declared, then the process which is activated on each positive edge of `clk` increases this signal by one. If you remember the sequencers, these clocked processes should look somewhat similar. Indeed, sequencers are built upon such processes. However, within processes, the fancy control statements like `sif`, `sloop`, or the enumerators **cannot** be used.
|
2318
2342
|
|
@@ -2320,7 +2344,7 @@ As seen in this code, a previously seen control statement has been used: the `hi
|
|
2320
2344
|
|
2321
2345
|
### 5.5. What about the control statements in processes?
|
2322
2346
|
|
2323
|
-
There is not much to say in this section... Because all
|
2347
|
+
There is not much to say in this section... Because all has been said in the section about [parallelism in sequencer](#parallelism). The fact is all the constructs described in this previous section were initially designed for the processes, and by extension could also be used in sequencers.
|
2324
2348
|
|
2325
2349
|
These statements can be used for both clocked processes and combinatorial processes. For example, the following is a combinatorial circuit that implements a simple ALU (Arithmetic and Logic Unit), able to perform the addition, the subtraction, the bitwise AND, and the bitwise OR of the two input values, while the operation is selected by a third input signal.
|
2326
2350
|
|
@@ -2369,7 +2393,9 @@ end
|
|
2369
2393
|
|
2370
2394
|
The result should be:
|
2371
2395
|
|
2372
|
-
|
2396
|
+
<p align="center">
|
2397
|
+
<img data-name="hruby_simulator.vcd" src="alu_vcd.png" width="80%">
|
2398
|
+
</p>
|
2373
2399
|
|
2374
2400
|
...So we can do controls with processes, but what about sharing signals?
|
2375
2401
|
|
@@ -2518,7 +2544,9 @@ end
|
|
2518
2544
|
|
2519
2545
|
And the simulation result should be:
|
2520
2546
|
|
2521
|
-
|
2547
|
+
<p align="center">
|
2548
|
+
<img data-name="hruby_simulator.vcd" src="ram_vcd.png" width="80%">
|
2549
|
+
</p>
|
2522
2550
|
|
2523
2551
|
The first module is an 8-bit address 8-bit data RAM with a single data bus for both reading and writing. When the RAM is not read (`en` is 0 or `rwb` is 0), the data bus is set to `Z`. The second module simply accesses the RAM, and for that purpose uses a process specifically for writing to the data bus: when a write operation is performed (`en` is 1 for activating the access to the RAM and `rwb` is set to 0), the value is written to the bus, otherwise, a value is nonetheless written but it is a `Z` value. The coding style of this process is crucial for ensuring synthesis tools will succeed: we cannot guarantee it will work if it is described differently.
|
2524
2552
|
|
@@ -2542,14 +2570,14 @@ But, now you may understand:
|
|
2542
2570
|
|
2543
2571
|
With that, you have all the constructs required for describing optimized hardware. Of course, mastering them requires a lot of know-how, but this is out of the scope of this tutorial. To go further, please follow lessons about hardware design using popular hardware description languages like Verilog HDL or VHDL since what you can do with them, you can do it with HDLRuby.
|
2544
2572
|
|
2545
|
-
However, there
|
2573
|
+
However, there remain a few major features for more efficient coding inherited from the Ruby language.
|
2546
2574
|
|
2547
2575
|
|
2548
2576
|
## 6. Not enough? What about genericity, object orientation, metaprogramming, and reflection?
|
2549
2577
|
|
2550
2578
|
Let us imagine you spent hours designing a circuit processing 8-bit values for outputting a 16-bit result and you learn that the input should be 16-bit and the output 8-bit. Well, that's not so tough, you just need to modify the data types in your circuit description... **everywhere**. When you just finished, you learn that it would be great if there was also another version of the circuit that could output flags about the result. Ok, so let us make another version of the circuit, and since it is not so different, let us do some copy and paste, and then make modifications for the flags... But whoop! You did not notice that with your modifications you unintentionally modified a few lines of code introducing new bugs... And of course, you realize it after hours of testing.
|
2551
2579
|
|
2552
|
-
Such situations happen all the time in software design... But it is much worse in hardware. Indeed, due to the variety of hardware circuits, it is very common to want to reuse a design with different bit widths. This is why, existing hardware description language
|
2580
|
+
Such situations happen all the time in software design... But it is much worse in hardware. Indeed, due to the variety of hardware circuits, it is very common to want to reuse a design with different bit widths. This is why, existing hardware description language supports genericity to a certain degree. However, HDLRuby ruby goes further in terms of genericity, and also adds object-oriented programming, metaprogramming, and reflection concepts to maximize the possibilities of code reuse. From now on, let us detail:
|
2553
2581
|
|
2554
2582
|
* [Genericity in HDLRuby](#61-genericity-in-hdlruby)
|
2555
2583
|
|
@@ -2559,7 +2587,7 @@ Such situations happen all the time in software design... But it is much worse i
|
|
2559
2587
|
|
2560
2588
|
### 6.1. Genericity in HDLRuby
|
2561
2589
|
|
2562
|
-
In HDLRuby, genericity is supported through the *generic module* constructs. These constructs describe circuits like standard module does
|
2590
|
+
In HDLRuby, genericity is supported through the *generic module* constructs. These constructs describe circuits like standard module does but with the addition of parameters that control their content. They are declared as follows:
|
2563
2591
|
|
2564
2592
|
```ruby
|
2565
2593
|
system :<name> do |<list of parameters>|
|
@@ -2652,7 +2680,9 @@ end
|
|
2652
2680
|
|
2653
2681
|
In this code, the syntax `proc <block>` is for creating a chunk of HDLRuby (or Ruby) code from the content of `block`. With this environment, the simulation result should be:
|
2654
2682
|
|
2655
|
-
|
2683
|
+
<p align="center">
|
2684
|
+
<img data-name="hruby_simulator.vcd" src="counter_ext_vcd.png" width="80%">
|
2685
|
+
</p>
|
2656
2686
|
|
2657
2687
|
|
2658
2688
|
### 6.2. Object-oriented programming in HDLRuby
|
@@ -2742,7 +2772,7 @@ end
|
|
2742
2772
|
|
2743
2773
|
After that, all the instances that are created from `addsub` will include the `zf` flag computation and output.
|
2744
2774
|
|
2745
|
-
In contrast, you may want
|
2775
|
+
In contrast, you may want only one instance to have the flag. In such a case, it may not be necessary to define a new module for that but just modify this instance only. This is possible using again the `open` method but on the instance. For example with the instance used in the following simulation environment:
|
2746
2776
|
|
2747
2777
|
```ruby
|
2748
2778
|
system :addsub_sim do
|
@@ -2775,7 +2805,9 @@ end
|
|
2775
2805
|
|
2776
2806
|
And the simulation result should be:
|
2777
2807
|
|
2778
|
-
|
2808
|
+
<p align="center">
|
2809
|
+
<img data-name="hruby_simulator.vcd" src="addsub_vcd.png" width="80%">
|
2810
|
+
</p>
|
2779
2811
|
|
2780
2812
|
#### 6.2.3. Overriding
|
2781
2813
|
|
@@ -2795,7 +2827,7 @@ system :adder_flags do |width|
|
|
2795
2827
|
end
|
2796
2828
|
```
|
2797
2829
|
|
2798
|
-
|
2830
|
+
Let us also assume that we want to make a circuit that does the same but saturates the addition result between -300 and +300. For that purpose, the computation of `z` must be replaced. This is called `overriding` and for the sake of code readability overriding is only permitted for *named sub-sections* of a module. A named subsection is declared as follows:
|
2799
2831
|
|
2800
2832
|
```ruby
|
2801
2833
|
sub(:<name>) <block>
|
@@ -2864,7 +2896,9 @@ end
|
|
2864
2896
|
|
2865
2897
|
And the simulation result should be:
|
2866
2898
|
|
2867
|
-
|
2899
|
+
<p align="center">
|
2900
|
+
<img data-name="hruby_simulator.vcd" src="adder_sat_flags_vcd.png" width="80%">
|
2901
|
+
</p>
|
2868
2902
|
|
2869
2903
|
__Note__: with this circuit, the `of` flag (overflow) is always 0 because there cannot be any overflow for a 10-bit number with saturation at -300, 300.
|
2870
2904
|
|
@@ -2874,16 +2908,877 @@ __Note__: with this circuit, the `of` flag (overflow) is always 0 because there
|
|
2874
2908
|
|
2875
2909
|
There is not much to say about metaprogramming and reflection because both have been used extensively in this tutorial.
|
2876
2910
|
|
2877
|
-
* Metaprogramming consists
|
2911
|
+
* Metaprogramming consists of using programs that generate the final code. In HDLRuby, this is done by using Ruby code for generating, passing as an argument, and appending chunks of HDLRuby code. This has been done when presenting how to describe parallel code in sequencers, but this can be used everywhere, e.g., within the general processes. Moreover, the possibility to use a chunk of code as a generic argument presented in this section is also metaprogramming.
|
2878
2912
|
|
2879
|
-
* Reflection has been used in several cases, e.g., the `width` method for knowing the bit width of a type or the `open` method.
|
2913
|
+
* Reflection has been used in several cases, e.g., the `width` method for knowing the bit width of a type or the `open` method. More generally, HDLRuby provides a large number of methods for inspecting and modifying directly the objects of the internal representation. Please consult the documentation of the HDLRuby classes for details about them (good luck, this is low-level coding documentation).
|
2880
2914
|
|
2881
2915
|
> __Note__: the sequencers, the shared signals, the arbiter, and the monitor are not native HDLRuby constructs, they have all been implemented with metaprogramming and reflection.
|
2882
2916
|
|
2883
2917
|
|
2884
|
-
## 7. What next?
|
2885
2918
|
|
2886
|
-
|
2919
|
+
## 7. How to mix hardware and software.
|
2920
|
+
|
2921
|
+
Digital electronic devices often contain one of several processors for executing software. That makes then more versatile and easier to update or fix. However, designing a mix of software and hardware (hardware-software co-design) can be challenging, especially because both worlds use different languages and models of computation.
|
2922
|
+
|
2923
|
+
For HDLRuby, the mix of hardware and software is done using a construct, called a program, that provides a basic communication interface made of registers and interrupts. This interface can then be extended both on the hardware and the software side to support more complex interactions.
|
2924
|
+
|
2925
|
+
### 7.1. The Program construct
|
2926
|
+
|
2927
|
+
A program construct, like the processes or the sequencers, is declared within a module, but unlike them, its content is not some code description (neither hardware nor software), but instead a description of its hardware-software interface.
|
2928
|
+
|
2929
|
+
More specifically, a program is declared as follows:
|
2930
|
+
|
2931
|
+
```ruby
|
2932
|
+
program(<programming language>, <function name>) do
|
2933
|
+
<location of the software files and description of its interface>
|
2934
|
+
end
|
2935
|
+
```
|
2936
|
+
|
2937
|
+
In the code above, `programming language` is a symbol representing the programming language used for the software. For now, only two languages are supported:
|
2938
|
+
|
2939
|
+
* `:ruby`: for programs in Ruby.
|
2940
|
+
|
2941
|
+
* `:c`: for programs in C. However, for this case, any language that can be compiled to a shared library linkable with C is supported.
|
2942
|
+
|
2943
|
+
The `function name` parameter indicates which function is to be executed when an activation event occurs. There can be only one such function per program, but any number of programs can be declared inside the same module.
|
2944
|
+
|
2945
|
+
The `location of the software files and description of its interface` part can include the following declaration statements:
|
2946
|
+
|
2947
|
+
* `actport <list of events>`: for declaring the list of events that activates the program, i.e., that will trigger the execution of the program's start function.
|
2948
|
+
|
2949
|
+
* `inport <list of port names associated with a signal>`: for declaring the list of ports that the software code of the program can read.
|
2950
|
+
|
2951
|
+
* `outport <list of port names associated with a signal>`: for declaring the list of ports that the software code of the program can write to.
|
2952
|
+
|
2953
|
+
* `code <list of filenames>`: for declaring the source code files.
|
2954
|
+
|
2955
|
+
For example the following declares a program in the Ruby language whose start function is `echo` that is activated on the positive edge of signal `req`, has a read port called `inP` that is connected to signal `count` and a write port called `outP` that is connected to signal `val`, finally the code of this program is given in a file named `echo.rb` (the extension `.rb` can also be omitted):
|
2956
|
+
|
2957
|
+
```ruby
|
2958
|
+
system :my_system do
|
2959
|
+
inner :req
|
2960
|
+
[8].inner :count, :val
|
2961
|
+
|
2962
|
+
program(:ruby,'echo') do
|
2963
|
+
actport req.posedge
|
2964
|
+
inport inP: count
|
2965
|
+
outport outP: val
|
2966
|
+
code "echo.rb"
|
2967
|
+
end
|
2968
|
+
|
2969
|
+
...
|
2970
|
+
|
2971
|
+
end
|
2972
|
+
```
|
2973
|
+
|
2974
|
+
A similar program construct can also use C as follows:
|
2975
|
+
|
2976
|
+
```ruby
|
2977
|
+
system :my_system do
|
2978
|
+
inner :req
|
2979
|
+
[8].inner :count, :val
|
2980
|
+
|
2981
|
+
program(:c,'echo') do
|
2982
|
+
actport req.posedge
|
2983
|
+
inport inP: count
|
2984
|
+
outport outP: val
|
2985
|
+
code "echo"
|
2986
|
+
end
|
2987
|
+
|
2988
|
+
...
|
2989
|
+
|
2990
|
+
end
|
2991
|
+
```
|
2992
|
+
|
2993
|
+
There is a difference from Ruby though in that the C code cannot be used directly and must be compiled to a library, and it is the name of the library that has to be indicated in the `code` section. Since the format of the compiler program depends highly on the target software environment, it is recommended to omit the extension (e.g., here only `echo` and not `echo.so` or any other extension), so that the framework looks for the suitable format automatically.
|
2994
|
+
|
2995
|
+
|
2996
|
+
|
2997
|
+
Now, let us see how the software code can be written to interact with hardware.
|
2998
|
+
|
2999
|
+
### 7.2. Writing software for a HDLRuby Program construct.
|
3000
|
+
|
3001
|
+
As long as the language is supported by HDLRuby (i.e., Ruby or a C-compatible compiled language), there should not be any restrictions for the software code. The function that has been declared in the HDLRuby program construct for being activated will act as a handler for an interrupt raised by one of the corresponding events.
|
3002
|
+
|
3003
|
+
Then, for accessing the ports on the hardware-software interface, a library that depends on the used software language must be loaded.
|
3004
|
+
|
3005
|
+
#### 7.2.1. In the case of Ruby
|
3006
|
+
|
3007
|
+
If the software language is Ruby, then the library is loaded by requiring the file `rubyHDL.rb`. If HDLRuby is correctly installed, adding the following line at the beginning of the code is enough:
|
3008
|
+
|
3009
|
+
```ruby
|
3010
|
+
require 'rubyHDL.rb'
|
3011
|
+
```
|
3012
|
+
|
3013
|
+
This library gives access to a Ruby module named "RubyHDL" which gives access to the ports of the hardware-software interface described in the HDLRuby program construct. Those ports are directly accessible by name as follows: `RubyHDL.<name>`.
|
3014
|
+
|
3015
|
+
For example, the following is an example of implementation of the `echo.rb` software used in the previous HDLRuby program example:
|
3016
|
+
|
3017
|
+
```ruby
|
3018
|
+
require 'rubyHDL'
|
3019
|
+
|
3020
|
+
def echo
|
3021
|
+
val = RubyHDL.inP
|
3022
|
+
RubyHDL.outP = val
|
3023
|
+
end
|
3024
|
+
```
|
3025
|
+
|
3026
|
+
This program is limited to the handler `echo`, that reads on port `inP` and write the result to port `outP`. If you remember the corresponding HDLRuby program construct, that means `echo` will get the value of signal `count` and send it to signal `val`.
|
3027
|
+
|
3028
|
+
#### 7.2.2. For the case of C
|
3029
|
+
|
3030
|
+
If the software language is C (or a compatible compiled language), then the library is accessed by including the file `cHDL.h`. However, this file is probably not within the includes path of the C compiler, so you need to bring it to your working directory, as well as other possible necessary files, using the `hdrcc` command as follows:
|
3031
|
+
|
3032
|
+
```bash
|
3033
|
+
hdrcc --ch <path to your working directory>
|
3034
|
+
```
|
3035
|
+
|
3036
|
+
Here, your working directory is assumed to contain the source code for your C program.
|
3037
|
+
|
3038
|
+
Then, you can include the file as follows in your C source code:
|
3039
|
+
|
3040
|
+
```ruby
|
3041
|
+
#include "cHDL.h"
|
3042
|
+
```
|
3043
|
+
|
3044
|
+
This library gives access to three C functions for interacting with the ports:
|
3045
|
+
|
3046
|
+
* `void* c_get_port(const char* name)`: returns a pointer to the port whose name is passed as argument.
|
3047
|
+
|
3048
|
+
* `int c_read_port(void* port)`: reads the port whose pointer is passed as argument and returns its value.
|
3049
|
+
|
3050
|
+
* `int c_write_port(void* port, int val)`: write the value `val` to the port passed as argument.
|
3051
|
+
|
3052
|
+
Please notice that whatever the bit width of the signal connected to the part may be, from the software side the type of the data going through a port is always `int`. If the signal is larger than an `int`, the extra bits will be truncated, and if it is smaller, the value of the extra bits from the software side is undefined.
|
3053
|
+
|
3054
|
+
|
3055
|
+
For example, the following is an example of implementation of the `echo.rb` software used in the previous HDLRuby program example:
|
3056
|
+
|
3057
|
+
```c
|
3058
|
+
#include "cHDL.h"
|
3059
|
+
|
3060
|
+
void echo() {
|
3061
|
+
static void* inP = c_get_port("inP");
|
3062
|
+
static void* outP = c_get_port("outP");
|
3063
|
+
int val = c_read_port(inP);
|
3064
|
+
c_write_port(outP);
|
3065
|
+
end
|
3066
|
+
```
|
3067
|
+
|
3068
|
+
This program is the same as the Ruby version, but the ports are only available after being looked up by name using the `c_get_port` function. It is enough to do that once though, so that in the example, we set the corresponding port variable `inP` and `outP` as static local.
|
3069
|
+
|
3070
|
+
|
3071
|
+
### 7.3. Hardware-software co-simulation
|
3072
|
+
|
3073
|
+
Hardware-software co-simulation is done, like pure hardware simulation, using the HDLRuby simulator using the `hdrcc --sim` command (please see [section 1.3.](#1-3-using-hdlruby).
|
3074
|
+
|
3075
|
+
When the software language is Ruby, there is nothing more to do. However, when the software language is C, its code must first be compiled to a shared library for the host (i.e., the computer that executes the HDLRuby simulator). For example, assuming you want to compile the C file `echo.c` located into the `echo` directory, if you are using GCC on a Linux system, you could type (after entering the `echo` directory):
|
3076
|
+
|
3077
|
+
```bash
|
3078
|
+
cd echo
|
3079
|
+
gcc -shared -fPIC -undefined dynamic_lookup -o c_program.so echo.c
|
3080
|
+
```
|
3081
|
+
|
3082
|
+
Otherwise, it may be easier to use the Ruby environment by first installing `rake-compiler` as follows:
|
3083
|
+
|
3084
|
+
```bash
|
3085
|
+
gem install rake-compiler
|
3086
|
+
```
|
3087
|
+
|
3088
|
+
And simply type the following command (after entering the `echo` directory):
|
3089
|
+
|
3090
|
+
```bash
|
3091
|
+
rake compile
|
3092
|
+
```
|
3093
|
+
|
3094
|
+
The rake tool will take care of everything for performing the compiling whatever your system may be.
|
3095
|
+
|
3096
|
+
When the C code is compiled, the HDLRuby simulator can be executed as usual.
|
3097
|
+
|
3098
|
+
__Note__:
|
3099
|
+
|
3100
|
+
**Important:** for windows, dynamically loaded functions must be declared with the following prefix: `__declspec(dllexport)`. If this prefix is not present before each function that is used as an HDLRuby program, the simulation will not work. For example, for Windows, the function echo *must* be written as follows:
|
3101
|
+
|
3102
|
+
```c
|
3103
|
+
#include "cHDL.h"
|
3104
|
+
|
3105
|
+
__declspec(dllexport) void echo() {
|
3106
|
+
void* inP = c_get_port("inP");
|
3107
|
+
void* outP = c_get_port("outP");
|
3108
|
+
int val;
|
3109
|
+
|
3110
|
+
val = c_read_port(inP);
|
3111
|
+
c_write_port(outP,val);
|
3112
|
+
}
|
3113
|
+
```
|
3114
|
+
|
3115
|
+
|
3116
|
+
### 7.4. Is that all?
|
3117
|
+
|
3118
|
+
Technically, there is nothing else to describe hardware-software interaction in HDLRuby. Hence, no shared memory, buffer, or other complex communication or synchronization systems. But the reason is that we do not need them in the core of HDLRuby, they can be described using a mix of standard HDLRuby and software programming. With the same method, you can also simulate external devices as black boxes.
|
3119
|
+
|
3120
|
+
To illustrate how this mix of HDLRuby and software can be implemented, let us see three examples: a memory shared between software and hardware, a model of an operating system, a real-time clock, and a method for modeling program execution time. In all these examples we will use Ruby as software language, but this can also be done with C.
|
3121
|
+
|
3122
|
+
#### 7.4.1. Modeling a memory shared between software and hardware
|
3123
|
+
|
3124
|
+
There are two possibilities for modeling such a memory: either describe it in plain HDLRuby as a full hardware component, and write a program construct that simulates the access of the software part to this memory, or describe the memory as a part of the software part and simulate its access from the hardware part using a program construct. The first approach is more accurate and may be useful when a specific kind of memory is to be used, but it is slower than the second one.
|
3125
|
+
|
3126
|
+
##### 7.4.1.1 Memory described as a hardware component
|
3127
|
+
|
3128
|
+
For the first approach, if we assume a memory with an 8-bit address bus, and an 8-bit data bus the HDLRuby code could be as follows:
|
3129
|
+
|
3130
|
+
```ruby
|
3131
|
+
system :hw_sw_with_hw_memory do
|
3132
|
+
[8].inner :addr
|
3133
|
+
[8].inner :din, :dout
|
3134
|
+
inner :rwb, :ce
|
3135
|
+
|
3136
|
+
inner :clk, :rst
|
3137
|
+
|
3138
|
+
# Instantiation of the memory.
|
3139
|
+
instance :memory do
|
3140
|
+
inner :clk
|
3141
|
+
[8].input :addr
|
3142
|
+
[8].input :din
|
3143
|
+
[8].output :dout
|
3144
|
+
input :rwb, :ce
|
3145
|
+
|
3146
|
+
bit[8][-256].inner :content
|
3147
|
+
|
3148
|
+
par(clk.posedge) do
|
3149
|
+
hif(ce) do
|
3150
|
+
hif(rwb) { dout <= content[addr] }
|
3151
|
+
helse { content[addr] <= din }
|
3152
|
+
end
|
3153
|
+
end.(clk,addr,din,dout,rwb,ce)
|
3154
|
+
|
3155
|
+
# The hw generating the tick activating the software part (e.g., an OS tick):
|
3156
|
+
# one tick every 10 clock cycles.
|
3157
|
+
inner :tick
|
3158
|
+
(tick <= ~rst & ~tick).at(clk.posedge * 5)
|
3159
|
+
|
3160
|
+
inner :req_sw, :ack_sw, :rwb_sw
|
3161
|
+
[8].inner :addr_sw
|
3162
|
+
[8].inner :din_sw, :dout_sw
|
3163
|
+
|
3164
|
+
inner[16] :addr_hw
|
3165
|
+
inner :req_hw, :ack_hw, :rwb_hw
|
3166
|
+
[8].inner :din_hw, :dout_hw
|
3167
|
+
|
3168
|
+
# Description of the software part.
|
3169
|
+
program(:ruby,:some_sw) do
|
3170
|
+
actport tick.posedge
|
3171
|
+
outport req :req_sw, ack: ack_sw, rwb: rwb_sw
|
3172
|
+
outport addr: addr_sw
|
3173
|
+
outport dout: dout_sw
|
3174
|
+
inport din: din_sw
|
3175
|
+
code "some_sw.rb"
|
3176
|
+
end
|
3177
|
+
|
3178
|
+
inner :priority # 0: priority in hw access, 1: priority in sw access.
|
3179
|
+
|
3180
|
+
# The arbiter, sharing the memory between software and hardware giving
|
3181
|
+
# priority to software. It uses a round-robin approach.
|
3182
|
+
par(clk.negedge) do
|
3183
|
+
hif(rst) { priority <= 0 }
|
3184
|
+
ack_sw <= 0 # By default no access granted to software.
|
3185
|
+
ack_hw <= 0 # Nor to hardware.
|
3186
|
+
ce <= 0
|
3187
|
+
hif(req_sw & (priority | ~req_hw)) do
|
3188
|
+
ce <= 1
|
3189
|
+
ack_sw <= 1
|
3190
|
+
rwb <= rwb_sw
|
3191
|
+
addr <= addr_sw
|
3192
|
+
din <= dout_sw
|
3193
|
+
din_sw <= dout
|
3194
|
+
priority <= ~priority
|
3195
|
+
end
|
3196
|
+
if(req_hw & (~priority | ~req_sw)) do
|
3197
|
+
ce <= 1
|
3198
|
+
ack_hw <= 1
|
3199
|
+
rwb <= rwb_hw
|
3200
|
+
addr <= addr_hw
|
3201
|
+
din <= dout_hw
|
3202
|
+
din_hw <= dout
|
3203
|
+
priority <= ~priority
|
3204
|
+
end
|
3205
|
+
end
|
3206
|
+
|
3207
|
+
# Some hardware code accessing the memory.
|
3208
|
+
sequencer(clk,rst) do
|
3209
|
+
# At first, fill the memory with 0.
|
3210
|
+
req_hw <= 1; rwb_hw <= 0
|
3211
|
+
swhile(~ack_hw) # Wait for the access grant to memory.
|
3212
|
+
dout_hw <= 0
|
3213
|
+
256.stimes { |i| addr_hw <= i }
|
3214
|
+
req_hw <= 0 # End the transactions.
|
3215
|
+
# Now, do infinitely some arbitrary computations with the content
|
3216
|
+
# of the memory.
|
3217
|
+
sloop do
|
3218
|
+
128.stimes do |i|
|
3219
|
+
[8].inner :tmp
|
3220
|
+
req_hw <= 1
|
3221
|
+
rwb_hw <= 1
|
3222
|
+
addr_hw <= i
|
3223
|
+
swhile(~ack_hw)
|
3224
|
+
tmp <= din_hw
|
3225
|
+
step
|
3226
|
+
rwb_hw <= 0
|
3227
|
+
addr_hw <= i + 128
|
3228
|
+
dout_hw <= tmp * tmp # Just some senseless data.
|
3229
|
+
step
|
3230
|
+
req_hw <= 0
|
3231
|
+
end
|
3232
|
+
end
|
3233
|
+
end
|
3234
|
+
```
|
3235
|
+
|
3236
|
+
This example contains four parts:
|
3237
|
+
|
3238
|
+
* A hardware description of a memory, described as an inline instance. It has an address input `addr`, a data input `din` a data outpout `dout` (it is common in FPGA nowadays to avoid three-state buses), a signal `rwb` indicating if a read (1) or a write (0) access is performed, and a chip enable signal `ce`.
|
3239
|
+
|
3240
|
+
*NOTE:* an inline `instance` is a construct that both describe a module (like `system`) and instantiates it in place. When a component is to be used only once, it is a convenient way to have fast access to the description of the module where it is used, while keeping the hierarchy.
|
3241
|
+
|
3242
|
+
* A hardware description of an arbiter for granting access to this memory to alternatively a software component and a hardware component using round-robin. Both the software part and the hardware part have their signals for accessing the memory (e.g., resp. `rwb_sw` and `rwb_hw` for `rwb`), and the arbiter will transmit them to the corresponding memory signal when their access is granted. For requiring access, both have to raise a memory request signal, resp. `req_sw` and `req_hw`, and will know they have access when the corresponding acknowledge signals are raised, resp., `ack_sw` and `ack_hw`.
|
3243
|
+
|
3244
|
+
* A software component described with a sequencer reading and writing the memory and performing arbitrary computations. This code is activated on each rising edge of signal `tick`.
|
3245
|
+
|
3246
|
+
* A hardware component described with a sequencer reading and writing the memory and performing arbitrary computations.
|
3247
|
+
|
3248
|
+
Then, the code of the software component in the file `some_sw.rb` can be as follows:
|
3249
|
+
|
3250
|
+
```ruby
|
3251
|
+
require 'rubyHDL.rb'
|
3252
|
+
|
3253
|
+
$addr = 0
|
3254
|
+
$state = 0
|
3255
|
+
$tmp = 0
|
3256
|
+
|
3257
|
+
def some_sw
|
3258
|
+
if(RubyHDL.ack == 0) then
|
3259
|
+
RubyHDL.req = 1
|
3260
|
+
else
|
3261
|
+
case($state)
|
3262
|
+
when 0:
|
3263
|
+
RubyHDL.addr = 128+$addr
|
3264
|
+
RubyHDL.rwb = 1
|
3265
|
+
$state = 1
|
3266
|
+
when 1:
|
3267
|
+
$tmp = RubyHDL.din
|
3268
|
+
puts "Read: #{$tmp}"
|
3269
|
+
$state = 2
|
3270
|
+
when 2:
|
3271
|
+
RubyHDL.rwb = 0
|
3272
|
+
RubyHDL.addr = $addr
|
3273
|
+
puts "Write: #{$tmp + 1}"
|
3274
|
+
RubyHDL.dout = $tmp + 1 # Write some random computation result from
|
3275
|
+
$state = 0 # the previously read data.
|
3276
|
+
$addr = ($addr + 1) & 255 # Next address.
|
3277
|
+
end
|
3278
|
+
end
|
3279
|
+
end
|
3280
|
+
```
|
3281
|
+
|
3282
|
+
This program simply writes in the first half of the memory the result of some arbitrary computation from the data read from the first half. That is to say that it does the opposite of the hardware component described in the HDLRuby code. This code makes sense only if the process has access to the memory via explicit access to address and data registers, which is the case for some embedded architectures. If the memory is directly accessible by the processor, the access procedure must be encapsulated by Ruby methods (or C functions) that are meant to be replaced by standard memory accesses in the final code. Moreover, since a software code is fully executed by the HDLRuby simulator before giving back the hand to the simulation, this code has been written like a state machine whose state is a global variable updated at each code, so that it simulates an interaction with the hardware. This is valid if the software code is meant to be a handler of interrupt for example, but not for more conventional software. For the second case, a model of an operating system will have to be described that handles the synchronizations. Such a model as well as the encapsulation of the hardware accesses will presented in section [7.5.2](#7-5-2-modeling-an-operating-system).
|
3283
|
+
|
3284
|
+
|
3285
|
+
##### 7.4.1.1 Memory described as a software component
|
3286
|
+
|
3287
|
+
Usually, CPUs have privileged access to the main memory and give direct access to it to software through a pointer, or an array. Moreover, memories are usually IP components that are instantiated as black boxes in HDL. For such cases, it is preferable to describe a memory as a program construct whose code is a simulation of its behavior described in software. This description is to be used when simulating, but it can be ignored when synthesizing the final hardware. With this approach, the previous HDLRuby code can be rewritten as follows:
|
3288
|
+
|
3289
|
+
```ruby
|
3290
|
+
system :hw_sw_with_sw_memory do
|
3291
|
+
[8].inner :addr
|
3292
|
+
[8].inner :din, :dout
|
3293
|
+
inner :rwb
|
3294
|
+
inner :req, :ack
|
3295
|
+
|
3296
|
+
inner :clk, :rst
|
3297
|
+
|
3298
|
+
# Description of the memory as a black box simulated in software.
|
3299
|
+
program(:ruby,:memory) do
|
3300
|
+
actport clk.posedge
|
3301
|
+
inport addr: addr
|
3302
|
+
inport din: din
|
3303
|
+
outport dout: dout
|
3304
|
+
inport rwb: rwb
|
3305
|
+
inport req: req
|
3306
|
+
outport ack: ack
|
3307
|
+
code "some_sw.rb"
|
3308
|
+
end
|
3309
|
+
|
3310
|
+
# The hw generating the tick activating the software part (e.g., an OS tick):
|
3311
|
+
# one tick every 10 clock cycles.
|
3312
|
+
inner :tick
|
3313
|
+
(tick <= ~rst & ~tick).at(clk.posedge * 5)
|
3314
|
+
|
3315
|
+
# Description of the software part.
|
3316
|
+
program(:ruby,:some_sw) do
|
3317
|
+
actport tick
|
3318
|
+
code "some_sw.rb"
|
3319
|
+
end
|
3320
|
+
|
3321
|
+
# Some hardware code accessing the memory.
|
3322
|
+
sequencer(clk,rst) do
|
3323
|
+
# At first, fill the memory with 0.
|
3324
|
+
req <= 1
|
3325
|
+
rwb <= 0
|
3326
|
+
swhile(~ack) # Wait for the access grant to memory.
|
3327
|
+
dout <= 0
|
3328
|
+
256.stimes { |i| addr_hw <= i }
|
3329
|
+
req <= 0 # End the transactions.
|
3330
|
+
# Now, do infinitively some arbitrary computations with the content
|
3331
|
+
# of the memory.
|
3332
|
+
sloop do
|
3333
|
+
128.stimes do |i|
|
3334
|
+
[8].inner :tmp
|
3335
|
+
req <= 1
|
3336
|
+
rwb <= 1
|
3337
|
+
addr <= i
|
3338
|
+
swhile(~ack)
|
3339
|
+
tmp <= din
|
3340
|
+
step
|
3341
|
+
rwb <= 0
|
3342
|
+
addr <= i + 128
|
3343
|
+
dout <= tmp *tmp # Just some senseless data.
|
3344
|
+
step
|
3345
|
+
req <= 0
|
3346
|
+
end
|
3347
|
+
end
|
3348
|
+
end
|
3349
|
+
```
|
3350
|
+
|
3351
|
+
This time the arbiter has been integrated into the black box modeling the memory so that the hardware part only has to use the direct memory signals `addr`, `din`, `dout`, `rwb`, `req` and `ack`.
|
3352
|
+
The code of the software part contains two functions, one for modeling the memory `memory`, and the previous software function `some_sw`. For the sake of concision, both are included in the same file given below, but for a real design, it would be better to put them in different files so that the real software is separated from the black box simulation code.
|
3353
|
+
|
3354
|
+
```ruby
|
3355
|
+
require 'rubyHDL.rb'
|
3356
|
+
|
3357
|
+
MEM = [0] * 256
|
3358
|
+
|
3359
|
+
$priority = 0
|
3360
|
+
|
3361
|
+
def memory
|
3362
|
+
# Is there an access request from the HW?
|
3363
|
+
if(RubyHDL.req == 1) then
|
3364
|
+
# Is it being processed?
|
3365
|
+
if (RubyHDL.ack == 1) then
|
3366
|
+
# Yes, go on processing.
|
3367
|
+
if (RubyHDL.rwb == 1) then
|
3368
|
+
# Read access.
|
3369
|
+
RubyHDL.dout = MEM[RubyHDL.addr]
|
3370
|
+
else
|
3371
|
+
# Write access.
|
3372
|
+
MEM[RubyHDL.addr] = RubyHDL.din
|
3373
|
+
end
|
3374
|
+
elsif ($priority == 0)
|
3375
|
+
# Grant the access.
|
3376
|
+
RubyHDL.ack = 1
|
3377
|
+
$priority = 1
|
3378
|
+
end
|
3379
|
+
end
|
3380
|
+
else
|
3381
|
+
# No, ensure the access grant is removed.
|
3382
|
+
RubyHDL.ack = 0
|
3383
|
+
end
|
3384
|
+
end
|
3385
|
+
|
3386
|
+
$addr = 0
|
3387
|
+
$state = 0
|
3388
|
+
$tmp = 0
|
3389
|
+
|
3390
|
+
def some_sw
|
3391
|
+
if(RubyHDL.ack == 0) then
|
3392
|
+
# The access from HW to memory not granted, can go an.
|
3393
|
+
case($state)
|
3394
|
+
when 0:
|
3395
|
+
if ($priority == 1) then
|
3396
|
+
$state = 1
|
3397
|
+
end
|
3398
|
+
when 1:
|
3399
|
+
$tmp = MEM[$addr+128]
|
3400
|
+
puts "Read: #{$tmp}"
|
3401
|
+
$state = 2
|
3402
|
+
when 2:
|
3403
|
+
puts "Write: #{$tmp + 1}"
|
3404
|
+
MEM[$addr] = $tmp + 1 # Write some random computation result from
|
3405
|
+
$state = 0 # the previously read data.
|
3406
|
+
$addr = ($addr + 1) & 255 # Next address.
|
3407
|
+
$priority = 0
|
3408
|
+
end
|
3409
|
+
end
|
3410
|
+
end
|
3411
|
+
```
|
3412
|
+
|
3413
|
+
In this example, the memory is modeled by a simple array. The function `memory` handles the accesses from the hardware and the arbitration between software and hardware access using a round-robin algorithm. The software program given by function `some_sw` is identical to the previous one, apart from the memory access which is a direct array access, and the check of the memory grant, since this time no software-specific signal is used for the arbitration.
|
3414
|
+
|
3415
|
+
|
3416
|
+
##### 7.4.2 Modeling an operating system
|
3417
|
+
|
3418
|
+
Usually, software is executed on top of an operating system, or a minimal runtime, e.g., even the plainest C runs on top of `crt0`. This low-level software is usually fixed and highly target-dependent. Hence, it is usually enough to simulate this behavior. Since the HDLRuby simulator supports any compiled C (or other compatible compiled language), or Ruby, all the techniques that can be used in these languages for abstract low-level software can be used. Here, we will use Ruby threads as an illustration for modeling a simple multitask system with an interrupt handler. In the example, there will be two tasks, one reading data from a dummy hardware device described in HDLRuby, transmitting it using a pipe to another one which writes its data to the standard output every second.
|
3419
|
+
|
3420
|
+
```ruby
|
3421
|
+
system :hw_sw_with_os do
|
3422
|
+
inner :clk,:rst
|
3423
|
+
|
3424
|
+
# The OS tick
|
3425
|
+
inner :tick
|
3426
|
+
(tick <= ~rst & ~tick).at(clk.posedge * 5)
|
3427
|
+
|
3428
|
+
# The general interrupt signal: interrupt request, acknowledge, and number
|
3429
|
+
inner :irq, :iak
|
3430
|
+
|
3431
|
+
# A register that
|
3432
|
+
|
3433
|
+
# The os model.
|
3434
|
+
program(:ruby, :os) do
|
3435
|
+
actport tick: tick
|
3436
|
+
inport register: register # Some register direcly acessible by software
|
3437
|
+
code "sw_with_os.rb"
|
3438
|
+
end
|
3439
|
+
|
3440
|
+
# The interrupt handler.
|
3441
|
+
program(:ruby,:handler) do
|
3442
|
+
actport irq: irq
|
3443
|
+
outport iak: iak
|
3444
|
+
code "sw_with_os.rb"
|
3445
|
+
end
|
3446
|
+
|
3447
|
+
|
3448
|
+
# Some dummy hardware generating data (counting clocks) and raising an
|
3449
|
+
# interrupt when the data is ready on a software-accessible register.
|
3450
|
+
par(clk.posedge) do
|
3451
|
+
hif(rst) { register <= 0 }
|
3452
|
+
helse do
|
3453
|
+
# Increase the value of the register.
|
3454
|
+
register <= register + 1
|
3455
|
+
# Handle the interrupts.
|
3456
|
+
hif(irq == 0) do
|
3457
|
+
# Is the prevous irq have been processed?
|
3458
|
+
hif (iak == 0) do
|
3459
|
+
# Yes, raise an interrupt.
|
3460
|
+
irq <= 1
|
3461
|
+
end
|
3462
|
+
else
|
3463
|
+
# Wait for the irq to be acknowledge.
|
3464
|
+
if (iak == 1) { irq <= 0 }
|
3465
|
+
end
|
3466
|
+
end
|
3467
|
+
end
|
3468
|
+
end
|
3469
|
+
```
|
3470
|
+
|
3471
|
+
The software code `sw_with_os.rb` is as follows:
|
3472
|
+
|
3473
|
+
```ruby
|
3474
|
+
require 'rubyHDL.rb'
|
3475
|
+
|
3476
|
+
$register = 0
|
3477
|
+
|
3478
|
+
$start = true
|
3479
|
+
|
3480
|
+
# The os simulation function. Initialize the tasks and the communication pipe
|
3481
|
+
# for the first call, then update the value of the $register variable.
|
3482
|
+
def os
|
3483
|
+
if($start) then
|
3484
|
+
# Create the communication pipe.
|
3485
|
+
$pout, $pin = IO.pipe
|
3486
|
+
$tasks = []
|
3487
|
+
# Create the first task reading the values.
|
3488
|
+
$tasks << Thread.new(&Kernel.method(:read_task))
|
3489
|
+
# Create the second task displaying the values.
|
3490
|
+
$tasks << Thread.new(&Kernel.method(:show_task))
|
3491
|
+
$start = false
|
3492
|
+
else
|
3493
|
+
# Update $register.
|
3494
|
+
$register = RubyHDL.register
|
3495
|
+
end
|
3496
|
+
end
|
3497
|
+
|
3498
|
+
# The interrupt handler: acknowledge the interrupt, wakes up the read task.
|
3499
|
+
def handler
|
3500
|
+
RubyHDL.iak = 1
|
3501
|
+
$tasks[0].run
|
3502
|
+
end
|
3503
|
+
|
3504
|
+
# Waits an interrupt: sleep and when waked up tell another interrupt can come.
|
3505
|
+
def wait_irq
|
3506
|
+
sleep
|
3507
|
+
RubyHDL.iak = 0
|
3508
|
+
end
|
3509
|
+
|
3510
|
+
# The reading task: wait an interrupt then get a value and add it to the pipe.
|
3511
|
+
def read_task
|
3512
|
+
loop do
|
3513
|
+
wait_irq
|
3514
|
+
$pin << $register.to_s
|
3515
|
+
end
|
3516
|
+
end
|
3517
|
+
|
3518
|
+
# The displaying task: write in stdout the data obtained from the pipe each
|
3519
|
+
# second.
|
3520
|
+
def show_task
|
3521
|
+
loop do
|
3522
|
+
sleep(1)
|
3523
|
+
puts $pout.readline
|
3524
|
+
end
|
3525
|
+
end
|
3526
|
+
```
|
3527
|
+
|
3528
|
+
|
3529
|
+
### 7.5. Hardware-software co-synthesis
|
3530
|
+
|
3531
|
+
The HDLRuby compiler can be applied to HDLRuby description containing program. However, when producing Verilog HDL or VHDL files, the compiler will ignore the prorgam session. The reason is because the integration of hardware and software is highly target dependent and often licensed. However, you can use the HDLRuby integration of programs using ports for decribing the integration specific to a given target.
|
3532
|
+
|
3533
|
+
As an illustration, let us assume that we want to design an application for a very basic SoC system including a CPU and a FPGA where a set of 8-bit FPGA registers are memory-mapped to the CPU through a set of predefined addresses: input with `fi0` to `fi3` for respective addresses `0xC000` to `0xC009`, and output with `fo0` to `fo3` for respective addresses `0xC010` to `0xC020`.
|
3534
|
+
For this case, the HDLRuby module representing the target system and the corresponding program construct can be written as follows:
|
3535
|
+
|
3536
|
+
```ruby
|
3537
|
+
system :soc_basic do
|
3538
|
+
input :clk
|
3539
|
+
[8].input :fi0, :fi1, :fi2, :fi3
|
3540
|
+
[8].output :fo0, :fo1, :fo2, :fo3
|
3541
|
+
...
|
3542
|
+
|
3543
|
+
program(:c, :my_func) do
|
3544
|
+
actport clk.posedge
|
3545
|
+
outport fi0: fi0, fi1: fi1, fi2: fi2, fi3: fi3
|
3546
|
+
inport fo0: fo0, fo1: fo1, fo2: fo2, fo3: fo3
|
3547
|
+
code "my_soft.c"
|
3548
|
+
end
|
3549
|
+
|
3550
|
+
< Some HDLRuby code describing the hardware part of the application >
|
3551
|
+
end
|
3552
|
+
```
|
3553
|
+
|
3554
|
+
And the C program `my_soft.c` can be written as follows using functions like `read_fo0()` for reading register `fo0` and `write_fi1(val)` for writing to register `fi1`, and calling only once `init_soclib` for initializing the API library:
|
3555
|
+
|
3556
|
+
```c
|
3557
|
+
#include "soclib.h"
|
3558
|
+
|
3559
|
+
void my_func() {
|
3560
|
+
static int start = 1;
|
3561
|
+
if(start) { init_scolib(); start = 0; }
|
3562
|
+
|
3563
|
+
char fo0 = read_fo0();
|
3564
|
+
...
|
3565
|
+
write_fi1(val);
|
3566
|
+
...
|
3567
|
+
}
|
3568
|
+
```
|
3569
|
+
|
3570
|
+
Alternatively, the initialisation can be delegated to another program construct in the HDLRuby code, activated on a reset signal. Then, the respective hardware and software code becomes as follows:
|
3571
|
+
|
3572
|
+
```ruby
|
3573
|
+
system :soc_basic do
|
3574
|
+
input :clk, :rst
|
3575
|
+
[8].input :fi0, :fi1, :fi2, :fi3
|
3576
|
+
[8].output :fo0, :fo1, :fo2, :fo3
|
3577
|
+
...
|
3578
|
+
|
3579
|
+
program(:c, :init_soclib) do
|
3580
|
+
actport rst.posedge
|
3581
|
+
end
|
3582
|
+
|
3583
|
+
program(:c, :my_func) do
|
3584
|
+
actport clk.posedge
|
3585
|
+
outport fi0: fi0, fi1: fi1, fi2: fi2, fi3: fi3
|
3586
|
+
inport fo0: fo0, fo1: fo1, fo2: fo2, fo3: fo3
|
3587
|
+
code "my_soft.c"
|
3588
|
+
end
|
3589
|
+
|
3590
|
+
< Some HDLRuby code describing the hardware part of the application >
|
3591
|
+
end
|
3592
|
+
```
|
3593
|
+
|
3594
|
+
```c
|
3595
|
+
#include "soclib.h"
|
3596
|
+
|
3597
|
+
void my_func() {
|
3598
|
+
char fo0 = read_fo0();
|
3599
|
+
...
|
3600
|
+
write_fi1(val);
|
3601
|
+
...
|
3602
|
+
}
|
3603
|
+
```
|
3604
|
+
|
3605
|
+
|
3606
|
+
Whatever the approach being used for the initialization, the read and write functions are defined in the `soclib.h` file. Two versions of this file are to be provided, one for running with the HDLRuby simulator, and one for running for the target SoC. These two files, with possible additional target constraints files (e.g., xcd file for Xilinx platforms) are the sole additional file that are to be provided by the user to have an application which can be both simulated on the HDLRuby simulator, and synthesized for the target SoC. For the example above, the HDLRuby version of `soclib.h` is as follows:
|
3607
|
+
|
3608
|
+
```c
|
3609
|
+
#include "cHDL.h"
|
3610
|
+
|
3611
|
+
static void *fo0, *fo1, *fo2, *fo3;
|
3612
|
+
static void *fi1, *fi1, *fi2, *fi3;
|
3613
|
+
|
3614
|
+
soclib_init() {
|
3615
|
+
fo0 = c_get_port("fo0");
|
3616
|
+
...
|
3617
|
+
}
|
3618
|
+
|
3619
|
+
|
3620
|
+
#define read_fo0() c_read_port(fo0)
|
3621
|
+
...
|
3622
|
+
#define write_fi1(val) c_write_port(fi1,(val))
|
3623
|
+
...
|
3624
|
+
```
|
3625
|
+
|
3626
|
+
And the SoC specific version could look as follows, where `target_soc_API.h` represents whatever include files required for using the target SoC API:
|
3627
|
+
|
3628
|
+
```c
|
3629
|
+
#include "target_soc_API.h"
|
3630
|
+
|
3631
|
+
soclib_init() {
|
3632
|
+
<Some initialization code if required by the target SoC API>
|
3633
|
+
}
|
3634
|
+
|
3635
|
+
#defined read_fo0() (*0xC010)
|
3636
|
+
...
|
3637
|
+
#define write_fi1(val) (*0xC001 = (val))
|
3638
|
+
...
|
3639
|
+
```
|
3640
|
+
|
3641
|
+
## 8. How to interact with the simulator.
|
3642
|
+
|
3643
|
+
### 8.1. Do-It-Yourself interaction.
|
3644
|
+
|
3645
|
+
Using the program construct presented in the prevous section, it easy add basic interactions to the simulation of your module. For example, you can read and write values from the standard input and map them to HDLRuby signals using the following Ruby program (`stdrw.rb`):
|
3646
|
+
|
3647
|
+
```ruby
|
3648
|
+
require 'RubyHDL'
|
3649
|
+
|
3650
|
+
def stdrw
|
3651
|
+
RubyHDL.sigI = $stdin.read.to_i
|
3652
|
+
$stdout.puts(RubyHDL.sigO)
|
3653
|
+
end
|
3654
|
+
```
|
3655
|
+
|
3656
|
+
Then, a corresponding HDLRuby module that accumulates the read inputs could be writen as follows:
|
3657
|
+
|
3658
|
+
```ruby
|
3659
|
+
system :accum do
|
3660
|
+
inner :clk
|
3661
|
+
[32].inner :sigI, :sigO
|
3662
|
+
|
3663
|
+
program(:ruby,:stdrw) do
|
3664
|
+
actport clk.posedge
|
3665
|
+
outport sigI: sigI
|
3666
|
+
inport sigO: sigO
|
3667
|
+
code "stdrw.rb"
|
3668
|
+
end
|
3669
|
+
|
3670
|
+
(sigO <= sigO+sigI).at(clk.posedge)
|
3671
|
+
|
3672
|
+
timed do
|
3673
|
+
clk <= 0
|
3674
|
+
sigO <= 0
|
3675
|
+
sigI <= 0
|
3676
|
+
repeat(1000) do
|
3677
|
+
!10.ns
|
3678
|
+
clk <= ~clk
|
3679
|
+
end
|
3680
|
+
end
|
3681
|
+
end
|
3682
|
+
```
|
3683
|
+
|
3684
|
+
__Note__: The input method used in the Ruby program requires to input a number with the keyboard, press `<ENTER>` then `<CTRL>-D` for validating it.
|
3685
|
+
|
3686
|
+
Since a Ruby (or C) code can be used for the program construct, more complex interactive interface can be made, for example, you can consult the sample code `with_program_ruby_cpu.rb` which utilizes the `curses` interface for simulating a UART keyboard and CRT monitor.
|
3687
|
+
|
3688
|
+
|
3689
|
+
### 8.2. Using the web browser-based GUI
|
3690
|
+
|
3691
|
+
HDLRuby also provides a construct derivated for the programs for easily building a web browser-based GUI. This GUI is described using a `board` construct as follows:
|
3692
|
+
|
3693
|
+
```ruby
|
3694
|
+
board(:<board name>,<server port>) do
|
3695
|
+
actport <event>
|
3696
|
+
<description of the GUI>
|
3697
|
+
end
|
3698
|
+
```
|
3699
|
+
|
3700
|
+
This construct can be declared within any module, and contains the following elements as given in the code above:
|
3701
|
+
|
3702
|
+
* `board name`: the name of the board.
|
3703
|
+
|
3704
|
+
* `server port`: the web port the GUI can be accessed through, by default, it is `8000`.
|
3705
|
+
|
3706
|
+
* `event`: the event (edge of a signal) indicating when the GUI is synchronized with the simulator. __Note__: the more frequent the event is, the slower the simulation will be.
|
3707
|
+
|
3708
|
+
* `description of the GUI`: a set of statements describing the content of the GUI.
|
3709
|
+
|
3710
|
+
There are two types of statements for describing the GUI: the active ones, that are connected to a HDLRuby signal, and the passive ones that configure the shape of the GUI. The statements of the first type are declared as follows:
|
3711
|
+
|
3712
|
+
```ruby
|
3713
|
+
<element> <element name>: <HDLRuby signal>
|
3714
|
+
```
|
3715
|
+
|
3716
|
+
And the comprise the following:
|
3717
|
+
|
3718
|
+
* `sw`: represents a set of slide switches, their number is set to match the bit-width of the attached signal.
|
3719
|
+
|
3720
|
+
* `bt`: represents a set of push buttons, their number is set to match the bit-width of the attached signal.
|
3721
|
+
|
3722
|
+
* `led`: represents a set of LEDs, their number is set to match the bit-width of the attached signal.
|
3723
|
+
|
3724
|
+
* `hexa`: represents a hexadecimal number display, its character width is set to match the width of the largest possible value of the attached signal.
|
3725
|
+
|
3726
|
+
* `digit`: represents a decimal number display, its character width is set to match the width of the largest possible positive or the smallest possible negative value of the attached signal.
|
3727
|
+
|
3728
|
+
* `scope`: represents an oscilloscope display, the vertical axis represents the value of the attached signal, its range is determined by its data type, and the horizontal axis represents the time is number of synchronization of the GUI.
|
3729
|
+
|
3730
|
+
There is for now only one statement of the second type: `row`. This statement is used without any argument and adds a new row to the GUI for placing components.
|
3731
|
+
|
3732
|
+
For example, we can use a GUI instead of the basic standard input and output for the example given in [the previous section](#8-1-do-it-yourself-interaction). The code could be as follows, for having one slide switch for setting the value to add, and LEDs for displaying the accumulation result (`accum.rb`):
|
3733
|
+
|
3734
|
+
```ruby
|
3735
|
+
system :accum do
|
3736
|
+
inner :clk
|
3737
|
+
[32].inner :sigI, :sigO
|
3738
|
+
|
3739
|
+
board(:boardrw) do
|
3740
|
+
actport clk.posedge
|
3741
|
+
sw sigI: sigI
|
3742
|
+
row
|
3743
|
+
led sigO: sigO
|
3744
|
+
end
|
3745
|
+
|
3746
|
+
(sigO <= sigO+sigI).at(clk.posedge)
|
3747
|
+
|
3748
|
+
timed do
|
3749
|
+
clk <= 0
|
3750
|
+
sigO <= 0
|
3751
|
+
sigI <= 0
|
3752
|
+
repeat(1000) do
|
3753
|
+
!10.ns
|
3754
|
+
clk <= ~clk
|
3755
|
+
end
|
3756
|
+
end
|
3757
|
+
end
|
3758
|
+
```
|
3759
|
+
|
3760
|
+
This code is simulated exactly like any other HDLRuby description, e.g.:
|
3761
|
+
|
3762
|
+
```bash
|
3763
|
+
hdrcc --sim --vcd accum.rb accum
|
3764
|
+
```
|
3765
|
+
|
3766
|
+
However, the simulator will wait until a browser connects to it. For that, you can open a web browser, and go to the local url: `http://localhost:8000`. The simulation will then start and you can interact with the GUI which should look as follows:
|
3767
|
+
|
3768
|
+
<p align="center">
|
3769
|
+
<img src="gui_accum.png" width="80%">
|
3770
|
+
</p>
|
3771
|
+
|
3772
|
+
A more complete example can be found among the HDLRuby samples: `with_board.rb`, which when simulated will use the following GUI:
|
3773
|
+
|
3774
|
+
<p align="center">
|
3775
|
+
<img src="gui_board.png" width="80%">
|
3776
|
+
</p>
|
3777
|
+
|
3778
|
+
|
3779
|
+
## 9. What next?
|
3780
|
+
|
3781
|
+
There are still many aspects of HDLRuby that have not been addressed in this tutorial. For example, finite state machines (FSM) and decoders are crucial hardware components that you should learn about, and HDLRuby provides specific constructs for easier design. So from now on, please consult the main documentation of HDLRuby, and have a look at the code samples provided in the HDLRuby distribution. They can be copied to your working directory using the following command:
|
2887
3782
|
|
2888
3783
|
```bash
|
2889
3784
|
hdrcc --get-samples
|