rpicsim 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/Gemfile +7 -6
  4. data/Introduction.md +3 -1
  5. data/README.md +2 -2
  6. data/docs/ChangeLog.md +6 -0
  7. data/docs/Contributing.md +1 -1
  8. data/docs/DefiningSimulationClass.md +11 -10
  9. data/docs/HowMPLABXIsFound.md +1 -1
  10. data/docs/IntegrationTesting.md +1 -1
  11. data/docs/IntroductionToRSpec.md +8 -5
  12. data/docs/IntroductionToRuby.md +2 -2
  13. data/docs/KnownIssues.md +46 -57
  14. data/docs/Labels.md +5 -4
  15. data/docs/Manual.md +1 -1
  16. data/docs/Memories.md +70 -0
  17. data/docs/PersistentExpectations.md +31 -2
  18. data/docs/Pins.md +5 -7
  19. data/docs/PreventingCallStackOverflow.md +4 -6
  20. data/docs/QuickStartGuide.md +5 -5
  21. data/docs/RSpecIntegration.md +2 -2
  22. data/docs/RamWatcher.md +22 -11
  23. data/docs/Running.md +4 -6
  24. data/docs/Stubbing.md +4 -4
  25. data/docs/SupportedDevices.md +2 -11
  26. data/docs/SupportedMPLABXVersions.md +1 -0
  27. data/docs/SupportedOperatingSystems.md +3 -2
  28. data/docs/UnitTesting.md +1 -1
  29. data/docs/Variables.md +81 -25
  30. data/lib/rpicsim.rb +0 -12
  31. data/lib/rpicsim/call_stack_info.rb +43 -47
  32. data/lib/rpicsim/composite_memory.rb +53 -0
  33. data/lib/rpicsim/flaws.rb +34 -22
  34. data/lib/rpicsim/instruction.rb +204 -48
  35. data/lib/rpicsim/label.rb +4 -4
  36. data/lib/rpicsim/memory.rb +44 -23
  37. data/lib/rpicsim/memory_watcher.rb +14 -22
  38. data/lib/rpicsim/mplab.rb +38 -119
  39. data/lib/rpicsim/mplab/mplab_assembly.rb +23 -18
  40. data/lib/rpicsim/mplab/mplab_device_info.rb +9 -9
  41. data/lib/rpicsim/mplab/mplab_disassembler.rb +5 -6
  42. data/lib/rpicsim/mplab/mplab_instruction.rb +87 -16
  43. data/lib/rpicsim/mplab/mplab_loader.rb +106 -0
  44. data/lib/rpicsim/mplab/mplab_memory.rb +19 -6
  45. data/lib/rpicsim/mplab/mplab_nmmr_info.rb +4 -4
  46. data/lib/rpicsim/mplab/mplab_observer.rb +15 -10
  47. data/lib/rpicsim/mplab/mplab_pin.rb +3 -3
  48. data/lib/rpicsim/mplab/mplab_processor.rb +5 -5
  49. data/lib/rpicsim/mplab/mplab_program_file.rb +29 -17
  50. data/lib/rpicsim/mplab/mplab_register.rb +5 -5
  51. data/lib/rpicsim/mplab/mplab_sfr_info.rb +4 -4
  52. data/lib/rpicsim/mplab/mplab_simulator.rb +27 -30
  53. data/lib/rpicsim/pin.rb +6 -6
  54. data/lib/rpicsim/program_counter.rb +3 -3
  55. data/lib/rpicsim/program_file.rb +39 -81
  56. data/lib/rpicsim/rspec/be_predicate.rb +1 -1
  57. data/lib/rpicsim/rspec/helpers.rb +1 -1
  58. data/lib/rpicsim/rspec/persistent_expectations.rb +17 -2
  59. data/lib/rpicsim/rspec/sim_diagnostics.rb +5 -5
  60. data/lib/rpicsim/search.rb +1 -1
  61. data/lib/rpicsim/sim.rb +153 -228
  62. data/lib/rpicsim/stack_pointer.rb +41 -0
  63. data/lib/rpicsim/stack_trace.rb +1 -1
  64. data/lib/rpicsim/storage/memory_integer.rb +235 -0
  65. data/lib/rpicsim/{register.rb → storage/register.rb} +18 -18
  66. data/lib/rpicsim/variable.rb +25 -211
  67. data/lib/rpicsim/variable_set.rb +93 -0
  68. data/lib/rpicsim/version.rb +2 -2
  69. metadata +9 -4
  70. data/docs/SFRs.md +0 -71
@@ -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
-