Created
November 10, 2011 02:03
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 [email protected]? | |
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