rpicsim 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/Gemfile +7 -6
  4. data/Introduction.md +3 -1
  5. data/README.md +2 -2
  6. data/docs/ChangeLog.md +6 -0
  7. data/docs/Contributing.md +1 -1
  8. data/docs/DefiningSimulationClass.md +11 -10
  9. data/docs/HowMPLABXIsFound.md +1 -1
  10. data/docs/IntegrationTesting.md +1 -1
  11. data/docs/IntroductionToRSpec.md +8 -5
  12. data/docs/IntroductionToRuby.md +2 -2
  13. data/docs/KnownIssues.md +46 -57
  14. data/docs/Labels.md +5 -4
  15. data/docs/Manual.md +1 -1
  16. data/docs/Memories.md +70 -0
  17. data/docs/PersistentExpectations.md +31 -2
  18. data/docs/Pins.md +5 -7
  19. data/docs/PreventingCallStackOverflow.md +4 -6
  20. data/docs/QuickStartGuide.md +5 -5
  21. data/docs/RSpecIntegration.md +2 -2
  22. data/docs/RamWatcher.md +22 -11
  23. data/docs/Running.md +4 -6
  24. data/docs/Stubbing.md +4 -4
  25. data/docs/SupportedDevices.md +2 -11
  26. data/docs/SupportedMPLABXVersions.md +1 -0
  27. data/docs/SupportedOperatingSystems.md +3 -2
  28. data/docs/UnitTesting.md +1 -1
  29. data/docs/Variables.md +81 -25
  30. data/lib/rpicsim.rb +0 -12
  31. data/lib/rpicsim/call_stack_info.rb +43 -47
  32. data/lib/rpicsim/composite_memory.rb +53 -0
  33. data/lib/rpicsim/flaws.rb +34 -22
  34. data/lib/rpicsim/instruction.rb +204 -48
  35. data/lib/rpicsim/label.rb +4 -4
  36. data/lib/rpicsim/memory.rb +44 -23
  37. data/lib/rpicsim/memory_watcher.rb +14 -22
  38. data/lib/rpicsim/mplab.rb +38 -119
  39. data/lib/rpicsim/mplab/mplab_assembly.rb +23 -18
  40. data/lib/rpicsim/mplab/mplab_device_info.rb +9 -9
  41. data/lib/rpicsim/mplab/mplab_disassembler.rb +5 -6
  42. data/lib/rpicsim/mplab/mplab_instruction.rb +87 -16
  43. data/lib/rpicsim/mplab/mplab_loader.rb +106 -0
  44. data/lib/rpicsim/mplab/mplab_memory.rb +19 -6
  45. data/lib/rpicsim/mplab/mplab_nmmr_info.rb +4 -4
  46. data/lib/rpicsim/mplab/mplab_observer.rb +15 -10
  47. data/lib/rpicsim/mplab/mplab_pin.rb +3 -3
  48. data/lib/rpicsim/mplab/mplab_processor.rb +5 -5
  49. data/lib/rpicsim/mplab/mplab_program_file.rb +29 -17
  50. data/lib/rpicsim/mplab/mplab_register.rb +5 -5
  51. data/lib/rpicsim/mplab/mplab_sfr_info.rb +4 -4
  52. data/lib/rpicsim/mplab/mplab_simulator.rb +27 -30
  53. data/lib/rpicsim/pin.rb +6 -6
  54. data/lib/rpicsim/program_counter.rb +3 -3
  55. data/lib/rpicsim/program_file.rb +39 -81
  56. data/lib/rpicsim/rspec/be_predicate.rb +1 -1
  57. data/lib/rpicsim/rspec/helpers.rb +1 -1
  58. data/lib/rpicsim/rspec/persistent_expectations.rb +17 -2
  59. data/lib/rpicsim/rspec/sim_diagnostics.rb +5 -5
  60. data/lib/rpicsim/search.rb +1 -1
  61. data/lib/rpicsim/sim.rb +153 -228
  62. data/lib/rpicsim/stack_pointer.rb +41 -0
  63. data/lib/rpicsim/stack_trace.rb +1 -1
  64. data/lib/rpicsim/storage/memory_integer.rb +235 -0
  65. data/lib/rpicsim/{register.rb → storage/register.rb} +18 -18
  66. data/lib/rpicsim/variable.rb +25 -211
  67. data/lib/rpicsim/variable_set.rb +93 -0
  68. data/lib/rpicsim/version.rb +2 -2
  69. metadata +9 -4
  70. data/docs/SFRs.md +0 -71
@@ -0,0 +1,41 @@
1
+ module RPicSim
2
+ # Instances of this class represent the call stack pointer in a running
3
+ # simulation.
4
+ # A value of 0 means that the stack is empty, regardless of what device
5
+ # architecture you are simulating.
6
+ class StackPointer
7
+ # Initializes the StackPointer object.
8
+ # This be called when the call stack is empty, because this object uses
9
+ # the initial value of stkptr to deduce how it works.
10
+ # @param stkptr The STKPTR register of the simulation.
11
+ def initialize(stkptr)
12
+ @stkptr = stkptr
13
+ @stkptr_initial_value = @stkptr.value
14
+ end
15
+
16
+ def value
17
+ if @stkptr_initial_value > 0
18
+ raw_value = @stkptr.value
19
+ if raw_value == @stkptr_initial_value
20
+ 0
21
+ else
22
+ raw_value + 1
23
+ end
24
+ else
25
+ @stkptr.value
26
+ end
27
+ end
28
+
29
+ def value=(value)
30
+ @stkptr.value = if @stkptr_initial_value > 0
31
+ if value == 0
32
+ @stkptr_initial_value
33
+ else
34
+ value - 1
35
+ end
36
+ else
37
+ value
38
+ end
39
+ end
40
+ end
41
+ end
@@ -29,4 +29,4 @@ module RPicSim
29
29
  description
30
30
  end
31
31
  end
32
- end
32
+ end
@@ -0,0 +1,235 @@
1
+ module RPicSim::Storage
2
+ # This class and its subclasses represent integers stored in RAM.
3
+ class MemoryInteger
4
+ attr_reader :name, :address
5
+ attr_writer :memory
6
+
7
+ # Creates a new MemoryInteger object not bound to any memory yet.
8
+ # @param name [Symbol] The name of the variable.
9
+ # @param address [Integer] should be the address of the variable
10
+ def initialize(name, address)
11
+ @name = name
12
+ @address = address
13
+ end
14
+
15
+ # Creates a new Variable that is bound to the specified memory.
16
+ # @param memory [RPicSim::Memory]
17
+ def bind(memory)
18
+ bound_var = dup
19
+ bound_var.memory = memory
20
+ bound_var
21
+ end
22
+
23
+ # @return [Range] The addresses of each byte that is part of this variable.
24
+ def addresses
25
+ address ... (address + size)
26
+ end
27
+
28
+ # Reads the value of the variable from memory.
29
+ # @return [Integer]
30
+ def value
31
+ raise NoMethodError, 'value not implemented'
32
+ end
33
+
34
+ # Writes to the value to the variable's memory.
35
+ # @return [Integer]
36
+ def value=(val)
37
+ raise NoMethodError, 'value= not implemented'
38
+ end
39
+
40
+ def memory_value=(val)
41
+ self.value = val
42
+ end
43
+
44
+ def memory_value(val)
45
+ value
46
+ end
47
+
48
+ def to_s
49
+ name.to_s
50
+ end
51
+
52
+ def inspect
53
+ '<%s %s 0x%x>' % [self.class, name, address]
54
+ end
55
+
56
+ private
57
+ def check_value(value, allowed_values)
58
+ if !allowed_values.include?(value)
59
+ raise ArgumentError, "Invalid value #{value} written to #{name}."
60
+ end
61
+ end
62
+ end
63
+
64
+ # Represents an unsigned 8-bit variable.
65
+ class MemoryUInt8 < MemoryInteger
66
+ def size
67
+ 1
68
+ end
69
+
70
+ def value
71
+ @memory.read_byte(@address)
72
+ end
73
+
74
+ def value=(val)
75
+ check_value val, 0..255
76
+ @memory.write_byte(@address, val)
77
+ end
78
+
79
+ end
80
+
81
+ # Represents a signed 8-bit variable.
82
+ class MemoryInt8 < MemoryInteger
83
+ def size
84
+ 1
85
+ end
86
+
87
+ def value
88
+ val = @memory.read_byte(@address)
89
+ val -= 0x100 if val >= 0x80
90
+ val
91
+ end
92
+
93
+ def value=(val)
94
+ check_value val, -0x80...0x80
95
+ @memory.write_byte(@address, val & 0xFF)
96
+ @memory.write_byte(@address + 1, (val >> 8) & 0xFF)
97
+ end
98
+ end
99
+
100
+ # Represents an unsigned 16-bit variable.
101
+ class MemoryUInt16 < MemoryInteger
102
+ def size
103
+ 2
104
+ end
105
+
106
+ def value
107
+ @memory.read_byte(@address) + 256 * @memory.read_byte(@address + 1)
108
+ end
109
+
110
+ def value=(val)
111
+ check_value val, 0...0x10000
112
+ @memory.write_byte(@address, val & 0xFF)
113
+ @memory.write_byte(@address + 1, (val >> 8) & 0xFF)
114
+ end
115
+ end
116
+
117
+ # Represents a signed 16-bit variable.
118
+ class MemoryInt16 < MemoryInteger
119
+ def size
120
+ 2
121
+ end
122
+
123
+ def value
124
+ val = @memory.read_byte(@address) + 256 * @memory.read_byte(@address + 1)
125
+ val -= 0x10000 if val >= 0x8000
126
+ val
127
+ end
128
+
129
+ def value=(val)
130
+ check_value val, -0x8000...0x8000
131
+ @memory.write_byte(@address, val & 0xFF)
132
+ @memory.write_byte(@address + 1, (val >> 8) & 0xFF)
133
+ end
134
+ end
135
+
136
+ # Represents an unsigned 24-bit variable.
137
+ class MemoryUInt24 < MemoryInteger
138
+ def size
139
+ 3
140
+ end
141
+
142
+ def value
143
+ @memory.read_byte(@address) + 0x100 * @memory.read_byte(@address + 1) +
144
+ 0x10000 * @memory.read_byte(@address + 2)
145
+ end
146
+
147
+ def value=(val)
148
+ check_value val, 0...0x1000000
149
+ @memory.write_byte(@address, val & 0xFF)
150
+ @memory.write_byte(@address + 1, (val >> 8) & 0xFF)
151
+ @memory.write_byte(@address + 2, (val >> 16) & 0xFF)
152
+ end
153
+ end
154
+
155
+ # Represents a signed 24-bit variable.
156
+ class MemoryInt24 < MemoryInteger
157
+ def size
158
+ 3
159
+ end
160
+
161
+ def value
162
+ val = @memory.read_byte(@address) + 0x100 * @memory.read_byte(@address + 1) +
163
+ 0x10000 * @memory.read_byte(@address + 2)
164
+ val -= 0x1000000 if val >= 0x800000
165
+ val
166
+ end
167
+
168
+ def value=(val)
169
+ check_value val, -0x800000..0x800000
170
+ @memory.write_byte(@address, val & 0xFF)
171
+ @memory.write_byte(@address + 1, (val >> 8) & 0xFF)
172
+ @memory.write_byte(@address + 2, (val >> 16) & 0xFF)
173
+ end
174
+ end
175
+
176
+ # Represents an unsigned 32-bit variable.
177
+ class MemoryUInt32 < MemoryInteger
178
+ def size
179
+ 4
180
+ end
181
+
182
+ def value
183
+ @memory.read_byte(@address) + 0x100 * @memory.read_byte(@address + 1) +
184
+ 0x10000 * @memory.read_byte(@address + 2) +
185
+ 0x1000000 * @memory.read_byte(@address + 3)
186
+ end
187
+
188
+ def value=(val)
189
+ check_value val, 0...0x100000000
190
+ @memory.write_byte(@address, val & 0xFF)
191
+ @memory.write_byte(@address + 1, (val >> 8) & 0xFF)
192
+ @memory.write_byte(@address + 2, (val >> 16) & 0xFF)
193
+ @memory.write_byte(@address + 3, (val >> 24) & 0xFF)
194
+ end
195
+ end
196
+
197
+ # Represents a signed 32-bit variable.
198
+ class MemoryInt32 < MemoryInteger
199
+ def size
200
+ 4
201
+ end
202
+
203
+ def value
204
+ val = @memory.read_byte(@address) + 0x100 * @memory.read_byte(@address + 1) +
205
+ 0x10000 * @memory.read_byte(@address + 2) +
206
+ 0x1000000 * @memory.read_byte(@address + 3) +
207
+ 0x100000000 * @memory.read_byte(@address + 4)
208
+ val -= 0x100000000 if val >= 0x80000000
209
+ val
210
+ end
211
+
212
+ def value=(val)
213
+ check_value val, -0x80000000..0x80000000
214
+ @memory.write_byte(@address, val & 0xFF)
215
+ @memory.write_byte(@address + 1, (val >> 8) & 0xFF)
216
+ @memory.write_byte(@address + 2, (val >> 16) & 0xFF)
217
+ @memory.write_byte(@address + 3, (val >> 24) & 0xFF)
218
+ end
219
+ end
220
+
221
+ # Represents a word-sized variable.
222
+ # The size of the word will depend on the memory the variable lives in.
223
+ class MemoryWord < MemoryInteger
224
+ attr_accessor :size
225
+
226
+ def value
227
+ @memory.read_word(@address)
228
+ end
229
+
230
+ def value=(val)
231
+ @memory.write_word(@address, val)
232
+ end
233
+ end
234
+
235
+ end
@@ -1,16 +1,16 @@
1
- module RPicSim
1
+ module RPicSim::Storage
2
2
  class Register
3
3
  attr_reader :name
4
-
4
+
5
5
  # The size of the register in bytes.
6
6
  attr_reader :size
7
-
7
+
8
8
  # @param mplab_register [Mplab::MplabRegister]
9
9
  # @param memory An optional parameter that enables memory_value.
10
10
  def initialize(mplab_register, memory, width)
11
11
  @mplab_register = mplab_register
12
12
  @name = mplab_register.name.to_sym
13
-
13
+
14
14
  @size = case width
15
15
  when 8 then 1
16
16
  when 16 then 2
@@ -18,14 +18,14 @@ module RPicSim
18
18
  when 32 then 4
19
19
  else raise "Unsupported register width: #{name} is #{width}-bit."
20
20
  end
21
-
21
+
22
22
  var_type = case size
23
- when 1 then VariableU8
24
- when 2 then VariableU16
25
- when 3 then VariableU24
26
- when 4 then VariableU32
23
+ when 1 then MemoryUInt8
24
+ when 2 then MemoryUInt16
25
+ when 3 then MemoryUInt24
26
+ when 4 then MemoryUInt32
27
27
  end
28
-
28
+
29
29
  @var = var_type.new(name, address).bind(memory)
30
30
  end
31
31
 
@@ -34,13 +34,13 @@ module RPicSim
34
34
  def value=(val)
35
35
  @mplab_register.write val
36
36
  end
37
-
37
+
38
38
  # Reads the value of the register.
39
39
  # @return [Integer]
40
40
  def value
41
41
  @mplab_register.read
42
42
  end
43
-
43
+
44
44
  # For some registers, like STATUS, you cannot read and write the full
45
45
  # range of possible values using {#value=} because some bits are not
46
46
  # writable by the CPU.
@@ -49,7 +49,7 @@ module RPicSim
49
49
  def memory_value=(value)
50
50
  @var.value = value
51
51
  end
52
-
52
+
53
53
  # Reads the value directly from the memory object backing the register.
54
54
  def memory_value
55
55
  @var.value
@@ -60,19 +60,19 @@ module RPicSim
60
60
  def address
61
61
  @mplab_register.address
62
62
  end
63
-
63
+
64
64
  # Gets the range of addresses occupied.
65
65
  # @return [Range] A range of integers.
66
66
  def addresses
67
67
  address...(address + size)
68
68
  end
69
-
69
+
70
70
  def to_s
71
71
  name.to_s
72
72
  end
73
-
73
+
74
74
  def inspect
75
- "<%s %s 0x%x>" % [self.class, name, address]
75
+ '<%s %s 0x%x>' % [self.class, name, address]
76
76
  end
77
77
  end
78
- end
78
+ end
@@ -1,236 +1,50 @@
1
1
  module RPicSim
2
- # This class and its subclasses represent firmware-defined variables
3
- # in RAM or flash.
4
- #
5
- # Instances of this class come in two varieties:
6
- # - Unbound variables are just a name and an address.
7
- # - Bound variables have a name and an address and they are attached to
8
- # a memory object so you can actually read and write values from them.
9
2
  class Variable
10
- module ClassMethods
11
- # @return (Integer) The size of this type of variable in memory words.
12
- attr_reader :size
13
-
14
- # Specifies the size of this class of variables in memory words.
15
- # The units for this are usually bytes for RAM and more than a byte
16
- # for flash. This should be called inside the definition of a subclass of
17
- # {Variable}, not from anywhere else.
18
- def size_is(size)
19
- @size = size
20
- end
21
- end
22
-
23
- extend ClassMethods
24
-
25
- attr_reader :name, :address
26
- attr_writer :memory
27
-
28
- # Creates a new Variable object not bound to any memory yet.
29
- # @param name [Symbol] The name of the variable.
30
- # @param address [Integer] should be the address of the variable
31
- def initialize(name, address)
32
- @name = name
33
- @address = address
34
- end
35
-
36
- # Creates a new Variable that is bound to the specified memory.
37
- # @param memory [RPicSim::Memory]
38
- def bind(memory)
39
- bound_var = dup
40
- bound_var.memory = memory
41
- bound_var
42
- end
43
-
44
- # @return [Range] The addresses of each byte that is part of this variable.
45
- def addresses
46
- address ... (address + self.class.size)
3
+ # Creates a new Variable object.
4
+ # @param storage The internal storage for the variable.
5
+ def initialize(storage)
6
+ @storage = storage
47
7
  end
48
8
 
49
- # Reads the value of the variable from memory.
9
+ # Reads the value of the variable.
50
10
  # @return [Integer]
51
11
  def value
52
- raise NoMethodError, "value not implemented"
12
+ @storage.value
53
13
  end
54
14
 
55
- # Writes to the value to the variable's memory.
15
+ # Writes the value to the variable.
56
16
  # @return [Integer]
57
17
  def value=(val)
58
- raise NoMethodError, "value= not implemented"
59
- end
60
-
61
- def to_s
62
- name.to_s
63
- end
64
-
65
- def inspect
66
- "<%s %s 0x%x>" % [self.class, name, address]
67
- end
68
-
69
- private
70
- def check_value(value, allowed_values)
71
- if !allowed_values.include?(value)
72
- raise ArgumentError, "Invalid value #{value} written to #{name}."
73
- end
74
- end
75
- end
76
-
77
- # Represents an unsigned 8-bit variable.
78
- class VariableU8 < Variable
79
- size_is 1
80
-
81
- def value
82
- @memory.read_word(@address)
83
- end
84
-
85
- def value=(val)
86
- check_value val, 0..255
87
- @memory.write_word(@address, val)
88
- end
89
-
90
- end
91
-
92
- # Represents a signed 8-bit variable.
93
- class VariableS8 < Variable
94
- size_is 1
95
-
96
- def value
97
- val = @memory.read_word(@address)
98
- val -= 0x100 if val >= 0x80
99
- val
100
- end
101
-
102
- def value=(val)
103
- check_value val, -0x80...0x80
104
- @memory.write_word(@address, val & 0xFF)
105
- @memory.write_word(@address + 1, (val >> 8) & 0xFF)
106
- end
107
- end
108
-
109
- # Represents an unsigned 16-bit variable.
110
- class VariableU16 < Variable
111
- size_is 2
112
-
113
- def value
114
- @memory.read_word(@address) + 256 * @memory.read_word(@address + 1)
115
- end
116
-
117
- def value=(val)
118
- check_value val, 0...0x10000
119
- @memory.write_word(@address, val & 0xFF)
120
- @memory.write_word(@address + 1, (val >> 8) & 0xFF)
121
- end
122
- end
123
-
124
- # Represents a signed 16-bit variable.
125
- class VariableS16 < Variable
126
- size_is 2
127
-
128
- def value
129
- val = @memory.read_word(@address) + 256 * @memory.read_word(@address + 1)
130
- val -= 0x10000 if val >= 0x8000
131
- val
132
- end
133
-
134
- def value=(val)
135
- check_value val, -0x8000...0x8000
136
- @memory.write_word(@address, val & 0xFF)
137
- @memory.write_word(@address + 1, (val >> 8) & 0xFF)
138
- end
139
- end
140
-
141
- # Represents an unsigned 24-bit variable.
142
- class VariableU24 < Variable
143
- size_is 3
144
-
145
- def value
146
- @memory.read_word(@address) + 0x100 * @memory.read_word(@address + 1) +
147
- 0x10000 * @memory.read_word(@address + 2)
148
- end
149
-
150
- def value=(val)
151
- check_value val, 0...0x1000000
152
- @memory.write_word(@address, val & 0xFF)
153
- @memory.write_word(@address + 1, (val >> 8) & 0xFF)
154
- @memory.write_word(@address + 2, (val >> 16) & 0xFF)
155
- end
156
- end
157
-
158
- # Represents a signed 24-bit variable.
159
- class VariableS24 < Variable
160
- size_is 3
161
-
162
- def value
163
- val = @memory.read_word(@address) + 0x100 * @memory.read_word(@address + 1) +
164
- 0x10000 * @memory.read_word(@address + 2)
165
- val -= 0x1000000 if val >= 0x800000
166
- val
167
- end
168
-
169
- def value=(val)
170
- check_value val, -0x800000..0x800000
171
- @memory.write_word(@address, val & 0xFF)
172
- @memory.write_word(@address + 1, (val >> 8) & 0xFF)
173
- @memory.write_word(@address + 2, (val >> 16) & 0xFF)
18
+ @storage.value = val
174
19
  end
175
- end
176
-
177
- # Represents an unsigned 32-bit variable.
178
- class VariableU32 < Variable
179
- size_is 4
180
20
 
181
- def value
182
- @memory.read_word(@address) + 0x100 * @memory.read_word(@address + 1) +
183
- 0x10000 * @memory.read_word(@address + 2) +
184
- 0x1000000 * @memory.read_word(@address + 3)
21
+ # Writes the value to the variable in a lower-level way that
22
+ # overrides any read-only bits.
23
+ # For some types of variables, this is the same as {#value=}.
24
+ def memory_value=(val)
25
+ @storage.memory_value = val
185
26
  end
186
27
 
187
- def value=(val)
188
- check_value val, 0...0x100000000
189
- @memory.write_word(@address, val & 0xFF)
190
- @memory.write_word(@address + 1, (val >> 8) & 0xFF)
191
- @memory.write_word(@address + 2, (val >> 16) & 0xFF)
192
- @memory.write_word(@address + 3, (val >> 24) & 0xFF)
28
+ # Reads the value directly from the memory object backing the register.
29
+ # For some types of variables, this is the same as {#value}.
30
+ def memory_value
31
+ @storage.memory_value
193
32
  end
194
- end
195
33
 
196
- # Represents a signed 32-bit variable.
197
- class VariableS32 < Variable
198
- size_is 4
199
-
200
- def value
201
- val = @memory.read_word(@address) + 0x100 * @memory.read_word(@address + 1) +
202
- 0x10000 * @memory.read_word(@address + 2) +
203
- 0x1000000 * @memory.read_word(@address + 3) +
204
- 0x100000000 * @memory.read_word(@address + 4)
205
- val -= 0x100000000 if val >= 0x80000000
206
- val
34
+ def to_s
35
+ @storage.to_s
207
36
  end
208
37
 
209
- def value=(val)
210
- check_value val, -0x80000000..0x80000000
211
- @memory.write_word(@address, val & 0xFF)
212
- @memory.write_word(@address + 1, (val >> 8) & 0xFF)
213
- @memory.write_word(@address + 2, (val >> 16) & 0xFF)
214
- @memory.write_word(@address + 3, (val >> 24) & 0xFF)
38
+ def addresses
39
+ @storage.addresses
215
40
  end
216
- end
217
-
218
- # Represents a word-sized variable.
219
- # The size of the word will depend on the memory the variable lives in.
220
- class VariableWord < Variable
221
- size_is 1
222
-
223
- attr_writer :max_value
224
41
 
225
- def value
226
- @memory.read_word(@address)
42
+ def address
43
+ @storage.address
227
44
  end
228
45
 
229
- def value=(val)
230
- if @max_value
231
- check_value val, 0..@max_value
232
- end
233
- @memory.write_word(@address, val)
46
+ def name
47
+ @storage.name
234
48
  end
235
49
  end
236
50
  end