rpicsim 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|