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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 70c4b4aec90e44d8229a4f13990112c3599b7104e3a867cf87741b2d1e4ff246
4
- data.tar.gz: 10c6957977c1df45cae0f270dd27db75824a92e51dc508b5ffa600ce4654f1a0
3
+ metadata.gz: 01c5cc3826577779dad647fb9c8811d4bec3445bc19ca873f1015990aa689e68
4
+ data.tar.gz: 84985eda69036fe35175cc5255786c53839d7611a98170d2d1d131b3d4c0ec1d
5
5
  SHA512:
6
- metadata.gz: 56b18d61f3945edaaff50244ecb8ae69fa5c29058942c456ca377eb54da83bb64677004dae4047a8f2aae9b4dd2fa57972c8cf6f0fc497bfe9785c447466cfaa
7
- data.tar.gz: 81e58ca1a9ae1b6ba038c5a43c857ebff9e93dd2627befe89706c0200389a5ee18e1d77261e21d13f291b547dc4cf5be2e9024d75d524e2d54d26c37f7943679
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
 
@@ -3067,7 +3067,7 @@ module HDLRuby::High
3067
3067
  end
3068
3068
 
3069
3069
  # Adds the unary operations generation.
3070
- [:"-@",:"@+",:"~", :abs,
3070
+ [:"-@",:"+@",:"~", :abs,
3071
3071
  :boolean, :bit, :signed, :unsigned].each do |operator|
3072
3072
  meth = proc do
3073
3073
  expr = self.to_expr
@@ -3031,7 +3031,8 @@ module HDLRuby::Low
3031
3031
 
3032
3032
  # Add a new code file.
3033
3033
  def add_code(code)
3034
- @codes << code.to_s
3034
+ # @codes << code.to_s
3035
+ @codes << (code.is_a?(Proc) ? code : code.to_s)
3035
3036
  end
3036
3037
 
3037
3038
  # Add a new input port.
@@ -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|