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,76 @@
1
+ module RPicSim
2
+ module Flaws
3
+ # Represents a flaw in RPicSim, usually due to bugs or limitation of the
4
+ # MPLAB X classes we are using. Stores the name of the flaw and knowledge
5
+ # about what versions of MPLAB X it affects and how.
6
+ class Flaw
7
+ # Creates a new flaw with the specified name.
8
+ # @param name [Symbol] The name of this flaw.
9
+ def initialize(name)
10
+ @versions = {}
11
+ end
12
+
13
+ # Returns the effect of this flaw on the specified version of MPLAB X.
14
+ # This might just be true/false to indicate if the flaw is present or it
15
+ # might be a more complicated thing if there is more than one effect the
16
+ # the flaw can have.
17
+ #
18
+ # @param version [String] A version of MPLAB X, e.g. "1.95".
19
+ # @return effect
20
+ def effect(version)
21
+ if @versions.has_key? version
22
+ @versions[version]
23
+ else
24
+ @probable_affect_for_other_versions
25
+ end
26
+ end
27
+
28
+ # Records the effect this flaw has on a given version of MPLAB X.
29
+ def affects_version(version, effect)
30
+ @versions[version] = effect
31
+ end
32
+
33
+ # Records the effect that this flaw probably has in other versions of
34
+ # MPLAB X that have not been tested. This allows us to record our guesses
35
+ # about how the next version of MPLAB X will behave.
36
+ def probably_affects_other_versions(effect)
37
+ @probable_affect_for_other_versions = effect
38
+ end
39
+ end
40
+
41
+ @flaw_hash = {}
42
+ def self.[](name)
43
+ @flaw_hash[name].effect Mplab.version
44
+ end
45
+
46
+ def self.add(name)
47
+ @flaw_hash[name] = flaw = Flaw.new(name)
48
+ yield flaw
49
+ end
50
+
51
+ add(:fr_memory_attach_useless) do |flaw|
52
+ flaw.affects_version "1.85", false
53
+ flaw.affects_version "1.90", false
54
+ flaw.affects_version "1.95", true
55
+ flaw.affects_version "2.00", true
56
+ flaw.probably_affects_other_versions true
57
+ end
58
+
59
+ add(:firmware_cannot_write_user_id0) do |flaw|
60
+ flaw.affects_version "1.85", true
61
+ flaw.affects_version "1.90", true
62
+ flaw.affects_version "1.95", false
63
+ flaw.affects_version "2.00", false
64
+ flaw.probably_affects_other_versions false
65
+ end
66
+
67
+ add(:adc_midrange) do |flaw|
68
+ flaw.affects_version "1.85", :no_middle_values
69
+ flaw.affects_version "1.90", :bad_modulus
70
+ flaw.affects_version "1.95", :bad_modulus
71
+ flaw.affects_version "2.00", :bad_modulus
72
+ flaw.probably_affects_other_versions :bad_modulus
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,178 @@
1
+ module RPicSim
2
+ # Instances of this class represent a particular instruction at a particular
3
+ # address in program memory. This class takes low-level information about a
4
+ # disassembled instruction and produces high-level information about what that
5
+ # instruction is and how it behaves.
6
+ #
7
+ # Instances of this class have links to the other instructions that the instruction
8
+ # could lead to, so the instances form a graph. This graph is traversed by
9
+ # classes like {CallStackInfo} to get useful information about the firmware.
10
+ class Instruction
11
+ include Comparable
12
+
13
+ # The flash address of the instruction.
14
+ # For PIC18s this will be the byte address.
15
+ # For other PIC architectures, it will be the word address.
16
+ # @return (Integer)
17
+ attr_reader :address
18
+
19
+ # The opcode as a capitalized string (e.g. "MOVLW").
20
+ # @return (String)
21
+ attr_reader :opcode
22
+
23
+ # The operands of the instruction as a hash like { "k" => 92 }.
24
+ # @return (Hash)
25
+ attr_reader :operands
26
+
27
+ # The number of flash address units that this instruction takes.
28
+ # The units of this are the same as the units of {#address}.
29
+ # @return (Integer)
30
+ attr_reader :size
31
+
32
+ # Creates a new instruction.
33
+ # @param instruction_store some object such as {ProgramFile} that responds to #instruction and #address_description.
34
+ def initialize(address, instruction_store, opcode, operands, size, address_increment, string, properties)
35
+ @address = address
36
+ @instruction_store = instruction_store
37
+ @opcode = opcode
38
+ @operands = operands
39
+ @size = size
40
+ @address_increment = address_increment
41
+ @string = string
42
+
43
+ modules = {
44
+ conditional_skip: ConditionalSkip,
45
+ goto: Goto,
46
+ return: Return,
47
+ call: Call
48
+ }
49
+
50
+ properties.each do |p|
51
+ mod = modules[p]
52
+ if !mod
53
+ raise ArgumentError, "Invalid property: #{p.inspect}."
54
+ end
55
+ extend mod
56
+ end
57
+ end
58
+
59
+ # Compares this instruction to another using the addresses. This means you can
60
+ # call +.sort+ on an array of instructions to put them in order by address.
61
+ def <=>(other)
62
+ self.address <=> other.address
63
+ end
64
+
65
+ # Human-readable string representation of the instruction.
66
+ def to_s
67
+ "Instruction(#{@instruction_store.address_description(address)}, #{@string})"
68
+ end
69
+
70
+ def inspect
71
+ "#<#{self.class}:#{@instruction_store.address_description(address)}, #{@string}>"
72
+ end
73
+
74
+ # Returns info about all the instructions that this instruction could directly lead to
75
+ # (not counting interrupts, returns, and not accounting
76
+ # at all for what happens after the last word in flash is executed).
77
+ # For instructions that pop from the call stack like RETURN and RETFIE, this will be
78
+ # the empty array.
79
+ # @return [Array(Transition)]
80
+ def transitions
81
+ @transitions ||= generate_transitions
82
+ end
83
+
84
+ # Returns the transition from this instruction to the specified instruction
85
+ # or nil if no such transition exists.
86
+ # @return Transition
87
+ def transition_to(instruction)
88
+ @transitions.find { |t| t.next_instruction == instruction }
89
+ end
90
+
91
+ # Returns the addresses of all the instructions this instruction could directly lead to.
92
+ # @return [Array(Integer)]
93
+ def next_addresses
94
+ transitions.collect do |t|
95
+ t.next_instruction.address
96
+ end
97
+ end
98
+
99
+ private
100
+ # For certain opcodes, this method gets over-written.
101
+ def generate_transitions
102
+ [ advance(1) ]
103
+ end
104
+
105
+ # Makes a transition representing the default behavior: the microcontroller
106
+ # will increment the program counter and execute the next instruction in memory.
107
+ def advance(num)
108
+ transition(address + num * size)
109
+ end
110
+
111
+ def transition(addr, attrs={})
112
+ next_instruction = @instruction_store.instruction(addr)
113
+ Transition.new(self, next_instruction, attrs)
114
+ end
115
+
116
+ private
117
+ # Returns the address indicated by the operand 'k'.
118
+ # k is assumed to be a word address and it is assumed to be absolute
119
+ # k=0 is word 0 of memory, k=1 is word one.
120
+ # We need to multiply by the address increment because on PIC18
121
+ # flash addresses are actually byte-based instead of word-based.
122
+ def k_address
123
+ operands['k'] * @address_increment
124
+ end
125
+
126
+ ### Modules that modify the behavior of the instruction. ###
127
+
128
+
129
+ # This module is mixed into any {Instruction} that represents a goto or branch.
130
+ module Goto
131
+ def generate_transitions
132
+ # Assumption: The GOTO instruction's k operand is absolute on all architectures
133
+ [ transition(k_address, non_local: true) ]
134
+ end
135
+ end
136
+
137
+ # This module is mixed into any {Instruction} that represents a conditional skip
138
+ # A conditional skip is an instruction that might cause the next instruction to be
139
+ # skipped depending on some condition.
140
+ module ConditionalSkip
141
+ def generate_transitions
142
+ [ advance(1), advance(2) ]
143
+ end
144
+ end
145
+
146
+ # This module is mixed into any {Instruction} that represents a return from a subroutine.
147
+ module Return
148
+ def generate_transitions
149
+ []
150
+ end
151
+ end
152
+
153
+ # This module is mixed into any {Instruction} that represents a subroutine call.
154
+ module Call
155
+ def generate_transitions
156
+ [ transition(k_address, call_depth_change: 1), advance(1) ]
157
+ end
158
+ end
159
+
160
+ class Transition
161
+ attr_reader :next_instruction, :previous_instruction
162
+
163
+ def initialize(previous_instruction, next_instruction, attrs)
164
+ @previous_instruction = previous_instruction
165
+ @next_instruction = next_instruction
166
+ @attrs = attrs
167
+ end
168
+
169
+ def non_local?
170
+ @attrs.fetch(:non_local, false)
171
+ end
172
+
173
+ def call_depth_change
174
+ @attrs.fetch(:call_depth_change, 0)
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,28 @@
1
+ module RPicSim
2
+ # A very simple class that represents a label in firmware.
3
+ class Label
4
+ # The name of the label from the firmware.
5
+ # @return (Symbol)
6
+ attr_reader :name
7
+
8
+ # The address/value of the label from the firmware.
9
+ # @return (Integer)
10
+ attr_reader :address
11
+
12
+ # Makes a new label with the specified name and address.
13
+ def initialize(name, address)
14
+ @name = name
15
+ @address = address
16
+ end
17
+
18
+ # Returns the address of the label.
19
+ def to_i
20
+ address
21
+ end
22
+
23
+ # Returns a nice string representation of the label.
24
+ def to_s
25
+ "<Label %s address=0x%x>" % [name, address]
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ class Memory
2
+ # This object allows read and write access to the current data
3
+ # stored in a memory space of the simulated device.
4
+ #
5
+ # The behavior of this class differs depending on what kind of Memory
6
+ # it represents, as shown in the table below:
7
+ #
8
+ # Address type Read/write chunk
9
+ # Any type of RAM: Byte address 1 byte (8 bits)
10
+ # Midrange Flash: Word address 1 word (14 bits)
11
+ # PIC18 flash: Byte address 1 word (16 bits)
12
+ #
13
+ # @param mplab_memory [Mplab::Memory]
14
+ def initialize(mplab_memory)
15
+ @mplab_memory = mplab_memory
16
+ end
17
+
18
+ def write_word(address, value)
19
+ @mplab_memory.write_word(address, value)
20
+ end
21
+
22
+ def read_word(address)
23
+ @mplab_memory.read_word(address)
24
+ end
25
+
26
+ def is_valid_address?(address)
27
+ @mplab_memory.is_valid_address?(address)
28
+ end
29
+ end
@@ -0,0 +1,98 @@
1
+ require 'set'
2
+
3
+ module RPicSim
4
+ # This class can attach to an MPLAB X memory class and watch for changes in it, and
5
+ # report those changes as a hash associating variable names to new values. This is
6
+ # very useful for writing tests that assert that not only were the desired variables
7
+ # written to, but also that no other variables were written to.
8
+ #
9
+ # This class does not analyze the current instruction being executed; it uses the
10
+ # Attach method of the MPLAB X memory classes. This means that it doesn't work properly
11
+ # in some versions of MPLAB X. See {file:docs/Flaws.textile}.
12
+ class MemoryWatcher
13
+ attr_accessor :var_names_ignored
14
+ attr_accessor :var_names_ignored_on_first_step
15
+
16
+ # Creates a new instance.
17
+ # @param sim [Sim]
18
+ # @param memory [Mplab::MplabMemory] The memory to watch
19
+ # @param vars [Array(Variable)]
20
+ def initialize(sim, memory, vars)
21
+ # Populate the @vars_by_address instance hash
22
+ @vars_by_address = {}
23
+ vars.each do |var|
24
+ var.addresses.each do |address|
25
+ if @vars_by_address[address]
26
+ raise "Variable %s overlaps with %s at 0x%x" %
27
+ [var, @vars_by_address[address], address]
28
+ end
29
+ @vars_by_address[address] = var
30
+ end
31
+ end
32
+
33
+ @sim = sim
34
+ @memory = memory
35
+ @memory.on_change { |ar| handle_change(ar) }
36
+
37
+ @vars_written = Set.new
38
+ @var_names_ignored = default_var_names_ignored(sim.device)
39
+ @var_names_ignored_on_first_step = default_var_names_ignored_on_first_step(sim.device)
40
+ end
41
+
42
+ # Generate a nice report of what variables have been written to since the
43
+ # last time {#clear} was called.
44
+ # @return [Hash] A hash where the keys are names (as symbols) of variables that were
45
+ # written to, and the value is the variable's current value.
46
+ # If an address was written to that does not correspond to a variable, the key for
47
+ # that will just be address as an integer.
48
+ def writes
49
+ hash = {}
50
+ @vars_written.each do |var|
51
+ if var.is_a? Integer
52
+ hash[var] = @memory.read_word(var)
53
+ else
54
+ hash[var.name] = var.value
55
+ end
56
+ end
57
+ hash
58
+ end
59
+
60
+ # Clears the record of what variables have been written to.
61
+ def clear
62
+ @vars_written.clear
63
+ end
64
+
65
+ def default_var_names_ignored(device_name)
66
+ # The datasheet says the PCLATH is not affected by pushing or popping the stack, but
67
+ # we still get spurious events for it when a return instruction is executed.
68
+
69
+ [:PCL, :PCLATH, :STATUS]
70
+ end
71
+
72
+ def default_var_names_ignored_on_first_step(device_name)
73
+ #TODO: get rid of this stuff? people should just have a goto or something harmless at
74
+ # the beginning of their program and take a single step like we do.
75
+ [:PORTA, :LATA, :OSCCON, :PMCON2, :INTCON]
76
+ end
77
+
78
+ def handle_change(address_ranges)
79
+ addresses = address_ranges.flat_map(&:to_a)
80
+ vars = addresses.map { |a| @vars_by_address[a] || a }
81
+
82
+ remove_vars(vars, @var_names_ignored)
83
+ remove_vars(vars, @var_names_ignored_on_first_step) if @sim.cycle_count <= 1
84
+
85
+ # The line below works because @vars_written is a Set, not a Hash.
86
+ @vars_written.merge vars
87
+ end
88
+
89
+ private
90
+ def remove_vars(vars, var_names_to_remove)
91
+ vars.reject! do |key, val|
92
+ name = key.is_a?(Integer) ? key : key.name
93
+ var_names_to_remove.include?(name)
94
+ end
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,138 @@
1
+ require 'java'
2
+ require 'pathname'
3
+
4
+ module RPicSim
5
+ module Mplab
6
+ # Returns a Pathname object representing the directory of the MPLAB X we are using.
7
+ # This can either come from the +RPICSIM_MPLABX+ environment variable or it can
8
+ # be auto-detected by looking in the standard places that MPLAB X is installed.
9
+ # @return [Pathname]
10
+ def self.dir
11
+ @dir ||= begin
12
+ dir = ENV['RPICSIM_MPLABX']
13
+
14
+ dir ||= [
15
+ "C:/Program Files (x86)/Microchip/MPLABX/",
16
+ "C:/Program Files/Microchip/MPLABX/",
17
+ # TODO: add entries here for MPLAB X folders in Linux and Mac OS X
18
+ ].detect { |d| File.directory?(d) }
19
+
20
+ if !dir
21
+ raise "Cannot find MPLABX. Install it in the standard location or " +
22
+ "set the RPICSIM_MPASM environment variable to its full path."
23
+ end
24
+
25
+ if !File.directory?(dir)
26
+ raise "MPLABX directory does not exist: #{dir}"
27
+ end
28
+
29
+ Pathname(dir)
30
+ end
31
+ end
32
+
33
+ # Adds all the needed MPLAB X jar files to the classpath so we can use the
34
+ # classes.
35
+ def self.load_dependencies
36
+ %w{ mplab_ide/mdbcore/modules/*.jar
37
+ mplab_ide/mplablibs/modules/*.jar
38
+ mplab_ide/mplablibs/modules/ext/*.jar
39
+ mplab_ide/platform/lib/org-openide-util*.jar
40
+ mplab_ide/platform/lib/org-openide-util.jar
41
+ mplab_ide/mdbcore/modules/ext/org-openide-filesystems.jar
42
+ }.each do |pattern|
43
+ Dir.glob(dir + pattern).each do |jar_file|
44
+ $CLASSPATH << jar_file
45
+ end
46
+ end
47
+
48
+ # Do a quick test to make sure we can load some MPLAB X classes.
49
+ # In case MPLAB X was uninstalled and its directory remains, this can provide
50
+ # a useful error message to the user.
51
+ begin
52
+ org.openide.util.Lookup
53
+ com.microchip.mplab.mdbcore.simulator.Simulator
54
+ rescue NameError
55
+ $stderr.puts "Failed to load MPLAB X classes.\n" +
56
+ "MPLAB X dir: #{dir}\nClass path:\n" + $CLASSPATH.to_a.join("\n") + "\n\n"
57
+ raise
58
+ end
59
+ end
60
+
61
+ load_dependencies
62
+
63
+
64
+ # JRuby makes it hard to access packages with capital letters in their names.
65
+ # This is a workaround to let us access those packages.
66
+ module CapitalizedPackages
67
+ include_package "com.microchip.mplab.libs.MPLABDocumentLocator"
68
+ end
69
+
70
+ # The com.microchip.mplab.libs.MPLABDocumentLocator.MPLABDocumentLocator class from MPLAB X.
71
+ DocumentLocator = CapitalizedPackages::MPLABDocumentLocator
72
+
73
+ # Returns a string like "1.95" representing the version of MPLAB X we are using.
74
+ # NOTE: You should probably NOT be doing this to work around flaws in MPLAB X.
75
+ # Instead, you should add a new entry in flaws.rb and then use
76
+ # RPicSim::Flaws[:FLAWNAME] to see if the flaw exists and choose the appropriate workaround.
77
+ def self.version
78
+ # This implementation is not pretty; I would prefer to just find the right function
79
+ # to call.
80
+ @mplabx_version ||= begin
81
+ paths = Dir.glob(dir + "Uninstall_MPLAB_X_IDE_v*.dat")
82
+ if paths.empty?
83
+ raise "Cannot detect MPLAB X version. The Uninstall_MPLAB_X_IDE_v*.dat file was not found in #{mplabx_dir}."
84
+ end
85
+ match_data = paths.first.match(/v([0-9][0-9\.]*[0-9]+)\./)
86
+ match_data[1]
87
+ end
88
+ end
89
+
90
+ # Mutes the standard output, calls the given block, and then unmutes it.
91
+ def self.mute_stdout
92
+ begin
93
+ orig = java.lang.System.out
94
+ java.lang.System.setOut(java.io.PrintStream.new(NullOutputStream.new))
95
+ yield
96
+ ensure
97
+ java.lang.System.setOut(orig)
98
+ end
99
+ end
100
+
101
+ # Mutes a particular type of exception printed by NetBeans,
102
+ # calls the given block, then unmutes it.
103
+ def self.mute_exceptions
104
+ log = java.util.logging.Logger.getLogger('org.openide.util.Exceptions')
105
+ level = log.getLevel
106
+ begin
107
+ log.setLevel(java.util.logging.Level::OFF)
108
+ yield
109
+ ensure
110
+ log.setLevel(level)
111
+ end
112
+ end
113
+
114
+ # The MCDisAsm class is the disassembly provided by MPLAB X.
115
+ # We added this part so we could get access to its strategy object, but
116
+ # we are not currently using that feature.
117
+ class Java::ComMicrochipMplabMdbcoreDisasm::MCDisAsm
118
+ field_accessor :strategy
119
+ end
120
+
121
+ # This class helps us suppress the standard output temporarily.
122
+ class NullOutputStream < Java::JavaIo::OutputStream
123
+ def write(b)
124
+ end
125
+ end
126
+
127
+ Lookup = org.openide.util.Lookup
128
+ Mdbcore = com.microchip.mplab.mdbcore
129
+ end
130
+
131
+ end
132
+
133
+ # We want as much awareness as possible; if it becomes a problem we can change this.
134
+ com.microchip.mplab.logger.MPLABLogger.mplog.setLevel(java.util.logging.Level::ALL)
135
+
136
+ require_relative 'mplab/mplab_pin'
137
+ require_relative 'mplab/mplab_assembly'
138
+ require_relative 'mplab/mplab_program_file'