rpicsim 0.1.0

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 +7 -0
  2. data/.yardopts +36 -0
  3. data/Gemfile +10 -0
  4. data/Introduction.md +64 -0
  5. data/LICENSE.txt +24 -0
  6. data/README.md +66 -0
  7. data/docs/ChangeLog.md +10 -0
  8. data/docs/Contributing.md +30 -0
  9. data/docs/Debugging.md +96 -0
  10. data/docs/DefiningSimulationClass.md +84 -0
  11. data/docs/DesignDecisions.md +48 -0
  12. data/docs/HowMPLABXIsFound.md +14 -0
  13. data/docs/IntegrationTesting.md +15 -0
  14. data/docs/IntroductionToRSpec.md +203 -0
  15. data/docs/IntroductionToRuby.md +90 -0
  16. data/docs/KnownIssues.md +204 -0
  17. data/docs/Labels.md +39 -0
  18. data/docs/MakingTestsRunFaster.md +29 -0
  19. data/docs/Manual.md +38 -0
  20. data/docs/PersistentExpectations.md +100 -0
  21. data/docs/Pins.md +143 -0
  22. data/docs/PreventingCallStackOverflow.md +94 -0
  23. data/docs/QuickStartGuide.md +85 -0
  24. data/docs/RSpecIntegration.md +119 -0
  25. data/docs/RamWatcher.md +46 -0
  26. data/docs/Running.md +129 -0
  27. data/docs/SFRs.md +71 -0
  28. data/docs/Stubbing.md +161 -0
  29. data/docs/SupportedCompilers.md +5 -0
  30. data/docs/SupportedDevices.md +14 -0
  31. data/docs/SupportedMPLABXVersions.md +12 -0
  32. data/docs/SupportedOperatingSystems.md +3 -0
  33. data/docs/UnitTesting.md +9 -0
  34. data/docs/Variables.md +140 -0
  35. data/lib/rpicsim.rb +15 -0
  36. data/lib/rpicsim/call_stack_info.rb +306 -0
  37. data/lib/rpicsim/flaws.rb +76 -0
  38. data/lib/rpicsim/instruction.rb +178 -0
  39. data/lib/rpicsim/label.rb +28 -0
  40. data/lib/rpicsim/memory.rb +29 -0
  41. data/lib/rpicsim/memory_watcher.rb +98 -0
  42. data/lib/rpicsim/mplab.rb +138 -0
  43. data/lib/rpicsim/mplab/mplab_assembly.rb +102 -0
  44. data/lib/rpicsim/mplab/mplab_device_info.rb +40 -0
  45. data/lib/rpicsim/mplab/mplab_disassembler.rb +23 -0
  46. data/lib/rpicsim/mplab/mplab_instruction.rb +32 -0
  47. data/lib/rpicsim/mplab/mplab_memory.rb +33 -0
  48. data/lib/rpicsim/mplab/mplab_nmmr_info.rb +21 -0
  49. data/lib/rpicsim/mplab/mplab_observer.rb +12 -0
  50. data/lib/rpicsim/mplab/mplab_pin.rb +61 -0
  51. data/lib/rpicsim/mplab/mplab_processor.rb +30 -0
  52. data/lib/rpicsim/mplab/mplab_program_file.rb +35 -0
  53. data/lib/rpicsim/mplab/mplab_register.rb +25 -0
  54. data/lib/rpicsim/mplab/mplab_sfr_info.rb +21 -0
  55. data/lib/rpicsim/mplab/mplab_simulator.rb +102 -0
  56. data/lib/rpicsim/pin.rb +61 -0
  57. data/lib/rpicsim/program_counter.rb +19 -0
  58. data/lib/rpicsim/program_file.rb +160 -0
  59. data/lib/rpicsim/register.rb +78 -0
  60. data/lib/rpicsim/rspec.rb +11 -0
  61. data/lib/rpicsim/rspec/be_predicate.rb +12 -0
  62. data/lib/rpicsim/rspec/helpers.rb +48 -0
  63. data/lib/rpicsim/rspec/persistent_expectations.rb +53 -0
  64. data/lib/rpicsim/rspec/sim_diagnostics.rb +51 -0
  65. data/lib/rpicsim/search.rb +20 -0
  66. data/lib/rpicsim/sim.rb +702 -0
  67. data/lib/rpicsim/stack_trace.rb +32 -0
  68. data/lib/rpicsim/variable.rb +236 -0
  69. data/lib/rpicsim/version.rb +3 -0
  70. metadata +114 -0
@@ -0,0 +1,102 @@
1
+ require_relative 'mplab_memory'
2
+ require_relative 'mplab_processor'
3
+
4
+ module RPicSim::Mplab
5
+ # DeviceInfo is a wrapper for the MPLAB xPIC class which gives us information
6
+ # about the target PIC device.
7
+ class MplabSimulator
8
+ # Makes a new DeviceInfo object.
9
+ # @param simulator [com.microchip.mplab.mdbcore.simulator.Simulator]
10
+ def initialize(simulator)
11
+ @simulator = simulator
12
+ end
13
+
14
+ def stopwatch_value
15
+ @simulator.GetStopwatchValue
16
+ end
17
+
18
+ def fr_memory
19
+ @fr_memory ||= MplabMemory.new data_store.getFileMemory
20
+ end
21
+
22
+ def sfr_memory
23
+ @sfr_memory ||= MplabMemory.new data_store.getSFRMemory
24
+ end
25
+
26
+ def nmmr_memory
27
+ @nmmr_memory ||= MplabMemory.new data_store.getNMMRMemory
28
+ end
29
+
30
+ def program_memory
31
+ @program_memory ||= MplabMemory.new data_store.getProgMemory
32
+ end
33
+
34
+ def stack_memory
35
+ @stack_memory ||= MplabMemory.new data_store.getStackMemory
36
+ end
37
+
38
+ def test_memory
39
+ @test_memory ||= MplabMemory.new data_store.getTestMemory
40
+ end
41
+
42
+ def processor
43
+ @processor ||= MplabProcessor.new data_store.getProcessor
44
+ end
45
+
46
+ def pins
47
+ pin_descs = (0...data_store.getNumPins).collect { |i| data_store.getPinDesc(i) }
48
+
49
+ pin_set = data_store.getProcessor.getPinSet
50
+
51
+ # The PinSet class has strangely-implemented lazy loading.
52
+ # We call getPin(String name) to force it to load pin data from
53
+ # the SimulatorDataStore.
54
+ pin_descs.each do |pin_desc|
55
+ name = pin_desc.getSignal(0).name # e.g. "RA0"
56
+ pin_set.getPin name # Trigger the lazy loading.
57
+ end
58
+
59
+ pins = (0...pin_set.getNumPins).collect do |i|
60
+ MplabPin.new pin_set.getPin(i)
61
+ end
62
+ end
63
+
64
+ def check_peripherals
65
+ check_peripherals_in_data_store
66
+ check_peripheral_set
67
+ end
68
+
69
+ private
70
+
71
+ def data_store
72
+ @simulator.getDataStore
73
+ end
74
+
75
+ def check_peripherals_in_data_store
76
+ if data_store.getNumPeriphs == 0
77
+ raise "MPLAB X failed to load any peripheral descriptions into the data store."
78
+ end
79
+ end
80
+
81
+ def check_peripheral_set
82
+ peripherals = data_store.getProcessor.getPeripheralSet
83
+ if peripherals.getNumPeripherals == 0
84
+ raise "MPLAB X failed to load any peripherals into the PeripheralSet."
85
+ end
86
+ end
87
+
88
+ # This is commented out because MPLAB X v2.00 with the PIC18F25K50
89
+ # reports three missing peripherals:
90
+ # PBADEN_PCFG
91
+ # config
92
+ # CONFIG3H.PBADEN
93
+ #
94
+ #def check_missing_peripherals
95
+ # peripherals = data_store.getProcessor.getPeripheralSet
96
+ # if peripherals.getMissingPeripherals.to_a.size > 0
97
+ # raise "This device has missing peripherals: " + peripherals.getMissingReasons().to_a.inspect
98
+ # end
99
+ #end
100
+
101
+ end
102
+ end
@@ -0,0 +1,61 @@
1
+ module RPicSim
2
+ # This class represents an external pin of the simulated device.
3
+ # It provides methods for reading the pin's output value and setting
4
+ # its input value.
5
+ class Pin
6
+ # Initializes a new Pin object to wrap the given MplabPin.
7
+ # @param mplab_pin [Mplab::MplabPin]
8
+ def initialize(mplab_pin)
9
+ raise ArgumentError, "mplab_pin is nil" if mplab_pin.nil?
10
+ @mplab_pin = mplab_pin
11
+ end
12
+
13
+ # Sets the external stimulus input voltage applied to the pin.
14
+ # The boolean values true and false correspond to high and low, respectively.
15
+ # Numeric values (e.g. Float or Integer) correspond to analog voltages.
16
+ def set(state)
17
+ case state
18
+ when false then @mplab_pin.set_low
19
+ when true then @mplab_pin.set_high
20
+ when Numeric then @mplab_pin.set_analog(state)
21
+ else raise ArgumentError, "Invalid pin state: #{state.inspect}."
22
+ end
23
+ end
24
+
25
+ # Returns true if the pin is currently configured to be an output.
26
+ def output?
27
+ @mplab_pin.output?
28
+ end
29
+
30
+ # Returns true if the pin is currently configured to be an input.
31
+ def input?
32
+ !@mplab_pin.output?
33
+ end
34
+
35
+ # Returns true if the pin is currently configured to be an output and
36
+ # it is driving high.
37
+ def driving_high?
38
+ output? && @mplab_pin.high?
39
+ end
40
+
41
+ # Returns true if the pin is currently configured to be an output and
42
+ # it is driving low.
43
+ def driving_low?
44
+ output? && !@mplab_pin.high?
45
+ end
46
+
47
+ # Returns an array of all the pin's names from the datasheet, like
48
+ # "RA4" or "TX".
49
+ def names
50
+ @mplab_pin.names
51
+ end
52
+
53
+ def to_s
54
+ @mplab_pin.name
55
+ end
56
+
57
+ def inspect
58
+ "#<%s %s>" % [self.class, to_s]
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,19 @@
1
+ module RPicSim
2
+
3
+ # Instances of this class represent the program counter in a
4
+ # simulated microcontroller.
5
+ class ProgramCounter
6
+ # @param processor [Mplab::Processor]
7
+ def initialize(processor)
8
+ @processor = processor
9
+ end
10
+
11
+ def value
12
+ @processor.get_pc
13
+ end
14
+
15
+ def value=(val)
16
+ @processor.set_pc(val)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,160 @@
1
+ require_relative 'mplab'
2
+ require_relative 'label'
3
+ require_relative 'instruction'
4
+
5
+ module RPicSim
6
+ # Represents a PIC program file (e.g. COF or HEX).
7
+ class ProgramFile
8
+ attr_reader :filename
9
+ attr_reader :device
10
+
11
+ # @param filename [String] The path to the program file.
12
+ # @param device [String] The name of the device the file is for (e.g. "PIC10F322").
13
+ def initialize(filename, device)
14
+ @filename = filename
15
+ @device = device
16
+ @mplab_program_file = Mplab::MplabProgramFile.new(filename, device)
17
+
18
+ @assembly = Mplab::MplabAssembly.new(device)
19
+ @assembly.load_file(filename)
20
+ @address_increment = @assembly.device_info.code_address_increment
21
+
22
+ @instructions = []
23
+ end
24
+
25
+ # Returns a hash associating RAM variable names (as symbols) to their addresses.
26
+ # @return (Hash)
27
+ def var_addresses
28
+ @var_addresses ||= @mplab_program_file.symbols_in_ram
29
+ end
30
+
31
+ # Returns a hash associating program memory label names (as symbols) to their addresses.
32
+ # @return (Hash)
33
+ def labels
34
+ @labels ||= begin
35
+ hash = {}
36
+ @mplab_program_file.symbols_in_code_space.each do |name, address|
37
+ hash[name] = Label.new(name, address)
38
+ end
39
+ hash
40
+ end
41
+ end
42
+
43
+ # Returns a {Label} object if a program label by that name is found.
44
+ # The name is specified in the code that defined the label. If you are using a C compiler,
45
+ # you will probably need to prefix the name with an underscore.
46
+ # @return [Label]
47
+ def label(name)
48
+ name = name.to_sym
49
+ label = labels[name]
50
+ if !label
51
+ raise ArgumentError, message_for_label_not_found(name)
52
+ end
53
+ return label
54
+ end
55
+
56
+ # Generates a friendly human-readable string description of the given address in
57
+ # program memory.
58
+ # @param address [Integer] An address in program memory.
59
+ # @return [String]
60
+ def address_description(address)
61
+ desc = address < 0 ? address.to_s : ("0x%04x" % [address])
62
+ reference_points = labels.values.reject { |label| label.address > address }
63
+ label = reference_points.max_by &:address
64
+
65
+ if label
66
+ offset = address - label.address
67
+ desc << " = " + label.name.to_s
68
+ desc << "+%#x" % [offset] if offset != 0
69
+ end
70
+
71
+ return desc
72
+ end
73
+
74
+ # Gets an {Instruction} object representing the PIC instruction at the given
75
+ # address in program memory.
76
+ # @param address [Integer]
77
+ # @return [Instruction]
78
+ def instruction(address)
79
+ @instructions[address] ||= make_instruction(address)
80
+ end
81
+
82
+ private
83
+ def message_for_label_not_found(name)
84
+ message = "Cannot find label named '#{name}'."
85
+
86
+ maybe_intended_labels = labels.keys.select do |label_sym|
87
+ name.to_s.start_with?(label_sym.to_s)
88
+ end
89
+ if !maybe_intended_labels.empty?
90
+ message << " MPASM truncates labels. You might have meant: " +
91
+ maybe_intended_labels.join(", ") + "."
92
+ end
93
+ message
94
+ end
95
+
96
+ def make_instruction(address)
97
+ mplab_instruction = @assembly.disassembler.disassemble(address)
98
+
99
+ # Convert the increment, which is the number of bytes, into 'size',
100
+ # which is the same units as the flash address space.
101
+ if @address_increment == 1
102
+ # Non-PIC18 architectures: flash addresses are in terms of words
103
+ # so we devide by two to convert from bytes to words.
104
+ size = mplab_instruction.inc / 2
105
+ elsif @address_increment == 2
106
+ # PIC18 architecture: No change necessary because both are in terms
107
+ # of bytes.
108
+ size = mplab_instruction.inc
109
+ else
110
+ raise "Cannot handle address increment value of #{@address_increment}."
111
+ end
112
+
113
+ # TODO: add support for all other 8-bit PIC architectures
114
+ properties = Array case mplab_instruction.opcode
115
+ when 'ADDWF'
116
+ when 'ANDWF'
117
+ when 'CPFSEQ' then [:conditional_skip]
118
+ when 'CLRF'
119
+ when 'CLRW'
120
+ when 'COMF'
121
+ when 'DECF'
122
+ when 'DECFSZ' then [:conditional_skip]
123
+ when 'INCF'
124
+ when 'INCFSZ' then [:conditional_skip]
125
+ when 'IORWF'
126
+ when 'MOVWF'
127
+ when 'MOVF'
128
+ when 'NOP'
129
+ when 'RLF'
130
+ when 'RRF'
131
+ when 'SUBWF'
132
+ when 'SWAPF'
133
+ when 'XORWF'
134
+ when 'BCF'
135
+ when 'BSF'
136
+ when 'BTFSC' then [:conditional_skip]
137
+ when 'BTFSS' then [:conditional_skip]
138
+ when 'ADDLW'
139
+ when 'ANDLW'
140
+ when 'CALL' then [:call]
141
+ when 'CLRWDT'
142
+ when 'GOTO' then [:goto]
143
+ when 'IORLW'
144
+ when 'MOVLW'
145
+ when 'RETFIE' then [:return]
146
+ when 'RETLW' then [:return]
147
+ when 'RETURN' then [:return]
148
+ when 'SLEEP'
149
+ when 'XORLW'
150
+ else
151
+ raise "Unrecognized opcode #{mplab_instruction.opcode} " +
152
+ "(#{address_description(address)}, operands #{mplab_instruction.operands.inspect})."
153
+ end
154
+
155
+ Instruction.new(address, self, mplab_instruction.opcode,
156
+ mplab_instruction.operands, size, @address_increment,
157
+ mplab_instruction.instruction_string, properties)
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,78 @@
1
+ module RPicSim
2
+ class Register
3
+ attr_reader :name
4
+
5
+ # The size of the register in bytes.
6
+ attr_reader :size
7
+
8
+ # @param mplab_register [Mplab::MplabRegister]
9
+ # @param memory An optional parameter that enables memory_value.
10
+ def initialize(mplab_register, memory, width)
11
+ @mplab_register = mplab_register
12
+ @name = mplab_register.name.to_sym
13
+
14
+ @size = case width
15
+ when 8 then 1
16
+ when 16 then 2
17
+ when 24 then 3
18
+ when 32 then 4
19
+ else raise "Unsupported register width: #{name} is #{width}-bit."
20
+ end
21
+
22
+ var_type = case size
23
+ when 1 then VariableU8
24
+ when 2 then VariableU16
25
+ when 3 then VariableU24
26
+ when 4 then VariableU32
27
+ end
28
+
29
+ @var = var_type.new(name, address).bind(memory)
30
+ end
31
+
32
+ # Sets the value of the register.
33
+ # @param val [Integer]
34
+ def value=(val)
35
+ @mplab_register.write val
36
+ end
37
+
38
+ # Reads the value of the register.
39
+ # @return [Integer]
40
+ def value
41
+ @mplab_register.read
42
+ end
43
+
44
+ # For some registers, like STATUS, you cannot read and write the full
45
+ # range of possible values using {#value=} because some bits are not
46
+ # writable by the CPU.
47
+ # This setter gets around that by writing directly to the memory object
48
+ # that backs the register.
49
+ def memory_value=(value)
50
+ @var.value = value
51
+ end
52
+
53
+ # Reads the value directly from the memory object backing the register.
54
+ def memory_value
55
+ @var.value
56
+ end
57
+
58
+ # Gets the address of the register.
59
+ # @return [Integer]
60
+ def address
61
+ @mplab_register.address
62
+ end
63
+
64
+ # Gets the range of addresses occupied.
65
+ # @return [Range] A range of integers.
66
+ def addresses
67
+ address...(address + size)
68
+ end
69
+
70
+ def to_s
71
+ name.to_s
72
+ end
73
+
74
+ def inspect
75
+ "<%s %s 0x%x>" % [self.class, name, address]
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,11 @@
1
+ require 'rpicsim'
2
+ require 'rspec'
3
+
4
+ require_relative 'rspec/helpers'
5
+ require_relative 'rspec/sim_diagnostics'
6
+ require_relative 'rspec/be_predicate'
7
+
8
+ RSpec.configure do |config|
9
+ config.add_setting :sim_shortcuts, default: :all
10
+ config.include RPicSim::RSpec::Helpers
11
+ end
@@ -0,0 +1,12 @@
1
+ # We override some parts of RSpec to provide better error messages.
2
+ # Instead of 'expected driving_high? to return true, got false' RSpec will actually
3
+ # tell us what object it called driving_high on.
4
+ class RSpec::Matchers::BuiltIn::BePredicate
5
+ def failure_message_for_should
6
+ "expected #{actual.inspect}.#{predicate}#{args_to_s} to return true, got #{@result.inspect}"
7
+ end
8
+
9
+ def failure_message_for_should_not
10
+ "expected #{actual.inspect}.#{predicate}#{args_to_s} to return false, got #{@result.inspect}"
11
+ end
12
+ end