class MicroCisc::Vm::Processor

Many of the coding decisions made in here are for performance reasons:

  1. Mostly one big class, avoiding object creation and referencing

  2. More verbose programming style in some cases

  3. Optimized/strange bitwise math for performance/avoiding ruby magic

  4. Some strange branching structures to shortcut common paths

  5. Few accessor methods, prefer instance vars

  6. Little runtime error checking

Performance isn't a deal breaker, but 1+ MIPS, roughly speaking, gives me a few times the MIPS of an original 6502 which puts me in the ball park of where I need to be to do some real programming. We just want something reasonable for debuging and doing some real coding, actual hardware will leave this in the dust most likely.

Constants

ALU_OP_MASK
DESTINATION_MASK
DESTINATION_SHIFT
EFFECT_MASK
EFFECT_SHIFT
HALT_MODE_FLAG
IMMEDIATE_MASK
IMMEDIATE_SIGN_MASK
INCREMENT_MASK
MEM_ARGS
NEGATIVE_MASK
OP_MASK
OP_SHIFT
OVERFLOW_MASK
PAGE_MASK
SIGNED_MODE_FLAG
SIGN_BIT
SOURCE_MASK
SOURCE_SHIFT
ZERO_MASK

Attributes

control[R]
count[R]
debug[RW]
flags[RW]
overflow[RW]
pc[R]

Public Class Methods

new(id, local_blocks, rom_blocks = []) click to toggle source
Calls superclass method MicroCisc::Vm::Device::new
# File lib/micro_cisc/vm/processor.rb, line 47
def initialize(id, local_blocks, rom_blocks = [])
  super(id, 1, local_blocks, rom_blocks)

  @registers = [0, 0, 0, 0]
  @pc = 0
  @flags = 0
  @overflow = 0
  @debug = false
  @run = false
  @pc_modified = false
  @count = 0
  @clocks_per_second = 200_000
end

Public Instance Methods

compute(alu_code, arg1, arg2, update_flags) click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 225
def compute(alu_code, arg1, arg2, update_flags)
  overflow = 0
  overflow_reg = 0

  case alu_code
  when 0x00
    # INV
    value = (arg1 & 0xFFFF) ^ 0xFFFF
  when 0x01
    # AND
    value = arg2 & arg1
  when 0x02
    # OR
    value = arg2 | arg1
  when 0x03
    # XOR
    value = arg2 ^ arg1
  when 0x04
    # Negate (2's compliment)
    value = -1 * arg1
  when 0x05
    # Shift left, zero extend
    value = arg2 << arg1
    overflow_reg = (value & 0xFFFF0000) >> 16
    value = value & 0xFFFF
  when 0x06
    # Shift right, repsect signed mode
    overflow_mask = ~(-1 << arg1) & 0xFFFF
    overflow_reg = arg2 & overflow_mask
    overflow_reg >> (arg1 - 16) if arg1 > 16

    if @control & SIGNED_MODE_FLAG == 0
      value = (arg2 & 0xFFFF) >> arg1
    else
      value = arg2 >> arg1
    end
  when 0x07
    # Swap MSB and LSB bytes
    value = ((arg1 & 0xFF00) >> 8) | ((arg1 & 0x00FF) << 8)
  when 0x08
    # Zero LSB
    value = arg1 & 0xFF00
  when 0x09
    # Zero MSB
    value = arg1 & 0x00FF
  when 0x0A,0x0B
    arg1 = (arg1 ^ 0xFFFF) + 1 if alu_code == 0x0B
    value = (arg1 & 0xFFFF) + (arg2 & 0xFFFF)
    # Add, respect signed mode
    if @control & SIGNED_MODE_FLAG == 0
      if value > 0xFFFF
        overflow = 1
        overflow_reg = 1
      end
    else
      if ((arg1 & SIGN_BIT) == (arg2 & SIGN_BIT)) &&
          ((arg1 & SIGN_BIT) != (value & SIGN_BIT))
        overflow = 1
        overflow_reg = (value & 0xFFFF0000 >> 16) & 0xFFFF
      end
    end
    value = value & 0xFFFF
  when 0x0C
    if @control & SIGNED_MODE_FLAG == 0
      arg1 = ~(~arg1 & 0xFFFF) if arg1 & SIGN_BIT != 0
      arg2 = ~(~arg2 & 0xFFFF) if arg2 & SIGN_BIT != 0
    end
    value = arg1 * arg2
    overflow_reg = (value & 0xFFFF0000 >> 16) & 0xFFFF
    if ((overflow_reg & SIGN_BIT) == (value & SIGN_BIT)) &&
        overflow_reg == 0xFFFF
      # There was no actual overflow, it's just the sign extension
      overflow = 0 
    else
      overflow = 1
    end
    value = value & 0xFFFF
  when 0x0D
    if @control & SIGNED_MODE_FLAG == 0
      arg1 = ~(~arg1 & 0xFFFF) if arg1 & SIGN_BIT != 0
      arg2 = ~(~arg2 & 0xFFFF) if arg2 & SIGN_BIT != 0
    end
    value = arg2 / arg1
    overflow_reg = arg2 % arg1
    value = value & 0xFFFF
  when 0x0E
    value = arg1 & PAGE_MASK
  when 0x0F
    value = arg1 + @overflow
  else
    raise ArgumentError, "Unsupported ALU code #{alu_code.to_s(16).upcase}"
  end

  zero = value == 0 ? 1 : 0
  negative = (value & 0x8000) == 0 ? 0 : 1

  if update_flags
    flags =
      (overflow * OVERFLOW_MASK) |
      (zero * ZERO_MASK) |
      (negative * NEGATIVE_MASK)
    @flags = flags
    @overflow = overflow_reg
  end
  value & 0xFFFF
end
compute_result(is_copy, source, destination, immediates, alu, update_flags) click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 192
def compute_result(is_copy, source, destination, immediates, alu, update_flags)
  source_value = source_value(source, immediates.first)
  if is_copy
    source_value
  else
    dest_immediate = immediates.size > 1 ? immediates.last : 0
    destination_value = destination_value(destination, dest_immediate)
    compute(alu, source_value, destination_value, update_flags)
  end
end
control=(value) click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 77
def control=(value)
  @control = value & 0xFFFF
end
destination_value(destination, immediate) click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 148
def destination_value(destination, immediate)
  case destination
  when 0
    @pc
  when 1,2,3
    read_mem(@id, (@registers[destination] + immediate) & 0xFFFF)
  when 4
    @control
  else
    @registers[destination - 4]
  end
end
do_command(prefix = '') click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 367
def do_command(prefix = '')
  return unless @debug
  $stdout.print "#{prefix}> "
  command = $stdin.readline
  exit(1) if /exit/.match(command)
  @debug = true if /debug|n|next/.match(command)
  @debug = false if /c|continue/.match(command)
  MicroCisc.logger.info(stack_string) if /stack/.match(command)
  byebug if /break/.match(command)
end
exec_instruction(word) click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 161
def exec_instruction(word)
  source = (word & SOURCE_MASK) >> SOURCE_SHIFT
  destination = (word & DESTINATION_MASK) >> DESTINATION_SHIFT
  effect = (word & EFFECT_MASK) >> EFFECT_SHIFT

  signed = !MEM_ARGS.include?(source)
  inc_is_immediate = signed && !MEM_ARGS.include?(destination)
  is_copy = (word & OP_MASK) == 0
  half_width = is_copy && MEM_ARGS.include?(source) && MEM_ARGS.include?(destination)
  immediates = extract_immediates(word, is_copy, inc_is_immediate, signed, half_width)

  alu = word & ALU_OP_MASK
  result = compute_result(is_copy, source, destination, immediates, alu, true)

  return false unless store?(@flags, effect)

  push = !inc_is_immediate && (word & INCREMENT_MASK) > 0
  store_result(result, source, destination, immediates, push, 0)

  # Detect halt instruction
  if immediates.first == 0 && source == 0 && destination == 0
    if alu == 0
      return 1
    else
      @pc += 1
      return 2
    end
  end
  0
end
extract_immediates(word, is_copy, inc_is_immediate, signed, half_width) click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 94
def extract_immediates(word, is_copy, inc_is_immediate, signed, half_width)
  if is_copy
    immediate_mask = IMMEDIATE_MASK | ALU_OP_MASK
    immediate_shift = 0
  else
    immediate_mask = IMMEDIATE_MASK
    immediate_shift = 4
  end

  sign_mask = IMMEDIATE_SIGN_MASK
  if inc_is_immediate
    sign_mask = INCREMENT_MASK
    immediate_mask = immediate_mask | INCREMENT_MASK
  end

  if signed && ((word & sign_mask) != 0)
    # Fancy bit inverse for high performance sign extend
    [~(~(word & immediate_mask) & immediate_mask) >> immediate_shift]
  elsif half_width
    [
      (word & immediate_mask) >> (immediate_shift + 3),
      (word & (immediate_mask >> 3)) >> immediate_shift
    ]
  else
    [(word & immediate_mask) >> immediate_shift]
  end
end
format_data(data) click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 378
def format_data(data)
  data.map { |w| '%04X' % w }.join(' ').gsub(/(([0-9A-Za-z]{4} ){16})/, "\\1\n")
end
halt() click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 382
def halt
  delta = (Time.now - @t0)
  MicroCisc.logger.info("HALT: #{@count} instructions in #{delta}s")
  MicroCisc.logger.info("Stack: " + stack_string)

  @run = false
end
handle_control_update(address, value) click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 61
def handle_control_update(address, value)
  if address == 0x7
    self.pc = value
  elsif address == 0x8
    set_register(1, value)
  elsif address == 0x9
    set_register(2, value)
  elsif address == 0xA
    set_register(3, value)
  elsif address == 0xB
    @flags = value & 0xFFFF
  elsif address == 0xC
    self.control = value
  end
end
pc=(value) click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 81
def pc=(value)
  @pc_modified = true
  @pc = value & 0xFFFF
end
register(id) click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 86
def register(id)
  @registers[id]
end
run() click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 338
def run
  @t0 = Time.now
  @count = 0
  while(@run) do
    word = read_mem(@id, @pc, true)
    if @debug
      # Pause before executing next command
      do_command("#{'%04x' % [@pc]} #{ucisc(word)} ")
    end
    special = exec_instruction(word)
    if special != 0
      halt if special == 1
      @debug = true if special == 2
    end
    if @pc_modified
      @pc_modified = false
    else
      @pc += 1
    end
    @count += 1

    allowed_count = (Time.now - @t0) * @clocks_per_second
    while @count >= allowed_count
      sleep(1.0 / @clocks_per_second)
      allowed_count = (Time.now - @t0) * @clocks_per_second
    end
  end
end
set_register(id, value) click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 90
def set_register(id, value)
  @registers[id] = value
end
source_value(source, immediate) click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 135
def source_value(source, immediate)
  case source
  when 0
    (@pc + immediate) & 0xFFFF
  when 1,2,3
    read_mem(@id, (@registers[source] + immediate) & 0xFFFF)
  when 4
    immediate
  else
    (@registers[source - 4] + immediate) & 0xFFFF
  end
end
stack_string() click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 390
def stack_string
  address = @registers[1] & 0xFFFF
  str = "#{'%04X' % address} => "
  while(address > 0xFF00 && address < 0x10000 && address - (0xFFFF & @registers[1]) < 10)
    str += "0x#{'%04X' % read_mem(@id, address)} "
    address += 1
  end
  str
end
start(debug = false) click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 332
def start(debug = false)
  @debug = debug
  @run = true
  run
end
store?(flags, effect) click to toggle source
  • 0 = zero?

  • 1 = not zero?

  • 2 = negative?

  • 3 = store

# File lib/micro_cisc/vm/processor.rb, line 126
def store?(flags, effect)
  return true if effect == 3 # handle the common case quickly

  zero = flags & ZERO_MASK != 0
  (effect == 0 && zero) ||
    (effect == 1 && !zero) ||
    (effect == 2 && (flags & NEGATIVE_MASK != 0))
end
store_result(value, source, destination, immediates, push, sign) click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 203
def store_result(value, source, destination, immediates, push, sign)
  case destination
  when 0
    @pc = value
    @pc_modified = true
  when 1,2,3
    if push
      @registers[destination] = (@registers[destination] - 1) & 0xFFFF
    end
    imm = immediates.size > 1 ? immediates.last : 0
    address = @registers[destination] + imm
    write_mem(@id, address, value)
  when 4
    self.control = value
  else
    @registers[destination - 4] = value
  end
  if push && !MEM_ARGS.include?(destination) && MEM_ARGS.include?(source)
    @registers[source] = (@registers[source] + 1) & 0xFFFF
  end
end
ucisc(word) click to toggle source
# File lib/micro_cisc/vm/processor.rb, line 400
def ucisc(word)
  source = (word & SOURCE_MASK) >> SOURCE_SHIFT
  destination = (word & DESTINATION_MASK) >> DESTINATION_SHIFT
  effect = (word & EFFECT_MASK) >> EFFECT_SHIFT

  src =
    if source == 0
      '0.reg'
    elsif source < 4
      "#{source}.mem"
    elsif source == 4
      '4.val'
    else
      "#{source - 4}.reg"
    end

  dest =
    if destination == 0
      '0.reg'
    elsif destination < 4
      "#{destination}.mem"
    elsif destination == 4
      '4.reg'
    else
      "#{destination - 4}.reg"
    end

  # Eh?
  signed = !MEM_ARGS.include?(source)
  inc_is_immediate = signed && !MEM_ARGS.include?(destination)
  is_copy = (word & OP_MASK) == 0
  half_width = is_copy && MEM_ARGS.include?(source) && MEM_ARGS.include?(destination)
  immediates = extract_immediates(word, is_copy, inc_is_immediate, signed, half_width)

  value = source_value(source, immediates.first)
  store = store?(@flags, effect)

  alu = word & ALU_OP_MASK
  result = compute_result(is_copy, source, destination, immediates, alu, true)
  alu = is_copy ? '' : "0x#{alu.to_s(16).upcase}.op "

  imm0 = immediates.first < 0 ? "-#{(immediates.first * -1)}.imm" : "#{immediates.first}.imm"
  imm1 =
    if half_width
      immediates.last < 0 ? "-#{(immediates.last * -1)}.imm " : "#{immediates.last}.imm "
    else
      ""
    end
  eff = "#{effect}.eff"
  push = !inc_is_immediate && (word & INCREMENT_MASK) > 0
  push = push ? 'push ' : ''
  ins = is_copy ? 'copy' : 'compute'


  "Stack: #{stack_string}\n#{ins} #{alu}#{src} #{imm0} #{dest} #{imm1}#{eff} #{push}# value: #{value} (0x#{'%04x' % (value & 0xFFFF)}), result: #{result} (0x#{'%04x' % (result & 0xFFFF)}), #{'not ' if !store}stored"
end