p1es/assembler.rb

268 lines
6.2 KiB
Ruby

# assembler.rb
# # # REGISTER NAMES # # #
def r0() 0 end # zero
def at() 1 end # assembler temporary
# subroutine return values, may be changed by subroutines
def v0() 2 end ; def v1() 3 end
# subroutine arguments, may be changed by subroutines
def a0() 4 end ; def a1() 5 end
def a2() 6 end ; def a3() 7 end
# temporaries, may be changed by subroutines
def t0() 8 end ; def t1() 9 end
def t2() 10 end ; def t3() 11 end
def t4() 12 end ; def t5() 13 end
def t6() 14 end ; def t7() 15 end
# static variables, must be saved by subs
def s0() 16 end ; def s1() 17 end
def s2() 18 end ; def s3() 19 end
def s4() 20 end ; def s5() 21 end
def s6() 22 end ; def s7() 23 end
# temporaries, may be changed by subroutines
def t8() 24 end ; def t9() 25 end
# reserved for kernel, detroyed by some irq handlers
def k0() 26 end ; def k1() 27 end
def gp() 28 end # global pointer, rarely used (we can use this to reffer to a struct or smtn)
def sp() 29 end # stack pointer
def fp() 30 end # frame pointer
def ra() 31 end # return address
class Assembler
def initialize(base, size)
@base_addr = base
@file_size = size
# moar vars defined in assemble()
end
def inspect
"(Assembler base_addr:0x#{@base_addr.to_s 16} file_size:0x#{@file_size.to_s 16})"
end
def label(l)
l = l.to_s
@sub_labels[l] = {} if not @sub_labels.member?(l)
@last_label_subs = @sub_labels[l]
return if @first_pass == false
raise "error redefinition of label #{l}" if @labels.member?(l)
@labels[l] = @bytes.length + @base_addr
end
def sub_label(l)
return if @first_pass == false
l = l.to_s
raise "error redefinition of sub_label #{l}" if @last_label_subs.member?(l)
@last_label_subs[l] = @bytes.length + @base_addr
end
def method_missing(m, *args, &block)
return 0 if @first_pass
if @last_label_subs[m.to_s] then
return @last_label_subs[m.to_s]
elsif @labels.member?(m.to_s) then # if m.to_s[0, 2] == "l_" then
return @labels[m.to_s] #return @labels[m.to_s[2..]] if @labels.member?(m.to_s[2..])
#raise "label doesn't exist #{m.to_s}"
else
super
end
end
def assemble(source)
@first_pass = true
@labels = {}
@bytes = []
@sub_labels = {}
@last_label_subs = {}
binding.eval source
@first_pass = false
@bytes = []
@last_label_subs = {}
binding.eval source
puts "assembled 0x#{@bytes.length.to_s 16} (#{@bytes.length}) bytes"
raise "CODE IS GREATER THAN FILESIZE" if @bytes.length > @file_size
zero_fill @file_size - 1
return @bytes
end
def zero_fill(pos)
while (@bytes.length <= pos) do
@bytes.push(0)
end
end
def word(i)
@bytes.push((i & 0x000000ff),
(i & 0x0000ff00) >> 8,
(i & 0x00ff0000) >> 16,
(i & 0xff000000) >> 24)
end
def string(s)
@bytes.push *s.bytes
@bytes.push 0 # null terminate
while ((@bytes.length % 4) != 0) do
@bytes.push 0
end
end
# # # INSTRUCTIONS # # #
# ops should always be in format of
# [src] [args] -> [dest]
def jmp(addr)
# TODO: this probs wont show up since we have < 3mb of ram but
# we can't jump to an address with a different 4 most significant bits
raise "jmp addr must be 4 byte aligned" if addr % 4 != 0
op = 0b0000_1000_0000_0000_0000_0000_0000_0000
op = op | ((addr >> 2) & 0b11_11111111_11111111_11111111)
word op
end
def jal(addr)
# TODO: same problem as jmp
raise "jmp addr must be 4 byte aligned" if addr % 4 != 0
op = 0b0000_1100_0000_0000_0000_0000_0000_0000
op = op | ((addr >> 2) & 0b11_11111111_11111111_11111111)
word op
end
# load upper immediate
# NOTE: val will be shifted left by 16
def lui(val, dest)
# oooooo ----- ddddd iiiiiiiiiiiiiiii
op = 0b001111_00000_00000_0000000000000000
op |= 0xffff & val
op |= (0b11111 & dest) << 16
word op
end
# shift right logical
def srl(src, amt, dest)
# oooooo ----- sssss ddddd hhhhh oooooo
op = 0b000000_00000_00000_00000_00000_000010
op |= (src & 0b11111) << 16
op |= (amt & 0b11111) << 6
op |= (dest & 0b11111) << 11
word op
end
# or immediate
def ori(src, bits, dest)
# oooooo sssss ddddd iiiiiiii iiiiiiii
op = 0b001101_00000_00000_00000000_00000000
op |= (src & 0b11111) << 21
op |= (bits & 0xffff)
op |= (dest & 0b11111) << 16
word op
end
# store word
# NOTE: this doesn't work unaligned
def sw(src, offset, dest)
# oooooo ddddd sssss iiiiiiii iiiiiiii
op = 0b101011_00000_00000_00000000_00000000
op |= (src & 0b11111) << 16
op |= (offset & 0xffff)
op |= (dest & 0b11111) << 21
word op
end
# add immediate unsigned
def addiu(src, val, dest)
# oooooo sssss ddddd iiiiiiii iiiiiiii
op = 0b001001_00000_00000_00000000_00000000
op |= (src & 0b11111) << 21
op |= (val & 0xffff)
op |= (dest & 0b11111) << 16
word op
end
# add immediate
def addi(src, val, dest)
# oooooo sssss ddddd iiiiiiii iiiiiiii
op = 0b001000_00000_00000_00000000_00000000
op |= (src & 0b11111) << 21
op |= (val & 0xffff)
op |= (dest & 0b11111) << 16
word op
end
# jump register
def jr(reg)
# oooooo rrrrr ----- ----- ----- oooooo
op = 0b000000_00000_00000_00000_00000_001000
op |= (reg & 0b11111) << 21
word op
end
# TODO: actually encode "comment" argument
def syscall(comment)
# iiiiii ccccc ccccc ccccc ccccc iiiiii
op = 0b000000_00000_00000_00000_00000_001100
word op
end
# TODO: expose the return address storage register
# NOTE: cannot use the same register for both operands
def jalr(reg)
# jumps to addr in j and stores return adr in r
# iiiiii jjjjj ----- rrrrr ----- iiiiii
op = 0b000000_00000_00000_00000_00000_001001
op |= (reg & 0b11111) << 21
op |= (ra) << 11 # ra
word op
end
# # # PSEUDO-INSTRUCTIONS # # #
def nop
word 0x00000000
end
# load immediate
# TODO: check if we need to use 1 or 2 ops and output that
def li(dest, val)
lui val >> 16, dest
ori dest, val, dest
end
end
# # # CONSTANTS # # #
base_addr = 0x80010000 # 0x10000 # 0x80010000
file_size = 0x800 * 3
f = File.new "exe-header.asm.rb", "r"
exe_header = Assembler.new(base_addr, 0x800).assemble f.read
# puts f.path
f.close
f = File.new "main.asm.rb", "r"
binary = Assembler.new(base_addr, file_size).assemble f.read
f.close
f = File.new "LOADTHIS.EXE", "wb"
f.write exe_header.pack("C*")
f.write binary.pack("C*")
f.close