Class: RPicSim::Sim

Inherits:
Object
  • Object
show all
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

Attributes included from ClassMethods

#pin_aliases

Instance Method Summary collapse

Methods included from ClassDefinitionMethods

def_pin, def_symbol, def_var, import_symbols, use_device, use_file

Constructor Details

#initializeSim

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

#eepromMemory (readonly)

Returns a Memory object that allows direct reading and writing of the bytes in the simulated EEPROM.

Returns:



250
251
252
# File 'lib/rpicsim/sim.rb', line 250

def eeprom
  @eeprom
end

#pcRPicSim::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_memoryMemory (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.

Returns:



245
246
247
# File 'lib/rpicsim/sim.rb', line 245

def program_memory
  @program_memory
end

#ramMemory (readonly)

Returns a Memory object that allows direct reading and writing of the bytes in the simulated RAM.

Returns:



238
239
240
# File 'lib/rpicsim/sim.rb', line 238

def ram
  @ram
end

#stack_memoryMemory (readonly)

Returns a Memory object that allows direct reading and writing of the bytes in the simulated hardware call stack.

Returns:



255
256
257
# File 'lib/rpicsim/sim.rb', line 255

def stack_memory
  @stack_memory
end

#stack_pointerStackPointer (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.

Returns:



233
234
235
# File 'lib/rpicsim/sim.rb', line 233

def stack_pointer
  @stack_pointer
end

#stkptrVariable (readonly)

Returns a Variable object corresponding to the stack pointer register. You can use this to read and write the value of the stack pointer.

Returns:



226
227
228
# File 'lib/rpicsim/sim.rb', line 226

def stkptr
  @stkptr
end

#wregVariable (readonly)

Returns a Variable object corresponding to WREG. You can use this to read and write the value of the W register.

Returns:



221
222
223
# File 'lib/rpicsim/sim.rb', line 221

def wreg
  @wreg
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.

Parameters:

  • c

    One of the following:

    • The symbol :return. The condition will be true if the current subroutine has returned. This is implemented by looking to see whether the stack pointer has decreased one level below the level it was at when this method was called.

    • The name of a program label, as a symbol or string, or a Label object. The condition will be true if the #pc value is equal to the label address.

    • An integer representing an address. The condition will be true if the #pc value is equal to the address.

    • A Proc. The Proc will be returned unchanged.

Returns:

  • (Integer)


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_countInteger

Returns the number of instruction cycles simulated in this simulation.

Returns:

  • (Integer)


388
389
390
# File 'lib/rpicsim/sim.rb', line 388

def cycle_count
  @simulator.stopwatch_value
end

#deviceString

Returns a string like “PIC10F322” specifying the PIC device number.

Returns:

  • (String)


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

#filenameString

Returns the path to the firmware file.

Returns:

  • (String)


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.

Parameters:



508
509
510
# File 'lib/rpicsim/sim.rb', line 508

def goto(location)
  pc.value = location_address(location)
end

#inspectString

Returns:

  • (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.

Returns:



377
378
379
# File 'lib/rpicsim/sim.rb', line 377

def label(name)
  program_file.label(name)
end

#labelsObject

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.

Parameters:

  • location

    One of the following:

    • The name of a program label, as a symbol or string.

    • A Label object.

    • An integer representing the address.

Returns:

  • (Integer)


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_watcherMemoryWatcher

Creates and returns a MemoryWatcher object configured to watch for changes to RAM. For more information, see RamWatcher.

Returns:



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_descriptionObject

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.

Parameters:

Returns:



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_fileProgramFile

Returns the ProgramFile representing the firmware being simulated.

Returns:



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.

Parameters:

  • name (Symbol)

    The name from the datasheet.

Returns:

  • (Register)


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

#returnObject

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.

Parameters:

  • num_cycles (Integer)


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.

Parameters:

  • step_count (Integer)

Returns:

  • nil



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

Parameters:

  • location

    Any valid argument to #location_address. It should generally point to a subroutine in program memory that will end by executing a return instructions.

  • opts (defaults to: {})

    Any of the options supported by #run_to.



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

Parameters:

  • conditions

    Each element of the conditions array should be a Proc that returns true when the condition is met, a symbol corresponding to a program label, or any other object that is a valid argument to #convert_condition_to_proc. If there is only one condition, you can pass it directly in as the first argument without wrapping it in an array.

  • opts (Hash) (defaults to: {})

    A hash of options.

    • cycle_limit: The maximum number of cycles to run, as an integer. It is recommended to always specify this to avoid accidentally making an infinite loop. Note that multi-cycle instructions mean that this limit will sometimes be violated by one cycle. If none of the conditions are met by the cycle limit, an exception is raised.

    • cycles: A range of integers specifying how long you expect it to take to reach one of the conditions, for example e.g. 1000..2000. If a condition is met before the minimum, an exception is raised. If none of the conditions are met after the maximum, an exception is raised.

      This option is a more powerful version of cycle_limit, so it cannot be used at the same time as cycle_limit.

Returns:

  • The condition that was met which caused the run to stop.



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.

Parameters:

  • count (Integer)


544
545
546
# File 'lib/rpicsim/sim.rb', line 544

def run_to_cycle_count(count)
  step while cycle_count < count
end

#shortcutsObject



682
683
684
# File 'lib/rpicsim/sim.rb', line 682

def shortcuts
  self.class::Shortcuts
end

#stack_contentsArray(Integer)

Gets the contents of the stack as an array of integers.

Returns:

  • (Array(Integer))

    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_traceStackTrace

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.

Returns:



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

#stepObject

Executes one more instruction.

Returns:

  • nil



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

#var(name) ⇒ Variable

Returns a Variable object if a variable by that name is found. If the variable cannot be found, this method raises an exception.

Returns:



369
370
371
# File 'lib/rpicsim/sim.rb', line 369

def var(name)
  @vars[name.to_sym] or raise ArgumentError, "Cannot find var named '#{name}'."
end