rpicsim 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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