Skip to content

Instantly share code, notes, and snippets.

@axiixc
Created November 10, 2011 02:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save axiixc/1353882 to your computer and use it in GitHub Desktop.
Save axiixc/1353882 to your computer and use it in GitHub Desktop.
A ruby assembler for our totally awesome processor. Also with almost no error handling.
#!/bin/ruby
# Expectations:
# 1) File contains only hardware instructions, there is NO pseudoinstruction translation
# 2) All immediates are the correct size. The assembler will generate errors and cease
# code generation, but will not convert large immediates to $at.
# 3) Labels must be on a line by themselves. If an instruction follows a label it will
# cause undefined errors in assembly!
# 4) All comments must be denoted by a hash (#) symbol. They may appear anywhere in the
# program, and anything after the hash will be dropped from the instruction parsing.
class Assembler
def self.assemble(input_file, output_file = nil)
self.new(input_file, output_file).write_to_disk
end
def initialize(input_file, output_file = nil)
raise "Input file not found" unless File.exists? input_file
@input_file = input_file
@output_file = output_file
@output_file ||= File.join(Dir.pwd, File.basename(input_file, '.asm'))
@instructions = []
@segments = {}
@labels = {}
end
def assemble
last_segment = nil
IO.foreach(@input_file) do |line|
instruction = InstructionBase.parse_instruction(line, last_segment)
next if instruction.nil?
@instructions << instruction
case instruction
when Segment
raise "Duplicate \"#{instruction.segment_name}\" segment found" if @segments.include? instruction.segment_name
last_segment = instruction
@segments[instruction.segment_name] = instruction
when Label
@labels[instruction.label_name] = instruction
when Instruction
# Currently nothing
end
end
@segments.each { |name,seg| seg.address_calculation }
puts "Finished address calculation"
# @labels.each { |k,v| puts "`#{v.label_name}` OR `#{k}` => %x" % [v.address]}
@instructions.each { |instr| instr.link @labels }
puts "Finished linking"
program = Array.new(InstructionBase::DataSegmentLength, 0x0)
@instructions.each do |instr|
if instr.class == Instruction
raise "Invalid address calculated" if instr.address%2 != 0
program[instr.address/2] = instr.to_binary![0]
end
end
File.open(@output_file + ".log", "w") do |log_file|
@instructions.each { |instr| puts instr; log_file.write "#{instr}\n" }
end
program
# InstructionBase::DataSegments_RAW.map do |seg|
# @segments[seg[0]].to_binary! unless @segments[seg[0]].nil?
# end.join
end
def write_to_disk
program = self.assemble
File.open(@output_file + '.mem', "w") { |f| program.each { |l| f.write "%04X\n" %[l] } }
File.open(@output_file + '.bin', "w") { |f| f.write program.pack('n*') }
end
end
class InstructionBase
def self.parse_instruction string, segment
string.gsub! /#.*/, ""
string.gsub! ",", ""
string.strip!
return nil if string.nil? || string.empty?
puts string
# Label
if string[-1,1] == ':'
raise "Orphaned label, specify segment (e.g., `.text`) before \"#{string}\"" if segment.nil?
return Label.new string, segment
# Segment
elsif string[0,1] == '.'
return Segment.new string
# Instruction
else
raise "Orphaned instruction, specify segment (e.g., `.text`) before \"#{string}\"" if segment.nil?
return Instruction.new string, segment
end
end
Instructions = {
:syscall => 0x0, :ori => 0x1, :addi => 0x2, :lui => 0x3,
:lw => 0x4, :sw => 0x5, :boz => 0x6, :bnz => 0x7,
:and => 0x8, :or => 0x9, :add => 0xA, :sbi => 0xB,
:jr => 0xC, :jal => 0xD, :sub => 0xE, :slt => 0xF
}
InstructionTypes = {
:syscall => :S, :ori => :M, :addi => :M, :lui => :M,
:lw => :S, :sw => :S, :boz => :M, :bnz => :M,
:and => :R, :or => :R, :add => :R, :sbi => :S,
:jr => :R, :jal => :L, :sub => :R, :slt => :R
}
Registers = {
:"0" => 0x0, :zero => 0x0, :at => 0x1, :rv => 0x2,
:a0 => 0x3, :a1 => 0x4, :t0 => 0x5, :t1 => 0x6,
:t2 => 0x7, :t3 => 0x8, :s0 => 0x9, :s1 => 0xA,
:s2 => 0xB, :s3 => 0xC, :k0 => 0xD, :sp => 0xE,
:ra => 0xF
}
RegisterOffsets = { :rs => 8, :rt => 4, :rd => 0 }
SImmediateBounds = -(2**3) .. (2**3)-1
MImmediateBounds = -(2**7) .. (2**7)-1
LImmediateBounds = -(2**11) .. (2**11)-1
WordBounds = -(2**15) .. (2**15)-1
DataSegments = {}
# Given as arrays with two elements (0: name, 1: start address)
DataSegments_RAW = [ # 1024 Total Words
[:data, 0x0000], # 0 - 49 (50)
[:text, 0x0064], # 50 - 249 (100)
[:kdata, 0x01F4], # 250 - 299 (50)
[:ktext_overflow, 0x0258], # 300 - 309 (10)
[:ktext_misalligned_word, 0x026C], # 310 - 319 (10)
[:ktext_interrupt, 0x0280], # 320 - 349 (30)
[:ktext, 0x02BC], # 350 - 499 (50)
[:stack, 0x03E8]] # 500 - 1023 (524)
DataSegmentLength = 0x03FF
class DataSegment
attr_reader :start_addr, :end_addr, :length
def initialize(start_addr, next_addr)
@start_addr = start_addr
@length = next_addr - start_addr
@next_addr = start_addr + @length
end
end
DataSegments_RAW.each_with_index do |segment, index|
DataSegments[segment[0]] = DataSegment.new(segment[1], (segment[0] == :stack) ? DataSegmentLength : DataSegments_RAW[index+1][1])
end
attr_reader :type, :string, :parent, :binary, :address
def initialize(string, parent = nil)
@type = :invalid
@string = string
@parent = parent
@parent << self unless @parent.nil?
@binary = []
@address = 0x0000
self.parse!
end
# Override these
def link(labels); end
def parse!; end
def to_binary!; end
def raise!(msg)
raise "Assembly error for instruction \"#{@string}\", #{msg}"
end
end
class Segment < InstructionBase
attr_reader :segment_name
def initialize(string)
super(string)
@type = :segment
@instructions = []
end
def <<(instruction)
@instructions << instruction
end
def parse!
@segment_name = @string[1 .. @string.length].to_sym
@segment_description = InstructionBase::DataSegments[@segment_name]
end
def address_calculation
@instructions.inject(DataSegments[@segment_name].start_addr) { |last_addr,instr| instr.next_address(last_addr) }
end
def to_binary!
# @instructions.each do |instr|
# instructions = instr.to_binary!
# instructions.each { |bin| @binary << bin } unless instructions.nil?
# end
#
# self.raise! "Too many instructions in #{@segment_name}" if (@binary.length > @segment_description.length)
# (@segment_description.length - @binary.length).times { @binary << 0x0 } # Pad with null
end
def to_s
" | .#{@segment_name}"
end
end
class Label < InstructionBase
attr_reader :label_name
def initialize(string, parent = nil)
super(string, parent)
@type = :label
end
def next_address(current_address)
@address = current_address
end
def parse!
@label_name = @string.sub(':', '')
end
def to_s
" | #{@label_name}:"
end
end
class Instruction < InstructionBase
def initialize(string, parent = nil)
super(string, parent)
@type = :instruction
@address = 0x0
end
def pack_register(addr, name)
return 0 if addr.nil?
reg_name = addr.sub('$', '').to_sym
reg_addr = Registers[reg_name]
self.raise! "Register #{addr} not found" if reg_addr.nil?
reg_addr << RegisterOffsets[name]
end
def parse!
components = @string.split(' ')
@instruction = components[0].to_sym
opcode = Instructions[@instruction]
self.raise! "Opcode not found" if opcode.nil?
@binary = 0x0
@binary |= opcode << 12 # op - Instruction Opcode
case InstructionTypes[@instruction]
when :R
if @instruction == :jr
@binary |= pack_register components[1], :rs
else
@binary |= pack_register components[1], :rd # Destination
@binary |= pack_register components[2], :rs # OperandA
@binary |= pack_register components[3], :rt # OperandB
end
when :S
components[3], components[2] = components[2].sub(')', '').split('(') if @instruction == :sw || @instruction == :lw
immediate = components[3].to_i
self.check_immediate immediate, SImmediateBounds unless @instruction == :syscall
if @instruction == :syscall
@binary |= pack_register components[1], :rs
@binary |= pack_register components[2], :rt
else
@binary |= pack_register components[1], :rt # Destination
@binary |= pack_register components[2], :rs # OperandA
end
@binary |= (immediate.to_i & 0xF) # OperandB
when :M
@binary |= pack_register components[1], :rs
# This will be a label if branching
if @instruction == :boz || @instruction == :bnz
@label = components[2]
# puts "Set label \"#{@label}\" for \"#{@string}\""
else
immediate = components[2].to_i
# self.check_immediate immediate, MImmediateBounds
@binary |= (immediate.to_i & 0xFF)
end
when :L
@label = components[1]
# puts "Set label \"#{@label}\" for \"#{@string}\""
end
end
def next_address(current_address)
(@address = current_address) + 2
end
def check_immediate(imm, bounds)
self.raise! "Immediate value `#{imm}` must be #{bounds.to_s}" unless bounds.include?(imm)
end
def link(label_dict)
if !@label.nil?
self.raise! "Could not link label" unless label_dict.include? @label
@label_addr = label_dict[@label].address
# printf "Label Addr: 0x%04x for \"%s\"\n" % [@label_addr, @string]
end
end
def to_binary!
binary = @binary
case InstructionTypes[@instruction]
when :M
if @instruction == :boz || @instruction == :bnz
offset = (@label_addr - (2 + @address)) >> 1
# puts "Branch offset of #{offset} for \"#{@string}\" => #{@label} @ #{@label_addr}"
self.check_immediate offset, MImmediateBounds
binary |= (offset & 0xFF)
end
when :L
pseudo_address = (@label_addr & 0x0FFF) >> 1
self.check_immediate pseudo_address, LImmediateBounds
# printf "Jump address of 0x%04x for label %s @ 0x%04x\n" % [pseudo_address, @label, @label_addr]
binary |= (pseudo_address & 0xFFF)
end
[ binary ]
end
def to_s
self.to_binary!.inject("") { |result, binary| result += "%04x: %04x | %s" % [@address, binary, @string] }
end
end
begin
raise "No input file specified" if ARGV.size < 1
Assembler.assemble ARGV[0], ARGV[1]
rescue Exception => e
puts "Usage: asym <input> [<output>]"
puts ""
puts e
# puts e.backtrace
exit
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment