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 parser.on("-h", "--help") { puts parser } 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