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.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/Gemfile +7 -6
- data/Introduction.md +3 -1
- data/README.md +2 -2
- data/docs/ChangeLog.md +6 -0
- data/docs/Contributing.md +1 -1
- data/docs/DefiningSimulationClass.md +11 -10
- data/docs/HowMPLABXIsFound.md +1 -1
- data/docs/IntegrationTesting.md +1 -1
- data/docs/IntroductionToRSpec.md +8 -5
- data/docs/IntroductionToRuby.md +2 -2
- data/docs/KnownIssues.md +46 -57
- data/docs/Labels.md +5 -4
- data/docs/Manual.md +1 -1
- data/docs/Memories.md +70 -0
- data/docs/PersistentExpectations.md +31 -2
- data/docs/Pins.md +5 -7
- data/docs/PreventingCallStackOverflow.md +4 -6
- data/docs/QuickStartGuide.md +5 -5
- data/docs/RSpecIntegration.md +2 -2
- data/docs/RamWatcher.md +22 -11
- data/docs/Running.md +4 -6
- data/docs/Stubbing.md +4 -4
- data/docs/SupportedDevices.md +2 -11
- data/docs/SupportedMPLABXVersions.md +1 -0
- data/docs/SupportedOperatingSystems.md +3 -2
- data/docs/UnitTesting.md +1 -1
- data/docs/Variables.md +81 -25
- data/lib/rpicsim.rb +0 -12
- data/lib/rpicsim/call_stack_info.rb +43 -47
- data/lib/rpicsim/composite_memory.rb +53 -0
- data/lib/rpicsim/flaws.rb +34 -22
- data/lib/rpicsim/instruction.rb +204 -48
- data/lib/rpicsim/label.rb +4 -4
- data/lib/rpicsim/memory.rb +44 -23
- data/lib/rpicsim/memory_watcher.rb +14 -22
- data/lib/rpicsim/mplab.rb +38 -119
- data/lib/rpicsim/mplab/mplab_assembly.rb +23 -18
- data/lib/rpicsim/mplab/mplab_device_info.rb +9 -9
- data/lib/rpicsim/mplab/mplab_disassembler.rb +5 -6
- data/lib/rpicsim/mplab/mplab_instruction.rb +87 -16
- data/lib/rpicsim/mplab/mplab_loader.rb +106 -0
- data/lib/rpicsim/mplab/mplab_memory.rb +19 -6
- data/lib/rpicsim/mplab/mplab_nmmr_info.rb +4 -4
- data/lib/rpicsim/mplab/mplab_observer.rb +15 -10
- data/lib/rpicsim/mplab/mplab_pin.rb +3 -3
- data/lib/rpicsim/mplab/mplab_processor.rb +5 -5
- data/lib/rpicsim/mplab/mplab_program_file.rb +29 -17
- data/lib/rpicsim/mplab/mplab_register.rb +5 -5
- data/lib/rpicsim/mplab/mplab_sfr_info.rb +4 -4
- data/lib/rpicsim/mplab/mplab_simulator.rb +27 -30
- data/lib/rpicsim/pin.rb +6 -6
- data/lib/rpicsim/program_counter.rb +3 -3
- data/lib/rpicsim/program_file.rb +39 -81
- data/lib/rpicsim/rspec/be_predicate.rb +1 -1
- data/lib/rpicsim/rspec/helpers.rb +1 -1
- data/lib/rpicsim/rspec/persistent_expectations.rb +17 -2
- data/lib/rpicsim/rspec/sim_diagnostics.rb +5 -5
- data/lib/rpicsim/search.rb +1 -1
- data/lib/rpicsim/sim.rb +153 -228
- data/lib/rpicsim/stack_pointer.rb +41 -0
- data/lib/rpicsim/stack_trace.rb +1 -1
- data/lib/rpicsim/storage/memory_integer.rb +235 -0
- data/lib/rpicsim/{register.rb → storage/register.rb} +18 -18
- data/lib/rpicsim/variable.rb +25 -211
- data/lib/rpicsim/variable_set.rb +93 -0
- data/lib/rpicsim/version.rb +2 -2
- metadata +9 -4
- data/docs/SFRs.md +0 -71
data/lib/rpicsim.rb
CHANGED
@@ -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
|
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 }.
|
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
|
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 }.
|
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).
|
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.
|
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.
|
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
|
-
|
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
|
data/lib/rpicsim/flaws.rb
CHANGED
@@ -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.
|
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
|
53
|
-
flaw.affects_version
|
54
|
-
flaw.affects_version
|
55
|
-
flaw.affects_version
|
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
|
61
|
-
flaw.affects_version
|
62
|
-
flaw.affects_version
|
63
|
-
flaw.affects_version
|
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
|
69
|
-
flaw.affects_version
|
70
|
-
flaw.affects_version
|
71
|
-
flaw.affects_version
|
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
|
data/lib/rpicsim/instruction.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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.
|
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
|
-
|
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
|
-
#
|
234
|
+
# program memory addresses are actually byte-based instead of word-based.
|
122
235
|
def k_address
|
123
|
-
operands[
|
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
|
-
|
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 :
|
162
|
-
|
163
|
-
|
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
|
-
@
|
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
|