rpicsim 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/Gemfile +7 -6
- data/Introduction.md +3 -1
- data/README.md +2 -2
- data/docs/ChangeLog.md +6 -0
- data/docs/Contributing.md +1 -1
- data/docs/DefiningSimulationClass.md +11 -10
- data/docs/HowMPLABXIsFound.md +1 -1
- data/docs/IntegrationTesting.md +1 -1
- data/docs/IntroductionToRSpec.md +8 -5
- data/docs/IntroductionToRuby.md +2 -2
- data/docs/KnownIssues.md +46 -57
- data/docs/Labels.md +5 -4
- data/docs/Manual.md +1 -1
- data/docs/Memories.md +70 -0
- data/docs/PersistentExpectations.md +31 -2
- data/docs/Pins.md +5 -7
- data/docs/PreventingCallStackOverflow.md +4 -6
- data/docs/QuickStartGuide.md +5 -5
- data/docs/RSpecIntegration.md +2 -2
- data/docs/RamWatcher.md +22 -11
- data/docs/Running.md +4 -6
- data/docs/Stubbing.md +4 -4
- data/docs/SupportedDevices.md +2 -11
- data/docs/SupportedMPLABXVersions.md +1 -0
- data/docs/SupportedOperatingSystems.md +3 -2
- data/docs/UnitTesting.md +1 -1
- data/docs/Variables.md +81 -25
- data/lib/rpicsim.rb +0 -12
- data/lib/rpicsim/call_stack_info.rb +43 -47
- data/lib/rpicsim/composite_memory.rb +53 -0
- data/lib/rpicsim/flaws.rb +34 -22
- data/lib/rpicsim/instruction.rb +204 -48
- data/lib/rpicsim/label.rb +4 -4
- data/lib/rpicsim/memory.rb +44 -23
- data/lib/rpicsim/memory_watcher.rb +14 -22
- data/lib/rpicsim/mplab.rb +38 -119
- data/lib/rpicsim/mplab/mplab_assembly.rb +23 -18
- data/lib/rpicsim/mplab/mplab_device_info.rb +9 -9
- data/lib/rpicsim/mplab/mplab_disassembler.rb +5 -6
- data/lib/rpicsim/mplab/mplab_instruction.rb +87 -16
- data/lib/rpicsim/mplab/mplab_loader.rb +106 -0
- data/lib/rpicsim/mplab/mplab_memory.rb +19 -6
- data/lib/rpicsim/mplab/mplab_nmmr_info.rb +4 -4
- data/lib/rpicsim/mplab/mplab_observer.rb +15 -10
- data/lib/rpicsim/mplab/mplab_pin.rb +3 -3
- data/lib/rpicsim/mplab/mplab_processor.rb +5 -5
- data/lib/rpicsim/mplab/mplab_program_file.rb +29 -17
- data/lib/rpicsim/mplab/mplab_register.rb +5 -5
- data/lib/rpicsim/mplab/mplab_sfr_info.rb +4 -4
- data/lib/rpicsim/mplab/mplab_simulator.rb +27 -30
- data/lib/rpicsim/pin.rb +6 -6
- data/lib/rpicsim/program_counter.rb +3 -3
- data/lib/rpicsim/program_file.rb +39 -81
- data/lib/rpicsim/rspec/be_predicate.rb +1 -1
- data/lib/rpicsim/rspec/helpers.rb +1 -1
- data/lib/rpicsim/rspec/persistent_expectations.rb +17 -2
- data/lib/rpicsim/rspec/sim_diagnostics.rb +5 -5
- data/lib/rpicsim/search.rb +1 -1
- data/lib/rpicsim/sim.rb +153 -228
- data/lib/rpicsim/stack_pointer.rb +41 -0
- data/lib/rpicsim/stack_trace.rb +1 -1
- data/lib/rpicsim/storage/memory_integer.rb +235 -0
- data/lib/rpicsim/{register.rb → storage/register.rb} +18 -18
- data/lib/rpicsim/variable.rb +25 -211
- data/lib/rpicsim/variable_set.rb +93 -0
- data/lib/rpicsim/version.rb +2 -2
- metadata +9 -4
- data/docs/SFRs.md +0 -71
@@ -32,7 +32,17 @@ To remove a persistent expectation, specify a matcher of `nil`:
|
|
32
32
|
!!!ruby
|
33
33
|
expecting main_output => nil
|
34
34
|
|
35
|
-
|
35
|
+
If `expecting` is given a block, expectations will only be valid for the duration of the block:
|
36
|
+
|
37
|
+
!!!ruby
|
38
|
+
# Verify that the main output stays high for 10 cycles.
|
39
|
+
expecting main_output => be_driving_high do
|
40
|
+
# The expectation will be checked within the block.
|
41
|
+
run_cycles 10
|
42
|
+
end
|
43
|
+
# The expectation will not be checked here.
|
44
|
+
|
45
|
+
The persistent expectations will not be checked immediately when they are added, but they will be checked after every step of the simulation.
|
36
46
|
You can also check them at any time by calling `check_expecations` inside your RSPec example.
|
37
47
|
|
38
48
|
Persistent expectations, when combined with RSpec's `satisfy` matcher, are very powerful. If `counter` is a {RPicSim::Variable variable} in your simulation, you could use this code to ensure that `counter` never goes above 120:
|
@@ -67,8 +77,27 @@ The following RSpec example tests that the main output pin is held low (after gi
|
|
67
77
|
|
68
78
|
In the above example, we removed the persistent expectation on `main_output` temporarily because the device was in a transitionary period and we didn't know exactly when the transition would happen.
|
69
79
|
We chose to stop monitoring the pin for the duration of the transition and then start monitoring it later, at which point we expect the pin to be in its new state.
|
80
|
+
We can rewrite that using block arguments instead of explicitly clearing the expectation:
|
81
|
+
|
82
|
+
!!!ruby
|
83
|
+
it "mirrors the main input onto the main output pin" do
|
84
|
+
run_cycles 120 # Give the device time to start up.
|
85
|
+
|
86
|
+
expecting main_output => be_driving_low do
|
87
|
+
run_cycles 800
|
88
|
+
end
|
89
|
+
|
90
|
+
main_input.set true
|
91
|
+
|
92
|
+
# Give the device time to detect the change in the input.
|
93
|
+
run_cycles 200
|
94
|
+
|
95
|
+
expecting main_output => be_driving_high do
|
96
|
+
run_cycles 800
|
97
|
+
end
|
98
|
+
end
|
70
99
|
|
71
|
-
If you need to repeat this patten many times in your tests, you might consider adding a method in `spec_helper.rb` to help you do it:
|
100
|
+
If you need to repeat this patten many times in your tests, you might consider adding a method in your `spec_helper.rb` to help you do it:
|
72
101
|
|
73
102
|
!!!ruby
|
74
103
|
def transition(opts={})
|
data/docs/Pins.md
CHANGED
@@ -23,8 +23,6 @@ The first argument of {RPicSim::Sim#pin} should be the name of the pin as a symb
|
|
23
23
|
The allowed names come from the MPLAB X code, but they should match the names given in the PIC datasheet.
|
24
24
|
For example, the PIC10F322 pin RA1 can be referred to by many names, including `:RA1`, `:PWM2`, `:AN1`, and `:NCO1CLK`.
|
25
25
|
|
26
|
-
RPicSim does not model the GND and VDD pins.
|
27
|
-
|
28
26
|
Pin aliases
|
29
27
|
----
|
30
28
|
|
@@ -46,19 +44,19 @@ This makes `:main_output` be an alias for `:RA1`. You can now access the Pin ob
|
|
46
44
|
!!!ruby
|
47
45
|
pin(:main_output)
|
48
46
|
|
49
|
-
Defining a pin alias also adds a
|
47
|
+
Defining a pin alias also adds a new method by the same name. This means that you can access the pin like this:
|
50
48
|
|
51
49
|
!!!ruby
|
52
50
|
sim.main_output
|
53
51
|
|
54
|
-
|
52
|
+
Shortcuts for these methods are also available in RSpec thanks to RPicsim's {file:RSpecIntegration.md RSpec integration}, so you can simply write `main_output` instead of `sim.main_output` in any of your RSpec examples:
|
55
53
|
|
56
54
|
!!!ruby
|
57
55
|
it "drives the main output high" do
|
58
56
|
expect(main_output).to be_driving_high
|
59
57
|
end
|
60
58
|
|
61
|
-
Note that since
|
59
|
+
Note that since these methods are available in many places, your pin names might conflict with names defined in other places.
|
62
60
|
|
63
61
|
|
64
62
|
Pin methods
|
@@ -102,8 +100,8 @@ In `spec/spec_helper.rb`, we make a simulation class that points to the compiled
|
|
102
100
|
require 'rpicsim/rspec'
|
103
101
|
|
104
102
|
class PinMirror < RPicSim::Pic
|
105
|
-
|
106
|
-
|
103
|
+
use_device "PIC10F322"
|
104
|
+
use_file File.dirname(__FILE__) + "../firmware/dist/firmware.cof"
|
107
105
|
|
108
106
|
def_pin :main_input, :RA0
|
109
107
|
def_pin :main_output, :RA1
|
@@ -4,7 +4,6 @@ Preventing call stack overflow
|
|
4
4
|
PIC microcontrollers feature a stack implemented in hardware that keeps track of return addresses for subroutine calls.
|
5
5
|
Every time a `CALL` instruction is executed, the return address is pushed onto the stack.
|
6
6
|
Every time a `RETURN` or similar instruction is executed, the return address is popped off of the stack.
|
7
|
-
The PIC datasheets tend to refer to this as the "stack", but in RPicSim it is known as the _call stack_ in order to make it clear that this is the stack that relates to the `CALL` instruction and it is different from any kind of stack a C compiler might place in RAM for local variables.
|
8
7
|
|
9
8
|
The call stack has a limited depth that depends on the device you are using.
|
10
9
|
If your program has too many levels of nested subroutines then the call stack could overflow.
|
@@ -25,7 +24,7 @@ Here is an example:
|
|
25
24
|
call_stack_info[0].max_depth be <= 5
|
26
25
|
end
|
27
26
|
|
28
|
-
specify "ISR code uses no more than
|
27
|
+
specify "ISR code uses no more than 1 level" do
|
29
28
|
call_stack_info[4].max_depth.should be <= 1
|
30
29
|
end
|
31
30
|
end
|
@@ -73,8 +72,6 @@ The {RPicSim::CallStackInfo} class traverses all possible paths through that gra
|
|
73
72
|
Limitations
|
74
73
|
----
|
75
74
|
|
76
|
-
The code for disassembling in {RPicSim::ProgramFile} currently only works with the midrange and baseline PIC instruction sets. However, it should be easy to expand it to the other instruction sets.
|
77
|
-
|
78
75
|
The algorithm is pessimistic:
|
79
76
|
|
80
77
|
* It does not try to track the runtime values of any of your program's variables in order to predict which code paths will happen.
|
@@ -88,7 +85,8 @@ However, there are some things that can mess up the algorithm in a bad way and g
|
|
88
85
|
* If you write to the PC register in order to do a computed jump, the algorithm does not currently detect that and will not correctly consider code paths coming from that instruction.
|
89
86
|
Be careful about this, because a computed jump might be generated automatically by a C compiler.
|
90
87
|
* Similarly, it cannot handle jumps on devices that have paged memory. In order to determine where a jump actually goes, it would need to know what page is selected by looking at the history of the program's execution.
|
88
|
+
* It does not account for the effect of PUSH and POP instructions on the call stack depth.
|
91
89
|
|
92
|
-
This code is not suitable (yet) for any firmware that uses a computed jump
|
90
|
+
This code is not suitable (yet) for any firmware that uses a computed jump, paged program memory, or PUSH or POP instructions.
|
93
91
|
|
94
|
-
This code only checks the hardware call stack; it does not check any kind of data stack that your compiler might use to store the values of local variables.
|
92
|
+
This code only checks the hardware call stack; it does not check any kind of data stack that your compiler might use to store the values of local variables.
|
data/docs/QuickStartGuide.md
CHANGED
@@ -8,7 +8,7 @@ By the end of this guide, you will have a suite of automated simulator-based tes
|
|
8
8
|
Installing prerequisites
|
9
9
|
----
|
10
10
|
|
11
|
-
First, on a computer running Windows, install RPicSim and the software it requires:
|
11
|
+
First, on a computer running Windows, Linux, or Mac OS X, install RPicSim and the software it requires:
|
12
12
|
|
13
13
|
1. Install [MPLAB X](http://www.microchip.com/pagehandler/en-us/family/mplabx/). RPicSim uses the Microchip Java classes from MPLAB X.
|
14
14
|
2. Install the latest version of [JRuby](http://jruby.org/).
|
@@ -65,13 +65,13 @@ We have not yet told RPicSim where to find the firmware file. To do this, make
|
|
65
65
|
require 'rpicsim/rspec'
|
66
66
|
|
67
67
|
class MySim < RPicSim::Sim
|
68
|
-
|
69
|
-
|
68
|
+
use_device "PIC10F322"
|
69
|
+
use_file File.dirname(__FILE__) + "/../firmware/dist/default/production/firmware_dir.production.cof"
|
70
70
|
end
|
71
71
|
|
72
|
-
Edit the `
|
72
|
+
Edit the `use_device` and `use_file` lines to match your actual device and the path to its COF file. The file specified here can either be COF or HEX, but COF is recommended because it allows convenient access to the variables, functions, and labels defined in the firmware.
|
73
73
|
|
74
|
-
Eventually you should rename the `MySim` class to something more specific
|
74
|
+
Eventually you should rename the `MySim` class to something more specific, such as the concatenation of the project name with "Sim".
|
75
75
|
|
76
76
|
To run the spec, go to your shell and run the command `rspec` from the directory that contains `spec`. In the example directory structure above, you would need to be inside the `project_dir` directory when you run `rspec`. If all goes well, the output from `rspec` should look like:
|
77
77
|
|
data/docs/RSpecIntegration.md
CHANGED
@@ -23,7 +23,7 @@ Requiring "rpicsim/rspec" causes the {RPicSim::RSpec::Helpers} module to get inc
|
|
23
23
|
|
24
24
|
This module provides the {RPicSim::RSpec::Helpers#start_sim start_sim} method and the methods described on the {file:PersistentExpectations.md persistent expectations page}.
|
25
25
|
You can call `start_sim` in an example or a before hook to start a new simulation.
|
26
|
-
The simulation object can then be accessed
|
26
|
+
The simulation object can then be accessed through a method named `sim` in your examples.
|
27
27
|
|
28
28
|
### Basic shortcuts
|
29
29
|
|
@@ -38,7 +38,7 @@ You can call these by simply typing a method name in an RSpec example:
|
|
38
38
|
### Firmware-specific shortcuts
|
39
39
|
|
40
40
|
Unless you disable them, you will get access to firmware-specific shortcuts defined by the simulation.
|
41
|
-
These shortcuts correspond to items defined with {RPicSim::Sim::ClassDefinitionMethods#def_var def_var}
|
41
|
+
These shortcuts correspond to items defined with {RPicSim::Sim::ClassDefinitionMethods#def_var def_var} and {RPicSim::Sim::ClassDefinitionMethods#def_pin def_pin}.
|
42
42
|
|
43
43
|
For example, if your {file:DefiningSimulationClass.md simulation class} defines a pin named `main_output`, then you can just write code like this in your RSpec examples:
|
44
44
|
|
data/docs/RamWatcher.md
CHANGED
@@ -2,9 +2,9 @@ RAM watcher
|
|
2
2
|
====
|
3
3
|
|
4
4
|
When writing a {file:UnitTesting.md unit test} for some part of your firmware, you should probably test any RAM variable that the code is supposed to write to, and make sure that it contains the correct value after the code executes.
|
5
|
-
However,
|
5
|
+
However, it is also helpful to try to make sure that your code does not write to any other parts of memory; it should only write to the places that you were expecting it to write to.
|
6
6
|
|
7
|
-
RPicSim's
|
7
|
+
RPicSim's _RAM watcher_ lets you see all the places in RAM (including SFRs) that were written by your code.
|
8
8
|
It detects writes to RAM even if the underlying RAM value didn't change.
|
9
9
|
For example, if your program runs a `clrf x` instruction, the RAM watcher will detect this even if `x` was already equal to 0.
|
10
10
|
|
@@ -15,12 +15,17 @@ However, that instruction technically counts as a read from `x` and a write of t
|
|
15
15
|
Please note that the RAM watcher works well in MPLAB X 1.85 and 1.90 but the latest versions of MPLAB X have an issue that makes the RAM watcher useless.
|
16
16
|
For more information, see {file:KnownIssues.md}.
|
17
17
|
|
18
|
-
|
18
|
+
To create a new RAM watcher object, call {RPicSim::Sim#new_ram_watcher}. There is a shortcut for this method, so if you are using RPicSim's {file:RSpecIntegration.md RSpec integration} then you can just write:
|
19
|
+
|
20
|
+
!!!ruby
|
21
|
+
ram_watcher = new_ram_watcher
|
22
|
+
|
23
|
+
The resulting object is an instance of the {RPicSim::MemoryWatcher} class and has two important methods:
|
19
24
|
|
20
25
|
* The {RPicSim::MemoryWatcher#writes writes} method provides a hash representing all the writes that have been recorded.
|
21
26
|
Each key of the hash is the name of the variable or SFR that was written to, or just the address that was written to if the write was to an unrecognized location in memory.
|
22
|
-
The values of the hash are the final value that the
|
23
|
-
If a
|
27
|
+
The values of the hash are the final value that the variable had after the last write.
|
28
|
+
If a variable is written to more than once, the RAM watcher will only report about the last write.
|
24
29
|
* The {RPicSim::MemoryWatcher#clear clear} method erases all previous records.
|
25
30
|
|
26
31
|
For example, to test the 16-bit addition routine from the {file:Variables.md Variables page} with the RAM watcher, you could write:
|
@@ -29,18 +34,24 @@ For example, to test the 16-bit addition routine from the {file:Variables.md Var
|
|
29
34
|
it "adds x to y and stores the result in z" do
|
30
35
|
x.value = 70
|
31
36
|
y.value = 22
|
32
|
-
step
|
37
|
+
step
|
38
|
+
ram_watcher = new_ram_watcher
|
33
39
|
run_subroutine :addition, cycle_limit: 100
|
34
|
-
expect(
|
40
|
+
expect(ram_watcher.writes).to eq({z: 92})
|
35
41
|
end
|
36
42
|
|
37
|
-
The third line in the example above
|
43
|
+
The third line in the example above advances the simulation by one step.
|
44
|
+
That initial step is necessary for two reasons:
|
45
|
+
|
46
|
+
* Without it, the RAM watcher would report the writes to the `x` and `z` variables performed above, even though those writes came from Ruby code.
|
47
|
+
* Without it, the RAM watcher would report spurious writes to several registers such as INTCON and LATA.
|
48
|
+
On the first step of the simulation, the MPLAB X code reports writes to several registers that were not caused by the firmware.
|
49
|
+
We can avoid seeing them by taking a single step before creating the RAM watcher.
|
38
50
|
|
39
51
|
The RAM watcher is an instance of {RPicSim::MemoryWatcher}.
|
40
52
|
|
41
53
|
Filters
|
42
54
|
----
|
43
55
|
|
44
|
-
The
|
45
|
-
|
46
|
-
Both of these filters might need to be updated to properly support your particular PIC (and your version of MPLAB X).
|
56
|
+
The {RPicSim::MemoryWatcher} class contains some special code to filter out reports about registers that very frequently change, like `PCL` and `STATUS`.
|
57
|
+
|
data/docs/Running.md
CHANGED
@@ -19,8 +19,7 @@ Single-stepping
|
|
19
19
|
|
20
20
|
The {RPicSim::Sim#step} method is the most basic way to run the simulation.
|
21
21
|
It executes a single instruction.
|
22
|
-
The program counter will be updated to point to the next instruction, and the {RPicSim::Sim#cycle_count cycle count} will be increased by the number of cycles that the instruction took.
|
23
|
-
This method might do some interesting things at times when the CPU is stalled (i.e. during a flash write) or during sleep and that behavior has not been characterized.
|
22
|
+
The {RPicSim::Sim#pc program counter} will be updated to point to the next instruction, and the {RPicSim::Sim#cycle_count cycle count} will be increased by the number of cycles that the instruction took.
|
24
23
|
|
25
24
|
The `step` method is the most basic way to run a simulation, and all the `run_*` methods described here call `step` in order to actually run the simulation.
|
26
25
|
|
@@ -120,10 +119,9 @@ The first argument should be a label name (or any valid argument to {RPicSim::Si
|
|
120
119
|
For example, to test a subroutine that drives the `main_output` pin high:
|
121
120
|
|
122
121
|
run_subroutine :drivePinHigh, cycle_limit: 20
|
123
|
-
main_output.
|
122
|
+
expect(main_output).to be_driving_high
|
124
123
|
|
125
124
|
In this example, `main_output` is a pin alias, as described in the {file:Pins.md Pins page}.
|
126
125
|
|
127
|
-
Some subroutine values might store input or output values in RAM
|
128
|
-
|
129
|
-
Some subroutine values might store input or output values in SFRs. To test those subroutines, you will need to be able to read and write SFRs as described in the {file:SFRs.md SFRs page}.
|
126
|
+
Some subroutine values might store input or output values in RAM, either in user-defined variables or in special function registers (SFRs).
|
127
|
+
To test those subroutines, you can read and write RAM as described in the {file:Variables.md Variables page}.
|
data/docs/Stubbing.md
CHANGED
@@ -21,7 +21,7 @@ To stub a method in the most basic way, you can do something like this:
|
|
21
21
|
The example above just alters our simulation so that whenever the `foo` subroutine is called, instead of running as normal it will return immediately using {RPicSim::Sim#return}.
|
22
22
|
|
23
23
|
The example above can be expanded in many ways:
|
24
|
-
You might read and write from {file:Variables.md variables}
|
24
|
+
You might read and write from {file:Variables.md variables}.
|
25
25
|
You might record information about how the subroutine was called.
|
26
26
|
|
27
27
|
|
@@ -110,9 +110,9 @@ In `spec/spec_helper.rb`, we make a simulation class that points to the compiled
|
|
110
110
|
require 'rpicsim/rspec'
|
111
111
|
|
112
112
|
class LongDelay < RPicSim::Sim
|
113
|
-
|
114
|
-
|
115
|
-
def_var :hot, :
|
113
|
+
use_device "PIC10F322"
|
114
|
+
use_file File.dirname(__FILE__) + "../firmware/dist/firmware.cof"
|
115
|
+
def_var :hot, :uint8
|
116
116
|
end
|
117
117
|
|
118
118
|
In `spec/cooldown_spec.rb`, we stub the `bigDelay` routine and test `cooldown` to make sure it calls `bigDelay` the right number of times:
|
data/docs/SupportedDevices.md
CHANGED
@@ -1,14 +1,5 @@
|
|
1
1
|
# Supported devices
|
2
2
|
|
3
|
-
RPicSim aims to support all 8-bit PIC microcontrollers. No support is planned for 16-bit or 32-bit PIC microcontrollers because
|
3
|
+
RPicSim aims to support all 8-bit PIC microcontrollers. No support is planned for 16-bit or 32-bit PIC microcontrollers because we do not have an interest in using those devices.
|
4
4
|
|
5
|
-
RPicSim relies on MPLAB X code to perform the actual simulation, and MPLAB X abstracts away most of the differences between
|
6
|
-
|
7
|
-
The 8-bit PICs have {http://www.microchip.com/pagehandler/en-us/family/8bit/architecture/home.html four different architectures}:
|
8
|
-
|
9
|
-
- Baseline
|
10
|
-
- Midrange
|
11
|
-
- Enhanced Midrange
|
12
|
-
- PIC18
|
13
|
-
|
14
|
-
There is currently some code in {RPicSim::ProgramFile#instruction} that only works with baseline and midrange PICs, but it should be easy to expand it to other PICs. This code is only used to calculate call stack depth information for users who want that feature; it is not used for actual simulations.
|
5
|
+
RPicSim relies on MPLAB X code to perform the actual simulation, and MPLAB X abstracts away most of the differences between different PIC microcontrollers.
|
@@ -6,6 +6,7 @@ RPicSim uses code in MPLAB X to actually perform the PIC simulation. Therefore,
|
|
6
6
|
- 1.90
|
7
7
|
- 1.95
|
8
8
|
- 2.00
|
9
|
+
- 2.05
|
9
10
|
|
10
11
|
The different versions of MPLAB X have different problems that affect the simulation. For more information, see the {file:KnownIssues.md Known issues page}.
|
11
12
|
|
@@ -1,3 +1,4 @@
|
|
1
|
-
# Supported
|
1
|
+
# Supported operating systems
|
2
2
|
|
3
|
-
RPicSim
|
3
|
+
RPicSim supports recent versions of Windows, Linux, and Mac OS X.
|
4
|
+
RPicSim should work on any computer that can run both MPLAB X and JRuby.
|
data/docs/UnitTesting.md
CHANGED
@@ -5,5 +5,5 @@ A _unit test_ is a test for a relatively small piece of a code, which tests that
|
|
5
5
|
Unit testing is simply the practice of writing and running units tests to go along with your code.
|
6
6
|
|
7
7
|
The {file:Running.md Running page} explains how to run small portions of your code using RPicSim.
|
8
|
-
RPicSim allows you to access {file:Variables.md variables} and {file:
|
8
|
+
RPicSim allows you to access {file:Variables.md variables} and {file:Registers Registers}, so you can put the simulation into the desired state before running the code and then test that the simulation is in the right state after the code has executed.
|
9
9
|
RPicSim can also be used for {file:Stubbing.md stubbing subroutines} so that you can simply test the behavior of one subroutine instead of all the subroutines it calls.
|
data/docs/Variables.md
CHANGED
@@ -1,23 +1,22 @@
|
|
1
1
|
Variables
|
2
2
|
====
|
3
3
|
|
4
|
-
RPicSim
|
4
|
+
RPicSim uses the {RPicSim::Variable} class to let you access simulated program variables stored in RAM, program memory, or EEPROM, as well as Special Function Registers, which can be useful for {file:UnitTesting.md unit testing}.
|
5
5
|
|
6
|
-
To access a variable, RPicSim needs to know the name it will be called in your Ruby code, what data type it is (e.g. 16-bit unsigned integer), and its address in memory.
|
7
|
-
|
8
|
-
type the address.
|
9
|
-
However, RPicSim cannot deduce the data type of a variable, so any variables used need to be explicitly defined beforehand.
|
6
|
+
To access a variable, RPicSim needs to know the name it will be called in your Ruby code, what type of memory it is stored in, what data type it is (e.g. 16-bit unsigned integer), and its address in memory.
|
7
|
+
This information is deduced in different ways for the different types of variables described below.
|
10
8
|
|
11
|
-
|
9
|
+
User-defined variables
|
12
10
|
----
|
13
|
-
|
14
|
-
|
11
|
+
For variables defined in your firmware, RPicSim can usually deduce the address by looking at the symbol table in your COF file, so you will not need to type the address.
|
12
|
+
However, RPicSim cannot deduce the data type of a variable, so any variables used need to be explicitly defined in the {file:DefiningSimulationClass.md simulation class} using {RPicSim::Sim::ClassDefinitionMethods#def_var def_var}.
|
13
|
+
For example:
|
15
14
|
|
16
15
|
!!!ruby
|
17
16
|
class MySim < RPicSim::Sim
|
18
17
|
#...
|
19
18
|
|
20
|
-
def_var :counter, :
|
19
|
+
def_var :counter, :uint8
|
21
20
|
|
22
21
|
end
|
23
22
|
|
@@ -26,12 +25,13 @@ The first argument to `def_var` specifies what to call the variable in Ruby code
|
|
26
25
|
!!!ruby
|
27
26
|
sim.var(:counter)
|
28
27
|
|
29
|
-
Each variable also has a
|
28
|
+
Each variable also has a method on the simulation object by the same name.
|
29
|
+
This means that you can access the variable like this:
|
30
30
|
|
31
31
|
!!!ruby
|
32
32
|
sim.counter
|
33
33
|
|
34
|
-
|
34
|
+
A shortcut is also available in RSpec thanks to RPicsim's {file:RSpecIntegration.md RSpec integration}, so you can simply write `counter` in any of your RSpec examples:
|
35
35
|
|
36
36
|
!!!ruby
|
37
37
|
it "drives the main output high" do
|
@@ -45,12 +45,12 @@ In the example above, RPicSim will look in your firmware's COF file for a RAM sy
|
|
45
45
|
You can use the `symbol` option to specify what symbol in the symbol table marks the location of the variable. For example:
|
46
46
|
|
47
47
|
!!!ruby
|
48
|
-
def_var :counter, :
|
48
|
+
def_var :counter, :uint8, symbol: :_counter
|
49
49
|
|
50
50
|
The example above shows how you could access a variable from a C compiler (which will generally be prefixed with an underscore) without having to type the underscore in your tests.
|
51
51
|
More generally, the `symbol` option allows you to call a variable one thing in your firmware and call it a different thing in your tests.
|
52
52
|
|
53
|
-
RPicSim will raise an exception if it cannot find the specified symbol in the symbol table. To troubleshoot this, you might print the list of
|
53
|
+
RPicSim will raise an exception if it cannot find the specified symbol in the symbol table. To troubleshoot this, you might print the list of symbols that RPicSim found:
|
54
54
|
|
55
55
|
!!!ruby
|
56
56
|
p sim.class.program_file.var_addresses.keys
|
@@ -58,17 +58,55 @@ RPicSim will raise an exception if it cannot find the specified symbol in the sy
|
|
58
58
|
You can use the `address` option to specify an arbitrary address instead of using the symbol table. For example:
|
59
59
|
|
60
60
|
!!!ruby
|
61
|
-
def_var :counter, :
|
61
|
+
def_var :counter, :uint8, address: 0x63
|
62
|
+
|
63
|
+
Variables are assumed to be in RAM by default, but you can specify that they are in program memory or EEPROM using the `memory` option.
|
64
|
+
|
65
|
+
!!!ruby
|
66
|
+
def_var :settings, :word, memory: :program_memory
|
67
|
+
def_var :checksum, :uint16, memory: :eeprom
|
68
|
+
|
69
|
+
### Program memory on non-PIC18 devices
|
70
|
+
|
71
|
+
On non-PIC18 devices, program memory is made up of words that are 12 bits or 14 bits wide.
|
72
|
+
|
73
|
+
The type of address used for program memory of these devices is called a _word address_ because it specifies the number of a word instead of the number of a byte. For example, a word address of `1` would correspond to the second word in program memory.
|
62
74
|
|
75
|
+
To access all the bits of a particular word, you can define your variable to be of the +:word+ type as shown in the example above.
|
76
|
+
If you specify any of the integer types like :uint8 or :int16, the bytes that comprise that variable will live in the least-significant 8 bits of one or more words in program memory.
|
77
|
+
The upper bits of the words will not be changed when writing to the variable.
|
63
78
|
|
64
|
-
|
79
|
+
This behavior is useful because if you store an integer in program memory as 1 to 4 consecutive RETLW instructions, you can read and write from it in Ruby without changing the bits that make those words be RETLW instructions.
|
80
|
+
|
81
|
+
|
82
|
+
Accessing special function registers
|
65
83
|
----
|
66
84
|
|
67
|
-
|
85
|
+
The Special Function Registers (SFRs) on a microcontroller enable the firmware to interact with the microcontroller's peripherals and talk to the outside world.
|
86
|
+
The {RPicSim::Sim#reg} method can be called on your simulation object to retrieve a {RPicSim::Variable} object:
|
87
|
+
|
88
|
+
!!!ruby
|
89
|
+
sim.reg(:LATA) # => returns a Variable object
|
90
|
+
|
91
|
+
If you are using RPicSim's {file:RSpecIntegration.md RSpec integration}, the `reg` method inside an example automatically redirects to the `@sim` object:
|
92
|
+
|
93
|
+
!!!ruby
|
94
|
+
it "works" do
|
95
|
+
reg(:LATA) # => returns a Variable object
|
96
|
+
end
|
97
|
+
|
98
|
+
The first argument of {RPicSim::Sim#reg} should be a symbol containing the name of the SFR.
|
99
|
+
The name comes from the MPLAB X code, but we expect it to match the name given in the microcontroller's datasheet.
|
68
100
|
|
69
|
-
|
70
|
-
|
71
|
-
|
101
|
+
Note that the MPLAB X code considers "SFRs" to only be the special registers that have an address in memory.
|
102
|
+
The special registers without a memory address are called Non-Memory-Mapped Registers (NMMRs).
|
103
|
+
For example, on some chips, WREG and STKPTR are NMMRs.
|
104
|
+
You can access NMMRs in exactly the same way as SFRs:
|
105
|
+
|
106
|
+
!!!ruby
|
107
|
+
it "sets W to 5" do
|
108
|
+
expect(reg(:WREG).value).to eq 5
|
109
|
+
end
|
72
110
|
|
73
111
|
|
74
112
|
Using a variable
|
@@ -81,6 +119,24 @@ Once you have defined a variable and accessed it using one of the methods above,
|
|
81
119
|
expect(counter.value).to eq 0x6A
|
82
120
|
|
83
121
|
|
122
|
+
Protected bits
|
123
|
+
----
|
124
|
+
|
125
|
+
When you write to a register with {RPicSim::Variable#value=}, you are (according to our understanding of MPLAB X) writing to it in the same way that the simulated microcontroller would write to it.
|
126
|
+
This means that some bits might not be writable or might have restrictions on what value can be written to them.
|
127
|
+
For example, the TO and PD bits of the STATUS register on the PIC10F322 are not writable by the microcontroller.
|
128
|
+
|
129
|
+
To get around this, you can use {RPicSim::Variable#memory_value=} instead, which should allow you to write to any of the bits.
|
130
|
+
|
131
|
+
|
132
|
+
Peripheral updating
|
133
|
+
----
|
134
|
+
|
135
|
+
The MPLAB X code contains various objects that simulate the peripherals on a chip, such as the ADC.
|
136
|
+
We have not determined whether writing to SFRs using the {RPicSim::Variable} object updates the simulation of those peripherals in the proper way.
|
137
|
+
Also, whether the peripherals get updated might depend on whether the `value` or the `memory_value` attribute is used for writing.
|
138
|
+
|
139
|
+
|
84
140
|
Addition example
|
85
141
|
----
|
86
142
|
|
@@ -114,11 +170,11 @@ In `spec/spec_helper.rb`, we make a simulation class that points to the compiled
|
|
114
170
|
require 'rpicsim/rspec'
|
115
171
|
|
116
172
|
class Addition < RPicSim::Sim
|
117
|
-
|
118
|
-
|
119
|
-
def_var :x, :
|
120
|
-
def_var :y, :
|
121
|
-
def_var :z, :
|
173
|
+
use_device "PIC10F322"
|
174
|
+
use_file File.dirname(__FILE__) + "../firmware/dist/firmware.cof"
|
175
|
+
def_var :x, :uint16
|
176
|
+
def_var :y, :uint16
|
177
|
+
def_var :z, :uint16
|
122
178
|
end
|
123
179
|
|
124
180
|
In `spec/addition_spec.rb`, we write a simple unit test that writes to `x` and `y`, runs the `addition` subroutine, and checks that the correct result is stored in `z`:
|
@@ -131,7 +187,7 @@ In `spec/addition_spec.rb`, we write a simple unit test that writes to `x` and `
|
|
131
187
|
start_sim Addition
|
132
188
|
end
|
133
189
|
|
134
|
-
it "can add 70 + 22"
|
190
|
+
it "can add 70 + 22" do
|
135
191
|
x.value = 70
|
136
192
|
y.value = 22
|
137
193
|
run_subroutine :addition, cycle_limit: 100
|