diff --git a/assembler.cr b/assembler.cr new file mode 100644 index 0000000..fb81401 --- /dev/null +++ b/assembler.cr @@ -0,0 +1,228 @@ +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