rpicsim 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|