HDLRuby 3.6.2 → 3.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +215 -1
- data/lib/HDLRuby/hdr_samples/ruby_program/with_sw_hruby.rb +68 -0
- data/lib/HDLRuby/hdr_samples/ruby_program/with_sw_hruby_hruby_test.rb +77 -0
- data/lib/HDLRuby/hdr_samples/with_program_ruby.rb +8 -1
- data/lib/HDLRuby/hruby_high.rb +1 -1
- data/lib/HDLRuby/hruby_low.rb +2 -1
- data/lib/HDLRuby/hruby_rcsim.rb +4 -0
- data/lib/HDLRuby/std/sequencer_sw.rb +3513 -0
- data/lib/HDLRuby/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01c5cc3826577779dad647fb9c8811d4bec3445bc19ca873f1015990aa689e68
|
4
|
+
data.tar.gz: 84985eda69036fe35175cc5255786c53839d7611a98170d2d1d131b3d4c0ec1d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 45fbf8ad20c226c543770c85e3b41fd3dabf9fb337830c518544f67b86afd417240e3309575993e58a13de17233b8a197b08e9ca3a7ba7eb9a6e505900b41251
|
7
|
+
data.tar.gz: 8728928e73e6ccd092c5741be652d8f0730c7cf07f04ddbb4af64615b8472df2db50dfa6d0ad4c310c09270f61cc6d3e760fe6d4a87850ca97156ef64e2518ed
|
data/README.md
CHANGED
@@ -17,6 +17,12 @@ hdrcc --get-tuto
|
|
17
17
|
|
18
18
|
__What's new__
|
19
19
|
|
20
|
+
For HDLRuby version 3.7.x:
|
21
|
+
|
22
|
+
* Added the possibility to run [sequencers in software](#sequencers-as-software-code). (WIP)
|
23
|
+
This allows both much faster simulation and the use of the same code for both hardware and software design.
|
24
|
+
|
25
|
+
|
20
26
|
For HDLRuby version 3.6.x:
|
21
27
|
|
22
28
|
* Added a new element for the GUI board that allows to assign an expression to a signal on the fly while simulating.
|
@@ -3215,7 +3221,7 @@ end
|
|
3215
3221
|
## Sequencer (software-like hardware coding):: `std/sequencer.rb`
|
3216
3222
|
<a name="sequencer"></a>
|
3217
3223
|
|
3218
|
-
This library provides a new set of control statements for describing the behavior of a circuit. Behind the curtain, these constructs build a finite state machine where states are deduced from the control points within the description.
|
3224
|
+
This library provides a new set of software-like control statements for describing the behavior of a circuit. Behind the curtain, these constructs build a finite state machine where states are deduced from the control points within the description. Eventhough sequencers are ment to describe hardware, they are software-compatible so that they can efficiently be executed as software programs as explain in section about [software sequencers](#sequencers-as-software-code).
|
3219
3225
|
|
3220
3226
|
A sequencer can be declared anywhere in a system provided it is outside a behavior using the `sequencer` keyword as follows:
|
3221
3227
|
|
@@ -3715,6 +3721,214 @@ __Notes__:
|
|
3715
3721
|
|
3716
3722
|
With the code above, the only restriction is that the signal `stack_overflow` is declared before the function `fact` is called.
|
3717
3723
|
|
3724
|
+
### Sequencers as Software Code
|
3725
|
+
|
3726
|
+
#### Introduction to Sequencer as Software Code
|
3727
|
+
|
3728
|
+
Sequencers can be executed in software using a Ruby interpreter while maintaining functional equivalence with the hardware implementation. To achieve this, the following headers must be added to your Ruby source code:
|
3729
|
+
|
3730
|
+
```ruby
|
3731
|
+
require 'HDLRuby/std/sequencer_sw'
|
3732
|
+
include RubyHDL::High
|
3733
|
+
using RubyHDL::High
|
3734
|
+
```
|
3735
|
+
|
3736
|
+
After this, signals and sequencers can be described exactly like in HDLRuby. However, the resulting sequencer objects are not executed immediately and must be stored in a variable for further reference. For example, the following Ruby code defines a sequencer, referred by the variable `my_seq`, which increments the signal `counter` up to 1000:
|
3737
|
+
|
3738
|
+
```ruby
|
3739
|
+
require 'HDLRuby/std/sequencer_sw'
|
3740
|
+
include RubyHDL::High
|
3741
|
+
using RubyHDL::High
|
3742
|
+
|
3743
|
+
[32].inner :counter
|
3744
|
+
|
3745
|
+
my_seq = sequencer do
|
3746
|
+
counter <= 0
|
3747
|
+
1000.stimes do
|
3748
|
+
counter <= counter + 1
|
3749
|
+
end
|
3750
|
+
end
|
3751
|
+
```
|
3752
|
+
|
3753
|
+
You may notice that no clock or start signal is provided to the sequencer. This is because, in software, execution is sequential, and no clock nor control signals are required. Instead, starting the execution of a sequencer is done using the call operators as follows: `my_seq.()`.
|
3754
|
+
|
3755
|
+
To verify whether the sequencer executed correctly, you can access signal values outside the sequencer using the `value` method. For example, the following code initializes `counter` to 0 and then displays the counter value after executing the sequencer.
|
3756
|
+
|
3757
|
+
```ruby
|
3758
|
+
require 'HDLRuby/std/sequencer_sw'
|
3759
|
+
include RubyHDL::High
|
3760
|
+
using RubyHDL::High
|
3761
|
+
|
3762
|
+
[32].inner :counter
|
3763
|
+
|
3764
|
+
counter.value = 0
|
3765
|
+
|
3766
|
+
my_seq = sequencer do
|
3767
|
+
counter <= 0
|
3768
|
+
1000.stimes do
|
3769
|
+
counter <= counter + 1
|
3770
|
+
end
|
3771
|
+
end
|
3772
|
+
|
3773
|
+
my_seq.()
|
3774
|
+
|
3775
|
+
puts "counter=#{counter.value}"
|
3776
|
+
```
|
3777
|
+
|
3778
|
+
__Note__: When displaying the value of a signal, the `.value` method can be omitted. For example, in the code above, the final statement can also be written as:
|
3779
|
+
|
3780
|
+
```ruby
|
3781
|
+
puts "counter=#{counter}"
|
3782
|
+
```
|
3783
|
+
|
3784
|
+
Internally, the HDLRuby code of a sequencer is converted to Ruby before execution. This code can be accessed through the `source` command. It can then be saved into a file for separate execution for example, as follows:
|
3785
|
+
|
3786
|
+
```ruby
|
3787
|
+
File.open("sequencer_in_ruby.rb","w") do |f|
|
3788
|
+
f << my_seq.source
|
3789
|
+
end
|
3790
|
+
```
|
3791
|
+
|
3792
|
+
__Note__: the ruby code for sequencers is compatible with mruby for execution on embedded systems.
|
3793
|
+
|
3794
|
+
|
3795
|
+
#### Why Would I Want to Execute a Sequencer in Software, and What are the Limitations?
|
3796
|
+
|
3797
|
+
There are two main reasons for executing sequencers in software:
|
3798
|
+
|
3799
|
+
1. High-speed simulation: Sequencers executed in software run approximately 10 times faster than in the HDLRuby simulator.
|
3800
|
+
|
3801
|
+
2. Seamless transition from software to hardware: During the early design stages, it may not be clear whether a given part will be implemented in software and hardware. Using the same code for both ensures:
|
3802
|
+
|
3803
|
+
* Reliability (guaranteed functional equivalence).
|
3804
|
+
|
3805
|
+
* Reduced design time (no need for recoding).
|
3806
|
+
|
3807
|
+
While software sequencer are functionally equivalent to their hardware implementations, their handling of time and parallelism is fundamentally different. In hardware, sequencers are implemented as finite state machines that transition according to a clock and that run in parallel with the remain of the circuits. By contrast, in software, sequencers are implemented as fibers executed sequentially. If parallel synchronization is important in your design (e.g., a communication protocol), software sequencers may not be useful. However, there are ways to add hardware timing and parallelism as described in the following sections.
|
3808
|
+
|
3809
|
+
|
3810
|
+
#### Adding a Clock to a Software Sequencer.
|
3811
|
+
|
3812
|
+
As mentioned earlier, there is no clock in software. However, it is possible to simulate one while executing a sequencer to estimate its performance when implemented in hardware. This is done by passing as argument a signal that will be increase at each estimated clock cycle as follows:
|
3813
|
+
|
3814
|
+
```ruby
|
3815
|
+
sequencer(<clock counting signal>) do
|
3816
|
+
...
|
3817
|
+
end
|
3818
|
+
```
|
3819
|
+
|
3820
|
+
After the execution of a sequencer with a clock, the estimated number of clock cycles required for the hardware implementation is stored into the clock signal. For example, the following code will display `1000 clocks`, which is the estimated number of executed clocks if the sequencer were implemented in hardware:
|
3821
|
+
|
3822
|
+
```ruby
|
3823
|
+
[32].inner :clk_count
|
3824
|
+
clk_count.value = 0
|
3825
|
+
|
3826
|
+
sequencer(clk_count) do
|
3827
|
+
1000.stimes
|
3828
|
+
end.()
|
3829
|
+
|
3830
|
+
puts "#{clk_count} clocks"
|
3831
|
+
```
|
3832
|
+
|
3833
|
+
__Note__: In the code above, the sequencer is not stored in a variable because it is executed immediately upon declaration.
|
3834
|
+
|
3835
|
+
#### Adding a Signal to Control the Execution of a Software Sequencer.
|
3836
|
+
|
3837
|
+
In addition to a clock counter signal, you can add a signal that, when set to 1, starts the execution of a software sequencer, just like in the hardware implementation. To achieve this, pass the signal as second argument of the sequencer. For example the following code, starts the execution of the sequencer using signal `start`:
|
3838
|
+
|
3839
|
+
```ruby
|
3840
|
+
[32].inner :clk_count
|
3841
|
+
[1].inner :start
|
3842
|
+
clk_count.value = 0
|
3843
|
+
|
3844
|
+
sequencer(clk_count,start) do
|
3845
|
+
1000.stimes
|
3846
|
+
end
|
3847
|
+
|
3848
|
+
start.value = 1
|
3849
|
+
|
3850
|
+
puts "#{clk_count} clocks"
|
3851
|
+
```
|
3852
|
+
|
3853
|
+
With this alternative execution method, storing the sequencer in a Ruby variable is no longer necessary. The execution can be started exactly as in hardware, and also from another sequencer. For example, in the following code, the execution of the second sequencer is controlled by the first one.
|
3854
|
+
|
3855
|
+
```ruby
|
3856
|
+
[1].inner :start0, :start1
|
3857
|
+
[8].inner :count0, :count1
|
3858
|
+
|
3859
|
+
sequencer(nil,start0) do
|
3860
|
+
count0 <= 0
|
3861
|
+
swhile(count0<100) { count0 <= count0 + 1 }
|
3862
|
+
start1 <= 1
|
3863
|
+
end
|
3864
|
+
|
3865
|
+
sequencer(nil,start1) do
|
3866
|
+
count1 <= 0
|
3867
|
+
swhile(count1<100) { count1 <= count1 + 1 }
|
3868
|
+
end
|
3869
|
+
```
|
3870
|
+
|
3871
|
+
#### Synchronizing Sequencers for Pseudo-Parallel Execution
|
3872
|
+
|
3873
|
+
While software sequencers normally execute to completion before any other code runs, they can be interrupted using the `sync` command. This command does not correspond to anything in hardware but can be used to simulate parallel execution of multiple sequencers.
|
3874
|
+
|
3875
|
+
When a `sync` command is encountered during execution, the sequencer is paused, and execution resumes with the code that follows the sequencer’s start. The paused sequencer can then be resumed using the call operator or by setting its start signal to 1.
|
3876
|
+
|
3877
|
+
For example, in the following code, the sequencer starts execution, then prints "stop at count=20" before resuming execution, and finally prints "end at count=40":
|
3878
|
+
|
3879
|
+
```ruby
|
3880
|
+
|
3881
|
+
[32].inner :count
|
3882
|
+
|
3883
|
+
my_seq = sequencer do
|
3884
|
+
count <= 0
|
3885
|
+
20.stimes
|
3886
|
+
count <= count + 1
|
3887
|
+
sync
|
3888
|
+
20.stimes
|
3889
|
+
count <= count + 1
|
3890
|
+
end
|
3891
|
+
end
|
3892
|
+
|
3893
|
+
my_seq.()
|
3894
|
+
puts "stop at count=#{count}"
|
3895
|
+
my_seq.()
|
3896
|
+
puts "end at count=#{count}"
|
3897
|
+
```
|
3898
|
+
|
3899
|
+
For full cycle-accurate synchronization, insert a sync command at each estimated cycle. However, sync has a significant performance cost, and depending on the Ruby interpreter and software configuration, excessive use may make execution slower than the HDLRuby hardware simulator. Hence it is recommended to use this command only when necessary, and use the HDLRuby hardware simulator for cycle-accurate synchronization.
|
3900
|
+
|
3901
|
+
Finally, to determine whether a sequencer has completed execution or is paused at sync command, use the `alive?` method. For example, the following code will resume execution of sequencer `my_seq` until it completes:
|
3902
|
+
|
3903
|
+
```ruby
|
3904
|
+
my_seq.() while(my_seq.alive?)
|
3905
|
+
```
|
3906
|
+
|
3907
|
+
#### Executing ruby code within a software sequencer.
|
3908
|
+
|
3909
|
+
When executing a sequencer in software, an additional command, `ruby`, is available for running plain Ruby code. For example, the following displays "Hello" ten times using the puts method:
|
3910
|
+
|
3911
|
+
|
3912
|
+
```ruby
|
3913
|
+
sequencer do
|
3914
|
+
stimes.10 do
|
3915
|
+
ruby { puts "Hello" }
|
3916
|
+
end
|
3917
|
+
end.()
|
3918
|
+
```
|
3919
|
+
|
3920
|
+
Another possibility is to put the code into a string as follows:
|
3921
|
+
|
3922
|
+
```
|
3923
|
+
sequencer do
|
3924
|
+
stimes.10 do
|
3925
|
+
ruby('puts "Hello"')
|
3926
|
+
end
|
3927
|
+
end.()
|
3928
|
+
```
|
3929
|
+
|
3930
|
+
Both method are functionally equivalent. However, the first is faster and safer but is incompatible with separated code generation, while the second allows separate code generation but is slower and less safe.
|
3931
|
+
|
3718
3932
|
|
3719
3933
|
## Fixed-point (fixpoint): `std/fixpoint.rb`
|
3720
3934
|
<a name="fixpoint"></a>
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# Ruby program for testing SW HDLRuby.
|
2
|
+
|
3
|
+
require 'HDLRuby/std/sequencer_sw'
|
4
|
+
|
5
|
+
include RubyHDL::High
|
6
|
+
using RubyHDL::High
|
7
|
+
|
8
|
+
[32].inner :clk
|
9
|
+
clk.value = 0
|
10
|
+
|
11
|
+
[32].inner :a,:b,:c,:i
|
12
|
+
[32].inner :d
|
13
|
+
bit[32][-4].inner :ar
|
14
|
+
[32].inner :res0, :res1
|
15
|
+
|
16
|
+
some_ruby_value = 1
|
17
|
+
|
18
|
+
|
19
|
+
prog0 = sequencer(clk) do
|
20
|
+
a <= 1
|
21
|
+
b <= 2
|
22
|
+
# c <= ruby { some_ruby_value }
|
23
|
+
c <= ruby("some_ruby_value")
|
24
|
+
d <= 0
|
25
|
+
i <= 0
|
26
|
+
# swhile(c<10000000) do
|
27
|
+
# 10000000.stimes do
|
28
|
+
sfor(0..10000000) do |u|
|
29
|
+
c <= a + b + d
|
30
|
+
d <= c + 1
|
31
|
+
# ar[i%4] <= i
|
32
|
+
ar[u%4] <= i
|
33
|
+
sif(i<1000) { i <= i + 1 }
|
34
|
+
selse { i <= 0 }
|
35
|
+
sync
|
36
|
+
end
|
37
|
+
a[4] <= 1
|
38
|
+
b[7..5] <= 5
|
39
|
+
res0 <= ar[0]
|
40
|
+
end
|
41
|
+
|
42
|
+
puts "Generating file from prog0 source code..."
|
43
|
+
File.open("with_sw_hruby_mruby.rb","w") do |f|
|
44
|
+
f << prog0.source
|
45
|
+
end
|
46
|
+
|
47
|
+
prog1 = sequencer do
|
48
|
+
sloop do
|
49
|
+
res1 <= ar[1]
|
50
|
+
sync
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
puts "Executing concurrently prog0 and prog1..."
|
55
|
+
|
56
|
+
while prog0.alive? do
|
57
|
+
prog0.call
|
58
|
+
prog1.call
|
59
|
+
end
|
60
|
+
|
61
|
+
puts "a=#{a}"
|
62
|
+
puts "b=#{b}"
|
63
|
+
puts "c=#{c}"
|
64
|
+
puts "d=#{d}"
|
65
|
+
puts "ar=#{ar}"
|
66
|
+
puts "res0=#{res0}"
|
67
|
+
puts "res1=#{res1}"
|
68
|
+
puts "clk=#{clk}"
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# Ruby program for testing SW HDLRuby.
|
2
|
+
|
3
|
+
# require 'HDLRuby/std/sequencer_sw'
|
4
|
+
|
5
|
+
# include RubyHDL::High
|
6
|
+
# using RubyHDL::High
|
7
|
+
|
8
|
+
system :test_with_sw_ruby do
|
9
|
+
|
10
|
+
[32].inner :a,:b,:c,:i
|
11
|
+
[32].inner :d, :e
|
12
|
+
bit[32][-4].inner :ar
|
13
|
+
[32].inner :res0, :res1
|
14
|
+
|
15
|
+
inner :clk,:start
|
16
|
+
|
17
|
+
|
18
|
+
sequencer(clk,start) do
|
19
|
+
a <= 1
|
20
|
+
b <= 2
|
21
|
+
c <= 0
|
22
|
+
d <= 0
|
23
|
+
i <= 0
|
24
|
+
e <= 0
|
25
|
+
# swhile(c<10000000) do
|
26
|
+
# 10000000.stimes do
|
27
|
+
sfor(0..10000000) do |u|
|
28
|
+
c <= a + b + d
|
29
|
+
d <= c + 1
|
30
|
+
# ar[i%4] <= i
|
31
|
+
ar[u%4] <= i
|
32
|
+
sif(i<1000) { i <= i + 1 }
|
33
|
+
selse { i <= 0 }
|
34
|
+
end
|
35
|
+
a[4] <= 1
|
36
|
+
b[7..5] <= 5
|
37
|
+
res0 <= ar[0]
|
38
|
+
e <= 1
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
sequencer(clk,start) do
|
43
|
+
sloop do
|
44
|
+
res1 <= ar[1]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
timed do
|
50
|
+
clk <= 0
|
51
|
+
start <= 0
|
52
|
+
!10.ns
|
53
|
+
clk <= 1
|
54
|
+
!10.ns
|
55
|
+
clk <= 0
|
56
|
+
start <= 1
|
57
|
+
!10.ns
|
58
|
+
clk <= 1
|
59
|
+
!10.ns
|
60
|
+
clk <= 0
|
61
|
+
start <= 0
|
62
|
+
!10.ns
|
63
|
+
clk <= 1
|
64
|
+
!10.ns
|
65
|
+
repeat(100000000) do
|
66
|
+
clk <= ~clk
|
67
|
+
!10.ns
|
68
|
+
hif(e == 1) do
|
69
|
+
hprint("c=",c,"\n")
|
70
|
+
hprint("res0=",res0,"\n")
|
71
|
+
hprint("res1=",res1,"\n")
|
72
|
+
terminate
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -8,7 +8,14 @@ system :with_ruby_prog do
|
|
8
8
|
actport clk.posedge
|
9
9
|
inport inP: count
|
10
10
|
outport outP: echo
|
11
|
-
code "ruby_program/echo.rb"
|
11
|
+
# code "ruby_program/echo.rb"
|
12
|
+
code(proc do
|
13
|
+
def echo
|
14
|
+
val = RubyHDL.inP
|
15
|
+
puts "Echoing: #{val}"
|
16
|
+
RubyHDL.outP = val
|
17
|
+
end
|
18
|
+
end)
|
12
19
|
end
|
13
20
|
|
14
21
|
|
data/lib/HDLRuby/hruby_high.rb
CHANGED
@@ -3067,7 +3067,7 @@ module HDLRuby::High
|
|
3067
3067
|
end
|
3068
3068
|
|
3069
3069
|
# Adds the unary operations generation.
|
3070
|
-
[:"-@",:"
|
3070
|
+
[:"-@",:"+@",:"~", :abs,
|
3071
3071
|
:boolean, :bit, :signed, :unsigned].each do |operator|
|
3072
3072
|
meth = proc do
|
3073
3073
|
expr = self.to_expr
|
data/lib/HDLRuby/hruby_low.rb
CHANGED
data/lib/HDLRuby/hruby_rcsim.rb
CHANGED
@@ -558,7 +558,11 @@ module HDLRuby::High
|
|
558
558
|
if self.language == :ruby then
|
559
559
|
# Loads the code files.
|
560
560
|
self.each_code do |code|
|
561
|
+
if code.is_a?(Proc)
|
562
|
+
TOPLEVEL_BINDING.eval(&code)
|
563
|
+
else
|
561
564
|
Kernel.require("./"+code.to_s)
|
565
|
+
end
|
562
566
|
end
|
563
567
|
# Add the input ports.
|
564
568
|
self.each_inport do |sym, sig|
|