class Plc::Emulator::EmuPlc

Constants

CLEAR_PROGRAM_FLAG
CYCLE_RUN_FLAG
INDEX_BIT_STACK
INDEX_BIT_STACK_COUNT
SIZE_OF_BIT_STACK
STOP_PLC_FLAG
SUFFIXES

Attributes

device_dict[R]
errors[R]
program_data[RW]
program_pointer[R]

Public Class Methods

new() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 52
def initialize
  SUFFIXES.each do |k|
    eval "@#{k}_devices = []"
  end
  @lock = Mutex.new
  reset
end

Public Instance Methods

bool() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 111
def bool
  (stack_device.word & 1) != 0 ? true : false
end
bool=(value) click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 115
def bool= value
  if value
    stack_device.word |= 1
  else
    stack_device.word &= 0xfffe;
  end
end
device_by_name(name) click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 60
def device_by_name name
  @lock.synchronize {
    d = device_dict[name]
    unless d
      d = EmuDevice.new name
      device_dict[name] = d
    end
    d
  }
end
device_by_type_and_number(type, number) click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 71
def device_by_type_and_number type, number
  d = EmuDevice.new type, number
  device_by_name d.name
end
execute_console_commands(line) click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 131
def execute_console_commands line
  a = line.chomp.split(/\s+/)
  case a.first
  when /^ST/i
    d = device_by_name a[1]
    d.set_value true, :in
    "OK\r"
  when /^RS/i
    d = device_by_name a[1]
    d.set_value false, :in
    "OK\r"
  when /^RDS/i
    d = device_by_name a[1]
    c = a[2].to_i
    r = []
    if d.bit_device?
      c.times do
        r << (d.bool(:out) ? 1 : 0)
        d = device_by_name (d+1).name
      end
    else
      case d.suffix
      when "PRG"
        c.times do
          r << program_data[d.number * 2, 2].pack("c*").unpack("n").first
          d = device_by_name (d+1).name
        end
      else
        c.times do
          r << d.word(:out)
          d = device_by_name (d+1).name
        end
      end
    end
    r.map{|e| e.to_s}.join(" ") + "\r"
  when /^WRS/i
    d = device_by_name a[1]
    c = a[2].to_i
    case d.suffix
    when "PRG"
      a[3, c].each do |v|
        program_data[d.number * 2, 2] = [v.to_i].pack("n").unpack("c*")
        d = device_by_name (d+1).name
      end
    else
      if d.bit_device?
        a[3, c].each do |v|
          d.set_value v == "0" ? false : true, :in
          d = device_by_name (d+1).name
        end
      else
        a[3, c].each do |v|
          d.word = v.to_i
          d.set_value v.to_i, :in
          d = device_by_name (d+1).name
        end
      end
    end
    "OK\r"
  when /E/
    eval(a[1..-1].join(" ")).inspect
  else
    raise "Unknown command #{a.first}"
  end
end
reset() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 76
def reset
  @word = 0
  @program_data = []
  @device_dict ||= {}
  @lock.synchronize {
    @device_dict.values.each do |d|
      d.reset
    end
  }
end
run() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 123
def run
  Thread.new do
    loop do
      run_cycle
    end
  end
end
run_cycle() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 87
def run_cycle
  status_to_plc = device_by_name "SD0"
  status_form_plc = device_by_name "SD1"
  sync_input
  case status_to_plc.value & (STOP_PLC_FLAG | CLEAR_PROGRAM_FLAG)
  when STOP_PLC_FLAG
    status_form_plc.value = 0
    sleep 0.1
  when STOP_PLC_FLAG | CLEAR_PROGRAM_FLAG
    reset
    status_form_plc.value = CLEAR_PROGRAM_FLAG
    sleep 0.1
  when 0
    status_form_plc.value = CYCLE_RUN_FLAG
    @program_pointer = 0
    clear_stacks
    while fetch_and_execution; end
    sleep 0.0001
  else
    sleep 0.1
  end
  sync_output
end

Private Instance Methods

add_error(reason) click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 382
def add_error reason
  @errors << {pc:@program_pointer, reason:reason}
end
anb() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 418
def anb
  b = self.bool
  pop_stack
  self.bool &= b
  true
end
and(inverse=false) click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 402
def and inverse=false
  b = fetch_bool_value inverse
  return false if b.nil?
  self.bool &= b
  true
end
and_join_stack() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 280
def and_join_stack
  d = stack_device
  b = d.word == 0xffff
  d.word = 0xffff
  push_stack b
end
ani() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 408
def ani; send :and, true; end
clear_stacks() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 224
def clear_stacks
  stack_device.word = 0xffff
  stack_count_device.word = 0
end
fetch_1_byte() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 326
def fetch_1_byte
  if @program_pointer < program_data.size
    program_data[@program_pointer].tap do
      @program_pointer += 1
    end
  else
    nil
  end
end
fetch_and_execution() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 314
def fetch_and_execution
  code = fetch_1_byte
  return false unless code
  mnemonic = mnenonic_table[code]
  if mnemonic && respond_to?(mnemonic, true)
    r = send mnemonic
    r
  else
    nil
  end
end
fetch_bool_value(inverse=false) click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 372
def fetch_bool_value inverse=false
  d = fetch_device_or_value
  return false unless d
  unless d.is_a? EmuDevice
    add_error "ld must be specify a device nor number #{d}"
    return false
  end
  inverse ? !d.bool : d.bool
end
fetch_device_or_value() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 336
def fetch_device_or_value
  c = fetch_1_byte
  return false unless c

  # check value type
  case c & 0xe0
  when 0x80
    # bit device
  when 0x00
    # immediate number
    return c
  else
    add_error "invalidate value type #{c.to_h(16)}"
    return false
  end

  # device type
  device_type = c & 0x0f

  # number
  number = 0
  if (c & 0x10) == 0
    number = fetch_1_byte
  else
    number = fetch_2_byte
  end

  # make device
  d = device_by_type_and_number device_type, number
  unless d
    add_error "invalid device type #{c&0x3} and number #{number}"
    return false
  end
  d
end
inv() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 396
def inv
  and_join_stack
  self.bool = !self.bool
  true
end
ld(inverse=false) click to toggle source

— mnenonic —

# File lib/plc/emulator/emu_plc.rb, line 388
def ld inverse=false
  b = fetch_bool_value inverse
  return false if b.nil?
  push_stack b
  true
end
ldi() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 394
def ldi; ld true; end
mnenonic_table() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 287
      def mnenonic_table
        @mnemonic_table ||= begin
          s = <<EOB
|00|END|INV|MEP|ANB|MEF|ORB|FEND|NOP|
|10|LD|LDI|LDP|LDPI|LDF|LDFI|MC|MCR|
|20|AND|ANI|ANDP|ANPI|ANDF|ANFI|
|30|OR|ORI|ORP|ORPI|ORF|ORFI|
|40|OUT|OUTI|MPS|MRD|MPP| |
|50|SET|RST|PLS| |PLF||
|60|FF||||||
|70|
|80|SFT|SFTP|SFL|SFLP|SFR|SFRP|
|90|BSFL|BSFLP|DSFL|DSFLP|BSFR|BSFRP|DSFR|DSFRP|
|A0|SFTL|SFTLP|WSFL|WSFLP|SFTR|SFTRP|WFSR|WSFRP|
EOB
          table = {}
          s.lines.each_with_index do |line, h|
            line.chomp.split("|")[2..-1].each_with_index do |mnemonic, l|
              unless mnemonic.length == 0
                table[h << 4 | l] = mnemonic.downcase.to_sym
              end
            end
          end
          table
        end
      end
mpp() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 449
def mpp; mrd; end
mps() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 434
def mps
  and_join_stack
  b = self.bool
  push_stacks
  push_stack b
  true
end
mrd() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 442
def mrd
  b = self.bool
  pop_stacks
  push_stacks
  push_stack b
  true
end
nop() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 432
def nop; true; end
or(inverse=false) click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 410
def or inverse=false
  b = fetch_bool_value inverse
  return false if b.nil?
  self.bool |= b
  true
end
orb() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 425
def orb
  b = self.bool
  pop_stack
  self.bool |= b
  true
end
ori() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 416
def ori; send :or, true; end
out(inverse=false) click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 451
def out inverse=false
  and_join_stack
  d = fetch_device_or_value
  unless d.is_a? EmuDevice
    add_error "ld must be specify a device nor number #{d}"
    return false
  end
  d.bool = inverse ? !self.bool : self.bool unless d.input?
  pop_stack
  true
end
outi() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 462
def outi; out true; end
pop_stack() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 219
def pop_stack
  stack_device.word >>= 1
  stack_device.word |= 0x8000
end
pop_stacks() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 246
def pop_stacks
  if stack_count_device.word <= 0
    puts "ERROR: stacks underflow pc: H#{program_pointer.to_s(16).rjust(4, "0")}"
    return
  end
  values = []
  (SIZE_OF_BIT_STACK - 1).downto 0 do |i|
    values << stack_device(i).word
  end
  v = values.pop
  values.each_with_index do |v, i|
    stack_device(i).word = v
  end
  stack_count_device.word -= 1
  v
end
push_stack(flag = false) click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 209
def push_stack flag = false
  stack_device.word <<= 1
  if flag
    stack_device.word |= 1
  else
    stack_device.word &= 0xfffe
  end
  stack_device.word &= 0xffff
end
push_stacks() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 229
def push_stacks
  if stack_count_device.word > SIZE_OF_BIT_STACK
    puts "ERROR: stacks overflow pc: H#{program_pointer.to_s(16).rjust(4, "0")}"
    return
  end
  values = []
  (SIZE_OF_BIT_STACK - 1).downto 0 do |i|
    values << stack_device(i).word
  end
  values.shift
  values << 0xffff
  SIZE_OF_BIT_STACK.times do |i|
    stack_device(i).word = values.pop
  end
  stack_count_device.word += 1
end
rst() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 475
def rst; set true; end
set(inverse=false) click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 464
def set inverse=false
  and_join_stack
  d = fetch_device_or_value
  unless d.is_a? EmuDevice
    add_error "ld must be specify a device nor number #{d}"
    return false
  end
  d.bool = !inverse if self.bool unless d.input?
  pop_stack
  true
end
stack_count_device() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 205
def stack_count_device
  device_by_name (device_by_name("SD0") + INDEX_BIT_STACK_COUNT).name
end
stack_device(index=0) click to toggle source

stack operations

# File lib/plc/emulator/emu_plc.rb, line 201
def stack_device index=0
  device_by_name (device_by_name("SD0") + INDEX_BIT_STACK + index).name
end
sync_input() click to toggle source

# File lib/plc/emulator/emu_plc.rb, line 264
def sync_input
  @lock.synchronize {
    device_dict.values.each do |d|
      d.sync_input
    end
  }
end
sync_output() click to toggle source
# File lib/plc/emulator/emu_plc.rb, line 272
def sync_output
  @lock.synchronize {
    device_dict.values.each do |d|
      d.sync_output
    end
  }
end