class MicroCisc::Compile::Instruction

Constants

COMPONENT_WITH_QUOTES_REGEX

Attributes

data[R]
dest[R]
dir[R]
immediates[R]
inc[R]
label[R]
minimal[R]
operation[R]
original[R]
sign[R]
src[R]

Public Class Methods

new(label_generator, minimal, original, statement) click to toggle source
# File lib/micro_cisc/compile/instruction.rb, line 7
def initialize(label_generator, minimal, original, statement)
  @label_generator = label_generator
  @original = original
  @label = nil
  @operation = nil
  @sign = nil
  @dir = nil
  @immediates = []
  @src = nil
  @dest = nil
  @statement = statement
  @data = nil
  @inc = nil
  @eff = nil
  @alu_code = nil
  parse_ucisc(minimal)
end

Public Instance Methods

comment?() click to toggle source
# File lib/micro_cisc/compile/instruction.rb, line 37
def comment?
  !label? && !instruction?
end
data?() click to toggle source
# File lib/micro_cisc/compile/instruction.rb, line 25
def data?
  !@data.nil?
end
encoded(label_dictionary = nil, current_address = nil) click to toggle source
# File lib/micro_cisc/compile/instruction.rb, line 253
def encoded(label_dictionary = nil, current_address = nil)
  imms = @immediates.map do |imm|
    if imm.is_a?(Numeric)
      imm
    elsif label_dictionary.nil?
      0
    elsif imm.is_a?(Array)
      raise ArgumentError, 'Current address is missing' if current_address.nil?
      label_address = label_dictionary[imm.first]
      raise ArgumentError, "Missing label '#{imm.first}'" if label_address.nil?
      label_address = label_address & 0xFFFF
      if imm.last == 'disp'
        label_address - current_address
      elsif imm.last == 'imm'
        label_address & 0xFFFF
      else
        raise ArgumentError, "Invalid immediate spec: #{imm.first}.#{imm.last}"
      end
    else
      raise ArgumentError, "Invalid immediate spec: #{imm.first}.#{imm.last}"
    end
  end

  op_code = @operation == 'alu' ? 1 : 0
  msb = (op_code << 7) | (@eff << 5) | (@dest << 2) | (@src >> 1)
  lsb = ((@src & 0x01) << 7)

  if @uses_mem_arg
    lsb = lsb | (@inc << 6)
  end

  imm_mask = ~(-1 << @bit_width)
  if @operation == 'alu'
    lsb = lsb | ((imms.first & imm_mask) << 4) | (@alu_code & 0xF)
  else
    imm =
      if @source_is_mem && @dest_is_mem
        ((imms.first & 0x7) << 3) | (imms.last & 0x7)
      else
        imms.first
      end
    lsb = lsb | (imm & imm_mask)
  end

  ((msb & 0xFF) << 8) | (lsb & 0xFF)
end
instruction?() click to toggle source
# File lib/micro_cisc/compile/instruction.rb, line 33
def instruction?
  !@operation.nil?
end
label?() click to toggle source
# File lib/micro_cisc/compile/instruction.rb, line 29
def label?
  !@label.nil?
end
parse(operation) click to toggle source
# File lib/micro_cisc/compile/instruction.rb, line 149
def parse(operation)
  @operation = operation
  components = @components[1..-1]
  args = []
  @immediates = []
  @uses_mem_arg = false
  @source_is_mem = false
  @dest_is_mem = false

  while components.size > 0
    to_parse = components.shift
    if(to_parse.start_with?('$') || to_parse.start_with?('&'))
      raise ArgumentError, "Missing ref #{to_parse}" unless @statement.get_var(to_parse)
      to_parse = @statement.get_var(to_parse)
      components = to_parse.split(/\s/) + components
      to_parse = components.shift
    end
    parsed = parse_component(to_parse)
    if ['val', 'reg', 'mem'].include?(parsed.last)
      @uses_mem_arg = true if parsed.last == 'mem'
      @source_is_mem = true if args.empty? && parsed.last == 'mem'
      @dest_is_mem = true if !args.empty? && parsed.last == 'mem'
      args << parsed
      @immediates << 0
    elsif parsed.last == 'op'
      @alu_code = validate_alu(parsed, @alu_code)
    elsif parsed.last == 'push'
      @inc = validate_boolean(parsed, @sign)
    elsif parsed.last == 'pop'
      @inc = validate_boolean(parsed, @sign)
    elsif parsed.last == 'eff'
      @eff = validate_effect(parsed, @eff)
    elsif (parsed.last == 'disp' || parsed.last == 'imm')
      if args.empty?
        # if immediate is first arg, this is a 4.val source
        args << [4, 'val']
        @immediates << 0
      end

      imm =
        if parsed.first.is_a?(Numeric)
          parsed.first
        else
          if parsed.first == 'break'
            [@label_generator.end_label, parsed.last]
          elsif parsed.first == 'loop'
            [@label_generator.start_label, parsed.last]
          else
            parsed
          end
        end
      @immediates[args.size - 1] = imm
    else
      raise ArgumentError, "Invalid argument for #{@operation}: #{to_parse}"
    end
  end

  if args.size != 2
    raise ArgumentError, "Missing source and/or destination arguments"
  end
  @eff ||= 3
  if @inc && !@uses_mem_arg
    raise ArgumentError, "Memory argument required to use push and pop"
  end
  if @operation == 'alu' && !@alu_code
    raise ArgumentError, "Compute instruction must have ALU op code"
  end
  @inc ||= 0
  @bit_width = 7
  @bit_width -= 4 if @operation == 'alu'
  @bit_width -= 1 if @uses_mem_arg
  @immediates.each_with_index do |imm, index|
    if imm.is_a?(Numeric)
      validate_immediate(imm, index)
    end
  end

  if @immediates.last != 0 && @operation == 'alu'
    raise ArgumentError, "Destination immediate is not allowed for compute instructions"
  elsif @immediates.last != 0 && (!@source_is_mem || !@dest_is_mem)
    raise ArgumentError, "Destination immediate is only allowed when both arguments are mem args"
  end

  @src, @dest, @dir = validate_args(args.first, args.last)
  nil
end
parse_component(component) click to toggle source
# File lib/micro_cisc/compile/instruction.rb, line 109
def parse_component(component)
  parts = component.split('.')
  if /^-{0,1}[0-9A-Fa-f]+$/.match(parts.first)
    [parts.first.to_i, parts.last.downcase]
  elsif /^-{0,1}(0x){0,1}[0-9A-Fa-f]+$/.match(parts.first)
    [parts.first.to_i(16), parts.last.downcase]
  else
    [parts.first, parts.last.downcase]
  end
end
parse_ucisc(minimal_instruction) click to toggle source
# File lib/micro_cisc/compile/instruction.rb, line 41
def parse_ucisc(minimal_instruction)
  @components = minimal_instruction.scan(COMPONENT_WITH_QUOTES_REGEX).flatten

  return if @components.empty?

  first = @components.first
  if first == '{'
    @label_generator.push_context
    @label = @label_generator.start_label
  elsif first == '}'
    @label = @label_generator.end_label
    @label_generator.pop_context
  else
    label = /(?<name>[^\s]+):/.match(first)
    @label = label['name'] if label
  end
  return if @label

  if @components.first == '%'
    @components.shift
    @components = @components.map do |component|
      if component =~ /"(\\"|[^"])*"/
        # Remove surrounding quotes
        component = component[1...(component.length - 1)]
        component = component.gsub("\\n","\n")
        component = component.gsub("\\\"","\"")
        hex = []
        offset = 0
        while(offset < component.length)
          pair = component[offset...(offset + 2)]
          pair = pair.bytes
          pair << 0 if pair.length < 2
          word = pair.pack("C*").unpack("S>").last
          hex_word = "%04X" % word
          hex << hex_word
          offset += 2
        end
        component = ["%04X" % component.length] + hex
      else
        component
      end
    end.flatten
    @data = @components.map do |component|
      if match = /(?<name>[^\s]+)\.(?<type>imm|disp)/.match(component)
        [match['name'], match['type']]
      else
        if component.length % 4 != 0
          raise ArgumentError, "Data segment length must be a multiple of 2-bytes"
        end
        words = []
        (0...(component.length / 4)).each do |index|
          words << ((component[index * 4, 4]).to_i(16) & 0xFFFF)
        end
        words.pack("S*")
      end
    end
    return
  end

  @opcode = parse_component(@components.first).first
  case @opcode
  when 'copy'
    parse('copy')
  when 'compute'
    parse('alu')
  end
end
validate_alu(component, current) click to toggle source
# File lib/micro_cisc/compile/instruction.rb, line 120
def validate_alu(component, current)
  raise ArgumentError, "Duplicate #{component.last} value" if current
  code = component.first
  unless code >= 0 && code < 16
    raise ArgumentError, "Value of #{component.last} must be between 0x0 and 0xF instead of 0x#{component.first.to_s(16).upcase}"
  end
  component.first
end
validate_args(first_arg, second_arg) click to toggle source
# File lib/micro_cisc/compile/instruction.rb, line 300
def validate_args(first_arg, second_arg)
  register = validate_reg(first_arg)
  dest = validate_dest(second_arg)

  [register, dest, dir]
end
validate_boolean(component, current) click to toggle source
# File lib/micro_cisc/compile/instruction.rb, line 137
def validate_boolean(component, current)
  raise ArgumentError, "Duplicate #{component.last} value" if current
  if component.first == component.last
    # Was used with syntax sugar without the numeric argument
    component[0] = 1
  end
  unless (0..1).include?(component.first)
    raise ArgumentError, "Value of #{component.last} must be 0x0 or 0x1 instead of 0x#{component.first.to_s(16).upcase}"
  end
  component.first
end
validate_dest(arg) click to toggle source
# File lib/micro_cisc/compile/instruction.rb, line 322
def validate_dest(arg)
  valid = arg.last == 'mem' && [1, 2, 3].include?(arg.first)
  valid = valid || (arg.last == 'reg' && [0, 4, 1, 2, 3].include?(arg.first))

  if valid
    reg = arg.first
    reg += 4 if [1, 2, 3].include?(arg.first) && arg.last == 'reg'
    reg
  else
    raise ArgumentError, "Invalid destination: #{arg.first.to_s}.#{arg.last}"
  end
end
validate_effect(component, current) click to toggle source
# File lib/micro_cisc/compile/instruction.rb, line 129
def validate_effect(component, current)
  raise ArgumentError, "Duplicate #{component.last} value" if current
  unless (0..7).include?(component.first)
    raise ArgumentError, "Value of #{component.last} must be between 0x0 and 0x7 instead of 0x#{component.first.to_s(16).upcase}"
  end
  component.first
end
validate_immediate(value, index) click to toggle source
# File lib/micro_cisc/compile/instruction.rb, line 236
def validate_immediate(value, index)
  width = @bit_width
  width = width / 2 if @operation == 'copy' && @source_is_mem && @dest_is_mem
  if index == 0 && @source_is_mem || index == 1 && @dest_is_mem
    min = 0
    max = (1 << width) - 1
  else
    magnitude = 1 << (width - 1)
    min = magnitude * -1
    max = magnitude - 1
  end
  if (value < min || value > max)
    signed = @source_is_mem ? 'unsigned' : 'signed'
    raise ArgumentError, "Immediate max bits is #{width} #{signed}; value must be between #{min} and #{max} instead of #{value}"
  end
end
validate_reg(arg) click to toggle source
# File lib/micro_cisc/compile/instruction.rb, line 307
def validate_reg(arg)
  valid = arg.first == 0 && arg.last == 'reg'
  valid = valid || ([1, 2, 3].include?(arg.first) && arg.last == 'mem')
  valid = valid || (arg.first == 4 && arg.last == 'val')
  valid = valid || ([1, 2, 3].include?(arg.first) && arg.last == 'reg')

  if valid
    reg = arg.first
    reg += 4 if [1, 2, 3].include?(arg.first) && 'reg' == arg.last
    reg
  else
    raise ArgumentError, "Invalid register: #{arg.first.to_s}.#{arg.last}"
  end
end