ruby-vpi 18.0.2 → 19.0.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/Rakefile +15 -19
- data/bin/generate/proto.rb +15 -10
- data/bin/ruby-vpi +2 -0
- data/doc/README +3 -5
- data/doc/Rakefile +3 -3
- data/doc/common.css +24 -136
- data/doc/common.tpl +48 -37
- data/doc/figures/figures.dia +19 -19
- data/doc/figures/ruby_relay.png +0 -0
- data/doc/history.html +252 -67
- data/doc/history.inc +98 -1
- data/doc/history.yaml +105 -0
- data/doc/intro.inc +43 -32
- data/doc/lib/doc_format.rb +19 -13
- data/doc/lib/doc_proxy.rb +7 -7
- data/doc/manual.doc +156 -117
- data/doc/manual.html +601 -560
- data/doc/memo.html +29 -25
- data/doc/print.css +63 -4
- data/doc/readme.doc +4 -6
- data/doc/readme.html +129 -111
- data/doc/rss.xml +168 -7
- data/doc/screen.css +146 -0
- data/doc/spacing.css +57 -0
- data/{samp → examples}/counter/RSpec/Rakefile +0 -0
- data/{samp → examples}/counter/RSpec/counter_design.rb +0 -0
- data/examples/counter/RSpec/counter_proto.rb +9 -0
- data/{samp → examples}/counter/RSpec/counter_runner.rake +0 -0
- data/{samp → examples}/counter/RSpec/counter_spec.rb +0 -0
- data/{samp → examples}/counter/Rakefile +0 -0
- data/{samp → examples}/counter/counter.v +0 -0
- data/{samp → examples}/counter/xUnit/Rakefile +0 -0
- data/{samp → examples}/counter/xUnit/counter_bench.rb +0 -0
- data/{samp → examples}/counter/xUnit/counter_bench.v +0 -0
- data/{samp → examples}/counter/xUnit/counter_design.rb +0 -0
- data/examples/counter/xUnit/counter_proto.rb +9 -0
- data/{samp → examples}/counter/xUnit/counter_runner.rake +0 -0
- data/{samp → examples}/counter/xUnit/counter_spec.rb +0 -0
- data/{samp → examples}/pipelined_alu/Hw5UnitModel.rb +0 -0
- data/{samp → examples}/pipelined_alu/README +0 -0
- data/{samp → examples}/pipelined_alu/Rakefile +0 -0
- data/{samp → examples}/pipelined_alu/TestHw5UnitModel.rb +0 -0
- data/{samp → examples}/pipelined_alu/hw5_unit.v +0 -0
- data/{samp → examples}/pipelined_alu/hw5_unit_design.rb +0 -7
- data/examples/pipelined_alu/hw5_unit_proto.rb +2 -0
- data/{samp → examples}/pipelined_alu/hw5_unit_runner.rake +0 -0
- data/{samp → examples}/pipelined_alu/hw5_unit_spec.rb +0 -0
- data/{samp → examples}/pipelined_alu/int_gen.rb +0 -0
- data/{samp → examples}/register_file/LICENSE +0 -0
- data/{samp → examples}/register_file/README +0 -0
- data/{samp → examples}/register_file/Rakefile +0 -0
- data/{samp → examples}/register_file/register_file.v +0 -0
- data/{samp → examples}/register_file/register_file_design.rb +0 -0
- data/examples/register_file/register_file_proto.rb +11 -0
- data/{samp → examples}/register_file/register_file_runner.rake +0 -0
- data/{samp → examples}/register_file/register_file_spec.rb +0 -0
- data/ext/main.c +5 -5
- data/ext/swig_vpi.i +6 -2
- data/lib/ruby-vpi/core/callback.rb +142 -0
- data/lib/ruby-vpi/core/edge.rb +128 -0
- data/lib/ruby-vpi/core/handle.rb +421 -0
- data/lib/ruby-vpi/core/scheduler.rb +244 -0
- data/lib/ruby-vpi/core/struct.rb +123 -0
- data/lib/ruby-vpi/core.rb +41 -0
- data/lib/ruby-vpi/rcov.rb +25 -12
- data/lib/ruby-vpi/runner.rb +30 -26
- data/lib/ruby-vpi/runner_boot_loader.rb +67 -37
- data/lib/ruby-vpi.rb +2 -2
- data/ref/c/annotated.html +1 -1
- data/ref/c/common_8h.html +1 -1
- data/ref/c/files.html +1 -1
- data/ref/c/functions.html +1 -1
- data/ref/c/functions_vars.html +1 -1
- data/ref/c/globals.html +1 -1
- data/ref/c/globals_0x63.html +1 -1
- data/ref/c/globals_0x65.html +1 -1
- data/ref/c/globals_0x66.html +1 -1
- data/ref/c/globals_0x6d.html +1 -1
- data/ref/c/globals_0x70.html +1 -1
- data/ref/c/globals_0x72.html +1 -1
- data/ref/c/globals_0x73.html +1 -1
- data/ref/c/globals_0x74.html +1 -1
- data/ref/c/globals_0x76.html +1 -1
- data/ref/c/globals_0x78.html +1 -1
- data/ref/c/globals_defs.html +1 -1
- data/ref/c/globals_defs_0x65.html +1 -1
- data/ref/c/globals_defs_0x70.html +1 -1
- data/ref/c/globals_defs_0x76.html +1 -1
- data/ref/c/globals_defs_0x78.html +1 -1
- data/ref/c/globals_enum.html +1 -1
- data/ref/c/globals_eval.html +1 -1
- data/ref/c/globals_func.html +1 -1
- data/ref/c/globals_type.html +1 -1
- data/ref/c/globals_vars.html +1 -1
- data/ref/c/index.html +1 -1
- data/ref/c/main_8c.html +1 -1
- data/ref/c/main_8h.html +1 -1
- data/ref/c/relay_8c.html +1 -1
- data/ref/c/relay_8h.html +1 -1
- data/ref/c/structt__cb__data.html +1 -1
- data/ref/c/structt__vpi__delay.html +1 -1
- data/ref/c/structt__vpi__error__info.html +1 -1
- data/ref/c/structt__vpi__strengthval.html +1 -1
- data/ref/c/structt__vpi__systf__data.html +1 -1
- data/ref/c/structt__vpi__time.html +1 -1
- data/ref/c/structt__vpi__value.html +1 -1
- data/ref/c/structt__vpi__vecval.html +1 -1
- data/ref/c/structt__vpi__vlog__info.html +1 -1
- data/ref/c/verilog_8h.html +1 -1
- data/ref/c/vlog_8c.html +1 -1
- data/ref/c/vlog_8h.html +1 -1
- data/ref/c/vpi__user_8h.html +1 -1
- data/ref/ruby/classes/ERB.html +7 -5
- data/ref/ruby/classes/ERB.src/{M000026.html → M000024.html} +0 -0
- data/ref/ruby/classes/FileUtils.html +11 -11
- data/ref/ruby/classes/FileUtils.src/{M000027.html → M000025.html} +0 -0
- data/ref/ruby/classes/FileUtils.src/{M000028.html → M000026.html} +0 -0
- data/ref/ruby/classes/Float.html +8 -6
- data/ref/ruby/classes/Float.src/{M000021.html → M000019.html} +0 -0
- data/ref/ruby/classes/Integer.html +67 -65
- data/ref/ruby/classes/Integer.src/M000007.html +25 -0
- data/ref/ruby/classes/Integer.src/{M000014.html → M000008.html} +5 -5
- data/ref/ruby/classes/Integer.src/M000009.html +5 -12
- data/ref/ruby/classes/Integer.src/M000010.html +5 -5
- data/ref/ruby/classes/Integer.src/M000011.html +5 -5
- data/ref/ruby/classes/Integer.src/M000012.html +5 -5
- data/ref/ruby/classes/Integer.src/M000015.html +25 -0
- data/ref/ruby/classes/Integer.src/M000016.html +31 -0
- data/ref/ruby/classes/Integer.src/M000017.html +12 -12
- data/ref/ruby/classes/Integer.src/M000018.html +17 -18
- data/ref/ruby/classes/Object.html +126 -0
- data/ref/ruby/classes/RDoc.html +5 -5
- data/ref/ruby/classes/RDoc.src/{M000061.html → M000081.html} +0 -0
- data/ref/ruby/classes/RubyVPI.html +50 -9
- data/ref/ruby/classes/String.html +22 -20
- data/ref/ruby/classes/String.src/M000020.html +36 -0
- data/ref/ruby/classes/String.src/M000021.html +41 -0
- data/ref/ruby/classes/String.src/M000022.html +5 -23
- data/ref/ruby/classes/String.src/M000023.html +5 -28
- data/ref/ruby/classes/{Vpi → VPI}/Handle.html +442 -140
- data/ref/ruby/classes/{Vpi/Handle.src/M000042.html → VPI/Handle.src/M000037.html} +4 -4
- data/ref/ruby/classes/VPI/Handle.src/M000038.html +21 -0
- data/ref/ruby/classes/VPI/Handle.src/M000039.html +18 -0
- data/ref/ruby/classes/{Vpi/Handle.src/M000036.html → VPI/Handle.src/M000040.html} +5 -5
- data/ref/ruby/classes/VPI/Handle.src/M000045.html +18 -0
- data/ref/ruby/classes/{Vpi/Handle.src/M000038.html → VPI/Handle.src/M000046.html} +5 -5
- data/ref/ruby/classes/VPI/Handle.src/M000057.html +18 -0
- data/ref/ruby/classes/{Vpi/Handle.src/M000040.html → VPI/Handle.src/M000058.html} +5 -5
- data/ref/ruby/classes/VPI/Handle.src/M000061.html +18 -0
- data/ref/ruby/classes/VPI/Handle.src/M000062.html +18 -0
- data/ref/ruby/classes/{Vpi/Handle.src/M000054.html → VPI/Handle.src/M000065.html} +11 -11
- data/ref/ruby/classes/VPI/Handle.src/M000067.html +21 -0
- data/ref/ruby/classes/VPI/Handle.src/M000068.html +28 -0
- data/ref/ruby/classes/VPI/Handle.src/M000069.html +50 -0
- data/ref/ruby/classes/{Vpi/Handle.src/M000048.html → VPI/Handle.src/M000070.html} +6 -6
- data/ref/ruby/classes/{Vpi/Handle.src/M000049.html → VPI/Handle.src/M000071.html} +6 -6
- data/ref/ruby/classes/{Vpi/Handle.src/M000050.html → VPI/Handle.src/M000072.html} +5 -5
- data/ref/ruby/classes/{Vpi/Handle.src/M000051.html → VPI/Handle.src/M000073.html} +17 -17
- data/ref/ruby/classes/VPI/Handle.src/M000075.html +18 -0
- data/ref/ruby/classes/VPI/Handle.src/M000076.html +40 -0
- data/ref/ruby/classes/{Vpi/Handle.src/M000056.html → VPI/Handle.src/M000077.html} +18 -18
- data/ref/ruby/classes/{Vpi → VPI}/S_vpi_time.html +22 -20
- data/ref/ruby/classes/VPI/S_vpi_time.src/M000078.html +18 -0
- data/ref/ruby/classes/VPI/S_vpi_time.src/M000079.html +19 -0
- data/ref/ruby/classes/{Vpi → VPI}/S_vpi_value.html +37 -23
- data/ref/ruby/classes/VPI/S_vpi_value.src/M000034.html +35 -0
- data/ref/ruby/classes/VPI/S_vpi_value.src/M000035.html +42 -0
- data/ref/ruby/classes/VPI/S_vpi_value.src/M000036.html +42 -0
- data/ref/ruby/classes/{Vpi.html → VPI.html} +129 -34
- data/ref/ruby/classes/VPI.src/M000027.html +19 -0
- data/ref/ruby/classes/VPI.src/M000028.html +18 -0
- data/ref/ruby/classes/VPI.src/M000029.html +19 -0
- data/ref/ruby/classes/VPI.src/M000031.html +25 -0
- data/ref/ruby/classes/VPI.src/M000032.html +26 -0
- data/ref/ruby/classes/VerilogParser/Module/Port.html +17 -15
- data/ref/ruby/classes/VerilogParser/Module/Port.src/M000004.html +23 -0
- data/ref/ruby/classes/VerilogParser/Module/Port.src/{M000007.html → M000005.html} +0 -0
- data/ref/ruby/classes/VerilogParser/Module/Port.src/M000006.html +5 -10
- data/ref/ruby/classes/VerilogParser/Module.html +7 -5
- data/ref/ruby/classes/VerilogParser/Module.src/{M000005.html → M000003.html} +0 -0
- data/ref/ruby/classes/VerilogParser.html +7 -5
- data/ref/ruby/classes/VerilogParser.src/{M000004.html → M000002.html} +0 -0
- data/ref/ruby/created.rid +1 -1
- data/ref/ruby/files/bin/generate_rb.html +2 -2
- data/ref/ruby/files/lib/ruby-vpi/{vpi_rb.html → core/callback_rb.html} +7 -8
- data/ref/ruby/files/lib/ruby-vpi/core/edge_rb.html +114 -0
- data/ref/ruby/files/lib/ruby-vpi/core/handle_rb.html +107 -0
- data/ref/ruby/files/lib/ruby-vpi/core/scheduler_rb.html +114 -0
- data/ref/ruby/files/lib/ruby-vpi/core/struct_rb.html +108 -0
- data/ref/ruby/files/lib/ruby-vpi/core_rb.html +121 -0
- data/ref/ruby/files/lib/ruby-vpi/rcov_rb.html +1 -1
- data/ref/ruby/files/lib/ruby-vpi/runner_boot_loader_rb.html +5 -41
- data/ref/ruby/files/lib/ruby-vpi/runner_boot_loader_rb.src/M000001.html +3 -3
- data/ref/ruby/files/lib/ruby-vpi/runner_rb.html +1 -1
- data/ref/ruby/files/lib/ruby-vpi_rb.html +1 -1
- data/ref/ruby/fr_class_index.html +5 -4
- data/ref/ruby/fr_file_index.html +6 -1
- data/ref/ruby/fr_method_index.html +80 -60
- metadata +126 -103
- data/ext/swig_vpi.h +0 -924
- data/ext/swig_wrap.cin +0 -7083
- data/lib/ruby-vpi/vpi.rb +0 -651
- data/ref/ruby/classes/Integer.src/M000013.html +0 -18
- data/ref/ruby/classes/Integer.src/M000019.html +0 -25
- data/ref/ruby/classes/Integer.src/M000020.html +0 -30
- data/ref/ruby/classes/String.src/M000024.html +0 -18
- data/ref/ruby/classes/String.src/M000025.html +0 -18
- data/ref/ruby/classes/VerilogParser/Module/Port.src/M000008.html +0 -18
- data/ref/ruby/classes/Vpi/Handle.src/M000035.html +0 -18
- data/ref/ruby/classes/Vpi/Handle.src/M000037.html +0 -18
- data/ref/ruby/classes/Vpi/Handle.src/M000039.html +0 -18
- data/ref/ruby/classes/Vpi/Handle.src/M000041.html +0 -18
- data/ref/ruby/classes/Vpi/Handle.src/M000043.html +0 -21
- data/ref/ruby/classes/Vpi/Handle.src/M000044.html +0 -21
- data/ref/ruby/classes/Vpi/Handle.src/M000045.html +0 -22
- data/ref/ruby/classes/Vpi/Handle.src/M000046.html +0 -50
- data/ref/ruby/classes/Vpi/Handle.src/M000047.html +0 -91
- data/ref/ruby/classes/Vpi/Handle.src/M000053.html +0 -18
- data/ref/ruby/classes/Vpi/Handle.src/M000057.html +0 -40
- data/ref/ruby/classes/Vpi/S_vpi_time.src/M000058.html +0 -18
- data/ref/ruby/classes/Vpi/S_vpi_time.src/M000059.html +0 -19
- data/ref/ruby/classes/Vpi/S_vpi_value.src/M000032.html +0 -18
- data/ref/ruby/classes/Vpi/S_vpi_value.src/M000033.html +0 -18
- data/ref/ruby/classes/Vpi/S_vpi_value.src/M000034.html +0 -18
- data/ref/ruby/classes/Vpi.src/M000029.html +0 -28
- data/ref/ruby/classes/Vpi.src/M000030.html +0 -39
- data/ref/ruby/classes/Vpi.src/M000031.html +0 -20
- data/ref/ruby/files/lib/ruby-vpi/runner_boot_loader_rb.src/M000002.html +0 -18
- data/samp/counter/RSpec/counter_proto.rb +0 -10
- data/samp/counter/xUnit/counter_proto.rb +0 -10
- data/samp/pipelined_alu/hw5_unit_proto.rb +0 -4
- data/samp/register_file/register_file_proto.rb +0 -11
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Simulation callbacks.
|
|
2
|
+
#--
|
|
3
|
+
# Copyright 2006 Suraj N. Kurapati
|
|
4
|
+
# See the file named LICENSE for details.
|
|
5
|
+
|
|
6
|
+
require 'singleton'
|
|
7
|
+
|
|
8
|
+
module RubyVPI
|
|
9
|
+
class CallbackClass #:nodoc:
|
|
10
|
+
include Singleton
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@id2handler = {}
|
|
14
|
+
@id2receipt = {}
|
|
15
|
+
@lock = Mutex.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def attach aData, &aHandler
|
|
19
|
+
raise ArgumentError, "block must be given" unless block_given?
|
|
20
|
+
id = aHandler.object_id.to_s
|
|
21
|
+
|
|
22
|
+
# register the callback with Verilog
|
|
23
|
+
aData.user_data = id
|
|
24
|
+
aData.cb_rtn = VPI::Vlog_relay_ruby
|
|
25
|
+
receipt = VPI::__callback__vpi_register_cb(aData)
|
|
26
|
+
|
|
27
|
+
@lock.synchronize do
|
|
28
|
+
@id2handler[id] = aHandler
|
|
29
|
+
@id2receipt[id] = receipt
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
receipt
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def detach aData
|
|
36
|
+
id = aData.user_data.to_s
|
|
37
|
+
receipt = @lock.synchronize{ @id2receipt[id] }
|
|
38
|
+
|
|
39
|
+
if receipt
|
|
40
|
+
VPI::__callback__vpi_remove_cb(receipt)
|
|
41
|
+
|
|
42
|
+
@lock.synchronize do
|
|
43
|
+
@id2handler.delete id
|
|
44
|
+
@id2receipt.delete id
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Transfers control to the simulator, which will return control
|
|
50
|
+
# during the given time slot after the given number of time steps.
|
|
51
|
+
def relay_verilog aTimeSlot, aNumSteps
|
|
52
|
+
# schedule wake-up callback from verilog
|
|
53
|
+
time = VPI::S_vpi_time.new
|
|
54
|
+
time.integer = aNumSteps
|
|
55
|
+
time.type = VPI::VpiSimTime
|
|
56
|
+
|
|
57
|
+
value = VPI::S_vpi_value.new
|
|
58
|
+
value.format = VPI::VpiSuppressVal
|
|
59
|
+
|
|
60
|
+
alarm = VPI::S_cb_data.new
|
|
61
|
+
alarm.reason = aTimeSlot
|
|
62
|
+
alarm.cb_rtn = VPI::Vlog_relay_ruby
|
|
63
|
+
alarm.obj = nil
|
|
64
|
+
alarm.time = time
|
|
65
|
+
alarm.value = value
|
|
66
|
+
alarm.index = 0
|
|
67
|
+
alarm.user_data = nil
|
|
68
|
+
|
|
69
|
+
VPI.vpi_free_object(VPI::__callback__vpi_register_cb(alarm))
|
|
70
|
+
|
|
71
|
+
# transfer control to verilog
|
|
72
|
+
loop do
|
|
73
|
+
VPI::__extension__relay_verilog
|
|
74
|
+
|
|
75
|
+
if reason = VPI::__extension__relay_ruby_reason # might be nil
|
|
76
|
+
id = reason.user_data.to_s
|
|
77
|
+
|
|
78
|
+
handler = @lock.synchronize do
|
|
79
|
+
@id2handler[id]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
if handler
|
|
83
|
+
handler.call reason
|
|
84
|
+
else
|
|
85
|
+
break
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
Callback = CallbackClass.instance
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
module VPI
|
|
96
|
+
class Handle
|
|
97
|
+
# Registers a callback that is invoked
|
|
98
|
+
# whenever the value of this object changes.
|
|
99
|
+
def cbValueChange aOptions = {}, &aHandler
|
|
100
|
+
raise ArgumentError unless block_given?
|
|
101
|
+
|
|
102
|
+
aOptions[:time] ||= S_vpi_time.new(:type => VpiSuppressTime)
|
|
103
|
+
aOptions[:value] ||= S_vpi_value.new(:format => VpiSuppressVal)
|
|
104
|
+
|
|
105
|
+
alarm = S_cb_data.new(
|
|
106
|
+
:reason => CbValueChange,
|
|
107
|
+
:obj => self,
|
|
108
|
+
:time => aOptions[:time],
|
|
109
|
+
:value => aOptions[:value],
|
|
110
|
+
:index => 0
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
vpi_register_cb alarm, &aHandler
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
alias_method :__callback__vpi_register_cb, :vpi_register_cb
|
|
119
|
+
module_function :__callback__vpi_register_cb
|
|
120
|
+
|
|
121
|
+
# This is a Ruby version of the vpi_register_cb C function. It is
|
|
122
|
+
# identical to the C function, except for the following differences:
|
|
123
|
+
#
|
|
124
|
+
# * This method accepts a block (callback handler)
|
|
125
|
+
# which is executed whenever the callback occurs.
|
|
126
|
+
#
|
|
127
|
+
# * This method overwrites the +cb_rtn+ and +user_data+
|
|
128
|
+
# fields of the given +S_cb_data+ object.
|
|
129
|
+
#
|
|
130
|
+
def vpi_register_cb aData, &aHandler # :yields: VPI::S_cb_data
|
|
131
|
+
raise ArgumentError, "block must be given" unless block_given?
|
|
132
|
+
RubyVPI::Callback.attach(aData, &aHandler)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
alias_method :__callback__vpi_remove_cb, :vpi_remove_cb
|
|
137
|
+
module_function :__callback__vpi_remove_cb
|
|
138
|
+
|
|
139
|
+
def vpi_remove_cb aData # :nodoc:
|
|
140
|
+
RubyVPI::Callback.detach(aData)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Value change / edge detection for handles.
|
|
2
|
+
#--
|
|
3
|
+
# Copyright 2007 Suraj N. Kurapati
|
|
4
|
+
# See the file named LICENSE for details.
|
|
5
|
+
|
|
6
|
+
require 'singleton'
|
|
7
|
+
|
|
8
|
+
module RubyVPI
|
|
9
|
+
class EdgeClass #:nodoc:
|
|
10
|
+
include Singleton
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@handles = []
|
|
14
|
+
@lock = Mutex.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Begins monitoring the given handle for value change.
|
|
18
|
+
def monitor aHandle
|
|
19
|
+
# ignore handles that cannot hold a meaningful value
|
|
20
|
+
type = VPI::vpi_get_str(VpiType, aHandle)
|
|
21
|
+
return if type =~ /Bit|Array|Module|Parameter/
|
|
22
|
+
|
|
23
|
+
@lock.synchronize do
|
|
24
|
+
unless @handles.include? aHandle
|
|
25
|
+
@handles << aHandle
|
|
26
|
+
refresh_handle aHandle
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Refreshes the cached value of all monitored handles.
|
|
32
|
+
def refresh_cache
|
|
33
|
+
@lock.synchronize do
|
|
34
|
+
@handles.each do |h|
|
|
35
|
+
refresh_handle h
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Remember the current value as the "previous" value.
|
|
41
|
+
def refresh_handle aHandle
|
|
42
|
+
aHandle.instance_eval do
|
|
43
|
+
@__edge__prev_val = get_value(VpiHexStrVal)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Edge = EdgeClass.instance
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
module VPI
|
|
52
|
+
class Handle
|
|
53
|
+
# create methods for detecting all possible value changes
|
|
54
|
+
vals = %w[0 1 x z]
|
|
55
|
+
edges = vals.map {|a| vals.map {|b| a + b}}.flatten
|
|
56
|
+
|
|
57
|
+
edges.each do |edge|
|
|
58
|
+
meth = "change_#{edge}?"
|
|
59
|
+
old, new = edge.split(//)
|
|
60
|
+
|
|
61
|
+
old_int = old =~ /[01]/
|
|
62
|
+
new_int = new =~ /[01]/
|
|
63
|
+
|
|
64
|
+
old_read = old_int ? 'int' : 'hex'
|
|
65
|
+
new_read = new_int ? 'VpiIntVal' : 'VpiHexStrVal'
|
|
66
|
+
|
|
67
|
+
old_test = old_int ? "== #{old}" : "=~ /#{old}/i"
|
|
68
|
+
new_test = new_int ? "== #{new}" : "=~ /#{new}/i"
|
|
69
|
+
|
|
70
|
+
class_eval %{
|
|
71
|
+
def #{meth}
|
|
72
|
+
old = __edge__prev_val_#{old_read}
|
|
73
|
+
new = get_value(#{new_read})
|
|
74
|
+
|
|
75
|
+
old #{old_test} and new #{new_test}
|
|
76
|
+
end
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
alias posedge? change_01?
|
|
81
|
+
alias negedge? change_10?
|
|
82
|
+
|
|
83
|
+
# Tests if either a positive or negative edge has occurred.
|
|
84
|
+
def edge?
|
|
85
|
+
posedge? or negedge?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Tests if the logic value of this handle has
|
|
89
|
+
# changed since the last simulation time step.
|
|
90
|
+
def change?
|
|
91
|
+
old = __edge__prev_val_hex
|
|
92
|
+
new = get_value(VpiHexStrVal)
|
|
93
|
+
|
|
94
|
+
old != new
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
# Returns the previous value as a hex string.
|
|
101
|
+
def __edge__prev_val_hex #:nodoc:
|
|
102
|
+
@__edge__prev_val.to_s
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Returns the previous value as an integer.
|
|
106
|
+
def __edge__prev_val_int #:nodoc:
|
|
107
|
+
__edge__prev_val_hex.to_i(16)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
%w[
|
|
112
|
+
vpi_handle_by_name
|
|
113
|
+
vpi_handle_by_index
|
|
114
|
+
vpi_handle
|
|
115
|
+
vpi_scan
|
|
116
|
+
].each do |src|
|
|
117
|
+
dst = "__value_change__#{src}"
|
|
118
|
+
alias_method dst, src
|
|
119
|
+
|
|
120
|
+
define_method src do |*args|
|
|
121
|
+
if result = __send__(dst, *args)
|
|
122
|
+
RubyVPI::Edge.monitor(result)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
result
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
# Interface to VPI handles.
|
|
2
|
+
#--
|
|
3
|
+
# Copyright 2006 Suraj N. Kurapati
|
|
4
|
+
# See the file named LICENSE for details.
|
|
5
|
+
|
|
6
|
+
module VPI
|
|
7
|
+
Handle = SWIG::TYPE_p_unsigned_int
|
|
8
|
+
|
|
9
|
+
# A handle is an object inside a Verilog simulation (see
|
|
10
|
+
# *vpiHandle* in IEEE Std. 1364-2005). VPI types and
|
|
11
|
+
# properties listed in ext/vpi_user.h can be specified by
|
|
12
|
+
# their names (strings or symbols) or integer constants.
|
|
13
|
+
#
|
|
14
|
+
# = Example names
|
|
15
|
+
# * "intVal"
|
|
16
|
+
# * :intVal
|
|
17
|
+
# * "vpiIntVal"
|
|
18
|
+
# * :vpiIntVal
|
|
19
|
+
# * "VpiIntVal"
|
|
20
|
+
# * :VpiIntVal
|
|
21
|
+
#
|
|
22
|
+
# = Example constants
|
|
23
|
+
# * VpiIntVal
|
|
24
|
+
# * VpiModule
|
|
25
|
+
# * VpiReg
|
|
26
|
+
#
|
|
27
|
+
class Handle
|
|
28
|
+
include VPI
|
|
29
|
+
|
|
30
|
+
# Tests if the logic value of this handle is unknown (x).
|
|
31
|
+
def x?
|
|
32
|
+
get_value(VpiHexStrVal) =~ /x/i
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Sets the logic value of this handle to unknown (x).
|
|
36
|
+
def x!
|
|
37
|
+
put_value('x', VpiHexStrVal)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
alias unknown? x?
|
|
41
|
+
alias unknown! x!
|
|
42
|
+
|
|
43
|
+
alias dont_care? x?
|
|
44
|
+
alias dont_care! x!
|
|
45
|
+
|
|
46
|
+
# Tests if the logic value of this handle is high impedance (z).
|
|
47
|
+
def z?
|
|
48
|
+
get_value(VpiHexStrVal) =~ /z/i
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Sets the logic value of this handle to high impedance (z).
|
|
52
|
+
def z!
|
|
53
|
+
put_value('z', VpiHexStrVal)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
alias hi_z? z?
|
|
57
|
+
alias hi_z! z!
|
|
58
|
+
|
|
59
|
+
alias high_z? z?
|
|
60
|
+
alias high_z! z!
|
|
61
|
+
|
|
62
|
+
alias high_impedance? z?
|
|
63
|
+
alias high_impedance! z!
|
|
64
|
+
|
|
65
|
+
alias tri_state? z?
|
|
66
|
+
alias tri_state! z!
|
|
67
|
+
|
|
68
|
+
alias floating? z?
|
|
69
|
+
alias floating! z!
|
|
70
|
+
|
|
71
|
+
# Tests if the logic value of this handle is at "logic high" level.
|
|
72
|
+
def high?
|
|
73
|
+
get_value(VpiIntVal) != 0
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Sets the logic value of this handle to "logic high" level.
|
|
77
|
+
def high!
|
|
78
|
+
put_value(1, VpiIntVal)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
alias one? high?
|
|
82
|
+
alias one! high!
|
|
83
|
+
|
|
84
|
+
# Tests if the logic value of this handle is at "logic low" level.
|
|
85
|
+
def low?
|
|
86
|
+
get_value(VpiHexStrVal) =~ /^0+$/
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Sets the logic value of this handle to "logic low" level.
|
|
90
|
+
def low!
|
|
91
|
+
put_value(0, VpiIntVal)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
alias zero? low?
|
|
95
|
+
alias zero! low!
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Inspects the given VPI property names, in
|
|
99
|
+
# addition to those common to all handles.
|
|
100
|
+
def inspect *aPropNames
|
|
101
|
+
aPropNames.unshift :name, :fullName, :size, :file, :lineNo, :hexStrVal
|
|
102
|
+
|
|
103
|
+
aPropNames.map! do |name|
|
|
104
|
+
"#{name}=#{__send__(name).inspect}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
"#<VPI::Handle #{vpi_get_str(VpiType, self)} #{aPropNames.join(', ')}>"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
alias to_s inspect
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
#---------------------------------------------------------------------------
|
|
114
|
+
# reading & writing values
|
|
115
|
+
#---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
# Reads the value using the given format (name or
|
|
118
|
+
# integer constant) and returns a +S_vpi_value+ object.
|
|
119
|
+
def get_value_wrapper aFormat
|
|
120
|
+
fmt = resolve_prop_type(aFormat)
|
|
121
|
+
val = S_vpi_value.new(:format => fmt)
|
|
122
|
+
vpi_get_value(self, val)
|
|
123
|
+
val
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Reads the value using the given format (name or integer constant) and
|
|
127
|
+
# returns it. If a format is not given, then it is assumed to be VpiIntVal.
|
|
128
|
+
def get_value aFormat = VpiIntVal
|
|
129
|
+
fmt = resolve_prop_type(aFormat)
|
|
130
|
+
@size ||= vpi_get(VpiSize, self)
|
|
131
|
+
|
|
132
|
+
if fmt == VpiIntVal and @size > INTEGER_BITS
|
|
133
|
+
fmt = VpiHexStrVal
|
|
134
|
+
val = get_value_wrapper(fmt)
|
|
135
|
+
val.read(fmt).to_i(16)
|
|
136
|
+
else
|
|
137
|
+
val = get_value_wrapper(fmt)
|
|
138
|
+
val.read(fmt)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Writes the given value using the given format (name or integer
|
|
143
|
+
# constant), time, and delay, and then returns the written value.
|
|
144
|
+
#
|
|
145
|
+
# * If a format is not given, then the Verilog simulator
|
|
146
|
+
# will attempt to determine the correct format.
|
|
147
|
+
#
|
|
148
|
+
def put_value aValue, aFormat = nil, aTime = nil, aDelay = VpiNoDelay
|
|
149
|
+
if vpi_get(VpiType, self) == VpiNet
|
|
150
|
+
aDelay = VpiForceFlag
|
|
151
|
+
|
|
152
|
+
if driver = self[VpiDriver].find {|d| vpi_get(VpiType, d) != VpiForce}
|
|
153
|
+
warn "forcing value #{aValue.inspect} onto wire #{self} that is already driven by #{driver.inspect}"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
aFormat =
|
|
158
|
+
if aFormat
|
|
159
|
+
resolve_prop_type(aFormat)
|
|
160
|
+
else
|
|
161
|
+
S_vpi_value.detect_format(aValue) ||
|
|
162
|
+
get_value_wrapper(VpiObjTypeVal).format # let the simulator detect
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
if aFormat == VpiIntVal
|
|
166
|
+
@size ||= vpi_get(VpiSize, self)
|
|
167
|
+
|
|
168
|
+
unless @size < INTEGER_BITS
|
|
169
|
+
aFormat = VpiHexStrVal
|
|
170
|
+
aValue = aValue.to_i.to_s(16)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
aTime ||= S_vpi_time.new(:type => VpiSimTime, :integer => 0)
|
|
175
|
+
|
|
176
|
+
wrapper = S_vpi_value.new(:format => aFormat)
|
|
177
|
+
result = wrapper.write(aValue, aFormat)
|
|
178
|
+
|
|
179
|
+
vpi_put_value(self, wrapper, aTime, aDelay)
|
|
180
|
+
|
|
181
|
+
result
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Forces the given value (see arguments for #put_value) onto this handle.
|
|
185
|
+
def force_value *args
|
|
186
|
+
args[3] = VpiForceFlag
|
|
187
|
+
put_value(*args)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Releases a previously forced value on this handle.
|
|
191
|
+
def release_value
|
|
192
|
+
# this doesn't really change the value, it only removes the force flag
|
|
193
|
+
put_value(0, VpiIntVal, nil, VpiReleaseFlag)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Tests if there is currently a value forced onto this handle.
|
|
197
|
+
def force?
|
|
198
|
+
self[VpiDriver].any? {|d| vpi_get(VpiType, d) == VpiForce}
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
#---------------------------------------------------------------------------
|
|
203
|
+
# accessing related handles / traversing the hierarchy
|
|
204
|
+
#---------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
# Returns an array of child handles of the
|
|
207
|
+
# given types (name or integer constant).
|
|
208
|
+
def [] *aTypes
|
|
209
|
+
handles = []
|
|
210
|
+
|
|
211
|
+
aTypes.each do |arg|
|
|
212
|
+
t = resolve_prop_type(arg)
|
|
213
|
+
|
|
214
|
+
if itr = vpi_iterate(t, self)
|
|
215
|
+
while h = vpi_scan(itr)
|
|
216
|
+
handles << h
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
handles
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# inherit Enumerable methods, such as #each, #map, #select, etc.
|
|
225
|
+
Enumerable.instance_methods.push('each').each do |meth|
|
|
226
|
+
# using a string because define_method
|
|
227
|
+
# does not accept a block until Ruby 1.9
|
|
228
|
+
class_eval %{
|
|
229
|
+
def #{meth}(*args, &block)
|
|
230
|
+
if ary = self[*args]
|
|
231
|
+
ary.#{meth}(&block)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
}, __FILE__, __LINE__
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# bypass Enumerable's #to_a method, which relies on #each
|
|
238
|
+
alias to_a []
|
|
239
|
+
|
|
240
|
+
# Sort by absolute VPI path.
|
|
241
|
+
def <=> other
|
|
242
|
+
get_value(VpiFullName) <=> other.get_value(VpiFullName)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
#---------------------------------------------------------------------------
|
|
247
|
+
# accessing VPI properties
|
|
248
|
+
#---------------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
@@propCache = Hash.new {|h, k| h[k] = Property.new(k)}
|
|
251
|
+
|
|
252
|
+
undef type # used to access VpiType
|
|
253
|
+
|
|
254
|
+
# Provides access to this handle's (1) child handles
|
|
255
|
+
# and (2) VPI properties through method calls. In the
|
|
256
|
+
# case that a child handle has the same name as a VPI
|
|
257
|
+
# property, the child handle will be accessed instead
|
|
258
|
+
# of the VPI property. However, you can still access
|
|
259
|
+
# the VPI property via #get_value and #put_value.
|
|
260
|
+
def method_missing aMeth, *aArgs, &aBlockArg
|
|
261
|
+
# cache the result for future accesses, in order
|
|
262
|
+
# to cut down number of calls to method_missing()
|
|
263
|
+
eigen_class = (class << self; self; end)
|
|
264
|
+
|
|
265
|
+
if child = vpi_handle_by_name(aMeth.to_s, self)
|
|
266
|
+
eigen_class.class_eval do
|
|
267
|
+
define_method aMeth do
|
|
268
|
+
child
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
child
|
|
273
|
+
else
|
|
274
|
+
# XXX: using a string because define_method() does
|
|
275
|
+
# not support a block argument until Ruby 1.9
|
|
276
|
+
eigen_class.class_eval %{
|
|
277
|
+
def #{aMeth}(*a, &b)
|
|
278
|
+
@@propCache[#{aMeth.inspect}].execute(self, *a, &b)
|
|
279
|
+
end
|
|
280
|
+
}, __FILE__, __LINE__
|
|
281
|
+
|
|
282
|
+
__send__(aMeth, *aArgs, &aBlockArg)
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
private
|
|
287
|
+
|
|
288
|
+
class Property # :nodoc:
|
|
289
|
+
attr_reader :name, :type, :accessor, :operation
|
|
290
|
+
|
|
291
|
+
def initialize aMethName
|
|
292
|
+
@methName = aMethName.to_s
|
|
293
|
+
|
|
294
|
+
# parse property information from the given method name
|
|
295
|
+
tokens = @methName.split('_')
|
|
296
|
+
|
|
297
|
+
tokens.last.sub!(/[\?!=]$/, '')
|
|
298
|
+
addendum = $&
|
|
299
|
+
@isAssign = $& == '='
|
|
300
|
+
isQuery = $& == '?'
|
|
301
|
+
|
|
302
|
+
tokens.last =~ /^[a-z]$/ && tokens.pop
|
|
303
|
+
@accessor = $&
|
|
304
|
+
|
|
305
|
+
@name = tokens.pop
|
|
306
|
+
|
|
307
|
+
@operation = unless tokens.empty?
|
|
308
|
+
tokens.join('_') << (addendum || '')
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# determine the VPI integer type for the property
|
|
312
|
+
@name = @name.to_ruby_const_name
|
|
313
|
+
@name.insert 0, 'Vpi' unless @name =~ /^[Vv]pi/
|
|
314
|
+
|
|
315
|
+
begin
|
|
316
|
+
@type = VPI.const_get(@name)
|
|
317
|
+
rescue NameError
|
|
318
|
+
raise ArgumentError, "#{@name.inspect} is not a valid VPI property"
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
@accessor = if @accessor
|
|
322
|
+
@accessor.to_sym
|
|
323
|
+
else
|
|
324
|
+
# infer accessor from VPI property @name
|
|
325
|
+
if isQuery
|
|
326
|
+
:b
|
|
327
|
+
else
|
|
328
|
+
case @name
|
|
329
|
+
when /Time$/
|
|
330
|
+
:d
|
|
331
|
+
|
|
332
|
+
when /Val$/
|
|
333
|
+
:l
|
|
334
|
+
|
|
335
|
+
when /Type$/, /Direction$/, /Index$/, /Size$/, /Strength\d?$/, /Polarity$/, /Edge$/, /Offset$/, /Mode$/, /LineNo$/
|
|
336
|
+
:i
|
|
337
|
+
|
|
338
|
+
when /Is[A-Z]/, /ed$/
|
|
339
|
+
:b
|
|
340
|
+
|
|
341
|
+
when /Name$/, /File$/, /Decompile$/
|
|
342
|
+
:s
|
|
343
|
+
|
|
344
|
+
when /Parent$/, /Inst$/, /Range$/, /Driver$/, /Net$/, /Load$/, /Conn$/, /Bit$/, /Word$/, /[LR]hs$/, /(In|Out)$/, /Term$/, /Argument$/, /Condition$/, /Use$/, /Operand$/, /Stmt$/, /Expr$/, /Scope$/, /Memory$/, /Delay$/
|
|
345
|
+
:h
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def execute aHandle, *aArgs, &aBlockArg
|
|
352
|
+
if @operation
|
|
353
|
+
aHandle.__send__(@operation, @type, *aArgs, &aBlockArg)
|
|
354
|
+
else
|
|
355
|
+
case @accessor
|
|
356
|
+
when :d # delay values
|
|
357
|
+
raise NotImplementedError, 'processing of delay values is not yet implemented.'
|
|
358
|
+
# TODO: vpi_put_delays
|
|
359
|
+
# TODO: vpi_get_delays
|
|
360
|
+
|
|
361
|
+
when :l # logic values
|
|
362
|
+
if @isAssign
|
|
363
|
+
value = aArgs.shift
|
|
364
|
+
aHandle.put_value(value, @type, *aArgs)
|
|
365
|
+
else
|
|
366
|
+
aHandle.get_value(@type)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
when :i # integer values
|
|
370
|
+
if @isAssign
|
|
371
|
+
raise NotImplementedError
|
|
372
|
+
else
|
|
373
|
+
vpi_get(@type, aHandle)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
when :b # boolean values
|
|
377
|
+
if @isAssign
|
|
378
|
+
raise NotImplementedError
|
|
379
|
+
else
|
|
380
|
+
value = vpi_get(@type, aHandle)
|
|
381
|
+
value && (value != 0) # zero is false in C
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
when :s # string values
|
|
385
|
+
if @isAssign
|
|
386
|
+
raise NotImplementedError
|
|
387
|
+
else
|
|
388
|
+
vpi_get_str(@type, aHandle)
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
when :h # handle values
|
|
392
|
+
if @isAssign
|
|
393
|
+
raise NotImplementedError
|
|
394
|
+
else
|
|
395
|
+
vpi_handle(@type, aHandle)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
when :a # array of child handles
|
|
399
|
+
if @isAssign
|
|
400
|
+
raise NotImplementedError
|
|
401
|
+
else
|
|
402
|
+
aHandle[@type]
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
else
|
|
406
|
+
raise NoMethodError, "cannot access VPI property #{@name.inspect} for handle #{aHandle.inspect} through method #{@methName.inspect} with arguments #{aArgs.inspect}"
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# resolve type names into type constants
|
|
413
|
+
def resolve_prop_type aNameOrType
|
|
414
|
+
if aNameOrType.respond_to? :to_int and not aNameOrType.is_a? Symbol
|
|
415
|
+
aNameOrType.to_int
|
|
416
|
+
else
|
|
417
|
+
@@propCache[aNameOrType.to_sym].type
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
end
|