rpicsim 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/Gemfile +7 -6
  4. data/Introduction.md +3 -1
  5. data/README.md +2 -2
  6. data/docs/ChangeLog.md +6 -0
  7. data/docs/Contributing.md +1 -1
  8. data/docs/DefiningSimulationClass.md +11 -10
  9. data/docs/HowMPLABXIsFound.md +1 -1
  10. data/docs/IntegrationTesting.md +1 -1
  11. data/docs/IntroductionToRSpec.md +8 -5
  12. data/docs/IntroductionToRuby.md +2 -2
  13. data/docs/KnownIssues.md +46 -57
  14. data/docs/Labels.md +5 -4
  15. data/docs/Manual.md +1 -1
  16. data/docs/Memories.md +70 -0
  17. data/docs/PersistentExpectations.md +31 -2
  18. data/docs/Pins.md +5 -7
  19. data/docs/PreventingCallStackOverflow.md +4 -6
  20. data/docs/QuickStartGuide.md +5 -5
  21. data/docs/RSpecIntegration.md +2 -2
  22. data/docs/RamWatcher.md +22 -11
  23. data/docs/Running.md +4 -6
  24. data/docs/Stubbing.md +4 -4
  25. data/docs/SupportedDevices.md +2 -11
  26. data/docs/SupportedMPLABXVersions.md +1 -0
  27. data/docs/SupportedOperatingSystems.md +3 -2
  28. data/docs/UnitTesting.md +1 -1
  29. data/docs/Variables.md +81 -25
  30. data/lib/rpicsim.rb +0 -12
  31. data/lib/rpicsim/call_stack_info.rb +43 -47
  32. data/lib/rpicsim/composite_memory.rb +53 -0
  33. data/lib/rpicsim/flaws.rb +34 -22
  34. data/lib/rpicsim/instruction.rb +204 -48
  35. data/lib/rpicsim/label.rb +4 -4
  36. data/lib/rpicsim/memory.rb +44 -23
  37. data/lib/rpicsim/memory_watcher.rb +14 -22
  38. data/lib/rpicsim/mplab.rb +38 -119
  39. data/lib/rpicsim/mplab/mplab_assembly.rb +23 -18
  40. data/lib/rpicsim/mplab/mplab_device_info.rb +9 -9
  41. data/lib/rpicsim/mplab/mplab_disassembler.rb +5 -6
  42. data/lib/rpicsim/mplab/mplab_instruction.rb +87 -16
  43. data/lib/rpicsim/mplab/mplab_loader.rb +106 -0
  44. data/lib/rpicsim/mplab/mplab_memory.rb +19 -6
  45. data/lib/rpicsim/mplab/mplab_nmmr_info.rb +4 -4
  46. data/lib/rpicsim/mplab/mplab_observer.rb +15 -10
  47. data/lib/rpicsim/mplab/mplab_pin.rb +3 -3
  48. data/lib/rpicsim/mplab/mplab_processor.rb +5 -5
  49. data/lib/rpicsim/mplab/mplab_program_file.rb +29 -17
  50. data/lib/rpicsim/mplab/mplab_register.rb +5 -5
  51. data/lib/rpicsim/mplab/mplab_sfr_info.rb +4 -4
  52. data/lib/rpicsim/mplab/mplab_simulator.rb +27 -30
  53. data/lib/rpicsim/pin.rb +6 -6
  54. data/lib/rpicsim/program_counter.rb +3 -3
  55. data/lib/rpicsim/program_file.rb +39 -81
  56. data/lib/rpicsim/rspec/be_predicate.rb +1 -1
  57. data/lib/rpicsim/rspec/helpers.rb +1 -1
  58. data/lib/rpicsim/rspec/persistent_expectations.rb +17 -2
  59. data/lib/rpicsim/rspec/sim_diagnostics.rb +5 -5
  60. data/lib/rpicsim/search.rb +1 -1
  61. data/lib/rpicsim/sim.rb +153 -228
  62. data/lib/rpicsim/stack_pointer.rb +41 -0
  63. data/lib/rpicsim/stack_trace.rb +1 -1
  64. data/lib/rpicsim/storage/memory_integer.rb +235 -0
  65. data/lib/rpicsim/{register.rb → storage/register.rb} +18 -18
  66. data/lib/rpicsim/variable.rb +25 -211
  67. data/lib/rpicsim/variable_set.rb +93 -0
  68. data/lib/rpicsim/version.rb +2 -2
  69. metadata +9 -4
  70. data/docs/SFRs.md +0 -71
@@ -9,4 +9,4 @@ class RSpec::Matchers::BuiltIn::BePredicate
9
9
  def failure_message_for_should_not
10
10
  "expected #{actual.inspect}.#{predicate}#{args_to_s} to return false, got #{@result.inspect}"
11
11
  end
12
- end
12
+ end
@@ -13,7 +13,7 @@ module RPicSim
13
13
  # integration with RSpec.
14
14
  module Helpers
15
15
  include RPicSim::RSpec::PersistentExpectations
16
-
16
+
17
17
  # This attribute allows you to type +sim+ in your specs instead of +@sim+ to
18
18
  # get access to the {RPicSim::Sim} instance which represents the simulation.
19
19
  # You must call {#start_sim} before using +sim+.
@@ -45,9 +45,24 @@ module RPicSim
45
45
  # expecting main_output_pin: be_driving_high
46
46
  #
47
47
  # To remove an expectation on an object, just provide +nil+ for the matcher.
48
+ #
49
+ # If given a block, applies the new expectations, executes the
50
+ # block, then resets expectations to their former state.
51
+ # Expectation blocks may be nested and freely mixed with other
52
+ # calls to `expecting`.
48
53
  def expecting(hash)
49
- expectations.merge! hash
54
+ if block_given?
55
+ saved_expectations = expectations.clone
56
+ begin
57
+ expecting(hash)
58
+ yield
59
+ ensure
60
+ @expectations = saved_expectations
61
+ end
62
+ else
63
+ expectations.merge! hash
64
+ end
50
65
  end
51
66
  end
52
67
  end
53
- end
68
+ end
@@ -21,7 +21,7 @@ RSpec::Core::Formatters::BaseTextFormatter
21
21
  # different functions so you can easily customize any of them without messing up
22
22
  # the other ones.
23
23
  class RSpec::Core::Formatters::BaseTextFormatter
24
- alias dump_backtrace_without_sim_diagnostics dump_backtrace
24
+ alias_method :dump_backtrace_without_sim_diagnostics, :dump_backtrace
25
25
 
26
26
  def dump_backtrace(example)
27
27
  dump_backtrace_without_sim_diagnostics(example)
@@ -35,7 +35,7 @@ class RSpec::Core::Formatters::BaseTextFormatter
35
35
 
36
36
  def dump_sim_cycle_count(example)
37
37
  cycle_count = example.metadata[:sim_cycle_count] or return
38
- output.puts long_padding
38
+ output.puts
39
39
  output.printf long_padding + "Simulation cycle count: %d\n", cycle_count
40
40
  end
41
41
 
@@ -44,8 +44,8 @@ class RSpec::Core::Formatters::BaseTextFormatter
44
44
  # appropriate indentation.
45
45
  def dump_sim_stack_trace(example)
46
46
  sim_stack_trace = example.metadata[:sim_stack_trace] or return
47
- output.puts long_padding
48
- output.puts long_padding + "Simulation stack trace:"
47
+ output.puts
48
+ output.puts long_padding + 'Simulation stack trace:'
49
49
  sim_stack_trace.output(output, long_padding)
50
50
  end
51
- end
51
+ end
@@ -17,4 +17,4 @@ module RPicSim
17
17
  end
18
18
  end
19
19
  end
20
- end
20
+ end
@@ -1,47 +1,42 @@
1
- # TODO: for performance, consider making people explicitly enable (or construct?) the ram_watcher
2
- # before they can use it. See if this would affect the run time of rcs03a specs before committing to it.
3
-
4
1
  require 'forwardable'
5
2
 
6
3
  require_relative 'mplab'
7
4
  require_relative 'flaws'
8
5
  require_relative 'pin'
9
6
  require_relative 'memory'
10
- require_relative 'register'
11
- require_relative 'variable'
7
+ require_relative 'composite_memory'
8
+ require_relative 'storage/register'
9
+ require_relative 'variable_set'
12
10
  require_relative 'program_counter'
13
11
  require_relative 'label'
14
12
  require_relative 'memory_watcher'
15
13
  require_relative 'program_file'
14
+ require_relative 'stack_pointer'
16
15
  require_relative 'stack_trace'
17
16
 
18
17
  module RPicSim
19
18
  # This class represents a PIC microcontroller simulation.
20
19
  # This class keeps track of the state of the simulation and provides methods for
21
20
  # running the simulation, reading the state, and changing the state.
22
- # This the main class of RPicSim.
23
21
  class Sim
24
22
 
25
23
  # These methods should be called while defining a subclass of {Sim}.
26
24
  module ClassDefinitionMethods
27
25
 
28
- # Specifies what exact device the firmware runs on. In theory we could extract this
29
- # from the COF file instead of requiring it to be specified in subclasses of {Sim}, but
30
- # MPLAB X classes do not seem to make that easy.
26
+ # Specifies what exact device the firmware runs on.
31
27
  # @param device [String] The device name, for example "PIC10F322".
32
- def device_is(device)
28
+ def use_device(device)
33
29
  @device = device
34
30
  @assembly = Mplab::MplabAssembly.new(device)
35
- @code_word_max_value = @assembly.device_info.code_word_max_value
36
31
  end
37
32
 
38
33
  # Specifies the path to the firmware file. The file can be a HEX or COF file, but
39
34
  # COF is recommended so that you can access label addresses and other debugging information.
40
- # You must call {#device_is} before calling this.
41
- def filename_is(filename)
42
- raise "Must specify device before filename (e.g. 'device_is \"PIC10F322\"')" unless @device
35
+ # You must call {#use_device} before calling this.
36
+ def use_file(filename)
37
+ raise "The device needs to be specified before filename (e.g. 'use_device \"PIC10F322\"')" unless @device
43
38
  @filename = filename
44
- initialize_symbols
39
+ load_program_file
45
40
  end
46
41
 
47
42
  # Define a pin alias.
@@ -58,17 +53,19 @@ module RPicSim
58
53
  self::Shortcuts.send(:define_method, our_name) { pin our_name }
59
54
  end
60
55
 
61
- # Define a RAM variable.
56
+ # Define a variable.
62
57
  # @param name [Symbol] Specifies what you would like to call the variable.
63
58
  # A method with this name will be added to your class's +Shortcuts+ module so it
64
59
  # is available as a method on instances of your class and also in your RSpec tests.
65
60
  # The method will return a {Variable} object that you can use to read or write the
66
61
  # value of the actual variable in the simulation.
67
62
  # @param type [Symbol] Specifies how to interpret the data in the variable and its size.
68
- # For integers, it should be one of +:u8+, +:s8+, +:u16+, +:s16+, +:u24+, +:s24+, +:u32+, or +:s32+.
63
+ # For integers, it should be one of +:uint8+, +:int8+, +:uint16+, +:int16+, +:uint24+, +:int24+, +:uint32+, +:int32+, or +:word+.
69
64
  # The +s+ stands for signed and the +u+ stands for unsigned, and the number stands for the number
70
65
  # of bits. All multi-byte integers are considered to be little Endian.
71
66
  # @param opts [Hash] Specifies additional options. The options are:
67
+ # * +:memory+: Specifies the memory that the variable lives in.
68
+ # Valid values are +:ram+ (default), +:eeprom+, and +:program_memory+.
72
69
  # * +:symbol+: By default, we look for a symbol with the same name as the variable and
73
70
  # use that as the location of the variable. This option lets you specify a different
74
71
  # symbol to look for in the firmware, so you could call the variable one thing in your
@@ -76,98 +73,15 @@ module RPicSim
76
73
  # This option is ignored if +:address is specified.
77
74
  # * +:address+: An integer to use as the address of the variable.
78
75
  def def_var(name, type, opts={})
79
- allowed_keys = [:symbol]
80
- invalid_keys = opts.keys - allowed_keys
81
- if !invalid_keys.empty?
82
- raise ArgumentError, "Unrecognized options: #{invalid_keys.join(", ")}"
83
- end
84
-
85
- name = name.to_sym
86
-
87
- if opts[:address]
88
- address = opts[:address].to_i
89
- else
90
- symbol = (opts[:symbol] || name).to_sym
91
- if symbol.to_s.include?('@')
92
- raise "Limitations in Microchip's code prevent us from accessing " +
93
- "variables with '@' in the name like '#{symbol}'"
94
- end
95
- address = @var_address[symbol] or raise "Cannot find variable named '#{symbol}'."
76
+ if @variable_set.nil?
77
+ raise 'The device and filename need to be specified before defining variables.'
96
78
  end
97
79
 
98
- klass = case type
99
- when Class then type
100
- when :u8 then VariableU8
101
- when :s8 then VariableS8
102
- when :u16 then VariableU16
103
- when :s16 then VariableS16
104
- when :u24 then VariableU24
105
- when :s24 then VariableS24
106
- when :u32 then VariableU32
107
- when :s32 then VariableS32
108
- else raise "Unknown type '#{type}'."
109
- end
110
-
111
- variable = klass.new(name, address)
112
- variable.addresses.each do |address|
113
- if @vars_by_address[address]
114
- raise "Variable %s overlaps with %s at 0x%x" %
115
- [variable, @vars_by_address[address], address]
116
- end
117
- @vars_by_address[address] = variable
118
- end
119
- @vars[name] = variable
80
+ @variable_set.def_var(name, type, opts)
120
81
 
121
82
  self::Shortcuts.send(:define_method, name) { var name }
122
83
  end
123
84
 
124
- # Define a flash (program memory or user ID) variable.
125
- # @param name [Symbol] Specifies what you would like to call the variable.
126
- # A method with this name will be added to your class's +Shortcuts+ module so it
127
- # is available as a method on instances of your class and also in your RSpec tests.
128
- # The method will return a {Variable} object that you can use to read or write the
129
- # value of the actual variable in the simulation.
130
- # @param type [Symbol] Specifies how to interpret the data in the variable and its size.
131
- # The only supported option current is +:word+, which represents a full word of flash.
132
- # @param opts [Hash] Specifies additional options. The options are:
133
- # * +:symbol+: By default, we look for a symbol with the same name as the variable and
134
- # use that as the location of the variable. This option lets you specify a different
135
- # symbol to look for in the firmware.
136
- # * +:address+: An integer to use as the address of the variable.
137
- def def_flash_var(name, type, opts={})
138
- allowed_keys = [:symbol, :address]
139
- invalid_keys = opts.keys - allowed_keys
140
- if !invalid_keys.empty?
141
- raise ArgumentError, "Unrecognized options: #{invalid_keys.join(", ")}"
142
- end
143
-
144
- name = name.to_sym
145
-
146
- if opts[:address]
147
- address = opts[:address].to_i
148
- else
149
- symbol = (opts[:symbol] || name).to_sym
150
- if symbol.to_s.include?('@')
151
- raise "Limitations in Microchip's code prevent us from accessing " +
152
- "variables with '@' in the name like '#{symbol}'"
153
- end
154
- label = labels[symbol] or raise "Could not find label named '#{symbol}'."
155
- address = label.address
156
- end
157
-
158
- klass = case type
159
- when Class then type
160
- when :word then VariableWord
161
- else raise "Unknown type '#{type}'."
162
- end
163
-
164
- variable = klass.new(name, address)
165
- variable.max_value = @code_word_max_value
166
- @flash_vars[name] = variable
167
-
168
- self::Shortcuts.send(:define_method, name) { flash_var name }
169
- end
170
-
171
85
  end
172
86
 
173
87
  # These are class methods that you can call on subclasses of {Sim}.
@@ -183,13 +97,9 @@ module RPicSim
183
97
  # pin names (like :RB3). These aliases are defined by {ClassDefinitionMethods#def_pin}.
184
98
  attr_reader :pin_aliases
185
99
 
186
- # A hash that associates RAM variable names to (unbound) {Variable} objects.
187
- # The variables are defined by {ClassDefinitionMethods#def_var}.
188
- attr_reader :vars
189
-
190
- # A hash that associates flash variable names to (unbound) {Variable} objects.
191
- # The variables are defined by {ClassDefinitionMethods#def_flash_var}.
192
- attr_reader :flash_vars
100
+ # A {VariableSet} that holds information about all the variables that were defined
101
+ # with {ClassDefinitionMethods#def_var def_var}.
102
+ attr_reader :variable_set
193
103
 
194
104
  # A hash that associates label names as symbols to {Label} objects.
195
105
  attr_reader :labels
@@ -202,19 +112,20 @@ module RPicSim
202
112
  def inherited(subclass)
203
113
  subclass.instance_eval do
204
114
  @pin_aliases = {}
205
- @vars = {}
206
- @vars_by_address = {}
207
- @flash_vars = {}
208
115
  const_set :Shortcuts, Module.new
209
116
  include self::Shortcuts
210
117
  end
211
-
212
118
  end
213
119
 
214
- def initialize_symbols
120
+ def load_program_file
215
121
  @program_file = ProgramFile.new(@filename, @device)
216
- @var_address = program_file.var_addresses
217
122
  @labels = program_file.labels
123
+
124
+ @variable_set = VariableSet.new
125
+ @variable_set.address_increment = program_file.address_increment
126
+ @variable_set.def_memory_type :ram, program_file.symbols_in_ram
127
+ @variable_set.def_memory_type :program_memory, program_file.symbols_in_program_memory
128
+ @variable_set.def_memory_type :eeprom, program_file.symbols_in_eeprom
218
129
  end
219
130
 
220
131
  end
@@ -231,26 +142,31 @@ module RPicSim
231
142
  # instead of +sim.cycle_count+.
232
143
  ForwardedMethods = [
233
144
  :cycle_count,
145
+ :eeprom,
234
146
  :every_step,
235
- :flash_var,
236
147
  :goto,
237
148
  :label,
238
149
  :location_address,
239
- :nmmr,
150
+ :new_ram_watcher,
240
151
  :pc,
241
152
  :pc_description,
242
153
  :pin,
243
- :ram_watcher,
154
+ :program_file,
155
+ :program_memory,
156
+ :ram,
244
157
  :run_cycles,
245
158
  :run_steps,
246
159
  :run_subroutine,
247
160
  :run_to,
248
161
  :run_to_cycle_count,
249
- :sfr,
250
- :sfr_or_nmmr,
162
+ :reg,
163
+ :stack_contents,
164
+ :stack_push,
165
+ :stack_trace,
251
166
  :step,
252
167
  :var,
253
168
  :wreg,
169
+ :stack_pointer,
254
170
  :stkptr,
255
171
  ]
256
172
 
@@ -265,20 +181,40 @@ module RPicSim
265
181
  # @return [RPicSim::ProgramCounter]
266
182
  attr_reader :pc
267
183
 
268
- # Returns a {MemoryWatcher} object configured to watch for changes to RAM.
269
- # @return [MemoryWatcher]
270
- attr_reader :ram_watcher
271
-
272
- # Returns a {Register} object corresponding to WREG. You can use this
184
+ # Returns a {Variable} object corresponding to WREG. You can use this
273
185
  # to read and write the value of the W register.
274
186
  # @return [Register]
275
187
  attr_reader :wreg
276
188
 
277
- # Returns a {Register} object corresponding to the stack pointer. You can use
278
- # this to read and write the value of the stack pointer.
189
+ # Returns a {Variable} object corresponding to the stack pointer register.
190
+ # You can use this to read and write the value of the stack pointer.
279
191
  # @return [Register]
280
192
  attr_reader :stkptr
281
193
 
194
+ # Returns a {StackPointer} object that is like {#stkptr} but it works
195
+ # consistently across all PIC devices. The initial value is always 0
196
+ # when the stack is empty and it points to the first unused space in
197
+ # the stack.
198
+ # @return [StackPointer]
199
+ attr_reader :stack_pointer
200
+
201
+ # Returns a {Memory} object that allows direct reading and writing of the
202
+ # bytes in the simulated RAM.
203
+ # @return [Memory]
204
+ attr_reader :ram
205
+
206
+ # Returns a {Memory} object that allows direct reading and writing of the
207
+ # data in the program memory.
208
+ # Besides the main program, the program memory also contains the
209
+ # configuration words and the user IDs.
210
+ # @return [Memory]
211
+ attr_reader :program_memory
212
+
213
+ # Returns a {Memory} object that allows direct reading and writing of the
214
+ # bytes in the simulated EEPROM.
215
+ # @return [Memory]
216
+ attr_reader :eeprom
217
+
282
218
  # Returns a string like "PIC10F322" specifying the PIC device number.
283
219
  # @return [String]
284
220
  def device; self.class.device; end
@@ -294,33 +230,39 @@ module RPicSim
294
230
  @simulator = @assembly.simulator
295
231
  @processor = @simulator.processor
296
232
 
297
- # Set up our stores and helper objects.
298
- @fr_memory = Memory.new @simulator.fr_memory
299
- @sfr_memory = Memory.new @simulator.sfr_memory
300
- @nmmr_memory = Memory.new @simulator.nmmr_memory
301
- @program_memory = Memory.new @simulator.program_memory
302
- @stack_memory = Memory.new @simulator.stack_memory
303
- @test_memory = Memory.new @simulator.test_memory
304
-
305
- @pc = ProgramCounter.new(@simulator.processor)
306
-
307
- @step_callbacks = []
308
-
233
+ initialize_memories
309
234
  initialize_pins
310
235
  initialize_sfrs_and_nmmrs
311
236
  initialize_vars
312
- initialize_flash_vars
313
237
 
314
- @ram_watcher = MemoryWatcher.new(self, @simulator.fr_memory, @vars.values + @sfrs.values)
238
+ @pc = ProgramCounter.new @simulator.processor
239
+
240
+ @step_callbacks = []
241
+
242
+ @stack_pointer = StackPointer.new(stkptr)
315
243
  end
316
244
 
317
245
  private
318
246
 
247
+ def initialize_memories
248
+ # Set up our stores and helper objects.
249
+ @ram = Memory.new @simulator.fr_memory
250
+ @eeprom = Memory.new @simulator.eeprom_memory
251
+ @sfr_memory = Memory.new @simulator.sfr_memory
252
+ @nmmr_memory = Memory.new @simulator.nmmr_memory
253
+ @stack_memory = Memory.new @simulator.stack_memory
254
+
255
+ # config_memory must be before test_memory, because test_memory provides
256
+ # bad values for the configuration words.
257
+ @program_memory = Memory.new CompositeMemory.new [
258
+ @simulator.program_memory,
259
+ @simulator.config_memory,
260
+ @simulator.test_memory,
261
+ ]
262
+ end
263
+
319
264
  def initialize_pins
320
- pins = @simulator.pins.collect { |mplab_pin| Pin.new(mplab_pin) }
321
-
322
- #pins.reject! { |p| p.to_s == "VDD" } or raise "Failed to filter out VDD pin."
323
- #pins.reject! { |p| p.to_s == "VSS" } or raise "Failed to filter out VSS pin."
265
+ pins = @simulator.pins.map { |mplab_pin| Pin.new(mplab_pin) }
324
266
 
325
267
  @pins_by_name = {}
326
268
  pins.each do |pin|
@@ -330,45 +272,32 @@ module RPicSim
330
272
  end
331
273
 
332
274
  self.class.pin_aliases.each do |our_name, datasheet_name|
333
- @pins_by_name[our_name] = @pins_by_name[datasheet_name] or raise "Pin #{datasheet_name} not found."
275
+ @pins_by_name[our_name] = @pins_by_name[datasheet_name] or raise "Pin #{datasheet_name} not found."
334
276
  end
335
277
  end
336
278
 
337
279
  def initialize_vars
338
- @vars = {}
339
- self.class.vars.each do |name, unbound_var|
340
- @vars[name] = unbound_var.bind(@fr_memory)
341
- end
342
- end
343
-
344
- def initialize_flash_vars
345
- @flash_vars = {}
346
- memories = [@program_memory, @test_memory]
347
- self.class.flash_vars.each do |name, unbound_var|
348
- possible_memories = memories.select { |m| m.is_valid_address?(unbound_var.address) }
349
- if possible_memories.empty?
350
- raise "Flash variable has an invalid address: #{unbound_var.inspect}"
351
- elsif possible_memories.size > 1
352
- raise "Flash variable's address is valid in both program memory and test memory. Not sure which memory to use: #{unbound_var.inspect}."
353
- end
354
-
355
- @flash_vars[name] = unbound_var.bind(possible_memories.first)
356
- end
280
+ memories = {
281
+ ram: ram,
282
+ program_memory: program_memory,
283
+ eeprom: eeprom,
284
+ }
285
+ @vars = self.class.variable_set.bind(memories)
357
286
  end
358
287
 
359
288
  def initialize_sfrs_and_nmmrs
360
289
  @sfrs = {}
361
290
  @assembly.device_info.sfrs.each do |sfr|
362
- @sfrs[sfr.name.to_sym] = Register.new @processor.get_sfr(sfr.name), @sfr_memory, sfr.width
291
+ @sfrs[sfr.name.to_sym] = Variable.new Storage::Register.new @processor.get_sfr(sfr.name), @sfr_memory, sfr.width
363
292
  end
364
-
293
+
365
294
  @nmmrs = {}
366
295
  @assembly.device_info.nmmrs.each do |nmmr|
367
- @nmmrs[nmmr.name.to_sym] = Register.new @processor.get_nmmr(nmmr.name), @nmmr_memory, nmmr.width
296
+ @nmmrs[nmmr.name.to_sym] = Variable.new Storage::Register.new @processor.get_nmmr(nmmr.name), @nmmr_memory, nmmr.width
368
297
  end
369
298
 
370
- @wreg = sfr_or_nmmr(:WREG)
371
- @stkptr = sfr_or_nmmr(:STKPTR)
299
+ @wreg = reg(:WREG)
300
+ @stkptr = reg(:STKPTR)
372
301
  end
373
302
 
374
303
  public
@@ -381,51 +310,29 @@ module RPicSim
381
310
  @pins_by_name[name.to_sym] or raise ArgumentError, "Cannot find pin named '#{name}'."
382
311
  end
383
312
 
384
- # Returns a {Register} object if an SFR by that name is found,
385
- # or raises an exception.
386
- # @param name [Symbol] The name from the datasheet.
387
- # @return [Register]
388
- def sfr(name)
389
- @sfrs[name.to_sym] or raise ArgumentError, "Cannot find SFR named '#{name}'."
390
- end
391
-
392
- # Returns a {Register} object if an SFR or NMMR by that name is found,
393
- # or raises an exception.
313
+ # Returns a {Variable} object if a Special Function Register (SFR) or
314
+ # Non-Memory-Mapped Register (NMMR) by that name is found.
315
+ # If the register cannot be found, this method raises an exception.
394
316
  # @param name [Symbol] The name from the datasheet.
395
317
  # @return [Register]
396
- def sfr_or_nmmr(name)
318
+ def reg(name)
397
319
  name = name.to_sym
398
320
  @sfrs[name] || @nmmrs[name] or raise ArgumentError, "Cannot find SFR or NMMR named '#{name}'."
399
321
  end
400
322
 
401
- # Returns a {Register} object if an NMMR by that name is found,
402
- # or raises an exception.
403
- # @param name [Symbol] The name from the datasheet.
404
- # @return [Register]
405
- def nmmr(name)
406
- @nmmrs[name.to_sym] or raise ArgumentError, "Cannot find NMMR named '#{name}'."
407
- end
408
-
409
- # Returns a {Variable} object if a RAM variable by that name is found,
410
- # or raises an exception.
323
+ # Returns a {Variable} object if a variable by that name is found.
324
+ # If the variable cannot be found, this method raises an exception.
411
325
  # @return [Variable]
412
326
  def var(name)
413
327
  @vars[name.to_sym] or raise ArgumentError, "Cannot find var named '#{name}'."
414
328
  end
415
329
 
416
- # Returns a {Variable} object if a flash (program memory) variable by that name is found,
417
- # or raises an exception.
418
- # @return [Variable]
419
- def flash_var(name)
420
- @flash_vars[name.to_sym] or raise ArgumentError, "Cannot find flash var named '#{name}'."
421
- end
422
-
423
330
  # Returns a {Label} object if a program label by that name is found.
424
331
  # The name is specified in the code that defined the label. If you are using a C compiler,
425
332
  # you will probably need to prefix the name with an underscore.
426
333
  # @return [Label]
427
334
  def label(name)
428
- self.class.program_file.label(name)
335
+ program_file.label(name)
429
336
  end
430
337
 
431
338
  # Returns the number of instruction cycles simulated in this simulation.
@@ -488,10 +395,10 @@ module RPicSim
488
395
  def run_to(conditions, opts={})
489
396
  conditions = Array(conditions)
490
397
  if conditions.empty?
491
- raise ArgumentError, "Must specify at least one condition."
398
+ raise ArgumentError, 'Must specify at least one condition.'
492
399
  end
493
400
 
494
- condition_procs = conditions.collect &method(:convert_condition_to_proc)
401
+ condition_procs = conditions.map(&method(:convert_condition_to_proc))
495
402
 
496
403
  allowed_keys = [:cycle_limit, :cycles]
497
404
  invalid_keys = opts.keys - allowed_keys
@@ -500,7 +407,7 @@ module RPicSim
500
407
  end
501
408
 
502
409
  if opts[:cycles] && opts[:cycle_limit]
503
- raise ArgumentError, "Cannot specify both :cycles and :cycle_limit."
410
+ raise ArgumentError, 'Cannot specify both :cycles and :cycle_limit.'
504
411
  end
505
412
 
506
413
  start_cycle = cycle_count
@@ -514,7 +421,7 @@ module RPicSim
514
421
  end
515
422
 
516
423
  # Loop as long as none of the conditions are satisfied.
517
- while !(met_condition_index = condition_procs.find_index &:call)
424
+ while !(met_condition_index = condition_procs.find_index(&:call))
518
425
  if max_cycle && cycle_count >= max_cycle
519
426
  raise "Failed to reach #{conditions.inspect} after #{cycle_count - start_cycle} cycles."
520
427
  end
@@ -530,7 +437,7 @@ module RPicSim
530
437
  end
531
438
 
532
439
  # Return the argument that specified the condition that was satisfied.
533
- return met_condition
440
+ met_condition
534
441
  end
535
442
 
536
443
  # Gets the address of the specified location in program memory.
@@ -587,9 +494,7 @@ module RPicSim
587
494
  # given cycle count.
588
495
  # @param count [Integer]
589
496
  def run_to_cycle_count(count)
590
- while cycle_count < count
591
- step
592
- end
497
+ step while cycle_count < count
593
498
  end
594
499
 
595
500
  # Simulates a return instruction being executed by popping the top value off
@@ -597,35 +502,35 @@ module RPicSim
597
502
  # This can be useful for speeding up your tests when you have a very slow function
598
503
  # and just want to skip it.
599
504
  def return
600
- if stkptr.value == 0
601
- raise "Cannot return because stack pointer is 0."
505
+ if stack_pointer.value == 0
506
+ raise 'Cannot return because stack is empty.'
602
507
  end
603
508
 
604
509
  # Simulate popping the stack.
605
- stkptr.value -= 1
606
- pc.value = @stack_memory.read_word(stkptr.value)
510
+ stack_pointer.value -= 1
511
+ pc.value = @stack_memory.read_word(stack_pointer.value)
607
512
  end
608
513
 
609
514
  # Generates a friendly human-readable string description of where the
610
515
  # program counter is currently using the symbol table.
611
516
  def pc_description
612
- self.class.program_file.address_description(pc.value)
517
+ program_file.address_description(pc.value)
613
518
  end
614
519
 
615
520
  # Pushes the given address onto the simulated call stack.
616
521
  def stack_push(value)
617
- if !@stack_memory.is_valid_address?(stkptr.value)
618
- raise "Simulated stack is full (stack pointer = #{stkptr.value})."
522
+ if !@stack_memory.valid_address?(stack_pointer.value)
523
+ raise "Simulated stack is full (stack pointer = #{stack_pointer.value})."
619
524
  end
620
525
 
621
- @stack_memory.write_word(stkptr.value, value)
622
- stkptr.value += 1
526
+ @stack_memory.write_word(stack_pointer.value, value)
527
+ stack_pointer.value += 1
623
528
  end
624
529
 
625
530
  # Gets the contents of the stack as an array of integers.
626
531
  # @return [Array(Integer)] An array of integers.
627
532
  def stack_contents
628
- (0...stkptr.value).collect do |n|
533
+ (0...stack_pointer.value).map do |n|
629
534
  @stack_memory.read_word(n)
630
535
  end
631
536
  end
@@ -636,17 +541,20 @@ module RPicSim
636
541
  # @return [StackTrace]
637
542
  def stack_trace
638
543
  # The stack stores return addresses, not call addresses.
639
- # We get the call addresses by calling pred (subtract 1).
640
- # TODO: make this work for PIC18 devices where we probably have to subtract 2
641
- addresses = stack_contents.collect(&:pred) + [pc.value]
642
- entries = addresses.collect do |address|
643
- StackTraceEntry.new address, self.class.program_file.address_description(address)
544
+ # We get the call addresses by subtracting the address increment,
545
+ # which is the number of address units that each word of program memory takes up.
546
+ addresses = stack_contents.map do |return_address|
547
+ return_address - address_increment
548
+ end
549
+ addresses << pc.value
550
+ entries = addresses.map do |address|
551
+ StackTraceEntry.new address, program_file.address_description(address)
644
552
  end
645
553
  StackTrace.new(entries)
646
554
  end
647
555
 
648
556
  def inspect
649
- "#<#{self.class}:0x%x, #{pc_description}, stkptr = #{stkptr.value}>" % object_id
557
+ "#<#{self.class}:0x%x, #{pc_description}, stack_pointer = #{stack_pointer.value}>" % object_id
650
558
  end
651
559
 
652
560
  # Converts the specified condition into a Proc that, when called, will return a
@@ -674,13 +582,13 @@ module RPicSim
674
582
  Proc.new { pc.value == c }
675
583
 
676
584
  when :return
677
- current_val = stkptr.value
585
+ current_val = stack_pointer.value
678
586
  if current_val == 0
679
- raise "The stack pointer is 0; waiting for a return would be strange and might not work."
587
+ raise 'The stack pointer is 0; waiting for a return would be strange and might not work.'
680
588
  else
681
589
  target_val = current_val - 1
682
590
  end
683
- Proc.new { stkptr.value == target_val }
591
+ Proc.new { stack_pointer.value == target_val }
684
592
 
685
593
  when Label
686
594
  convert_condition_to_proc c.address
@@ -693,10 +601,27 @@ module RPicSim
693
601
  end
694
602
  end
695
603
 
604
+ # Creates and returns a {MemoryWatcher} object configured to watch for
605
+ # changes to RAM. For more information, see {file:RamWatcher.md}.
606
+ # @return [MemoryWatcher]
607
+ def new_ram_watcher
608
+ MemoryWatcher.new(self, @simulator.fr_memory, @ram_vars.values + @sfrs.values)
609
+ end
610
+
696
611
  def shortcuts
697
612
  self.class::Shortcuts
698
613
  end
614
+
615
+ # Returns the {RPicSim::ProgramFile} representing the firmware being simulated.
616
+ # @return [ProgramFile]
617
+ def program_file
618
+ self.class.program_file
619
+ end
620
+
621
+ private
622
+ def address_increment
623
+ @assembly.device_info.code_address_increment
624
+ end
699
625
  end
700
626
 
701
627
  end
702
-