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.
- checksums.yaml +7 -0
- data/.yardopts +36 -0
- data/Gemfile +10 -0
- data/Introduction.md +64 -0
- data/LICENSE.txt +24 -0
- data/README.md +66 -0
- data/docs/ChangeLog.md +10 -0
- data/docs/Contributing.md +30 -0
- data/docs/Debugging.md +96 -0
- data/docs/DefiningSimulationClass.md +84 -0
- data/docs/DesignDecisions.md +48 -0
- data/docs/HowMPLABXIsFound.md +14 -0
- data/docs/IntegrationTesting.md +15 -0
- data/docs/IntroductionToRSpec.md +203 -0
- data/docs/IntroductionToRuby.md +90 -0
- data/docs/KnownIssues.md +204 -0
- data/docs/Labels.md +39 -0
- data/docs/MakingTestsRunFaster.md +29 -0
- data/docs/Manual.md +38 -0
- data/docs/PersistentExpectations.md +100 -0
- data/docs/Pins.md +143 -0
- data/docs/PreventingCallStackOverflow.md +94 -0
- data/docs/QuickStartGuide.md +85 -0
- data/docs/RSpecIntegration.md +119 -0
- data/docs/RamWatcher.md +46 -0
- data/docs/Running.md +129 -0
- data/docs/SFRs.md +71 -0
- data/docs/Stubbing.md +161 -0
- data/docs/SupportedCompilers.md +5 -0
- data/docs/SupportedDevices.md +14 -0
- data/docs/SupportedMPLABXVersions.md +12 -0
- data/docs/SupportedOperatingSystems.md +3 -0
- data/docs/UnitTesting.md +9 -0
- data/docs/Variables.md +140 -0
- data/lib/rpicsim.rb +15 -0
- data/lib/rpicsim/call_stack_info.rb +306 -0
- data/lib/rpicsim/flaws.rb +76 -0
- data/lib/rpicsim/instruction.rb +178 -0
- data/lib/rpicsim/label.rb +28 -0
- data/lib/rpicsim/memory.rb +29 -0
- data/lib/rpicsim/memory_watcher.rb +98 -0
- data/lib/rpicsim/mplab.rb +138 -0
- data/lib/rpicsim/mplab/mplab_assembly.rb +102 -0
- data/lib/rpicsim/mplab/mplab_device_info.rb +40 -0
- data/lib/rpicsim/mplab/mplab_disassembler.rb +23 -0
- data/lib/rpicsim/mplab/mplab_instruction.rb +32 -0
- data/lib/rpicsim/mplab/mplab_memory.rb +33 -0
- data/lib/rpicsim/mplab/mplab_nmmr_info.rb +21 -0
- data/lib/rpicsim/mplab/mplab_observer.rb +12 -0
- data/lib/rpicsim/mplab/mplab_pin.rb +61 -0
- data/lib/rpicsim/mplab/mplab_processor.rb +30 -0
- data/lib/rpicsim/mplab/mplab_program_file.rb +35 -0
- data/lib/rpicsim/mplab/mplab_register.rb +25 -0
- data/lib/rpicsim/mplab/mplab_sfr_info.rb +21 -0
- data/lib/rpicsim/mplab/mplab_simulator.rb +102 -0
- data/lib/rpicsim/pin.rb +61 -0
- data/lib/rpicsim/program_counter.rb +19 -0
- data/lib/rpicsim/program_file.rb +160 -0
- data/lib/rpicsim/register.rb +78 -0
- data/lib/rpicsim/rspec.rb +11 -0
- data/lib/rpicsim/rspec/be_predicate.rb +12 -0
- data/lib/rpicsim/rspec/helpers.rb +48 -0
- data/lib/rpicsim/rspec/persistent_expectations.rb +53 -0
- data/lib/rpicsim/rspec/sim_diagnostics.rb +51 -0
- data/lib/rpicsim/search.rb +20 -0
- data/lib/rpicsim/sim.rb +702 -0
- data/lib/rpicsim/stack_trace.rb +32 -0
- data/lib/rpicsim/variable.rb +236 -0
- data/lib/rpicsim/version.rb +3 -0
- 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
|
data/lib/rpicsim/pin.rb
ADDED
@@ -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
|