229 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Crystal
		
	
	
	
	
	
			
		
		
	
	
			229 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Crystal
		
	
	
	
	
	
| require "option_parser"
 | |
| 
 | |
| input = ""
 | |
| output = ""
 | |
| unit = "ns"
 | |
| cycle = 20
 | |
| start = 20
 | |
| 
 | |
| prog_pin = "{sim:/cpu/prog}"
 | |
| inst_pin = "{sim:/cpu/pinst}"
 | |
| addr_pin = "{sim:/cpu/paddr}"
 | |
| 
 | |
| OptionParser.parse! do |parser|
 | |
|     parser.banner = "Usage: assembler [arguments]"
 | |
|     parser.on("-f NAME", "--from=NAME", "Select the input file.") do |file|
 | |
|         input = file
 | |
|     end
 | |
|     parser.on("-t NAME", "--to=NAME", "Select the output file.") do |file|
 | |
|         output = file
 | |
|     end
 | |
|     parser.on("-u UNIT", "--unit=UNIT", "select the time units.") do |u|
 | |
|         unit = u
 | |
|     end
 | |
|     parser.on("-c CYCLE", "--cycle=CYCLE", "select the clock cycle speed") do |c|
 | |
|         cycle = c.to_i? || 20
 | |
|     end
 | |
|     parser.on("-b BEGIN", "--begin=BEGIN", "select the beginning of the programming mode") do |b|
 | |
|         start = b.to_i? || 20
 | |
|     end
 | |
|     parser.on("-p PPIN", "--prog-pin=PPIN", "select the programming enable pin") do |pp|
 | |
|         prog_pin = pp
 | |
|     end
 | |
|     parser.on("-a APIN", "--addr-pin=APIN", "select address pin") do |ap|
 | |
|         addr_pin = ap
 | |
|     end
 | |
|     parser.on("-i IPIN", "--inst-pin=IPIN", "select instruction pin") do |ip|
 | |
|         inst_pin = ip
 | |
|     end
 | |
| end
 | |
| 
 | |
| enum InstType
 | |
|     RInst,
 | |
|     IInst,
 | |
|     JInst,
 | |
|     SInst,
 | |
|     DInst
 | |
| end
 | |
| 
 | |
| # w c j s o p   
 | |
| # 0 0 0 0 0 0
 | |
| 
 | |
| INSTS = {
 | |
|     "add" =>  {  "100010", InstType::RInst },
 | |
|     "sub" =>  {  "100110", InstType::RInst },
 | |
|     "and" =>  {  "100000", InstType::RInst },
 | |
|     "or"  =>  {  "100001", InstType::RInst },
 | |
|     "slt" =>  {  "100011", InstType::RInst },
 | |
|     "addc" => {  "110010", InstType::IInst },
 | |
|     "subc" => {  "110110", InstType::IInst },
 | |
|     "andc" => {  "110000", InstType::IInst },
 | |
|     "orc"  => {  "110001", InstType::IInst },
 | |
|     "sltc" => {  "110011", InstType::IInst },
 | |
|     "jmp" =>  {  "001100", InstType::JInst },
 | |
|     "jez" =>  {  "001000", InstType::JInst },
 | |
|     "jnz" =>  {  "001001", InstType::JInst },
 | |
|     "disp" =>  { "000000", InstType::DInst},
 | |
|     "read" =>  { "100011", InstType::SInst},
 | |
| }
 | |
| 
 | |
| def decode_register(reg)
 | |
|     if reg.size != 2
 | |
|         puts "Invalid register operand size."
 | |
|         return nil
 | |
|     end
 | |
| 
 | |
|     if reg[0] != 'r'
 | |
|         puts "Expected register parameter."
 | |
|         return nil
 | |
|     end
 | |
| 
 | |
|     if val = reg[1].to_i?
 | |
|         if val <= 7
 | |
|             return val
 | |
|         else
 | |
|             puts "Out of bounds register number"
 | |
|         end
 | |
|     else
 | |
|         puts "Register must be a number."
 | |
|     end
 | |
| 
 | |
|     return nil
 | |
| end
 | |
| 
 | |
| def decode_int(param)
 | |
|     if param.starts_with? "0x"
 | |
|         return param[2..-1].to_i?(16)
 | |
|     elsif param.starts_with? "0b"
 | |
|         return param[2..-1].to_i?(2)
 | |
|     else
 | |
|         return param.to_i?
 | |
|     end
 | |
| end
 | |
| 
 | |
| def decode_params(params, string): Array(Int32|String)?
 | |
|     params = params.dup
 | |
| 
 | |
|     if params.size != string.size
 | |
|         puts "Invalid number of parameters."
 | |
|         return nil
 | |
|     end
 | |
| 
 | |
|     output = string.chars.map do |char|
 | |
|         param = params.shift
 | |
|         case char
 | |
|         when 'r'
 | |
|             decode_register(param)
 | |
|         when 'c'
 | |
|             decode_int(param)
 | |
|         when 's'
 | |
|             param
 | |
|         else
 | |
|             nil
 | |
|         end
 | |
|     end
 | |
| 
 | |
|     if output.includes? nil
 | |
|         return nil
 | |
|     else
 | |
|         return output.compact
 | |
|     end
 | |
| end
 | |
| 
 | |
| def decode_inst(labels, params)
 | |
|     inst = params.shift
 | |
|     if inst_data = INSTS[inst]?
 | |
|         code, type = inst_data
 | |
|         case type
 | |
|         when InstType::RInst
 | |
|             data = decode_params(params, "rrr")
 | |
|             return nil unless data
 | |
|             return code + 
 | |
|                 data[0].as(Int32).to_s(2).rjust(3, '0') +
 | |
|                 data[1].as(Int32).to_s(2).rjust(3, '0') +
 | |
|                 data[2].as(Int32).to_s(2).rjust(3, '0') +
 | |
|                 "00000000000000000"
 | |
|         when InstType::IInst
 | |
|             data = decode_params(params, "rrc")
 | |
|             return nil unless data
 | |
|             return code +
 | |
|                 data[0].as(Int32).to_s(2).rjust(3, '0') +
 | |
|                 data[1].as(Int32).to_s(2).rjust(3, '0') +
 | |
|                 "0000" +
 | |
|                 data[2].as(Int32).to_s(2).rjust(16, '0')
 | |
|         when InstType::JInst
 | |
|             if inst == "jmp"
 | |
|                 data = decode_params(params, "s")
 | |
|                 return nil unless data
 | |
|                 return code +
 | |
|                     "0000000000" + labels[data[0].as(String)].to_s(2).rjust(16, '0')
 | |
|             else
 | |
|                 data = decode_params(params, "rs")
 | |
|                 return nil unless data
 | |
|                 return code +
 | |
|                     "000" +
 | |
|                     data[0].as(Int32).to_s(2).rjust(3, '0') +
 | |
|                     "0000" +
 | |
|                     labels[data[1].as(String)].to_s(2).rjust(16, '0')
 | |
|             end
 | |
|         when InstType::DInst
 | |
|             data = decode_params(params, "r")
 | |
|             return nil unless data
 | |
|             return code +
 | |
|                 "000" +
 | |
|                 data[0].as(Int32).to_s(2).rjust(3, '0') +
 | |
|                 "000" +
 | |
|                 "00000000000000000"
 | |
|         when InstType::SInst
 | |
|             data = decode_params(params, "r")
 | |
|             return nil unless data
 | |
|             return code +
 | |
|                 data[0].as(Int32).to_s(2).rjust(3, '0') +
 | |
|                 "000" +
 | |
|                 "000" +
 | |
|                 "00000000000000000"
 | |
|         end
 | |
|     end
 | |
| end
 | |
| 
 | |
| def decode_insts(labels, insts)
 | |
|     decoded = insts.map {|inst| decode_inst(labels, inst) }
 | |
|     if decoded.includes? nil
 | |
|         return nil
 | |
|     else
 | |
|         return decoded.compact
 | |
|     end
 | |
| end
 | |
| 
 | |
| if input == ""
 | |
|     puts "Please select an input file."
 | |
| elsif output == ""
 | |
|     puts "Please select an output file."
 | |
| elsif !File.exists?(input) || File.directory?(input)
 | |
|     puts "Please select a valid input file."
 | |
| else
 | |
|     labels = {} of String => Int32
 | |
|     raw_instructions = [] of Array(String)
 | |
|     lines = File.read_lines(input)
 | |
|     lines.map_with_index do |line, index|
 | |
|         beforecomment = line.split(/#/).first.strip.split /\s+/
 | |
|         if beforecomment[0].ends_with? ":"
 | |
|             labels[beforecomment.shift.split(/:/).first] = index
 | |
|         end
 | |
|         raw_instructions.push(beforecomment) unless  beforecomment.empty?
 | |
|     end
 | |
| 
 | |
|     if instructions = decode_insts(labels, raw_instructions)
 | |
|         dofile = File.open(output, "w")
 | |
|         dofile << "force " << prog_pin << " 1 @ " << start << unit << "\n"
 | |
|         instructions.each_with_index do |instruction, index|
 | |
|             timestamp = (start + index * cycle)
 | |
|             dofile << "force " << inst_pin << " {16'b" << instruction << "} @ " << timestamp << unit << "\n"
 | |
|             dofile << "force " << addr_pin << " " << index.to_s << " @ " << timestamp << unit << "\n"
 | |
|         end
 | |
|         dofile << "force " <<  prog_pin << " 0 @ " << (start + instructions.size * cycle) << unit << "\n"
 | |
|         dofile.close
 | |
|     else
 | |
|     end
 | |
| end
 |