rpicsim 0.1.0

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 +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