Class: RPicSim::Sim
- Inherits:
-
Object
- Object
- RPicSim::Sim
- Extended by:
- ClassDefinitionMethods, ClassMethods
- Defined in:
- lib/rpicsim/sim.rb
Overview
This class represents a PIC microcontroller simulation. This class keeps track of the state of the simulation and provides methods for running the simulation, reading the state, and changing the state.
Defined Under Namespace
Modules: BasicShortcuts, ClassDefinitionMethods, ClassMethods
Instance Attribute Summary collapse
-
#eeprom ⇒ Memory
readonly
Returns a Memory object that allows direct reading and writing of the bytes in the simulated EEPROM.
-
#pc ⇒ RPicSim::ProgramCounter
readonly
Gets the program counter, an object that lets you read and write the current address in program space that is being executed.
-
#program_memory ⇒ Memory
readonly
Returns a Memory object that allows direct reading and writing of the data in the program memory.
-
#ram ⇒ Memory
readonly
Returns a Memory object that allows direct reading and writing of the bytes in the simulated RAM.
-
#stack_memory ⇒ Memory
readonly
Returns a Memory object that allows direct reading and writing of the bytes in the simulated hardware call stack.
-
#stack_pointer ⇒ StackPointer
readonly
Returns a StackPointer object that is like #stkptr but it works consistently across all PIC devices.
-
#stkptr ⇒ Variable
readonly
Returns a Variable object corresponding to the stack pointer register.
-
#wreg ⇒ Variable
readonly
Returns a Variable object corresponding to WREG.
Attributes included from ClassMethods
Instance Method Summary collapse
-
#convert_condition_to_proc(c) ⇒ Integer
Converts the specified condition into a Proc that, when called, will return a truthy value if the condition is satisfied.
-
#cycle_count ⇒ Integer
Returns the number of instruction cycles simulated in this simulation.
-
#device ⇒ String
Returns a string like “PIC10F322” specifying the PIC device number.
-
#every_step(&proc) ⇒ Object
Registers a new callback to be run after every simulation step.
-
#filename ⇒ String
Returns the path to the firmware file.
-
#goto(location) ⇒ Object
Changes the #pc value to be equal to the address of the given location.
-
#initialize ⇒ Sim
constructor
Makes a new simulation using the settings specified when the class was defined.
- #inspect ⇒ String
-
#label(name) ⇒ Label
Returns a Label object if a program label by that name is found.
-
#labels ⇒ Object
Returns a hash that associates label names as Ruby symbols to Label objects.
-
#location_address(location) ⇒ Integer
Gets the address of the specified location in program memory.
-
#new_ram_watcher ⇒ MemoryWatcher
Creates and returns a MemoryWatcher object configured to watch for changes to RAM.
-
#pc_description ⇒ Object
Generates a friendly human-readable string description of where the program counter is currently using the symbol table.
-
#pin(name) ⇒ Pin
Returns a Pin object if a pin by that name is found, or raises an exception.
-
#program_file ⇒ ProgramFile
Returns the ProgramFile representing the firmware being simulated.
-
#reg(name) ⇒ Register
Returns a Variable object if a Special Function Register (SFR) or Non-Memory-Mapped Register (NMMR) by that name is found.
-
#return ⇒ Object
Simulates a return instruction being executed by popping the top value off of the stack and setting the #pc value equal to it.
-
#run_cycles(num_cycles) ⇒ Object
Runs the simulation for the given number of instruction cycles.
-
#run_steps(step_count) ⇒ Object
Executes the specified number of instructions.
-
#run_subroutine(location, opts = {}) ⇒ Object
Runs the subroutine at the given location.
-
#run_to(conditions, opts = {}) ⇒ Object
Runs the simulation until one of the given conditions has been met, then stops and returns the condition that was met.
-
#run_to_cycle_count(count) ⇒ Object
Runs the simulation until the #cycle_count is greater than or equal to the given cycle count.
- #shortcuts ⇒ Object
-
#stack_contents ⇒ Array(Integer)
Gets the contents of the stack as an array of integers.
-
#stack_push(value) ⇒ Object
Pushes the given address onto the simulated call stack.
-
#stack_trace ⇒ StackTrace
Returns a call stack trace representing the current state of the simulation.
-
#step ⇒ Object
Executes one more instruction.
-
#var(name) ⇒ Variable
Returns a Variable object if a variable by that name is found.
Methods included from ClassDefinitionMethods
def_pin, def_symbol, def_var, import_symbols, use_device, use_file
Constructor Details
#initialize ⇒ Sim
Makes a new simulation using the settings specified when the class was defined.
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/rpicsim/sim.rb', line 270 def initialize @assembly = Mplab::MplabAssembly.new(device) @assembly.start_simulator_and_debugger(filename) @simulator = @assembly.simulator @processor = @simulator.processor initialize_memories initialize_pins initialize_sfrs_and_nmmrs initialize_vars @pc = ProgramCounter.new @simulator.processor @step_callbacks = [] @stack_pointer = StackPointer.new(stkptr) end |
Instance Attribute Details
#eeprom ⇒ Memory (readonly)
Returns a Memory object that allows direct reading and writing of the bytes in the simulated EEPROM.
250 251 252 |
# File 'lib/rpicsim/sim.rb', line 250 def eeprom @eeprom end |
#pc ⇒ RPicSim::ProgramCounter (readonly)
Gets the program counter, an object that lets you read and write the current address in program space that is being executed.
216 217 218 |
# File 'lib/rpicsim/sim.rb', line 216 def pc @pc end |
#program_memory ⇒ Memory (readonly)
Returns a Memory object that allows direct reading and writing of the data in the program memory. Besides the main program, the program memory also contains the configuration words and the user IDs.
245 246 247 |
# File 'lib/rpicsim/sim.rb', line 245 def program_memory @program_memory end |
#ram ⇒ Memory (readonly)
Returns a Memory object that allows direct reading and writing of the bytes in the simulated RAM.
238 239 240 |
# File 'lib/rpicsim/sim.rb', line 238 def ram @ram end |
#stack_memory ⇒ Memory (readonly)
Returns a Memory object that allows direct reading and writing of the bytes in the simulated hardware call stack.
255 256 257 |
# File 'lib/rpicsim/sim.rb', line 255 def stack_memory @stack_memory end |
#stack_pointer ⇒ StackPointer (readonly)
Returns a RPicSim::StackPointer object that is like #stkptr but it works consistently across all PIC devices. The initial value is always 0 when the stack is empty and it points to the first unused space in the stack.
233 234 235 |
# File 'lib/rpicsim/sim.rb', line 233 def stack_pointer @stack_pointer end |
Instance Method Details
#convert_condition_to_proc(c) ⇒ Integer
Converts the specified condition into a Proc that, when called, will return a truthy value if the condition is satisfied. This is a helper for processing the main argument to #run_to.
646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 |
# File 'lib/rpicsim/sim.rb', line 646 def convert_condition_to_proc(c) case c when Proc c when Integer proc { pc.value == c } when :return current_val = stack_pointer.value if current_val == 0 raise 'The stack pointer is 0; waiting for a return would be strange and might not work.' else target_val = current_val - 1 end proc { stack_pointer.value == target_val } when Label convert_condition_to_proc c.address when String, Symbol convert_condition_to_proc label(c).address else raise ArgumentError, "Invalid run-termination condition #{c.inspect}" end end |
#cycle_count ⇒ Integer
Returns the number of instruction cycles simulated in this simulation.
388 389 390 |
# File 'lib/rpicsim/sim.rb', line 388 def cycle_count @simulator.stopwatch_value end |
#device ⇒ String
Returns a string like “PIC10F322” specifying the PIC device number.
259 260 261 |
# File 'lib/rpicsim/sim.rb', line 259 def device self.class.device end |
#every_step(&proc) ⇒ Object
Registers a new callback to be run after every simulation step. Each time the simulation takes a step, the provided block will be called.
394 395 396 |
# File 'lib/rpicsim/sim.rb', line 394 def every_step(&proc) @step_callbacks << proc end |
#filename ⇒ String
Returns the path to the firmware file.
265 266 267 |
# File 'lib/rpicsim/sim.rb', line 265 def filename self.class.filename end |
#goto(location) ⇒ Object
Changes the #pc value to be equal to the address of the given location.
508 509 510 |
# File 'lib/rpicsim/sim.rb', line 508 def goto(location) pc.value = location_address(location) end |
#inspect ⇒ String
627 628 629 |
# File 'lib/rpicsim/sim.rb', line 627 def inspect "#<#{self.class}:0x%x, #{pc_description}, stack_pointer = #{stack_pointer.value}>" % object_id end |
#label(name) ⇒ Label
Returns a Label object if a program label by that name is found. The name is specified in the code that defined the label. If you are using a C compiler, you will probably need to prefix the name with an underscore.
377 378 379 |
# File 'lib/rpicsim/sim.rb', line 377 def label(name) program_file.label(name) end |
#labels ⇒ Object
Returns a hash that associates label names as Ruby symbols to Label objects.
382 383 384 |
# File 'lib/rpicsim/sim.rb', line 382 def labels program_file.labels end |
#location_address(location) ⇒ Integer
Gets the address of the specified location in program memory. This is a helper for processing the main argument to #goto and #run_subroutine.
498 499 500 501 502 503 504 |
# File 'lib/rpicsim/sim.rb', line 498 def location_address(location) case location when Integer then location when Label then location.address when Symbol, String then label(location).address end end |
#new_ram_watcher ⇒ MemoryWatcher
Creates and returns a MemoryWatcher object configured to watch for changes to RAM. For more information, see RamWatcher.
678 679 680 |
# File 'lib/rpicsim/sim.rb', line 678 def new_ram_watcher MemoryWatcher.new(self, @simulator.fr_memory, ram_vars + sfr_vars) end |
#pc_description ⇒ Object
Generates a friendly human-readable string description of where the program counter is currently using the symbol table.
565 566 567 |
# File 'lib/rpicsim/sim.rb', line 565 def pc_description program_file.address_description(pc.value) end |
#pin(name) ⇒ Pin
Returns a Pin object if a pin by that name is found, or raises an exception.
352 353 354 |
# File 'lib/rpicsim/sim.rb', line 352 def pin(name) @pins_by_name[name.to_sym] or raise ArgumentError, "Cannot find pin named '#{name}'." end |
#program_file ⇒ ProgramFile
Returns the ProgramFile representing the firmware being simulated.
688 689 690 |
# File 'lib/rpicsim/sim.rb', line 688 def program_file self.class.program_file end |
#reg(name) ⇒ Register
Returns a Variable object if a Special Function Register (SFR) or Non-Memory-Mapped Register (NMMR) by that name is found. If the register cannot be found, this method raises an exception.
361 362 363 364 |
# File 'lib/rpicsim/sim.rb', line 361 def reg(name) name = name.to_sym @sfrs[name] || @nmmrs[name] or raise ArgumentError, "Cannot find SFR or NMMR named '#{name}'." end |
#return ⇒ Object
Simulates a return instruction being executed by popping the top value off of the stack and setting the #pc value equal to it. This can be useful for speeding up your tests when you have a very slow function and just want to skip it.
552 553 554 555 556 557 558 559 560 561 |
# File 'lib/rpicsim/sim.rb', line 552 def return if stack_pointer.value == 0 raise 'Cannot return because stack is empty.' end # Simulate popping the stack. stack_pointer.value -= 1 pc.value = @stack_memory.read_word(stack_pointer.value) update_top_of_stack_registers end |
#run_cycles(num_cycles) ⇒ Object
Runs the simulation for the given number of instruction cycles. Note that the existence of multi-cycle instructions means that sometimes this method can run one cycle longer than desired.
537 538 539 |
# File 'lib/rpicsim/sim.rb', line 537 def run_cycles(num_cycles) run_to_cycle_count cycle_count + num_cycles end |
#run_steps(step_count) ⇒ Object
Executes the specified number of instructions.
409 410 411 412 |
# File 'lib/rpicsim/sim.rb', line 409 def run_steps(step_count) step_count.times { step } nil # To make using the ruby debugger more pleasant. end |
#run_subroutine(location, opts = {}) ⇒ Object
Runs the subroutine at the given location. This can be useful for doing unit tests of subroutines in your firmware.
The current program counter value will be pushed onto the stack before running the subroutine so that after the subroutine is done the simulation can proceed as it was before.
Example usage in RSpec:
run_subroutine :calculateSum, cycle_limit: 20
sum.value.should == 30
527 528 529 530 531 |
# File 'lib/rpicsim/sim.rb', line 527 def run_subroutine(location, opts = {}) stack_push pc.value goto location run_to :return, opts end |
#run_to(conditions, opts = {}) ⇒ Object
Runs the simulation until one of the given conditions has been met, then stops and returns the condition that was met.
Example usage in RSpec:
result = run_to [:mylabel, :return], cycle_limit: 400
result.should == :return
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 |
# File 'lib/rpicsim/sim.rb', line 443 def run_to(conditions, opts = {}) conditions = Array(conditions) if conditions.empty? raise ArgumentError, 'Must specify at least one condition.' end condition_procs = conditions.map(&method(:convert_condition_to_proc)) allowed_keys = [:cycle_limit, :cycles] invalid_keys = opts.keys - allowed_keys if !invalid_keys.empty? raise ArgumentError, "Unrecognized options: #{invalid_keys.join(", ")}" end if opts[:cycles] && opts[:cycle_limit] raise ArgumentError, 'Cannot specify both :cycles and :cycle_limit.' end start_cycle = cycle_count if opts[:cycles] raise "Invalid range: #{opts[:cycles].inspect}." unless opts[:cycles].min && opts[:cycles].max min_cycle = start_cycle + opts[:cycles].min max_cycle = start_cycle + opts[:cycles].max max_cycle -= 1 if opts[:cycles].exclude_end? elsif opts[:cycle_limit] max_cycle = start_cycle + opts[:cycle_limit] if opts[:cycle_limit] end # Loop until one of the conditions is satisfied. until (met_condition_index = condition_procs.find_index(&:call)) if max_cycle && cycle_count >= max_cycle raise "Failed to reach #{conditions.inspect} after #{cycle_count - start_cycle} cycles." end step end met_condition = conditions[met_condition_index] if min_cycle && cycle_count < min_cycle raise "Reached #{met_condition.inspect} in only #{cycle_count - start_cycle} cycles " + "but expected it to take at least #{min_cycle - start_cycle}." end # Return the argument that specified the condition that was satisfied. met_condition end |
#run_to_cycle_count(count) ⇒ Object
Runs the simulation until the #cycle_count is greater than or equal to the given cycle count.
544 545 546 |
# File 'lib/rpicsim/sim.rb', line 544 def run_to_cycle_count(count) step while cycle_count < count end |
#shortcuts ⇒ Object
682 683 684 |
# File 'lib/rpicsim/sim.rb', line 682 def shortcuts self.class::Shortcuts end |
#stack_contents ⇒ Array(Integer)
Gets the contents of the stack as an array of integers.
582 583 584 585 586 |
# File 'lib/rpicsim/sim.rb', line 582 def stack_contents (0...stack_pointer.value).map do |n| @stack_memory.read_word(n) end end |
#stack_push(value) ⇒ Object
Pushes the given address onto the simulated call stack.
570 571 572 573 574 575 576 577 578 |
# File 'lib/rpicsim/sim.rb', line 570 def stack_push(value) if !@stack_memory.valid_address?(stack_pointer.value) raise "Simulated stack is full (stack pointer = #{stack_pointer.value})." end @stack_memory.write_word(stack_pointer.value, value) stack_pointer.value += 1 update_top_of_stack_registers end |
#stack_trace ⇒ StackTrace
Returns a call stack trace representing the current state of the simulation. Printing this stack trace can help you figure out what part of your code is running and why.
592 593 594 595 596 597 598 599 600 601 602 603 604 |
# File 'lib/rpicsim/sim.rb', line 592 def stack_trace # The stack stores return addresses, not call addresses. # We get the call addresses by subtracting the address increment, # which is the number of address units that each word of program memory takes up. addresses = stack_contents.map do |return_address| return_address - address_increment end addresses << pc.value entries = addresses.map do |address| StackTraceEntry.new address, program_file.address_description(address) end StackTrace.new(entries) end |
#step ⇒ Object
Executes one more instruction.
400 401 402 403 404 |
# File 'lib/rpicsim/sim.rb', line 400 def step @assembly.debugger_step @step_callbacks.each(&:call) nil # To make using the ruby debugger more pleasant. end |