# 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