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
@@ -2,39 +2,39 @@ require_relative 'mplab_sfr_info'
2
2
  require_relative 'mplab_nmmr_info'
3
3
 
4
4
  module RPicSim::Mplab
5
- # DeviceInfo is a wrapper for the MPLAB xPIC class which gives us information
6
- # about the target PIC device.
5
+ # DeviceInfo is a wrapper for the com.microchip.mplab.crownkingx.xPIC class
6
+ # which gives us information about the target PIC device.
7
7
  class MplabDeviceInfo
8
8
  # Makes a new DeviceInfo object.
9
9
  # @param xpic [com.microchip.mplab.crownkingx.xPIC]
10
10
  def initialize(xpic)
11
11
  @xpic = xpic
12
12
  end
13
-
13
+
14
14
  def code_word_max_value
15
15
  # Assumption: the initial value is the same as the maximum value
16
16
  # because all bits start as 1.
17
17
  @xpic.getMemTraits.getCodeWordTraits.getInitValue
18
18
  end
19
-
20
- # The number that a flash address increases when you
21
- # advance to the next word of flash.
19
+
20
+ # The number that a code-space address increases by when you
21
+ # advance to the next word of code space.
22
22
  # For PIC18s this is 2.
23
23
  # For other architectures this is 1.
24
24
  def code_address_increment
25
25
  @xpic.getMemTraits.getCodeWordTraits.getAddrInc
26
26
  end
27
-
27
+
28
28
  def sfrs
29
29
  @sfrs ||= @xpic.getAddrOntoSFR.map do |addr, node|
30
30
  MplabSfrInfo.new addr, com.microchip.crownking.edc.Register.new(node)
31
31
  end
32
32
  end
33
-
33
+
34
34
  def nmmrs
35
35
  @xpic.getIDOntoCoreNMMR.map do |id, node|
36
36
  MplabNmmrInfo.new id, com.microchip.crownking.edc.Register.new(node)
37
37
  end
38
38
  end
39
39
  end
40
- end
40
+ end
@@ -6,18 +6,17 @@ module RPicSim::Mplab
6
6
  def initialize(disasm)
7
7
  @disasm = disasm
8
8
  end
9
-
9
+
10
10
  def disassemble(address)
11
11
  # To avoid showing a large, difficult to understand Java trace, we
12
12
  # catch the InvalidInstructionException here.
13
13
  begin
14
14
  instr = @disasm.Disassemble(address, nil, nil)
15
- rescue Java::ComMicrochipMplabMdbcoreDisasm::InvalidInstructionException => e
16
- # TODO: actually this should not be an exception because it is expected;
17
- # so probably you should return nil
18
- raise "Invalid instruction at address 0x%x." % address
15
+ rescue Java::ComMicrochipMplabMdbcoreDisasm::InvalidInstructionException
16
+ # The instruction is invalid.
17
+ return :invalid
19
18
  end
20
19
  MplabInstruction.new instr
21
20
  end
22
21
  end
23
- end
22
+ end
@@ -1,32 +1,103 @@
1
1
  module RPicSim::Mplab
2
+ # This class wraps a com.microchip.mplab.mdbcore.disasm.Instruction, which
3
+ # represents a disassembled instruction from Microchip's disassembler.
2
4
  class MplabInstruction
5
+ attr_reader :opcode
6
+
3
7
  # @param instruction [com.microchip.mplab.mdbcore.disasm.Instruction]
4
8
  def initialize(instruction)
5
9
  @instruction = instruction
10
+
11
+ @opcode = @instruction.opcode
12
+ @string = @instruction.instruction
13
+
14
+ # Fix a typo in MPLAB X.
15
+ if @opcode == 'RBLRD+*'
16
+ @opcode = 'TBLRD+*'
17
+ @string = @string.gsub('RBLRD', 'TBLRD')
18
+ end
19
+ @opcode.freeze
6
20
  end
7
-
8
- def opcode
9
- @instruction.opcode
10
- end
11
-
21
+
12
22
  def instruction_string
13
- @instruction.instruction
23
+ @string
14
24
  end
15
-
25
+
16
26
  def operands
17
27
  @operands ||= operands_hash(@instruction.operands)
18
28
  end
19
-
20
- # The number of bytes that the instruction takes.
21
- def inc
22
- @instruction.inc
29
+
30
+ # Returns the size of the instruction in the same units that are used
31
+ # for program memory addresses. (Bytes for the PIC18, otherwise words.)
32
+ # @param address_increment The number of address units per word of
33
+ # program memory in this architecture. See {MplabDeviceInfo#code_address_increment}.
34
+ def compute_size(address_increment)
35
+ if RPicSim::Flaws[:instruction_inc_is_in_byte_units]
36
+ # Convert the increment, which is the number of bytes, into 'size',
37
+ # which is the same units as the program memory address space.
38
+ if address_increment == 1
39
+ # Non-PIC18 architectures: program memory addresses are in terms of words
40
+ # so we divide by two to convert from bytes to words.
41
+ @instruction.inc / 2
42
+ elsif address_increment == 2
43
+ # PIC18 architecture: No change necessary because both are in terms
44
+ # of bytes.
45
+ @instruction.inc
46
+ else
47
+ raise "Cannot handle address increment value of #{@address_increment}."
48
+ end
49
+ else
50
+ # inc is in the same units as the code space addresses.
51
+ @instruction.inc
52
+ end
23
53
  end
24
-
54
+
25
55
  private
56
+
26
57
  def operands_hash(map)
27
- # Convert from Map<String, Integer> to a Ruby hash.
28
- # TODO: use symbols for keys instead of strings
29
- map.to_hash
58
+ operands = convert_map_to_hash(map)
59
+ fix_signed_fields(operands)
60
+ operands
61
+ end
62
+
63
+ def convert_map_to_hash(map)
64
+ # Convert from Map<String, Integer> to a Ruby hash
65
+ # with symbols as keys instead of strings.
66
+ operands = {}
67
+ map.each do |operand_name, value|
68
+ operands[operand_name.to_sym] = value
69
+ end
70
+ operands
71
+ end
72
+
73
+ # Warning: This mutates the supplied hash.
74
+ def fix_signed_fields(operands)
75
+ case opcode
76
+ when 'BC', 'BN', 'BNC', 'BNN', 'BNOV', 'BNZ', 'BOV', 'BZ'
77
+ convert_if_present(operands, :n, 8) # PIC18
78
+ when 'RCALL'
79
+ convert_if_present(operands, :n, 11) # PIC18
80
+ when 'BRA'
81
+ convert_if_present(operands, :n, 11) # PIC18
82
+ convert_if_present(operands, :k, 9) # enhanced midrange
83
+ when 'ADDFSR'
84
+ convert_if_present(operands, :k, 6) # enhanced midrange
85
+ end
86
+
87
+ operands
88
+ end
89
+
90
+ def convert_if_present(operands, name, bits)
91
+ operands[name] = convert_unsigned_to_signed(operands[name], bits) if operands[name]
92
+ end
93
+
94
+ def convert_unsigned_to_signed(unsigned, bits)
95
+ if unsigned >= (1 << (bits - 1))
96
+ unsigned - (1 << bits)
97
+ else
98
+ unsigned
99
+ end
30
100
  end
101
+
31
102
  end
32
- end
103
+ end
@@ -0,0 +1,106 @@
1
+ require 'java'
2
+ require 'pathname'
3
+ require 'singleton'
4
+
5
+ module RPicSim::Mplab
6
+ # This class helps find MPLAB X on the disk, add it to the Java class path
7
+ # so we can use it from JRuby, and figure out what version of MPLAB X we
8
+ # are using.
9
+ #
10
+ # It should not be confused with com.microchip.mplab.mdbcore.loader.Loader,
11
+ # which is used for loading program files.
12
+ class MplabLoader
13
+ include Singleton
14
+
15
+ # Adds all the needed MPLAB X jar files to the classpath so we can use the
16
+ # classes.
17
+ def load
18
+ %w{ mdbcore/modules/*.jar
19
+ mplablibs/modules/*.jar
20
+ mplablibs/modules/ext/*.jar
21
+ platform/lib/org-openide-util*.jar
22
+ platform/lib/org-openide-util.jar
23
+ mdbcore/modules/ext/org-openide-filesystems.jar
24
+ }.each do |pattern|
25
+ Dir.glob(jar_dir + pattern).each do |jar_file|
26
+ $CLASSPATH << jar_file
27
+ end
28
+ end
29
+
30
+ # Do a quick test to make sure we can load some MPLAB X classes.
31
+ # In case MPLAB X was uninstalled and its directory remains, this can provide
32
+ # a useful error message to the user.
33
+ begin
34
+ org.openide.util.Lookup
35
+ com.microchip.mplab.mdbcore.simulator.Simulator
36
+ rescue NameError
37
+ $stderr.puts "Failed to load MPLAB X classes.\n" +
38
+ "MPLAB X dir: #{dir}\nMPLAB X jar dir: #{jar_dir}\nClass path:\n" + $CLASSPATH.to_a.join("\n") + "\n\n"
39
+ raise
40
+ end
41
+ end
42
+
43
+ # Returns a string like "1.95" representing the version of MPLAB X we are using.
44
+ # NOTE: You should probably NOT be calling this to work around flaws in MPLAB X.
45
+ # Instead, you should add a new entry in flaws.rb and then use
46
+ # RPicSim::Flaws[:FLAWNAME] to see if the flaw exists and choose the appropriate workaround.
47
+ def mplab_version
48
+ # This implementation is not pretty; I would prefer to just find the right function
49
+ # to call.
50
+ @mplab_version ||= begin
51
+ glob_pattern = 'Uninstall_MPLAB_X_IDE_v*'
52
+ paths = Dir.glob(dir + glob_pattern)
53
+ if paths.empty?
54
+ raise "Cannot detect MPLAB X version. No file matching #{glob_pattern} found in #{dir}."
55
+ end
56
+ matches = paths.map { |p| p.match(/IDE_v([0-9][0-9\.]*[0-9]+)\./) }.compact
57
+ match_data = matches.first
58
+ if !match_data
59
+ raise "Failed to get version number from #{paths.inspect}."
60
+ end
61
+ match_data[1]
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ # Returns a Pathname object representing the directory of the MPLAB X we are using.
68
+ # This can either come from the +RPICSIM_MPLABX+ environment variable or it can
69
+ # be auto-detected by looking in the standard places that MPLAB X is installed.
70
+ # @return [Pathname]
71
+ def dir
72
+ @dir ||= begin
73
+ dir = ENV['RPICSIM_MPLABX'] || auto_detect_mplab_dir
74
+ raise "MPLABX directory does not exist: #{dir}" if !File.directory?(dir)
75
+ Pathname(dir)
76
+ end
77
+ end
78
+
79
+ def auto_detect_mplab_dir
80
+ # Default installation directories for MPLAB X:
81
+ candidates = [
82
+ 'C:/Program Files (x86)/Microchip/MPLABX/', # 64-bit Windows
83
+ 'C:/Program Files/Microchip/MPLABX/', # 32-bit Windows
84
+ '/opt/microchip/mplabx/', # Linux
85
+ '/Applications/microchip/mplabx/', # Mac OS X
86
+ ]
87
+ dir = candidates.find { |d| File.directory?(d) }
88
+ raise cannot_find_mplab_error if !dir
89
+ dir
90
+ end
91
+
92
+ def cannot_find_mplab_error
93
+ 'Cannot find MPLABX. Install it in the standard location or ' +
94
+ 'set the RPICSIM_MPLABX environment variable to its full path.'
95
+ end
96
+
97
+ def jar_dir
98
+ @jar_dir ||= if (dir + 'mplab_ide.app').exist?
99
+ # Mac OS X
100
+ dir + 'mplab_ide.app/Contents/Resources/mplab_ide'
101
+ else
102
+ dir + 'mplab_ide'
103
+ end
104
+ end
105
+ end
106
+ end
@@ -6,20 +6,33 @@ module RPicSim::Mplab
6
6
  def initialize(memory)
7
7
  @memory = memory
8
8
  end
9
-
9
+
10
+ def read_byte(address)
11
+ array = [0].to_java(:byte)
12
+ @memory.Read(address, 1, array)
13
+ array.ubyte_get(0)
14
+ end
15
+
16
+ def write_byte(address, byte)
17
+ array = Java.byte[1].new
18
+ array.ubyte_set(0, byte)
19
+ @memory.Write(address, 1, array)
20
+ byte
21
+ end
22
+
10
23
  def write_word(address, value)
11
24
  @memory.WriteWord(address, value)
12
25
  value
13
26
  end
14
-
27
+
15
28
  def read_word(address)
16
29
  @memory.ReadWord(address)
17
30
  end
18
-
19
- def is_valid_address?(address)
31
+
32
+ def valid_address?(address)
20
33
  @memory.IsValidAddress(address)
21
34
  end
22
-
35
+
23
36
  def on_change(&callback)
24
37
  MplabObserver.new(@memory) do |event|
25
38
  next if event.EventType != Mdbcore.memory::MemoryEvent::EVENTS::MEMORY_CHANGED
@@ -30,4 +43,4 @@ module RPicSim::Mplab
30
43
  end
31
44
  end
32
45
  end
33
- end
46
+ end
@@ -1,21 +1,21 @@
1
1
  module RPicSim::Mplab
2
2
  class MplabNmmrInfo
3
3
  attr_reader :id
4
-
4
+
5
5
  # @param id The id of the register. This is like an address.
6
6
  # @param register [com.microchip.crownking.edc.Register]
7
7
  def initialize(id, register)
8
8
  @id = id
9
9
  @register = register
10
10
  end
11
-
11
+
12
12
  # Returns how many bits the register has.
13
13
  def width
14
14
  @register.width
15
15
  end
16
-
16
+
17
17
  def name
18
18
  @register.name
19
19
  end
20
20
  end
21
- end
21
+ end
@@ -1,12 +1,17 @@
1
- class MplabObserver
2
- include Java::comMicrochipMplabUtilObservers::Observer
1
+ module RPicSim::Mplab
2
+ # This class implements the com.microchip.mplab.util.observers.Observer
3
+ # interface, so we can easily receive events from objects that support
4
+ # sending updates to observers.
5
+ class MplabObserver
6
+ include Java::ComMicrochipMplabUtilObservers::Observer
3
7
 
4
- def initialize(subject, &callback)
5
- @callback = callback
6
- subject.Attach(self, nil)
7
- end
8
-
9
- def Update(event)
10
- @callback.call(event)
8
+ def initialize(subject, &callback)
9
+ @callback = callback
10
+ subject.Attach(self, nil)
11
+ end
12
+
13
+ def Update(event)
14
+ @callback.call(event)
15
+ end
11
16
  end
12
- end
17
+ end
@@ -3,7 +3,7 @@ module RPicSim::Mplab
3
3
  # Initializes a new Pin object to wrap the given PinPhysical.
4
4
  # @param pin_physical [com.microchip.mplab.mdbcore.simulator.PinPhysical]
5
5
  def initialize(pin_physical)
6
- raise ArgumentError, "pin_physical is nil" if pin_physical.nil?
6
+ raise ArgumentError, 'pin_physical is nil' if pin_physical.nil?
7
7
  @pin_physical = pin_physical
8
8
  end
9
9
 
@@ -47,12 +47,12 @@ module RPicSim::Mplab
47
47
  end
48
48
 
49
49
  def names
50
- @pin_physical.collect(&:name)
50
+ @pin_physical.map(&:name)
51
51
  end
52
52
 
53
53
  def name
54
54
  @pin_physical.pinName
55
- end
55
+ end
56
56
 
57
57
  PinState = Mdbcore.simulator.Pin::PinState # HIGH or LOW
58
58
  IoState = Mdbcore.simulator.Pin::IOState # INPUT or OUTPUT
@@ -6,25 +6,25 @@ module RPicSim::Mplab
6
6
  def initialize(processor)
7
7
  @processor = processor
8
8
  end
9
-
9
+
10
10
  def get_pc
11
11
  @processor.getPC
12
12
  end
13
-
13
+
14
14
  def set_pc(value)
15
15
  @processor.setPC(value)
16
16
  end
17
-
17
+
18
18
  def get_sfr(name)
19
19
  reg = @processor.getSFRSet.getSFR(name)
20
20
  raise "Cannot find SFR named '#{name}'." if !reg
21
21
  MplabRegister.new(reg)
22
22
  end
23
-
23
+
24
24
  def get_nmmr(name)
25
25
  reg = @processor.getNMMRSet.getNMMR(name)
26
26
  raise "Cannot find NMMR named '#{name}'." if !reg
27
27
  MplabRegister.new(reg)
28
28
  end
29
29
  end
30
- end
30
+ end