rpicsim 0.1.0 → 0.2.1

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