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
@@ -1,15 +1,3 @@
1
1
  require_relative 'rpicsim/version'
2
2
  require_relative 'rpicsim/sim'
3
3
  require_relative 'rpicsim/call_stack_info'
4
-
5
- # TODO: add a feature for Flash size reports
6
- # TODO: add a feature for RAM usage reports
7
-
8
- # TODO: add a feature for calculating the minimum and maximum iteration
9
- # time from one point in the program to another
10
- # (as a special case, this lets you calculate the iteration time of the
11
- # main loop if both points are the same)
12
-
13
- # TODO: add matchers: have_value(44), have_value.in(0..3), and maybe:
14
- # have_value.between(0, 14) (like RSpec built-in matcher)
15
- # have_value.within(3).of(56) (like RSpec built-in matcher)
@@ -18,7 +18,7 @@ module RPicSim
18
18
  # infos = CallStackInfo.hash_from_program_file(program_file, [0, 4])
19
19
  # infos[0].max_depth.should <= 5 # main-line code should take at most 5 levels
20
20
  # infos[4].max_depth.should <= 1 # ISR should take at most 1 stack level
21
- #
21
+ #
22
22
  # Additionally, it can generate reports of all different ways that the maximum
23
23
  # call stack depth can be achieved, which can be helpful if you need to reduce
24
24
  # your maximum stack depth.
@@ -27,7 +27,7 @@ module RPicSim
27
27
  # report. Returns the reports in a hash.
28
28
  #
29
29
  # @param program_file (ProgramFile)
30
- # @param root_instruction_addresses Array(Integer) The flash
30
+ # @param root_instruction_addresses Array(Integer) The program memory
31
31
  # addresses of the entry vectors for your program. On a midrange device, these
32
32
  # are typically 0 for the main-line code and 4 for the interrupt.
33
33
  # @return [Hash(address => CallStackInfo)]
@@ -38,7 +38,7 @@ module RPicSim
38
38
  end
39
39
  infos
40
40
  end
41
-
41
+
42
42
  # Generates a {CallStackInfo} from the given root instruction.
43
43
  # This will tell you the maximum value the call stack could get to for a
44
44
  # program that starts at the given instruction with an empty call stack
@@ -46,7 +46,7 @@ module RPicSim
46
46
  def self.from_root_instruction(root)
47
47
  new(root)
48
48
  end
49
-
49
+
50
50
  # The maximum call stack depth for all the reachable instructions.
51
51
  # If your program starts executing at the root node and the call stack
52
52
  # is empty, then (not accounting for interrupts) the call stack will
@@ -58,7 +58,7 @@ module RPicSim
58
58
  #
59
59
  # @return (Integer)
60
60
  attr_reader :max_depth
61
-
61
+
62
62
  # @return (Instruction) The root instruction that this report was generated from.
63
63
  attr_reader :root
64
64
 
@@ -68,7 +68,7 @@ module RPicSim
68
68
  generate
69
69
  @max_depth = @max_depths.values.max
70
70
  end
71
-
71
+
72
72
  private
73
73
  def generate
74
74
  @max_depths = { @root => 0 }
@@ -86,33 +86,31 @@ module RPicSim
86
86
  end
87
87
 
88
88
  if prev_depth.nil? || new_depth > prev_depth
89
- #puts "%30s, MD=%d" % [ni, new_depth]
90
89
  @max_depths[ni] = new_depth
91
90
  instructions_to_process << ni
92
91
  @back_links[ni] = []
93
92
  end
94
-
93
+
95
94
  if new_depth == @max_depths[ni]
96
- #puts "Adding backlink #{ni} -> #{instruction}"
97
95
  @back_links[ni] << instruction
98
96
  end
99
97
  end
100
- end
98
+ end
101
99
  end
102
100
  public
103
-
101
+
104
102
  # Returns all the {Instruction}s that have the worse case possible call stack depth.
105
103
  # @return [Array(Instruction)]
106
104
  def instructions_with_worst_case
107
- @max_depths.select { |instr, depth| depth == @max_depth }.collect(&:first).sort
105
+ @max_depths.select { |instr, depth| depth == @max_depth }.map(&:first).sort
108
106
  end
109
-
107
+
110
108
  # Returns all the {Instruction}s that are reachable from the given root.
111
109
  # @return [Array(Instruction)]
112
110
  def reachable_instructions
113
111
  @max_depths.keys
114
112
  end
115
-
113
+
116
114
  # Check the max-depth data hash for consistency.
117
115
  def double_check!
118
116
  reachable_instructions.each do |instr|
@@ -121,14 +119,14 @@ module RPicSim
121
119
  instr.transitions.each do |transition|
122
120
  next_instruction = transition.next_instruction
123
121
  if @max_depths[next_instruction] < @max_depths[instr] + transition.call_depth_change
124
- raise "Call stack info double check failed: %s has max_depth %d and leads (%d) to %s with max_depth %d." %
122
+ raise 'Call stack info double check failed: %s has max_depth %d and leads (%d) to %s with max_depth %d.' %
125
123
  [instr, depth, transition.call_depth_change, next_instruction, @max_depths[next_instruction]]
126
124
  end
127
125
  end
128
-
126
+
129
127
  end
130
128
  end
131
-
129
+
132
130
  # Returns an array of {CodePath}s representing all possible ways that the call stack
133
131
  # could reach the worst-case depth. This will often be a very large amount of data,
134
132
  # even for a small project.
@@ -138,7 +136,7 @@ module RPicSim
138
136
  code_paths(instruction)
139
137
  end
140
138
  end
141
-
139
+
142
140
  # Returns a filtered version of {#worst_case_code_paths}.
143
141
  # Filters out any code paths that are just a superset of another code path.
144
142
  # For each instruction that has a back trace leading to it, it just returns
@@ -147,8 +145,6 @@ module RPicSim
147
145
  def worst_case_code_paths_filtered
148
146
  all_code_paths = worst_case_code_paths
149
147
 
150
- #puts "all worst-case code paths: #{all_code_paths.size}"
151
-
152
148
  # Filter out code path that are just a superset of another code path.
153
149
  previously_seen_instruction_sequences = Set.new
154
150
  code_paths = []
@@ -165,50 +161,50 @@ module RPicSim
165
161
 
166
162
  # For each instruction that has a code path leading to it, pick out
167
163
  # the shortest code path (in terms of interesting instructions).
168
- code_paths = code_paths.group_by { |cp| cp.instructions.last }.collect do |instr, code_paths|
164
+ code_paths = code_paths.group_by { |cp| cp.instructions.last }.map do |instr, code_paths|
169
165
  code_paths.min_by { |cp| cp.interesting_instructions.count }
170
166
  end
171
-
167
+
172
168
  code_paths
173
169
  end
174
-
170
+
175
171
  # Returns a nice report string of all the {#worst_case_code_paths_filtered}.
176
172
  # @return [String]
177
173
  def worst_case_code_paths_filtered_report
178
- s = ""
174
+ s = ''
179
175
  worst_case_code_paths_filtered.each do |code_path|
180
176
  s << code_path.to_s + "\n"
181
177
  s << "\n"
182
178
  end
183
179
  s
184
180
  end
185
-
181
+
186
182
  # @return [Array(CodePaths)] all the possible code paths that lead to the given instruction.
187
183
  def code_paths(instruction)
188
184
  code_paths = []
189
185
  Search.depth_first_search_simple([[instruction]]) do |instrs|
190
186
  prev_instrs = @back_links[instrs.first]
191
-
187
+
192
188
  if prev_instrs.empty?
193
189
  # This must be the root node.
194
190
  if instrs.first != @root
195
191
  raise "This instruction is not the root and has no back links: #{instrs}."
196
192
  end
197
-
193
+
198
194
  code_paths << CodePath.new(instrs)
199
195
  [] # don't search anything from this node
200
196
  else
201
197
  # Investigate all possible code paths that could get to this instruction.
202
198
  # However, exclude code paths that have the same instruction twice;
203
199
  # otherwise we get stuck in an infinite loop.
204
- (prev_instrs - instrs).collect do |instr|
200
+ (prev_instrs - instrs).map do |instr|
205
201
  [instr] + instrs
206
202
  end
207
203
  end
208
204
  end
209
205
  code_paths
210
206
  end
211
-
207
+
212
208
  def inspect
213
209
  "#<#{self.class}:root=#{@root.inspect}>"
214
210
  end
@@ -219,34 +215,34 @@ module RPicSim
219
215
  # It has method for reducing this list of instructions by only showing the interesting ones.
220
216
  class CodePath
221
217
  include Enumerable
222
-
218
+
223
219
  # An array of {Instruction}s that represents a possible path through the program.
224
220
  # Each instruction in the list could possibly execute after the previous one.
225
221
  attr_reader :instructions
226
-
222
+
227
223
  # A new instance that wraps the given instructions.
228
224
  # @param instructions Array(Instruction) The instructions to wrap.
229
225
  def initialize(instructions)
230
226
  @instructions = instructions.freeze
231
227
  end
232
-
228
+
233
229
  # Iterates over the wrapped instructions by calling <tt>each</tt> on the underlying array.
234
230
  # Since this class also includes <tt>Enumerable</tt>, it means you can use any of the
235
231
  # usual methods of Enumerable (e.g. <tt>select</tt>) on this class.
236
232
  def each(&proc)
237
233
  instructions.each(&proc)
238
234
  end
239
-
235
+
240
236
  # Returns the addresses of the underlying instructions.
241
237
  def addresses
242
- instructions.collect(&:address)
238
+ instructions.map(&:address)
243
239
  end
244
-
240
+
245
241
  # Returns an array of the addresses of the interesting instructions.
246
242
  def interesting_addresses
247
- interesting_instructions.collect(&:address)
243
+ interesting_instructions.map(&:address)
248
244
  end
249
-
245
+
250
246
  # Returns just the interesting instructions, as defined by {#interesting_instruction?}.
251
247
  #
252
248
  # @return [Array(Integer)]
@@ -256,7 +252,7 @@ module RPicSim
256
252
  interesting_instruction?(instruction, next_instruction)
257
253
  end
258
254
  end
259
-
255
+
260
256
  # Returns true if the given instruction is interesting. An instruction is
261
257
  # interesting if you would need to see it in order to understand the path
262
258
  # program has taken through the code and understand why the call stack
@@ -269,14 +265,14 @@ module RPicSim
269
265
  if instruction == @instructions.first || instruction == @instructions.last
270
266
  return true
271
267
  end
272
-
268
+
273
269
  transition = instruction.transition_to(next_instruction)
274
-
270
+
275
271
  if transition.call_depth_change >= 1
276
272
  # This edge represents a function call so it is interesting.
277
273
  return true
278
274
  end
279
-
275
+
280
276
  if transition.non_local?
281
277
  # This edge represents a goto, so that is interesting
282
278
  # because you need to know which gotos were taken to understand
@@ -284,23 +280,23 @@ module RPicSim
284
280
  # suppress that by adding more information to the edge.
285
281
  return true
286
282
  end
287
-
283
+
288
284
  # Everything else is not interesting.
289
-
285
+
290
286
  # We are purposing deciding that skips are not interesting.
291
287
  # because if you are trying to understand the full code path
292
288
  # it is obvious whether any given skip was taken or not, as long
293
289
  # as you know which calls and gotos were taken.
294
290
 
295
- return false
291
+ false
296
292
  end
297
-
293
+
298
294
  # Returns a multi-line string representing this execution path.
299
295
  def to_s
300
296
  "CodePath:\n" +
301
297
  interesting_instructions.join("\n") + "\n"
302
298
  end
303
299
  end
304
-
300
+
305
301
  end
306
- end
302
+ end
@@ -0,0 +1,53 @@
1
+ module RPicSim
2
+ # This class wraps two or more memory objects and
3
+ # provides an interface that is also like {RPicSim::Memory}.
4
+ # Any reads or writes from the composite memory will go to the first
5
+ # component memory for which the address is valid.
6
+ class CompositeMemory
7
+ # Creates a new instance.
8
+ # The memory objects given must support the following methods:
9
+ #
10
+ # * +read_byte+
11
+ # * +write_byte+
12
+ # * +read_word+
13
+ # * +write_word+
14
+ # * +valid_address?+
15
+ #
16
+ # @param memories [Array]
17
+ def initialize(memories)
18
+ @memories = memories
19
+ end
20
+
21
+ def read_byte(address)
22
+ memory(address).read_byte(address)
23
+ end
24
+
25
+ def write_byte(address, value)
26
+ memory(address).write_byte(address, value)
27
+ end
28
+
29
+ def read_word(address)
30
+ memory(address).read_word(address)
31
+ end
32
+
33
+ def write_word(address, value)
34
+ memory(address).write_word(address, value)
35
+ end
36
+
37
+ def valid_address?(address)
38
+ find_memory(address) != nil
39
+ end
40
+
41
+ private
42
+
43
+ def find_memory(address)
44
+ @memories.find do |memory|
45
+ memory.valid_address?(address)
46
+ end
47
+ end
48
+
49
+ def memory(address)
50
+ find_memory(address) or raise 'Invalid memory address %#x.' % address
51
+ end
52
+ end
53
+ end
@@ -18,18 +18,18 @@ module RPicSim
18
18
  # @param version [String] A version of MPLAB X, e.g. "1.95".
19
19
  # @return effect
20
20
  def effect(version)
21
- if @versions.has_key? version
21
+ if @versions.key? version
22
22
  @versions[version]
23
23
  else
24
24
  @probable_affect_for_other_versions
25
25
  end
26
26
  end
27
-
27
+
28
28
  # Records the effect this flaw has on a given version of MPLAB X.
29
29
  def affects_version(version, effect)
30
30
  @versions[version] = effect
31
31
  end
32
-
32
+
33
33
  # Records the effect that this flaw probably has in other versions of
34
34
  # MPLAB X that have not been tested. This allows us to record our guesses
35
35
  # about how the next version of MPLAB X will behave.
@@ -37,40 +37,52 @@ module RPicSim
37
37
  @probable_affect_for_other_versions = effect
38
38
  end
39
39
  end
40
-
40
+
41
41
  @flaw_hash = {}
42
42
  def self.[](name)
43
43
  @flaw_hash[name].effect Mplab.version
44
44
  end
45
-
45
+
46
46
  def self.add(name)
47
47
  @flaw_hash[name] = flaw = Flaw.new(name)
48
48
  yield flaw
49
49
  end
50
-
50
+
51
51
  add(:fr_memory_attach_useless) do |flaw|
52
- flaw.affects_version "1.85", false
53
- flaw.affects_version "1.90", false
54
- flaw.affects_version "1.95", true
55
- flaw.affects_version "2.00", true
52
+ flaw.affects_version '1.85', false
53
+ flaw.affects_version '1.90', false
54
+ flaw.affects_version '1.95', true
55
+ flaw.affects_version '2.00', true
56
+ flaw.affects_version '2.05', true
56
57
  flaw.probably_affects_other_versions true
57
58
  end
58
-
59
+
59
60
  add(:firmware_cannot_write_user_id0) do |flaw|
60
- flaw.affects_version "1.85", true
61
- flaw.affects_version "1.90", true
62
- flaw.affects_version "1.95", false
63
- flaw.affects_version "2.00", false
61
+ flaw.affects_version '1.85', true
62
+ flaw.affects_version '1.90', true
63
+ flaw.affects_version '1.95', false
64
+ flaw.affects_version '2.00', false
65
+ flaw.affects_version '2.05', false
64
66
  flaw.probably_affects_other_versions false
65
67
  end
66
-
68
+
67
69
  add(:adc_midrange) do |flaw|
68
- flaw.affects_version "1.85", :no_middle_values
69
- flaw.affects_version "1.90", :bad_modulus
70
- flaw.affects_version "1.95", :bad_modulus
71
- flaw.affects_version "2.00", :bad_modulus
70
+ flaw.affects_version '1.85', :no_middle_values
71
+ flaw.affects_version '1.90', :bad_modulus
72
+ flaw.affects_version '1.95', :bad_modulus
73
+ flaw.affects_version '2.00', :bad_modulus
74
+ flaw.affects_version '2.05', :bad_modulus
72
75
  flaw.probably_affects_other_versions :bad_modulus
73
76
  end
74
-
77
+
78
+ add(:instruction_inc_is_in_byte_units) do |flaw|
79
+ flaw.affects_version '1.85', true
80
+ flaw.affects_version '1.90', true
81
+ flaw.affects_version '1.95', true
82
+ flaw.affects_version '2.00', true
83
+ flaw.affects_version '2.05', false
84
+ flaw.probably_affects_other_versions :false
85
+ end
86
+
75
87
  end
76
- end
88
+ end
@@ -9,44 +9,155 @@ module RPicSim
9
9
  # classes like {CallStackInfo} to get useful information about the firmware.
10
10
  class Instruction
11
11
  include Comparable
12
-
13
- # The flash address of the instruction.
12
+
13
+ # The program memory address of the instruction.
14
14
  # For PIC18s this will be the byte address.
15
15
  # For other PIC architectures, it will be the word address.
16
16
  # @return (Integer)
17
17
  attr_reader :address
18
-
18
+
19
19
  # The opcode as a capitalized string (e.g. "MOVLW").
20
20
  # @return (String)
21
21
  attr_reader :opcode
22
-
22
+
23
23
  # The operands of the instruction as a hash like { "k" => 92 }.
24
24
  # @return (Hash)
25
25
  attr_reader :operands
26
-
27
- # The number of flash address units that this instruction takes.
26
+
27
+ # The number of program memory address units that this instruction takes.
28
28
  # The units of this are the same as the units of {#address}.
29
29
  # @return (Integer)
30
30
  attr_reader :size
31
31
 
32
+ # A line of assembly language that would represent this instruction.
33
+ # For example "GOTO 0x2".
34
+ # @return (String)
35
+ attr_reader :string
36
+
32
37
  # Creates a new instruction.
33
- # @param instruction_store some object such as {ProgramFile} that responds to #instruction and #address_description.
34
- def initialize(address, instruction_store, opcode, operands, size, address_increment, string, properties)
38
+ def initialize(mplab_instruction, address, address_increment, instruction_store)
35
39
  @address = address
36
40
  @instruction_store = instruction_store
37
- @opcode = opcode
38
- @operands = operands
39
- @size = size
40
41
  @address_increment = address_increment
41
- @string = string
42
-
42
+
43
+ if mplab_instruction == :invalid
44
+ @valid = false
45
+ @size = @address_increment
46
+ @string = '[INVALID]'
47
+ return
48
+ end
49
+
50
+ @valid = true
51
+ @opcode = mplab_instruction.opcode
52
+ @operands = mplab_instruction.operands
53
+ @string = mplab_instruction.instruction_string
54
+
55
+ @size = mplab_instruction.compute_size(address_increment)
56
+ raise "Invalid size #{@size} for #{inspect}" if ![1, 2, 4].include?(@size)
57
+
58
+ properties = Array case mplab_instruction.opcode
59
+ when 'ADDFSR'
60
+ when 'ADDLW'
61
+ when 'ADDWF'
62
+ when 'ADDWFC'
63
+ when 'ANDLW'
64
+ when 'ANDWF'
65
+ when 'ASRF'
66
+ when 'BC' then [:conditional_relative_branch]
67
+ when 'BCF'
68
+ when 'BN' then [:conditional_relative_branch]
69
+ when 'BNC' then [:conditional_relative_branch]
70
+ when 'BNN' then [:conditional_relative_branch]
71
+ when 'BNOV' then [:conditional_relative_branch]
72
+ when 'BNZ' then [:conditional_relative_branch]
73
+ when 'BRA' then [:relative_branch]
74
+ when 'BRW' then [:control_ender] # Hard to predict
75
+ when 'BSF'
76
+ when 'BTG'
77
+ when 'BTFSC' then [:conditional_skip]
78
+ when 'BTFSS' then [:conditional_skip]
79
+ when 'BZ' then [:conditional_relative_branch]
80
+ when 'CALL' then [:call]
81
+ when 'CALLW' then [:control_ender] # Hard to predict
82
+ when 'CPFSEQ' then [:conditional_skip]
83
+ when 'CPFSGT' then [:conditional_skip]
84
+ when 'CPFSLT' then [:conditional_skip]
85
+ when 'CLRF'
86
+ when 'CLRW'
87
+ when 'CLRWDT'
88
+ when 'COMF'
89
+ when 'DAW'
90
+ when 'DECF'
91
+ when 'DECFSZ' then [:conditional_skip]
92
+ when 'DCFSNZ' then
93
+ when 'GOTO' then [:goto]
94
+ when 'INCF'
95
+ when 'INCFSZ' then [:conditional_skip]
96
+ when 'INFSNZ' then [:conditional_skip]
97
+ when 'IORLW'
98
+ when 'IORWF'
99
+ when 'LFSR'
100
+ when 'LSLF'
101
+ when 'LSRF'
102
+ when 'MOVIW'
103
+ when 'MOVWI'
104
+ when 'MOVLB'
105
+ when 'MOVLP'
106
+ when 'MOVLW'
107
+ when 'MOVWF'
108
+ when 'MOVF'
109
+ when 'MOVFF'
110
+ when 'MULLW'
111
+ when 'MULWF'
112
+ when 'NEGF'
113
+ when 'NOP'
114
+ when 'OPTION'
115
+ when 'PUSH'
116
+ when 'POP'
117
+ when 'RCALL' then [:relative_call]
118
+ when 'RESET' then [:control_ender]
119
+ when 'RETFIE' then [:control_ender]
120
+ when 'RETLW' then [:control_ender]
121
+ when 'RETURN' then [:control_ender]
122
+ when 'RLCF'
123
+ when 'RLF'
124
+ when 'RLNCF'
125
+ when 'RRCF'
126
+ when 'RRF'
127
+ when 'RRNCF'
128
+ when 'SETF'
129
+ when 'SLEEP'
130
+ when 'SUBLW'
131
+ when 'SUBWF'
132
+ when 'SUBWFB'
133
+ when 'SWAPF'
134
+ when 'TBLRD*'
135
+ when 'TBLRD*+'
136
+ when 'TBLRD*-'
137
+ when 'TBLRD+*'
138
+ when 'TBLWT*'
139
+ when 'TBLWT*+'
140
+ when 'TBLWT*-'
141
+ when 'TBLWT+*'
142
+ when 'TRIS'
143
+ when 'TSTFSZ' then [:conditional_skip]
144
+ when 'XORLW'
145
+ when 'XORWF'
146
+ else
147
+ raise "Unrecognized opcode #{mplab_instruction.opcode} " +
148
+ "(#{address_description(address)}, operands #{mplab_instruction.operands.inspect})."
149
+ end
150
+
43
151
  modules = {
44
152
  conditional_skip: ConditionalSkip,
153
+ conditional_relative_branch: ConditionalRelativeBranch,
154
+ relative_branch: RelativeBranch,
155
+ relative_call: RelativeCall,
45
156
  goto: Goto,
46
- return: Return,
47
- call: Call
157
+ control_ender: ControlEnder,
158
+ call: Call,
48
159
  }
49
-
160
+
50
161
  properties.each do |p|
51
162
  mod = modules[p]
52
163
  if !mod
@@ -55,32 +166,32 @@ module RPicSim
55
166
  extend mod
56
167
  end
57
168
  end
58
-
169
+
59
170
  # Compares this instruction to another using the addresses. This means you can
60
171
  # call +.sort+ on an array of instructions to put them in order by address.
61
172
  def <=>(other)
62
- self.address <=> other.address
173
+ address <=> other.address
63
174
  end
64
175
 
65
176
  # Human-readable string representation of the instruction.
66
177
  def to_s
67
178
  "Instruction(#{@instruction_store.address_description(address)}, #{@string})"
68
179
  end
69
-
180
+
70
181
  def inspect
71
182
  "#<#{self.class}:#{@instruction_store.address_description(address)}, #{@string}>"
72
183
  end
73
-
184
+
74
185
  # Returns info about all the instructions that this instruction could directly lead to
75
186
  # (not counting interrupts, returns, and not accounting
76
- # at all for what happens after the last word in flash is executed).
187
+ # at all for what happens after the last word in the main program memory is executed).
77
188
  # For instructions that pop from the call stack like RETURN and RETFIE, this will be
78
189
  # the empty array.
79
190
  # @return [Array(Transition)]
80
191
  def transitions
81
192
  @transitions ||= generate_transitions
82
193
  end
83
-
194
+
84
195
  # Returns the transition from this instruction to the specified instruction
85
196
  # or nil if no such transition exists.
86
197
  # @return Transition
@@ -91,9 +202,7 @@ module RPicSim
91
202
  # Returns the addresses of all the instructions this instruction could directly lead to.
92
203
  # @return [Array(Integer)]
93
204
  def next_addresses
94
- transitions.collect do |t|
95
- t.next_instruction.address
96
- end
205
+ transitions.map(&:next_address)
97
206
  end
98
207
 
99
208
  private
@@ -101,31 +210,52 @@ module RPicSim
101
210
  def generate_transitions
102
211
  [ advance(1) ]
103
212
  end
104
-
213
+
105
214
  # Makes a transition representing the default behavior: the microcontroller
106
215
  # will increment the program counter and execute the next instruction in memory.
107
216
  def advance(num)
108
217
  transition(address + num * size)
109
218
  end
110
-
219
+
111
220
  def transition(addr, attrs={})
112
- next_instruction = @instruction_store.instruction(addr)
113
- Transition.new(self, next_instruction, attrs)
221
+ Transition.new(self, addr, @instruction_store, attrs)
114
222
  end
115
-
223
+
224
+ def valid?
225
+ @valid
226
+ end
227
+
116
228
  private
229
+
117
230
  # Returns the address indicated by the operand 'k'.
118
231
  # k is assumed to be a word address and it is assumed to be absolute
119
232
  # k=0 is word 0 of memory, k=1 is word one.
120
233
  # We need to multiply by the address increment because on PIC18
121
- # flash addresses are actually byte-based instead of word-based.
234
+ # program memory addresses are actually byte-based instead of word-based.
122
235
  def k_address
123
- operands['k'] * @address_increment
236
+ operands[:k] * @address_increment
237
+ end
238
+
239
+ def n_address
240
+ address + @address_increment * (operands[:n] + 1)
241
+ end
242
+
243
+ def relative_k_address
244
+ address + @address_increment * (operands[:k] + 1)
245
+ end
246
+
247
+ def relative_target_address
248
+ if operands[:k]
249
+ relative_k_address
250
+ elsif operands[:n]
251
+ n_address
252
+ else
253
+ raise 'This instruction does not have fields k or n.'
254
+ end
124
255
  end
125
-
256
+
126
257
  ### Modules that modify the behavior of the instruction. ###
127
-
128
-
258
+
129
259
  # This module is mixed into any {Instruction} that represents a goto or branch.
130
260
  module Goto
131
261
  def generate_transitions
@@ -133,7 +263,7 @@ module RPicSim
133
263
  [ transition(k_address, non_local: true) ]
134
264
  end
135
265
  end
136
-
266
+
137
267
  # This module is mixed into any {Instruction} that represents a conditional skip
138
268
  # A conditional skip is an instruction that might cause the next instruction to be
139
269
  # skipped depending on some condition.
@@ -142,37 +272,63 @@ module RPicSim
142
272
  [ advance(1), advance(2) ]
143
273
  end
144
274
  end
145
-
146
- # This module is mixed into any {Instruction} that represents a return from a subroutine.
147
- module Return
275
+
276
+ # This module is mixed into any {Instruction} that represents a return from a subroutine
277
+ # or a RESET instruction.
278
+ module ControlEnder
148
279
  def generate_transitions
149
280
  []
150
281
  end
151
282
  end
152
-
283
+
153
284
  # This module is mixed into any {Instruction} that represents a subroutine call.
154
285
  module Call
155
286
  def generate_transitions
156
287
  [ transition(k_address, call_depth_change: 1), advance(1) ]
157
288
  end
158
289
  end
159
-
290
+
291
+ module RelativeCall
292
+ def generate_transitions
293
+ [ transition(n_address, call_depth_change: 1), advance(1) ]
294
+ end
295
+ end
296
+
297
+ module ConditionalRelativeBranch
298
+ def generate_transitions
299
+ [ transition(n_address, non_local: true), advance(1) ]
300
+ end
301
+ end
302
+
303
+ module RelativeBranch
304
+ def generate_transitions
305
+ [ transition(relative_target_address, non_local: true) ]
306
+ end
307
+ end
308
+
160
309
  class Transition
161
- attr_reader :next_instruction, :previous_instruction
162
-
163
- def initialize(previous_instruction, next_instruction, attrs)
310
+ attr_reader :previous_instruction
311
+ attr_reader :next_address
312
+
313
+ def initialize(previous_instruction, next_address, instruction_store, attrs)
164
314
  @previous_instruction = previous_instruction
165
- @next_instruction = next_instruction
315
+ @next_address = next_address
316
+ @instruction_store = instruction_store
166
317
  @attrs = attrs
167
318
  end
168
-
319
+
320
+ def next_instruction
321
+ @next_instruction ||= @instruction_store.instruction(next_address)
322
+ end
323
+
169
324
  def non_local?
170
325
  @attrs.fetch(:non_local, false)
171
326
  end
172
-
327
+
173
328
  def call_depth_change
174
329
  @attrs.fetch(:call_depth_change, 0)
175
330
  end
331
+
176
332
  end
177
333
  end
178
- end
334
+ end