VerilogCPU/assembler.cr

230 lines
6.6 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
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