HDLRuby 3.2.0 → 3.3.0

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.html +2330 -2670
  3. data/README.md +391 -101
  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 +2263 -1890
  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
@@ -0,0 +1,1079 @@
1
+ require 'socket'
2
+
3
+ require 'rubyHDL'
4
+
5
+ # PCB Colors RGB: 42 100 36, 34 83 20, 106 155 108,
6
+
7
+
8
+ module HDLRuby::High::Std
9
+
10
+ ##
11
+ # Standard HDLRuby::High library:
12
+ # The idea is to be able to write sw-like sequential code.
13
+ #
14
+ ########################################################################
15
+
16
+
17
+ ## Class describing a board.
18
+ class Board
19
+
20
+ include Hmissing
21
+
22
+ attr_reader :namespace
23
+
24
+ ## Class describing a row of slide switches.
25
+ SW = Struct.new(:id, :size, :hwrite) do
26
+ def to_html
27
+ return "<div class=\"swset\" id=\"#{self.id}\" data-value=\"0\">\\n" +
28
+ '<span class="name">' + self.hwrite.to_s.chop + '</span>' +
29
+ '<span>&nbsp;&nbsp;</span>' +
30
+ self.size.times.map do |i|
31
+ '<label class="sw"><input type="checkbox" data-bit="' +
32
+ (self.size-i-1).to_s + '" ' +
33
+ 'onchange="sw_change(this)">' +
34
+ '<span class="slider"></span></label>\n'
35
+ end.join + "</div>\\n"
36
+ end
37
+ end
38
+
39
+ ## Class describing a row of buttons.
40
+ BT = Struct.new(:id, :size, :hwrite) do
41
+ def to_html
42
+ return "<div class=\"btset\" id=\"#{self.id}\" data-value=\"0\">\\n" +
43
+ '<span class="name">' + self.hwrite.to_s.chop + '</span>' +
44
+ '<span>&nbsp;&nbsp;</span>' +
45
+ self.size.times.map do |i|
46
+ '<button class="bt" data-bit="' +
47
+ (self.size-i-1).to_s + '" ' +
48
+ 'onmousedown="bt_click(this)" onmouseup="bt_release(this)" onmouseleave="bt_release(this)"><i class="bt_off"></i></button>\n'
49
+ end.join + "</div>\\n"
50
+ end
51
+ end
52
+
53
+ ## Class describing a row of LEDs.
54
+ LED = Struct.new(:id, :size, :hread) do
55
+ def to_html
56
+ return "<div class=\"ledset\" id=\"#{self.id}\" data-value=\"0\">\\n" +
57
+ '<span class="name">' + self.hread.to_s + '</span>' +
58
+ '<span>&nbsp;&nbsp;</span>' +
59
+ self.size.times.map do |i|
60
+ '<i class="led" class="led_off"></i>\n'
61
+ end.join + "</div>\\n"
62
+ end
63
+ end
64
+
65
+ ## Class describing a digit display.
66
+ DIGIT = Struct.new(:id, :size, :hread) do
67
+ def to_html
68
+ return '<div class="digitset" id=' + self.id.to_s +
69
+ ' data-width="' + self.size.to_s + '" data-value="0" >' +
70
+ '<span class="name">' + self.hread.to_s + '</span>' +
71
+ '<span>&nbsp;&nbsp;</span>' +
72
+ '<span class="matrix">' + "&nbsp;"*self.size + '</span>' +
73
+ "</div>\\n"
74
+ end
75
+ end
76
+
77
+ ## Class describing an hexadecimal display.
78
+ HEXA = Struct.new(:id, :size, :hread) do
79
+ def to_html
80
+ return "<div class=\"hexaset\" id=\"#{self.id}\" " +
81
+ "data-width=\"#{self.size}\" data-value=\"0\">" +
82
+ '<span class="name">' + self.hread.to_s + '</span>' +
83
+ '<span>&nbsp;&nbsp;</span>' +
84
+ "<span class=\"matrix\">#{"0"*self.size}</span>" + "</div>\\n"
85
+ end
86
+ end
87
+
88
+ ## Class describing an oscilloscope.
89
+ SCOPE = Struct.new(:id, :min, :max, :hread) do
90
+ def to_html
91
+ # Prepare the min, max and blank strings.
92
+ min = self.min.to_s
93
+ max = self.max.to_s
94
+ blank = (min.size > max.size) ? min : max;
95
+ # Generate the html.
96
+ return '<div>' +
97
+ '<div class="hdiv"><div class="r-blank">' + blank + '</div>' + # just for adjusting the position with the scope.
98
+ '<div class="scopetitle">' +self.hread.to_s + '</div>\\n' +
99
+ "</div>\\n" +
100
+ '<div class="scopepane">' +
101
+ '<div class="hdiv">\\n' +
102
+ '<div class="y-range">' +
103
+ '<div class="u-value">' + max + '</div>' +
104
+ '<div class="d-value">' + min + '</div></div>' +
105
+ '<div class="scope" id=' + self.id.to_s +
106
+ ' data-min="' + min + '" data-max="' + max + '"' +
107
+ ' data-pos="0" data-previous="0" data-value="0">' +
108
+ '<canvas class="screen"></canvas>' +
109
+ "</div>\\n" +
110
+ "</div>\\n" +
111
+ '<div class="hdiv"><div class="r-blank">' + blank + '</div>' + # just for adjusting the position with the scope.
112
+ '<div class="x-range">' +
113
+ '<div class="l-value">' + "&nbsp;0&nbsp;" + '</div>' +
114
+ '<div class="r-value">' + "100" + '</div></div>' +
115
+ "</div>\\n" +
116
+ "</div>\\n" +
117
+ "</div>\\n" +
118
+ "</div>\\n"
119
+ end
120
+ end
121
+
122
+
123
+ ## Class describing an ascii display.
124
+ ASCII = Struct.new(:id, :size, :hread)
125
+
126
+ ## Class describing a bitmap display with keyboard input supported
127
+ BITMAP = Struct.new(:id, :width, :height, :hread)
128
+
129
+ ## Class describing a new panel row.
130
+ PROW = Struct.new(:id) do
131
+ def to_html
132
+ return '<div class="prow" id="' + self.id.to_s + '"></div>';
133
+ end
134
+ end
135
+
136
+ ## The base HTML/Javascript/Ajax code of the FPGA UI:
137
+ # header
138
+ UI_header = <<-HTMLHEADER
139
+ HTTP/1.1 200
140
+ Content-Type: text/html
141
+
142
+ <!DOCTYPE html>
143
+ <html>
144
+ <head>
145
+ <meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1">
146
+ <style>
147
+
148
+ html {
149
+ background-color: #2a6424;
150
+ }
151
+
152
+ #header {
153
+ width: 100%;
154
+ /* border: solid 2px white; */
155
+ text-align: center;
156
+ }
157
+
158
+ .panel {
159
+ width: 100%;
160
+ height: 100%;
161
+ display: flex;
162
+ flex-direction: column;
163
+ justify-content: center;
164
+ align-content: flex-start;
165
+ gap: 20px;
166
+ }
167
+
168
+ .prow {
169
+ width: 100%;
170
+ display: flex;
171
+ flex-direction: row;
172
+ justify-content: center;
173
+ gap: 15px;
174
+ }
175
+
176
+ .title {
177
+ font-size: 36px;
178
+ color: #d0d0d0;
179
+ /* color: #2a6424; */
180
+ /* color: white; */
181
+ /* text-shadow: -1px -1px 1px white, 1px 1px 1px #101010; */
182
+ text-shadow: -1px -1px 1px #63c359, 1px 1px 1px #153212;
183
+ background-color: #265a20;
184
+ box-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
185
+ -moz-box-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
186
+ -webkit-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
187
+ padding: 4px;
188
+ padding-left: 16px;
189
+ padding-right: 16px;
190
+ display: inline-block;
191
+ }
192
+
193
+ .name {
194
+ font-size: 20px;
195
+ font-family: "Lucida Console", "Courier New", monospace;
196
+ color: white;
197
+ background-color: #265a20;
198
+ box-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
199
+ -moz-box-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
200
+ -webkit-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
201
+ padding-left: 8px;
202
+ padding-right: 8px;
203
+ /* display: table-cell;
204
+ vertical-align: middle; */
205
+ height: 28px;
206
+ line-height: 28px;
207
+ }
208
+
209
+ .vl {
210
+ /* border-left: 10px solid #2a6424; */
211
+ color: #2a6424;
212
+ box-shadow: -1px -1px 1px #63c359, 1px 1px 1px #153212;
213
+ -moz-box-shadow: -1px -1px 1px #63c359, 1px 1px 1px #153212;
214
+ -webkit-shadow: -1px -1px 1px #63c359, 1px 1px 1px #153212;
215
+ width: 10px;
216
+ }
217
+
218
+ .hdiv {
219
+ display: flex;
220
+ flex-direction: row;
221
+ align-item: stretch;
222
+ }
223
+
224
+ .vdiv {
225
+ display: flex;
226
+ flex-direction: column;
227
+ justify-content: stretch;
228
+ }
229
+
230
+ .swset {
231
+ display: flex;
232
+ flex-direction: row;
233
+ justify-content: center;
234
+ align-items: center;
235
+ margin-left: 8px;
236
+ margin-right: 8px;
237
+ height: 40px;
238
+ }
239
+
240
+ .btset {
241
+ display: flex;
242
+ flex-direction: row;
243
+ justify-content: center;
244
+ align-items: center;
245
+ margin-left: 8px;
246
+ margin-right: 8px;
247
+ height: 40px;
248
+ }
249
+
250
+ .ledset {
251
+ display: flex;
252
+ flex-direction: row;
253
+ justify-content: center;
254
+ align-items: center;
255
+ margin-left: 8px;
256
+ margin-right: 8px;
257
+ height: 40px;
258
+ }
259
+
260
+ .digitset {
261
+ display: flex;
262
+ flex-direction: row;
263
+ justify-content: center;
264
+ align-items: center;
265
+ margin-left: 8px;
266
+ margin-right: 8px;
267
+ height: 40px;
268
+ }
269
+
270
+ .hexaset {
271
+ display: flex;
272
+ flex-direction: row;
273
+ justify-content: center;
274
+ align-items: center;
275
+ margin-left: 8px;
276
+ margin-right: 8px;
277
+ height: 40px;
278
+ }
279
+
280
+ .scopetitle {
281
+ font-size: 20px;
282
+ font-family: "Lucida Console", "Courier New", monospace;
283
+ color: white;
284
+ background-color: #265a20;
285
+ box-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
286
+ -moz-box-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
287
+ -webkit-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
288
+ display: inline-block;
289
+ width: 30vw;
290
+ height: 28px;
291
+ line-height: 28px;
292
+ margin-right: auto;
293
+ margin-bottom: 4px;
294
+ text-align:center;
295
+ }
296
+
297
+ .scopepane {
298
+ background-color: #ccc;
299
+ border: solid 2px #505050;
300
+ box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
301
+ -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
302
+ -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
303
+ padding: 10px;
304
+ width: max-content;
305
+ margin-left: auto;
306
+ margin-right: auto;
307
+ }
308
+
309
+ .y-range {
310
+ margin-left: auto;
311
+ /* border: solid 2px white; */
312
+ display: flex;
313
+ flex-direction: column;
314
+ }
315
+
316
+ .x-range {
317
+ width: 30vw;
318
+ /* border: solid 2px white; */
319
+ display: flex;
320
+ flex-direction: row;
321
+ margin-right: auto;
322
+ margin-left: 4px;
323
+ margin-top: 4px;
324
+ }
325
+
326
+ .u-value {
327
+ font-size: 16px;
328
+ font-family: "Lucida Console", "Courier New", monospace;
329
+ font-weight: bold;
330
+ color: black;
331
+ background-color: #ddd;
332
+ box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
333
+ -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
334
+ -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
335
+ padding-left: 8px;
336
+ padding-right: 8px;
337
+ text-align:center;
338
+ }
339
+
340
+ .d-value {
341
+ font-size: 16px;
342
+ font-family: "Lucida Console", "Courier New", monospace;
343
+ font-weight: bold;
344
+ color: black;
345
+ background-color: #ddd;
346
+ box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
347
+ -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
348
+ -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
349
+ padding-left: 8px;
350
+ padding-right: 8px;
351
+ margin-top: auto;
352
+ text-align:center;
353
+ }
354
+
355
+ .l-value {
356
+ font-size: 16px;
357
+ font-family: "Lucida Console", "Courier New", monospace;
358
+ font-weight: bold;
359
+ color: black;
360
+ background-color: #ddd;
361
+ box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
362
+ -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
363
+ -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
364
+ padding-left: 8px;
365
+ padding-right: 8px;
366
+ height: fit-content;
367
+ }
368
+
369
+ .r-value {
370
+ font-size: 16px;
371
+ font-family: "Lucida Console", "Courier New", monospace;
372
+ font-weight: bold;
373
+ color: black;
374
+ background-color: #ddd;
375
+ box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
376
+ -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
377
+ -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
378
+ padding-left: 8px;
379
+ padding-right: 8px;
380
+ margin-left: auto;
381
+ height: fit-content;
382
+ }
383
+
384
+ .r-blank {
385
+ font-size: 20px;
386
+ color: rgba(0,0,0,0);
387
+ border: solid 1px rgba(0,0,0,0);
388
+ padding-left: 8px;
389
+ padding-right: 8px;
390
+ margin-left: auto;
391
+ height: fit-content;
392
+ }
393
+
394
+ .scope {
395
+ width: 30vw;
396
+ height: 30vw;
397
+ margin-right: auto;
398
+ border: solid 2px #505050;
399
+ box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
400
+ -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
401
+ -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
402
+ }
403
+
404
+
405
+ .screen {
406
+ object-fit:contain;
407
+ color: yellow;
408
+ background-color: black;
409
+ /* border: solid 2px #505050;
410
+ box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
411
+ -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
412
+ -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010; */
413
+ }
414
+
415
+ .sw {
416
+ position: relative;
417
+ display: inline-block;
418
+ width: 24px;
419
+ height: 40px;
420
+ margin: 2px;
421
+ box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
422
+ -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
423
+ -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
424
+ }
425
+
426
+ .sw input {
427
+ opacity: 0;
428
+ width: 0;
429
+ height: 0;
430
+ }
431
+
432
+ .slider {
433
+ position: absolute;
434
+ cursor: pointer;
435
+ top: 0;
436
+ left: 0;
437
+ right: 0;
438
+ bottom: 0;
439
+ background-color: #ccc;
440
+ -webkit-transition: .2s;
441
+ transition: .2s;
442
+ border: solid 2px #505050;
443
+ }
444
+
445
+ .slider:before {
446
+ position: absolute;
447
+ content: "";
448
+ height: 16px;
449
+ width: 16px;
450
+ left: 2px;
451
+ bottom: 2px;
452
+ background-color: black;
453
+ -webkit-transition: .2s;
454
+ transition: .2s;
455
+ }
456
+
457
+ input:checked + .slider {
458
+ background-color: yellow;
459
+ }
460
+
461
+ input:checked + .slider:before {
462
+ -webkit-transform: translateY(-16px);
463
+ -ms-transform: translateY(-16px);
464
+ transform: translateY(-16px);
465
+ }
466
+
467
+ .matrix {
468
+ font-size: 26px;
469
+ font-family: "Lucida Console", "Courier New", monospace;
470
+ color: yellow;
471
+ background-color: black;
472
+ border: solid 2px #505050;
473
+ box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
474
+ -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
475
+ -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
476
+ }
477
+
478
+ .bt {
479
+ background-color: #ccc;
480
+ border: solid 2px #505050;
481
+ box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
482
+ -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
483
+ -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
484
+ }
485
+
486
+ .bt:hover {
487
+ background-color: #aaa;
488
+ }
489
+
490
+ .bt_off {
491
+ height: 20px;
492
+ width: 20px;
493
+ border: solid 1px #505050;
494
+ border-radius: 50%;
495
+ background: linear-gradient(to bottom right, #A0A0A0, black 60%);
496
+ display: inline-block;
497
+ margin-top: 1px;
498
+ box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
499
+ -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
500
+ -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
501
+ }
502
+
503
+ .bt_on {
504
+ height: 20px;
505
+ width: 20px;
506
+ border: solid 1px #505050;
507
+ border-radius: 50%;
508
+ background: linear-gradient(to top left, #A0A0A0, black 80%);
509
+ display: inline-block;
510
+ margin-top: 3px;
511
+ margin-bottom: -2px;
512
+ box-shadow: -1px -1px 1px #101010, 1px 1px 1px white;
513
+ -moz-box-shadow: -1px -1px 1px #101010, 1px 1px 1px white;
514
+ -webkit-shadow: -1px -1px 1px #101010, 1px 1px 1px white;
515
+ }
516
+
517
+ .led_off {
518
+ height: 20px;
519
+ width: 20px;
520
+ border: solid 2px #505050;
521
+ border-radius: 50%;
522
+ background: radial-gradient(circle at 30% 30%, red 20%, #8B0000);
523
+ display: inline-block;
524
+ margin: 2px;
525
+ box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
526
+ -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
527
+ -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
528
+ }
529
+
530
+ .led_on {
531
+ height: 20px;
532
+ width: 20px;
533
+ border: solid 2px #505050;
534
+ border-radius: 50%;
535
+ background: radial-gradient(circle, yellow 15%, orange 50%, red);
536
+ display: inline-block;
537
+ margin: 2px;
538
+ box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
539
+ -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
540
+ -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
541
+ }
542
+
543
+ </style>
544
+ </head>
545
+
546
+ <body>
547
+
548
+ <div id="header">
549
+ <div id="cartouche" class="title">
550
+ Name of the FPGA board
551
+ </div>
552
+ </div>
553
+ <br>
554
+
555
+ <div id="panel" class="panel"><div class="prow"></div></div>
556
+
557
+ <script>
558
+ // Access to the components of the UI.
559
+ const cartouche = document.getElementById("cartouche");
560
+ const panel = document.getElementById("panel");
561
+
562
+ // The input and output elements' ids.
563
+ const input_ids = [];
564
+ const output_ids = [];
565
+
566
+ // The control functions.
567
+
568
+ // Set the cartouche of the board.
569
+ function set_cartouche(txt) {
570
+ cartouche.innerHTML = txt;
571
+ }
572
+
573
+ // Add an element.
574
+ function add_element(txt) {
575
+ if (txt.includes('prow')) {
576
+ // New panel row.
577
+ panel.innerHTML += txt;
578
+ return;
579
+ }
580
+ const prow = panel.lastElementChild;
581
+ prow.innerHTML += txt;
582
+ const element = prow.lastElementChild;
583
+ // Depending of the kind of element.
584
+ if (element.classList.contains('swset') ||
585
+ element.classList.contains('btset')) {
586
+ // Input element.
587
+ input_ids.push(element.id);
588
+ } else {
589
+ // Output element.
590
+ output_ids.push(element.id);
591
+ }
592
+ }
593
+
594
+ // Set the size of a canvas.
595
+ function setCanvasSize(canvas) {
596
+ // Get the DPR and size of the canvas
597
+ const dpr = window.devicePixelRatio;
598
+ const parentRect = canvas.parentElement.getBoundingClientRect();
599
+ // console.log("parentRect=[" + parentRect.width + "," + parentRect.height + "]");
600
+
601
+ // Set the "actual" size of the canvas
602
+ canvas.width = parentRect.width * dpr;
603
+ canvas.height = parentRect.height * dpr;
604
+
605
+ // Scale the context to ensure correct drawing operations
606
+ canvas.getContext("2d").scale(dpr, dpr);
607
+
608
+ // Set the "drawn" size of the canvas
609
+ canvas.style.width = `${parentRect.width}px`;
610
+ canvas.style.height = `${parentRect.height}px`;
611
+ }
612
+
613
+ // Handler of sw change.
614
+ function sw_change(sw) {
615
+ // Get the set holding sw.
616
+ const swset = sw.parentElement.parentElement;
617
+ const bit = sw.dataset.bit;
618
+ if (sw.checked) {
619
+ swset.dataset.value = swset.dataset.value | (1 << bit);
620
+ }
621
+ else {
622
+ swset.dataset.value = swset.dataset.value & ~(1 << bit);
623
+ }
624
+ // console.log("sw value=" + swset.dataset.value);
625
+ }
626
+
627
+ // Set the aspect of a button to clicked.
628
+ function bt_on(bt) {
629
+ bt.innerHTML = '<i class="bt_on"></i>';
630
+ }
631
+
632
+ // Set the aspect of a button to not clicked.
633
+ function bt_off(bt) {
634
+ bt.innerHTML = '<i class="bt_off"></i>';
635
+ }
636
+
637
+ // Handler of button clicked.
638
+ function bt_click(bt) {
639
+ // Change the aspect of the button.
640
+ bt_on(bt);
641
+ // Get the set holding bt.
642
+ const btset = bt.parentElement;
643
+ const bit = bt.dataset.bit;
644
+ // Update its value.
645
+ btset.dataset.value = btset.dataset.value | (1 << bit);
646
+ }
647
+
648
+ // Handler of button released.
649
+ function bt_release(bt) {
650
+ // Change the aspect of the button.
651
+ bt_off(bt);
652
+ // Get the set holding bt.
653
+ const btset = bt.parentElement;
654
+ const bit = bt.dataset.bit;
655
+ // Update its value.
656
+ btset.dataset.value = btset.dataset.value & ~(1 << bit);
657
+ }
658
+
659
+
660
+ // Switch a led on.
661
+ function led_on(led) {
662
+ led.classList.remove('led_off');
663
+ led.classList.add('led_on');
664
+ }
665
+
666
+ // Switch a led off.
667
+ function led_off(led) {
668
+ led.classList.remove('led_on');
669
+ led.classList.add('led_off');
670
+ }
671
+
672
+ // Update a led set.
673
+ function ledset_update(ledset,value) {
674
+ // Update the ledset value.
675
+ ledset.dataset.value = value;
676
+ // Update each led.
677
+ // Get the individual leds.
678
+ const leds = ledset.getElementsByTagName("i");
679
+ const num = leds.length;
680
+ // Set each led of the set.
681
+ for(let i=0; i < num; ++i) {
682
+ const val = (value >> i) & 1;
683
+ if (val == 1) { led_on(leds[num-i-1]); }
684
+ else { led_off(leds[num-i-1]); }
685
+ }
686
+ }
687
+
688
+ // Update a digit set.
689
+ function digitset_update(digitset,value) {
690
+ // Update the digiset value.
691
+ digitset.dataset.value = value;
692
+ // Unsigned case.
693
+ const num = digitset.dataset.width;
694
+ digitset.lastElementChild.innerHTML = String(value).padStart(num,"\u00A0");
695
+ }
696
+
697
+ // Update a hexadecimal set.
698
+ function hexaset_update(hexaset,value) {
699
+ // Update the digiset value.
700
+ hexaset.dataset.value = value;
701
+ // Update its display.
702
+ const num = hexaset.dataset.width;
703
+ hexaset.lastElementChild.innerHTML = Number(value).toString(16).padStart(num,'0');
704
+ }
705
+
706
+ // Update an oscilloscope.
707
+ function scope_update(scope,value) {
708
+ // Get the canvas.
709
+ const canvas = scope.lastElementChild;
710
+ // Shall we set up its size?
711
+ let first = 0;
712
+ if (scope.dataset.configured != 1) {
713
+ // First time, so yes.
714
+ setCanvasSize(canvas);
715
+ scope.dataset.configured = 1;
716
+ first = 1;
717
+ }
718
+ // Its size.
719
+ const { width, height } = canvas.getBoundingClientRect();
720
+ // Its context.
721
+ const cxt = canvas.getContext("2d");
722
+ // Get the properties of the scope.
723
+ const min = Number(scope.dataset.min);
724
+ const max = Number(scope.dataset.max);
725
+ const pos = Number(scope.dataset.pos);
726
+ const previous = Number(scope.dataset.previous);
727
+ // console.log("min=" + min + " max=" + max);
728
+ const toPx = function(val) { // Convert a percentage to x position.
729
+ return (val*width)/100;
730
+ }
731
+ const toPy = function(val) { // Convert an input value to y position.
732
+ return height - ((val-min) * height) / (max-min);
733
+ }
734
+ // Shall we restart the drawing?
735
+ if (pos >= 100 || first == 1) {
736
+ // Yes, clears the canvas.
737
+ cxt.clearRect(0, 0, width, height);
738
+ /* Draw the grid. */
739
+ cxt.strokeStyle = "#b3b3b3";
740
+ cxt.setLineDash([2,1]);
741
+ cxt.lineWidth = 1;
742
+ cxt.beginPath();
743
+ for(let i=0; i<100; i += 10) {
744
+ cxt.moveTo(toPx(0), toPy((i*(max-min))/100+min));
745
+ cxt.lineTo(toPx(100),toPy((i*(max-min))/100+min));
746
+ cxt.moveTo(toPx(i), toPy(min));
747
+ cxt.lineTo(toPx(i), toPy(max));
748
+ }
749
+ cxt.stroke();
750
+ cxt.setLineDash([]);
751
+ cxt.beginPath();
752
+ for(let i=0; i<100; i+= 2) {
753
+ cxt.moveTo(toPx(50-0.7), toPy((i*(max-min))/100+min));
754
+ cxt.lineTo(toPx(50+0.7), toPy((i*(max-min))/100+min));
755
+ cxt.moveTo(toPx(i), toPy((max-min)/2 + min - 0.7*(max-min)/100));
756
+ cxt.lineTo(toPx(i), toPy((max-min)/2 + min + 0.7*(max-min)/100));
757
+ }
758
+ cxt.stroke();
759
+ // Set the pen color for drawing the signal.
760
+ cxt.strokeStyle = "yellow";
761
+ cxt.lineWidth = 2;
762
+ // Draw a single pixel.
763
+ cxt.beginPath();
764
+ cxt.moveTo(toPx(0), toPy(value));
765
+ cxt.lineTo(toPx(0), toPy(value));
766
+ cxt.stroke();
767
+ /* Update the values. */
768
+ scope.dataset.previous = value;
769
+ scope.dataset.pos = 0;
770
+ } else {
771
+ // Go on, draw a line to the new position.
772
+ // Set the pen color for drawing the signal.
773
+ cxt.strokeStyle = "yellow";
774
+ cxt.lineWidth = 2;
775
+ // Draw a line to the new position.
776
+ cxt.beginPath();
777
+ cxt.moveTo(toPx(pos), toPy(previous));
778
+ cxt.lineTo(toPx(pos+1), toPy(value));
779
+ cxt.stroke();
780
+ /* Update the values. */
781
+ scope.dataset.previous = value;
782
+ scope.dataset.pos = pos + 1;
783
+ }
784
+ }
785
+
786
+ // Update a general display element.
787
+ function element_update(element,value) {
788
+ if(element.classList.contains('ledset')) { ledset_update(element,value); }
789
+ if(element.classList.contains('digitset')){ digitset_update(element,value); }
790
+ if(element.classList.contains('signedset')){signedset_update(element,value);}
791
+ if(element.classList.contains('hexaset')) { hexaset_update(element,value); }
792
+ if(element.classList.contains('scope')) { scope_update(element,value); }
793
+ }
794
+
795
+
796
+ // Synchronize with the HDLRuby simulator.
797
+ function hruby_sync() {
798
+ let xhttp = new XMLHttpRequest();
799
+ xhttp.onreadystatechange = function() {
800
+ // console.log("response=" + this.responseText);
801
+ if (this.readyState == 4 && this.status == 200) {
802
+ if (/[0-9]+:[0-9]/.test(this.responseText)) {
803
+ // There is a real response.
804
+ // Update the interface with the answer.
805
+ const commands = this.responseText.split(';');
806
+ for(command of commands) {
807
+ const toks = command.split(':');
808
+ element_update(document.getElementById(toks[0]),toks[1]);
809
+ }
810
+ }
811
+ }
812
+ };
813
+ // Builds the action from the state of the input elements.
814
+ act = '';
815
+ for(id of input_ids) {
816
+ act += id + ':' + document.getElementById(id).dataset.value + ';';
817
+ }
818
+ // console.log("act=" + act);
819
+ xhttp.open("GET", act, true);
820
+ xhttp.overrideMimeType("text/plain; charset=x-user-defined");
821
+ xhttp.send();
822
+ }
823
+
824
+ // First call of synchronisation.
825
+ hruby_sync();
826
+
827
+ // Then periodic synchronize.
828
+ setInterval(function() { hruby_sync(); }, 100);
829
+
830
+ </script>
831
+
832
+ HTMLHEADER
833
+
834
+ # Footer
835
+ UI_footer = <<-HTMLFOOTER
836
+ </body>
837
+ </html>
838
+ HTMLFOOTER
839
+
840
+ UI_response = <<-HTMLRESPONSE
841
+ HTTP/1.1 200
842
+ Content-Type: text/plain
843
+
844
+ HTMLRESPONSE
845
+
846
+
847
+ # The already used ports.
848
+ @@http_ports = []
849
+
850
+
851
+ # Create a new board named +name+ accessible on HTTP port +http_port+
852
+ # and whose content is describe in +hdlruby_block+.
853
+ def initialize(name, http_port = 8000, &hdlruby_block)
854
+ # Set the name.
855
+ @name = name.to_s
856
+ # Check and set the port.
857
+ http_port = http_port.to_i
858
+ if (@@http_ports.include?(http_port)) then
859
+ # Port already used, error.
860
+ raise UIError.new("UI (http) port #{http_port} already in use.")
861
+ end
862
+ @http_port = http_port
863
+ @@http_ports << @http_port
864
+ # Create the server
865
+ @server = TCPServer.new(@http_port)
866
+ # Create the running function.
867
+ this = self
868
+ Kernel.define_method(@name.to_sym) { this.run }
869
+ # Initialize the list of board elements to empty.
870
+ @elements = []
871
+ @out_elements = []
872
+ # And build the board.
873
+ # Create the namespace for the program.
874
+ @namespace = Namespace.new(self)
875
+ # Build the program object.
876
+ High.space_push(@namespace)
877
+ pr = nil
878
+ High.top_user.instance_eval { pr = program(:ruby, @name.to_sym) {} }
879
+ @program = pr
880
+ # Fill it.
881
+ High.top_user.instance_eval(&hdlruby_block)
882
+ High.space_pop
883
+ end
884
+
885
+ # Adds new activation ports.
886
+ def actport(*evs)
887
+ @program.actport(*evs)
888
+ end
889
+
890
+ # Add a new slide switch element attached to HDLRuby port +hport+.
891
+ def sw(hport)
892
+ if !hport.is_a?(Hash) or hport.size != 1 then
893
+ raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
894
+ end
895
+ # Create the HDLRuby program port.
896
+ @program.outport(hport)
897
+ # Create the ui component.
898
+ hport = hport.first
899
+ @elements << SW.new(@elements.size,hport[1].type.width,:"#{hport[0]}=")
900
+ end
901
+
902
+ # Add a new button element attached to HDLRuby port +hport+.
903
+ def bt(hport)
904
+ if !hport.is_a?(Hash) or hport.size != 1 then
905
+ raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
906
+ end
907
+ # Create the HDLRuby program port.
908
+ @program.outport(hport)
909
+ hport = hport.first
910
+ # Create the ui component.
911
+ @elements << BT.new(@elements.size,hport[1].type.width,:"#{hport[0]}=")
912
+ end
913
+
914
+ # Add a new LED element attached to HDLRuby port +hport+.
915
+ def led(hport)
916
+ if !hport.is_a?(Hash) or hport.size != 1 then
917
+ raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
918
+ end
919
+ # Create the HDLRuby program port.
920
+ @program.inport(hport)
921
+ hport = hport.first
922
+ # Createthe ui component.
923
+ @elements << LED.new(@elements.size,hport[1].type.width,hport[0])
924
+ @out_elements << @elements[-1]
925
+ end
926
+
927
+ # Add a new digit element attached to HDLRuby port +hport+.
928
+ def digit(hport)
929
+ if !hport.is_a?(Hash) or hport.size != 1 then
930
+ raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
931
+ end
932
+ # Create the HDLRuby program port.
933
+ @program.inport(hport)
934
+ hport = hport.first
935
+ sign = hport[1].type.signed?
936
+ # Createthe ui component.
937
+ @elements << DIGIT.new(@elements.size,
938
+ Math.log10(2**hport[1].type.width - 1).to_i + (sign ? 2 : 1),
939
+ hport[0])
940
+ @out_elements << @elements[-1]
941
+ end
942
+
943
+ # Add a new hexadecimal element attached to HDLRuby port +hport+.
944
+ def hexa(hport)
945
+ if !hport.is_a?(Hash) or hport.size != 1 then
946
+ raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
947
+ end
948
+ # Create the HDLRuby program port.
949
+ @program.inport(hport)
950
+ hport = hport.first
951
+ # Createthe ui component.
952
+ @elements << HEXA.new(@elements.size,
953
+ (hport[1].type.width-1)/4+1, hport[0])
954
+ @out_elements << @elements[-1]
955
+ end
956
+
957
+ # Add a new scope element attached to HDLRuby port +hport+.
958
+ def scope(hport)
959
+ if !hport.is_a?(Hash) or hport.size != 1 then
960
+ raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
961
+ end
962
+ # Create the HDLRuby program port.
963
+ @program.inport(hport)
964
+ hport = hport.first
965
+ width = hport[1].type.width
966
+ if hport[1].type.signed? then
967
+ min = -2**(width-1)
968
+ max = 2**(width-1) - 1
969
+ else
970
+ min = 0
971
+ max = 2**width - 1
972
+ end
973
+ # Createthe ui component.
974
+ @elements << SCOPE.new(@elements.size, min, max, hport[0])
975
+ @out_elements << @elements[-1]
976
+ end
977
+
978
+ # Add a new ASCII element of +n+ columns attached to HDLRuby port +hport+.
979
+ def ascii(n, hport)
980
+ if !hport.is_a?(Hash) or hport.size != 1 then
981
+ raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
982
+ end
983
+ # Create the HDLRuby program port.
984
+ @program.inport(hport)
985
+ hport = hport.first
986
+ # Createthe ui component.
987
+ @elements << ASCII.new(@elements.size,hport[1].type.width,hport[0])
988
+ @out_elements << @elements[-1]
989
+ end
990
+
991
+ # Add a new bitmap element of width +w+ and height +h+ attached to HDLRuby
992
+ # port +hport+.
993
+ def bitmap(w,h, hport)
994
+ if !hport.is_a?(Hash) or hport.size != 1 then
995
+ raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
996
+ end
997
+ # Create the HDLRuby program port.
998
+ @program.inport(hport)
999
+ hport = hport.first
1000
+ # Createthe ui component.
1001
+ @elements << BITMAP.new(@elements.size,w.to_i,h.to_i,hport[0])
1002
+ @out_elements << @elements[-1]
1003
+ end
1004
+
1005
+ # Add a new panel row.
1006
+ def row()
1007
+ # Createthe ui component.
1008
+ @elements << PROW.new(@elements.size)
1009
+ end
1010
+
1011
+
1012
+
1013
+ # Update port number +id+ with value +val+.
1014
+ def update_port(id,val)
1015
+ RubyHDL.send(@elements[id].hwrite,val)
1016
+ end
1017
+
1018
+ # Generate a response to a request to the server.
1019
+ def make_response(request)
1020
+ # puts "request=#{request}"
1021
+ if (request.empty?) then
1022
+ # First or re-connection, generate the UI.
1023
+ return UI_header + "\n<script>\n" +
1024
+ "set_cartouche('#{@name}');\n" +
1025
+ @elements.map do |elem|
1026
+ "add_element('#{elem.to_html}');"
1027
+ end.join("\n") +
1028
+ "\n</script>\n" + UI_footer
1029
+ else
1030
+ # This should be an AJAX request, process it.
1031
+ commands = request.split(";")
1032
+ commands.each do |command|
1033
+ id, val = command.split(":").map {|t| t.to_i}
1034
+ self.update_port(id,val)
1035
+ end
1036
+ # And generate the response: an update of each board output element.
1037
+ return UI_response + @out_elements.each.map do |e|
1038
+ # puts "resp=" + "#{e.id}:#{RubyHDL.send(e.hread)}"
1039
+ "#{e.id}:#{RubyHDL.send(e.hread)}"
1040
+ end.join(";")
1041
+ end
1042
+ end
1043
+
1044
+ # Start the ui.
1045
+ def run
1046
+ # At first the u is not running.
1047
+ @connected = false
1048
+ # Create the ui thread.
1049
+ @thread = Thread.new do
1050
+ while session = @server.accept
1051
+ # A request came, process it.
1052
+ request = session.gets
1053
+ verb,path,protocol = request.split(' ')
1054
+ # puts "verb=#{verb} path=#{path} protocol=#{protocol}"
1055
+ if protocol === "HTTP/1.1"
1056
+ # The request is valid, generate the response.
1057
+ session.print self.make_response(path[1..-1])
1058
+ # And tell the ui has been connected.
1059
+ @connected = true
1060
+ else
1061
+ session.print 'Connection Refuse'
1062
+ end
1063
+
1064
+ session.close
1065
+ end
1066
+ end
1067
+ # Wait for a first connection.
1068
+ sleep(0.1) while(!@connected)
1069
+ end
1070
+ end
1071
+
1072
+
1073
+ # Create a new board named +name+ accessible on HTTP port +http_port+
1074
+ # and whose content is describe in +block+.
1075
+ def board(name, http_port = 8000, &block)
1076
+ return Board.new(name,http_port,&block)
1077
+ end
1078
+
1079
+ end