libfst 0.1.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.
data/lib/libfst/vcd.rb ADDED
@@ -0,0 +1,597 @@
1
+ # Copyright (C) 2024 Théotime Bollengier <theotime.bollengier@ensta-bretagne.fr>
2
+ #
3
+ # This file is part of libfst.rb <https://gitlab.ensta-bretagne.fr/bollenth/libfst.rb>
4
+ #
5
+ # libfst.rb is free software: you can redistribute it and/or modify it
6
+ # under the terms of the GNU General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License,
8
+ # or (at your option) any later version.
9
+ #
10
+ # libfst.rb is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
+ # See the GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with libfst.rb. If not, see <https://www.gnu.org/licenses/>. 
17
+
18
+
19
+ # Handle [Value Change Dump](https://en.wikipedia.org/wiki/Value_change_dump) files.
20
+ module VCD
21
+ # A Scope corresponds to a design element
22
+ class Scope
23
+ attr_reader :name # @return [String]
24
+ attr_reader :type # @return [Symbol] begin, fork, function, module or task
25
+ attr_reader :variables # @return [Array<Variable>]
26
+ attr_reader :children # @return [Array<Scope>]
27
+ attr_reader :parent # @return [Scope,nil]
28
+
29
+ # @!visibility private
30
+ def initialize(name, type, parent)
31
+ @name = name.to_s
32
+ @type = type.to_s.to_sym
33
+ @variables = []
34
+ @children = []
35
+ @parent = parent
36
+ end
37
+
38
+ # @return [String]
39
+ def to_s
40
+ "<#{self.class} @name=#{@name.inspect}, @type=#{@type.inspect}>"
41
+ end
42
+
43
+ # @return [String]
44
+ def path
45
+ return '/' if @parent.nil? # root
46
+ "#{@parent.path}#{@name}/"
47
+ end
48
+
49
+ # @return [String]
50
+ # @overload tree
51
+ def tree(prefix = '', last: true, first: true)
52
+ return @children.first.tree(prefix, last: last, first: first) if @parent.nil? && @variables.empty? && @children.length == 1
53
+ str = prefix + (first ? '' : (last ? '└─ ' : '├─ '))
54
+ str += "\e[34;1m#{@name}\e[0m (#{@type})\n"
55
+ prefix = prefix + (first ? '' : (last ? ' ' : '│  '))
56
+ mxvarnamelen = ([0] + @variables.collect{|v| v.name.length}).max
57
+ mxlenlen = ([0] + @variables.collect{|v| v.width == 0 ? 0 : v.width.to_s.length}).max
58
+ @variables.each_with_index do |v, i|
59
+ l = (@children.empty? and (i+1 == @variables.length))
60
+ str += prefix + (l ? '└─ ' : '├─ ') + v.name + ' '*(mxvarnamelen+1-v.name.length)
61
+ str += (v.width > 0 ? v.width.to_s : '').rjust(mxlenlen)
62
+ str += " #{v.type}\n"
63
+ end
64
+ @children.each_with_index do |c, i|
65
+ str += c.tree(prefix, last: (i+1 == @children.length), first: false)
66
+ end
67
+ str
68
+ end
69
+ end
70
+
71
+
72
+ # A Variable corresponds to a signal in the hierarchy of the design.
73
+ # It points to a {Trace}.
74
+ # Different variables can point to the same {Trace} : for example the clock signals from different modules
75
+ # are actualy the same physical signal.
76
+ class Variable
77
+ attr_reader :name # @return [String]
78
+ attr_reader :type # @return [Symbol] event, integer, parameter, real, realtime, reg, supply0, supply1, time, tri, triand, trior, trireg, tri0, tri1, wand, wire or wor
79
+ attr_reader :width # @return [Integer]
80
+ attr_reader :id # @return [String]
81
+ attr_reader :parent # @return [Scope]
82
+ attr_reader :trace # @return [Trace]
83
+
84
+ # @!visibility private
85
+ def initialize(name, type, width, id, parent)
86
+ @name = name.to_s
87
+ @type = type.to_s.to_sym
88
+ @width = width.to_i
89
+ @id = id.to_s
90
+ @parent = parent
91
+ end
92
+
93
+ # @return [String]
94
+ def to_s
95
+ "<#{self.class} @name=#{@name.inspect}, @type=#{@type.inspect}, @width=#{@width.inspect}, @id=#{@id.inspect}>"
96
+ end
97
+
98
+ # @return [String]
99
+ def path
100
+ "#{@parent.path}#{@name}"
101
+ end
102
+
103
+ # @!visibility private
104
+ def set_trace(trace)
105
+ raise "Trace allready set for variable #{path}" if @trace
106
+ @trace = trace
107
+ end
108
+ end
109
+
110
+
111
+ class Trace
112
+ attr_reader :type # @return [Symbol] event, integer, parameter, real, realtime, reg, supply0, supply1, time, tri, triand, trior, trireg, tri0, tri1, wand, wire or wor
113
+ attr_reader :width # @return [Integer]
114
+ attr_reader :variables # @return [Array<Variable>]
115
+ attr_reader :id # @return [String]
116
+
117
+ # @!visibility private
118
+ def initialize(type, width, variables, id)
119
+ @type = type.to_sym
120
+ @width = width.to_i
121
+ @variables = variables.freeze
122
+ @id = id.to_s
123
+ end
124
+ end
125
+
126
+
127
+ class Reader
128
+ attr_reader :date # @return [String,nil]
129
+ attr_reader :version # @return [String,nil]
130
+ attr_reader :comments # @return [Array<String>]
131
+ attr_reader :timescale # @return [Float]
132
+ attr_reader :root # @return [Scope]
133
+ attr_reader :traces # @return [Hash{String => Trace}] Traces stored by ID
134
+
135
+
136
+ # @param io [String,IO] either a file name or an opened IO.
137
+ def initialize(io)
138
+ @io_is_file = false
139
+ if io.is_a?(String) then
140
+ @io_is_file = true
141
+ @io = File.open(io)
142
+ else
143
+ @io = io
144
+ end
145
+ @buf = []
146
+ @root = Scope.new('root', :module, nil)
147
+ @scope_stack = [@root]
148
+ @state = :process_definitions
149
+ @comments = []
150
+ @stop_parsing = false
151
+ @io.each_line do |line|
152
+ process_line(line.chomp)
153
+ break if @stop_parsing
154
+ end
155
+ if @io_is_file then
156
+ @io.close
157
+ @io = io
158
+ end
159
+
160
+ remove_instance_variable :@stop_parsing
161
+ remove_instance_variable :@state
162
+ remove_instance_variable :@buf
163
+ remove_instance_variable :@scope_stack
164
+
165
+ @on_comment = nil
166
+ @on_time_change = nil
167
+ end
168
+
169
+
170
+ # Find a {Variable} by its name or path
171
+ # @param name [String] name or path to look for
172
+ # @return [Variable,nil]
173
+ def variable(name)
174
+ @traces.each_value do |t|
175
+ t.variables.each do |v|
176
+ return v if v.name == name or v.path == name
177
+ end
178
+ end
179
+ nil
180
+ end
181
+
182
+
183
+ # Returns the scope or variable corresponding to the specified path
184
+ # @param path [String]
185
+ # @return [Scope,Variable,nil]
186
+ def [](path)
187
+ return @root if path == '/'
188
+ raise ArgumentError, "expecting a String, not a #{path.class}" unless path.is_a?(String)
189
+ a = path.split('/').reject(&:empty?)
190
+ return nil if a.empty?
191
+ scope = @root
192
+ var = nil
193
+ a.each do |w|
194
+ var = scope.variables.select{ |v| v.name == w }.first
195
+ return var if var
196
+ s = scope.children.select{ |v| v.name == w }.first
197
+ return nil if s.nil?
198
+ scope = s
199
+ end
200
+ scope
201
+ end
202
+
203
+
204
+ # Set the block of code to be executed when a comment is found while reading the value change section of the file
205
+ # @return [self]
206
+ # @yieldparam comment [String]
207
+ # @yieldparam time [Integer] this integer is to be multiplied with {#timescale} the get the real time
208
+ # @yieldparam vcd [self]
209
+ def on_comment(&block)
210
+ @on_comment = block
211
+ self
212
+ end
213
+
214
+
215
+ # Set the block of code to be called when a time change is encountered.
216
+ # @return [self]
217
+ # @yieldparam time [Integer] this integer is to be multiplied with {#timescale} the get the real time
218
+ # @yieldparam vcd [self]
219
+ def on_time_change(&block)
220
+ @on_time_change = block
221
+ self
222
+ end
223
+
224
+
225
+ # Stop the parsing of the file.
226
+ # This method can be called from an {on_comment} or {read} block.
227
+ # @return [self]
228
+ def stop_parsing
229
+ @stop_parsing = true
230
+ self
231
+ end
232
+
233
+
234
+ # Read the value change section of the VCD file.
235
+ # @return [self]
236
+ # @param signals [Trace,Variable,Scope,String] if signals are specified, only those will trigger the block.
237
+ # A path can be provided as a string, which correspond to a {Variable} or a {Scope}.
238
+ # In the case of a {Scope}, all its variables will be added (NOT recursively).
239
+ # @param start_time [Integer,Float,String,nil] start time from which to trigger the block
240
+ # @param end_time [Integer,Float,String,nil] end time until which to trigger the block
241
+ # @yieldparam trace [Trace]
242
+ # @yieldparam value [String,Float]
243
+ # @yieldparam time [Integer] this integer is to be multiplied with {#timescale} the get the real time
244
+ # @yieldparam vcd [self]
245
+ def read(*signals, start_time: nil, end_time: nil, &block)
246
+ @time_win_start = nil
247
+ @time_win_end = nil
248
+ @trace_mask = nil
249
+
250
+ @time_win_start = time_to_itime(start_time, false) if start_time
251
+ @time_win_end = time_to_itime(end_time, true) if end_time
252
+
253
+ unless signals.empty? then
254
+ @trace_mask = {}
255
+ signals.each do |sig|
256
+ if sig.is_a?(String) then
257
+ s = self[sig]
258
+ raise "Cannot find #{sig.inspect} in the hierarchy" if s.nil?
259
+ sig = s
260
+ end
261
+ case sig
262
+ when Variable
263
+ @trace_mask[sig.trace.id] = true
264
+ when Trace
265
+ @trace_mask[sig.id] = true
266
+ when Scope
267
+ sig.variables.each do |var|
268
+ @trace_mask[var.trace.id] = true
269
+ end
270
+ else
271
+ raise TypeError, "Expecting a path, a #{Variable}, a #{Trace} or a #{Scope}, not a #{sig.class}"
272
+ end
273
+ end
274
+ end
275
+
276
+ @on_read = block
277
+
278
+ if @io_is_file then
279
+ io = @io
280
+ @io = File.open(io)
281
+ @io.seek(@value_change_pos, IO::SEEK_SET)
282
+ end
283
+
284
+ @stop_parsing = false
285
+ @state = nil
286
+ @buf = nil
287
+ @time = 0
288
+
289
+ @io.each_line do |line|
290
+ process_line(line.chomp)
291
+ break if @stop_parsing
292
+ end
293
+
294
+ remove_instance_variable :@time_win_start
295
+ remove_instance_variable :@time_win_end
296
+ remove_instance_variable :@trace_mask
297
+ remove_instance_variable :@on_read
298
+ remove_instance_variable :@stop_parsing
299
+ remove_instance_variable :@state
300
+ remove_instance_variable :@buf
301
+
302
+ if @io_is_file then
303
+ @io.close
304
+ @io = io
305
+ end
306
+
307
+ @time = nil
308
+ end
309
+
310
+
311
+ # When called in one of the callbacks, returns the current time as an integer, `nil` otherwise.
312
+ # @return [Integer,nil] this value is to be multiplied with {#timescale} the get the real time
313
+ def time
314
+ @time
315
+ end
316
+
317
+
318
+ # When called in one of the callbacks, returns the current time in seconds, `nil` otherwise.
319
+ # @return [Float,nil]
320
+ def real_time
321
+ return nil unless @time
322
+ @time*@timescale
323
+ end
324
+
325
+
326
+ # When called in one of the callbacks, returns the current time as a string, `nil` otherwise.
327
+ # @param t [Integer,nil]
328
+ # @return [String,nil] example: `"2.57 µs"`
329
+ def pretty_time(t = nil)
330
+ t ||= @time
331
+ return nil unless t
332
+ return '0 s' if t == 0
333
+ e = Math.log10(@timescale).round
334
+ while (-e % 3) != 0 do
335
+ t *= 10
336
+ e -= 1
337
+ end
338
+ while e < -15 do
339
+ t /= 10
340
+ e += 1
341
+ end
342
+
343
+ while e < 0 and (t % 1000) == 0 do
344
+ t /= 1000
345
+ e += 3
346
+ end
347
+
348
+ ent = t
349
+ qe = e
350
+ mf = 1
351
+ while qe < 0 and (ent / 1000) != 0 do
352
+ ent /= 1000
353
+ qe += 3
354
+ mf *= 1000
355
+ end
356
+
357
+ rem = t - mf*ent
358
+ while rem != 0 and (rem % 10) == 0 do
359
+ rem /= 10
360
+ e += 1
361
+ end
362
+
363
+ s = ent.to_s
364
+ if rem != 0 then
365
+ s += '.' + rem.to_s.rjust(qe - e, '0')
366
+ end
367
+ s += case qe
368
+ when 0; ' s'
369
+ when -3; ' ms'
370
+ when -6; ' µs'
371
+ when -9; ' ns'
372
+ when -12; ' ps'
373
+ when -15; ' fs'
374
+ else
375
+ raise "unknown unit for exponent #{qe}"
376
+ end
377
+ s
378
+ end
379
+
380
+
381
+ # @return [String]
382
+ def to_s
383
+ [
384
+ "Date: #{@date.inspect}",
385
+ "Version: #{@version.inspect}",
386
+ "Timescale: #{@timescale}",
387
+ "Comments:",
388
+ @comments.collect{|s| " " + s.inspect},
389
+ @root.tree
390
+ ].flatten.join("\n")
391
+ end
392
+
393
+
394
+ private
395
+
396
+
397
+ def process_line(line)
398
+ line.split(/\s+/).reject{|s| s.empty?}.each do |word|
399
+ process_word(word)
400
+ end
401
+ end
402
+
403
+
404
+ def process_word(word)
405
+ case @state
406
+ when :process_definitions
407
+ @buf << word
408
+ process_buffer if word == '$end'
409
+ when :get_bin_vector_vc
410
+ on_value_change_callback(word, @buf)
411
+ @state = nil
412
+ when :get_real_vc
413
+ on_value_change_callback(word, @buf)
414
+ @state = nil
415
+ when :get_string_vc
416
+ on_value_change_callback(word, @buf)
417
+ @state = nil
418
+ when :in_comment
419
+ if word == '$end' then
420
+ cmnt = @buf.join(' ')
421
+ @comments.push(cmnt)
422
+ @state = nil
423
+ on_comment_callback(cmnt)
424
+ else
425
+ @buf.push(word)
426
+ end
427
+ else
428
+ case word
429
+ when /^#(\d+)$/ # simulation time
430
+ @time = $1.to_i
431
+ on_time_change_callback
432
+ when /^([01xXzZuUwWlLhH-])([!-~]+)$/ # scalar value change
433
+ on_value_change_callback($2, $1)
434
+ when /^[bB]([01xXzZuUwWlLhH-]+)$/ # binary vector value change
435
+ @buf = $1
436
+ @state = :get_bin_vector_vc
437
+ when /^[sS](.+)$/
438
+ @buf = $1
439
+ @state = :get_string_vc
440
+ when /^[rR]([0-9.+eE-]+)$/ # real value change
441
+ @state = :get_real_vc
442
+ @buf = $1.to_f
443
+ when '$comment'
444
+ @buf = []
445
+ @state = :in_comment
446
+ when '$end'
447
+ @state = nil
448
+ when '$dumpall'
449
+ raise 'intricated simulation commands' if @state
450
+ @state = :in_dumpall
451
+ when '$dumpoff'
452
+ raise 'intricated simulation commands' if @state
453
+ @state = :in_dumpoff
454
+ when '$dumpon'
455
+ raise 'intricated simulation commands' if @state
456
+ @state = :in_dumpon
457
+ when '$dumpvars'
458
+ raise 'intricated simulation commands' if @state
459
+ @state = :in_dumpvars
460
+ else
461
+ raise "unexpected \"#{word}\""
462
+ end
463
+ end
464
+ end
465
+
466
+
467
+ def process_buffer
468
+ case @buf.first
469
+ when '$scope'
470
+ scope = Scope.new(@buf[2], @buf[1], @scope_stack.last)
471
+ @scope_stack.last.children.push(scope)
472
+ @scope_stack.push(scope)
473
+ when '$var'
474
+ var = Variable.new(@buf[4], @buf[1], @buf[2], @buf[3], @scope_stack.last)
475
+ @scope_stack.last.variables.push(var)
476
+ when '$upscope'
477
+ raise 'VCD hierarchy error: poping root scope with $upscope' if @scope_stack.pop == @root
478
+ when '$date'
479
+ if @date.nil? then
480
+ @date = ''
481
+ else
482
+ @date += ' '
483
+ end
484
+ @date += @buf[1...-1].join(' ')
485
+ when '$version'
486
+ if @version.nil? then
487
+ @version = ''
488
+ else
489
+ @version += ' '
490
+ end
491
+ @version += @buf[1...-1].join(' ')
492
+ when '$comment'
493
+ @comments.push(@buf[1...-1].join(' '))
494
+ when '$attrbegin'
495
+ ## Ignore $attrbegin generated by nvc
496
+ when '$timescale'
497
+ s = @buf[1...-1].join
498
+ m = s.match(/^(\d+)([munpf]?)s$/)
499
+ raise "Cannot parse timescale: \"#{s}\"" unless m
500
+ @timescale = m[1].to_i * { '' => 1.0, 'm' => 1e-3, 'u' => 1e-6, 'n' => 1e-9, 'p' => 1e-12, 'f' => 1e-15 }[m[2]]
501
+ when '$enddefinitions'
502
+ @state = nil
503
+ gather_signals
504
+ @value_change_pos = @io.tell if @io_is_file
505
+ @stop_parsing = true
506
+ else
507
+ raise "VCD parsing ERROR: unexpected tocken: \"#{@buf.first}\""
508
+ end
509
+ @buf = []
510
+ end
511
+
512
+
513
+ def gather_variables(id_vararr, scope)
514
+ scope.variables.each do |var|
515
+ arr = id_vararr[var.id]
516
+ unless arr then
517
+ arr = []
518
+ id_vararr[var.id] = arr
519
+ end
520
+ arr.push(var)
521
+ end
522
+ scope.children.each do |subscope|
523
+ gather_variables(id_vararr, subscope)
524
+ end
525
+ end
526
+
527
+
528
+ def gather_signals
529
+ @traces = {}
530
+ variables = {}
531
+ gather_variables(variables, @root)
532
+ variables.each do |k, v|
533
+ type = v.first.type
534
+ width = v.first.width
535
+ trace = Trace.new(type, width, v, k)
536
+ @traces[k] = trace
537
+ v.each do |var|
538
+ raise "trace with variables of different types (#{type}, #{var.type})" unless var.type == type
539
+ raise "trace with variables of different width (#{width}, #{var.width})" unless var.width == width
540
+ var.set_trace(trace)
541
+ end
542
+ end
543
+ end
544
+
545
+
546
+ def on_time_change_callback
547
+ return unless @on_time_change
548
+ return if @time_win_start && @time < @time_win_start
549
+ return if @time_win_end && @time > @time_win_end
550
+ @on_time_change.call(@time, self)
551
+ end
552
+
553
+
554
+ def on_comment_callback(cmnt)
555
+ return unless @on_comment
556
+ return if @time_win_start && @time < @time_win_start
557
+ return if @time_win_end && @time > @time_win_end
558
+ @on_comment.call(cmnt, @time, self)
559
+ end
560
+
561
+
562
+ def on_value_change_callback(id, value)
563
+ return unless @on_read
564
+ return if @time_win_start && @time < @time_win_start
565
+ return if @time_win_end && @time > @time_win_end
566
+ return if @trace_mask && @trace_mask[id].nil?
567
+ @on_read.call(@traces[id], value, @time, self)
568
+ end
569
+
570
+
571
+ def time_to_itime(t, up = false)
572
+ u = case t
573
+ when Integer
574
+ t
575
+ when Float
576
+ if up then
577
+ (t / @timescale).ceil
578
+ else
579
+ (t / @timescale).floor
580
+ end
581
+ when String
582
+ m = t.match(/^([\d.eE+-]+)\s*([muµnpf]?)s?$/)
583
+ raise "cannot parse \"#{t}\" as a time" unless m
584
+ t = m[1].to_f
585
+ e = { '' => 1.0, 'm' => 1e-3, 'u' => 1e-6, 'µ' => 1e-6, 'n' => 1e-9, 'p' => 1e-12, 'f' => 1e-15 }[m[2]]
586
+ t *= e
587
+ t /= @timescale
588
+ up ? t.ceil : t.floor
589
+ else
590
+ raise "expecting an integer, a float or a string, not a #{t.class}"
591
+ end
592
+ [0, u].max
593
+ end
594
+
595
+ end
596
+ end
597
+
@@ -0,0 +1,4 @@
1
+ module LibFST
2
+ # LibFST gem version
3
+ VERSION = '0.1.0'
4
+ end
@@ -0,0 +1,50 @@
1
+ # Copyright (C) 2024 Théotime Bollengier <theotime.bollengier@ensta-bretagne.fr>
2
+ #
3
+ # This file is part of libfst.rb <https://gitlab.ensta-bretagne.fr/bollenth/libfst.rb>
4
+ #
5
+ # libfst.rb is free software: you can redistribute it and/or modify it
6
+ # under the terms of the GNU General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License,
8
+ # or (at your option) any later version.
9
+ #
10
+ # libfst.rb is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
+ # See the GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with libfst.rb. If not, see <https://www.gnu.org/licenses/>. 
17
+
18
+
19
+ module LibFST
20
+ class Writer
21
+ # Helper to create a variable of type `:vcd_real`
22
+ # @see create_variable
23
+ # @return [Variable]
24
+ def create_float_variable(name, direction: :implicit)
25
+ create_variable(name, type: :vcd_real, direction: direction)
26
+ end
27
+
28
+ # Helper to create a 32-bit variable of type `:vcd_integer`
29
+ # @see create_variable
30
+ # @return [Variable]
31
+ def create_integer_variable(name, direction: :implicit)
32
+ create_variable(name, type: :vcd_integer, direction: direction, length: 32)
33
+ end
34
+
35
+ # Helper to create a 64-bit variable of type `:vcd_integer`
36
+ # @see create_variable
37
+ # @return [Variable]
38
+ def create_integer64_variable(name, direction: :implicit)
39
+ create_variable(name, type: :vcd_integer, direction: direction, length: 64)
40
+ end
41
+
42
+ # Helper to create a variable of type `:sv_logic`
43
+ # @see create_variable
44
+ # @return [Variable]
45
+ def create_logic_variable(name , direction: :implicit, length: 1)
46
+ create_variable(name, type: :sv_logic, direction: direction, length: length)
47
+ end
48
+ end
49
+ end
50
+
data/lib/libfst.rb ADDED
@@ -0,0 +1,6 @@
1
+ require_relative './libfst/libfst'
2
+ require_relative './libfst/version.rb'
3
+ require_relative './libfst/reader.rb'
4
+ require_relative './libfst/writer.rb'
5
+ require_relative './libfst/vcd.rb'
6
+ require_relative './libfst/tfp.rb'
data/libfst.gemspec ADDED
@@ -0,0 +1,50 @@
1
+ require File.expand_path('../lib/libfst/version.rb', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'libfst'
5
+ s.version = LibFST::VERSION
6
+ s.date = Time.now.strftime '%Y-%m-%d'
7
+ s.summary = 'Fast Signal Trace bindings for Ruby'
8
+ s.description = 'fstlib allows to read and write FST files (Fast Signal Trace) from Ruby'
9
+ s.license = 'GPL-3.0-or-later'
10
+ s.authors = ['Théotime Bollengier']
11
+ s.email = 'theotime.bollengier@ensta-bretagne.fr'
12
+ s.homepage = 'https://gitlab.ensta-bretagne.fr/bollenth/fstlib.rb'
13
+ s.extensions = ['ext/extconf.rb']
14
+ s.files = [
15
+ 'LICENSE',
16
+ 'README.md',
17
+ 'libfst.gemspec',
18
+ 'lib/libfst.rb',
19
+ 'lib/libfst/version.rb',
20
+ 'lib/libfst/reader.rb',
21
+ 'lib/libfst/writer.rb',
22
+ 'lib/libfst/vcd.rb',
23
+ 'lib/libfst/tfp.rb',
24
+ 'ext/extconf.rb',
25
+ 'ext/libfst_rb.c',
26
+ 'ext/fstapi.c',
27
+ 'ext/fastlz.c',
28
+ 'ext/lz4.c',
29
+ 'ext/fst_config.h',
30
+ 'ext/lz4.h',
31
+ 'ext/fstapi.h',
32
+ 'ext/fastlz.h',
33
+ 'ext/fst_win_unistd.h',
34
+ 'samples/create_file.rb',
35
+ 'samples/read_file.rb',
36
+ 'samples/read2.rb',
37
+ 'samples/skinny_rand.fst',
38
+ 'samples/gtkwave.png',
39
+ 'samples/out.gtkw',
40
+ 'samples/vcd/skinny_rand.vcd.xz',
41
+ 'samples/vcd/vcd_read.rb',
42
+ 'samples/transaction_filter_process/full_boot.fst',
43
+ 'samples/transaction_filter_process/full_boot.gtkw',
44
+ 'samples/transaction_filter_process/uart.rb',
45
+ 'samples/transaction_filter_process/sdcard.rb',
46
+ '.yardopts'
47
+ ]
48
+ s.required_ruby_version = '>= 2.7'
49
+ end
50
+