HDLRuby 3.2.0 → 3.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.html +4411 -2930
  3. data/README.md +396 -102
  4. data/ext/hruby_sim/hruby_rcsim_build.c +400 -3
  5. data/ext/hruby_sim/hruby_sim.h +2 -1
  6. data/ext/hruby_sim/hruby_sim_calc.c +1 -1
  7. data/ext/hruby_sim/hruby_sim_core.c +15 -5
  8. data/ext/hruby_sim/hruby_sim_tree_calc.c +1 -1
  9. data/lib/HDLRuby/hdr_samples/c_program/echo.c +33 -0
  10. data/lib/HDLRuby/hdr_samples/ruby_program/echo.rb +9 -0
  11. data/lib/HDLRuby/hdr_samples/ruby_program/stdrw.rb +6 -0
  12. data/lib/HDLRuby/hdr_samples/ruby_program/sw_cpu_terminal.rb +614 -0
  13. data/lib/HDLRuby/hdr_samples/ruby_program/sw_inc_mem.rb +32 -0
  14. data/lib/HDLRuby/hdr_samples/ruby_program/sw_log.rb +33 -0
  15. data/lib/HDLRuby/hdr_samples/with_board.rb +63 -0
  16. data/lib/HDLRuby/hdr_samples/with_clocks.rb +42 -0
  17. data/lib/HDLRuby/hdr_samples/with_of.rb +1 -1
  18. data/lib/HDLRuby/hdr_samples/with_program_c.rb +28 -0
  19. data/lib/HDLRuby/hdr_samples/with_program_ruby.rb +28 -0
  20. data/lib/HDLRuby/hdr_samples/with_program_ruby_cpu.rb +234 -0
  21. data/lib/HDLRuby/hdr_samples/with_program_ruby_io.rb +23 -0
  22. data/lib/HDLRuby/hdr_samples/with_program_ruby_mem.rb +58 -0
  23. data/lib/HDLRuby/hdr_samples/with_program_ruby_threads.rb +56 -0
  24. data/lib/HDLRuby/hdr_samples/with_sequencer_func.rb +2 -4
  25. data/lib/HDLRuby/hdrcc.rb +60 -21
  26. data/lib/HDLRuby/hruby_error.rb +13 -0
  27. data/lib/HDLRuby/hruby_high.rb +50 -7
  28. data/lib/HDLRuby/hruby_low.rb +74 -30
  29. data/lib/HDLRuby/hruby_rcsim.rb +89 -5
  30. data/lib/HDLRuby/std/clocks.rb +118 -50
  31. data/lib/HDLRuby/std/std.rb +5 -0
  32. data/lib/HDLRuby/ui/hruby_board.rb +1079 -0
  33. data/lib/HDLRuby/version.rb +1 -1
  34. data/lib/c/Rakefile +8 -0
  35. data/lib/c/cHDL.h +12 -0
  36. data/lib/c/extconf.rb +7 -0
  37. data/lib/rubyHDL.rb +33 -0
  38. data/tuto/gui_accum.png +0 -0
  39. data/tuto/gui_board.png +0 -0
  40. data/tuto/tutorial_sw.html +4163 -2081
  41. data/tuto/tutorial_sw.md +957 -62
  42. metadata +24 -5
  43. data/README.pdf +0 -0
  44. data/tuto/tutorial_sw.pdf +0 -0
data/tuto/tutorial_sw.md CHANGED
@@ -1,4 +1,4 @@
1
- # HDLRuby Tutorial for Software People
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 underlining 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:
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 aiming 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.
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 followings:
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 in specifying its name and its ports. In HDLRuby this is done as follows:
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? For checking that just type the following command:
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 for being reused. This copy is called an *instance* and the act of copying an *instantiation*. In HDLRuby, an instantiation is done as follows:
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 what to use copies of the previously defined circuit `my_circuit` in a new circuit called `another_circuit` you can do as follows:
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 work 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:
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 seemed 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.
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 followings:
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 storages component are closer to the software variable than the wires are.
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, or 1-bit unsigned values, i.e., the only possible values are 0 and 1.
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 them.
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
- ![hruby_simulator.vcd](the_counter_vcd.png)
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, you have to use `sif` for doing an *if*.
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 that what we have seen up to now, so let us study it progressively:
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 controls the execution of the sequencer (nothing new here).
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 have the following syntax:
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!` do 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.
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
- ![hruby_simulator.vcd](fact_vcd.png)
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 exactly the same result. However, if you try to access `res` or `val` outside the main loop, then an error will be raised.
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
- ![hruby_simulator.vcd](serializer_vcd.png)
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 for forcing the stack to support 64 recursions:
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 create by providing the block that will be applied on each element like in ruby, or executed later using the `seach` method again. E.g., the following sequencer code first sums the bits of signal sig at once, then do it again later with another enumerator previously stored in the Ruby variable `another_enumerator`:
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 for completing.
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 outputs its content one byte per cycle.
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 accessign the signal containing the sort result.
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, lets 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 tells the first one to proceed with the increase:
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
- ![hruby_simulator.vcd](bit_pong_vcd.png)
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
- ![hruby_simulator.vcd](pingpong0_vcd.png)
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
- ![hruby_simulator.vcd](pingpong1_vcd.png)
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 monitors locks processes, no `step` is required, and the simulation result should be:
1821
+ As seen in the example, since the monitor locks processes, no `step` is required, and the simulation result should be:
1806
1822
 
1807
- ![hruby_simulator.vcd](pingpong2_vcd.png)
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 / monitor
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 for obtaining the right `d` input for computing `z`.
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
- ![hruby_simulator.vcd](maxxer_vcd.png)
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 large a combinatorial circuit is, the longer its delays are.
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 choose for yourself, 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.
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 have been already said in the previous sessions. But now it is time to go past the sequencers and dive into real RTL design.
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 has very little (nothing?) to do with any kind of software process.
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
- ![hruby_simulator.vcd](checksum_vcd.png)
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
- ![hruby_simulator.vcd](clock_counter_vcd.png)
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 have 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.
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
- ![hruby_simulator.vcd](alu_vcd.png)
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
- ![hruby_simulator.vcd](ram_vcd.png)
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 remains a few major features for more efficient coding inherited from the Ruby language.
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 support genericity to a certain degree. However, HDLRuby ruby goes further in term of genericity, and also add object-oriented programming, metaprogramming, and reflection concepts to maximize the possibilities of code reuse. From now on, let us detail:
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, but with the addition of parameters that control their content. They are declared as follows:
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
- ![hruby_simulator.vcd](counter_ext_vcd.png)
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 that only one instance has 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:
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
- ![hruby_simulator.vcd](addsub_vcd.png)
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
- And let us assume 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 sake of code readability overriding is only permitted for *named sub-sections* of a module. A named subsection is declared as follows:
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
- ![hruby_simulator.vcd](adder_sat_flags_vcd.png)
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 in 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 process too. Moreover, the possibility to use a chunk of code as a generic argument presented in this section is also metaprogramming.
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. For 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).
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
- 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 which 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:
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