zig/src/arch/riscv64/Encoding.zig
2024-05-11 02:17:24 -07:00

401 lines
11 KiB
Zig

mnemonic: Mnemonic,
data: Data,
pub const Mnemonic = enum {
// I Type
ld,
lw,
lwu,
lh,
lhu,
lb,
lbu,
sltiu,
sltu,
xori,
andi,
slli,
srli,
srai,
addi,
jalr,
// U Type
lui,
// S Type
sd,
sw,
sh,
sb,
// J Type
jal,
// B Type
beq,
// R Type
add,
sub,
slt,
mul,
xor,
// System
ecall,
ebreak,
unimp,
pub fn encoding(mnem: Mnemonic) Enc {
return switch (mnem) {
// zig fmt: off
.add => .{ .opcode = 0b0110011, .funct3 = 0b000, .funct7 = 0b0000000 },
.sub => .{ .opcode = 0b0110011, .funct3 = 0b000, .funct7 = 0b0100000 },
.ld => .{ .opcode = 0b0000011, .funct3 = 0b011, .funct7 = null },
.lw => .{ .opcode = 0b0000011, .funct3 = 0b010, .funct7 = null },
.lwu => .{ .opcode = 0b0000011, .funct3 = 0b110, .funct7 = null },
.lh => .{ .opcode = 0b0000011, .funct3 = 0b001, .funct7 = null },
.lhu => .{ .opcode = 0b0000011, .funct3 = 0b101, .funct7 = null },
.lb => .{ .opcode = 0b0000011, .funct3 = 0b000, .funct7 = null },
.lbu => .{ .opcode = 0b0000011, .funct3 = 0b100, .funct7 = null },
.sltiu => .{ .opcode = 0b0010011, .funct3 = 0b011, .funct7 = null },
.addi => .{ .opcode = 0b0010011, .funct3 = 0b000, .funct7 = null },
.andi => .{ .opcode = 0b0010011, .funct3 = 0b111, .funct7 = null },
.xori => .{ .opcode = 0b0010011, .funct3 = 0b100, .funct7 = null },
.jalr => .{ .opcode = 0b1100111, .funct3 = 0b000, .funct7 = null },
.slli => .{ .opcode = 0b0010011, .funct3 = 0b001, .funct7 = null },
.srli => .{ .opcode = 0b0010011, .funct3 = 0b101, .funct7 = null },
.srai => .{ .opcode = 0b0010011, .funct3 = 0b101, .funct7 = null, .offset = 1 << 10 },
.lui => .{ .opcode = 0b0110111, .funct3 = null, .funct7 = null },
.sd => .{ .opcode = 0b0100011, .funct3 = 0b011, .funct7 = null },
.sw => .{ .opcode = 0b0100011, .funct3 = 0b010, .funct7 = null },
.sh => .{ .opcode = 0b0100011, .funct3 = 0b001, .funct7 = null },
.sb => .{ .opcode = 0b0100011, .funct3 = 0b000, .funct7 = null },
.jal => .{ .opcode = 0b1101111, .funct3 = null, .funct7 = null },
.beq => .{ .opcode = 0b1100011, .funct3 = 0b000, .funct7 = null },
.slt => .{ .opcode = 0b0110011, .funct3 = 0b010, .funct7 = 0b0000000 },
.sltu => .{ .opcode = 0b0110011, .funct3 = 0b011, .funct7 = 0b0000000 },
.xor => .{ .opcode = 0b0110011, .funct3 = 0b100, .funct7 = 0b0000000 },
.mul => .{ .opcode = 0b0110011, .funct3 = 0b000, .funct7 = 0b0000001 },
.ecall => .{ .opcode = 0b1110011, .funct3 = 0b000, .funct7 = null },
.ebreak => .{ .opcode = 0b1110011, .funct3 = 0b000, .funct7 = null },
.unimp => .{ .opcode = 0b0000000, .funct3 = 0b000, .funct7 = null },
// zig fmt: on
};
}
};
pub const InstEnc = enum {
R,
I,
S,
B,
U,
J,
/// extras that have unusual op counts
system,
pub fn fromMnemonic(mnem: Mnemonic) InstEnc {
return switch (mnem) {
.addi,
.ld,
.lw,
.lwu,
.lh,
.lhu,
.lb,
.lbu,
.jalr,
.sltiu,
.xori,
.andi,
.slli,
.srli,
.srai,
=> .I,
.lui,
=> .U,
.sd,
.sw,
.sh,
.sb,
=> .S,
.jal,
=> .J,
.beq,
=> .B,
.slt,
.sltu,
.mul,
.xor,
.add,
.sub,
=> .R,
.ecall,
.ebreak,
.unimp,
=> .system,
};
}
pub fn opsList(enc: InstEnc) [3]std.meta.FieldEnum(Operand) {
return switch (enc) {
// zig fmt: off
.R => .{ .reg, .reg, .reg, },
.I => .{ .reg, .reg, .imm, },
.S => .{ .reg, .reg, .imm, },
.B => .{ .reg, .reg, .imm, },
.U => .{ .reg, .imm, .none, },
.J => .{ .reg, .imm, .none, },
.system => .{ .none, .none, .none, },
// zig fmt: on
};
}
};
pub const Data = union(InstEnc) {
R: packed struct {
opcode: u7,
rd: u5,
funct3: u3,
rs1: u5,
rs2: u5,
funct7: u7,
},
I: packed struct {
opcode: u7,
rd: u5,
funct3: u3,
rs1: u5,
imm0_11: u12,
},
S: packed struct {
opcode: u7,
imm0_4: u5,
funct3: u3,
rs1: u5,
rs2: u5,
imm5_11: u7,
},
B: packed struct {
opcode: u7,
imm11: u1,
imm1_4: u4,
funct3: u3,
rs1: u5,
rs2: u5,
imm5_10: u6,
imm12: u1,
},
U: packed struct {
opcode: u7,
rd: u5,
imm12_31: u20,
},
J: packed struct {
opcode: u7,
rd: u5,
imm12_19: u8,
imm11: u1,
imm1_10: u10,
imm20: u1,
},
system: void,
pub fn toU32(self: Data) u32 {
return switch (self) {
.R => |v| @as(u32, @bitCast(v)),
.I => |v| @as(u32, @bitCast(v)),
.S => |v| @as(u32, @bitCast(v)),
.B => |v| @as(u32, @intCast(v.opcode)) + (@as(u32, @intCast(v.imm11)) << 7) + (@as(u32, @intCast(v.imm1_4)) << 8) + (@as(u32, @intCast(v.funct3)) << 12) + (@as(u32, @intCast(v.rs1)) << 15) + (@as(u32, @intCast(v.rs2)) << 20) + (@as(u32, @intCast(v.imm5_10)) << 25) + (@as(u32, @intCast(v.imm12)) << 31),
.U => |v| @as(u32, @bitCast(v)),
.J => |v| @as(u32, @bitCast(v)),
.system => unreachable,
};
}
pub fn construct(mnem: Mnemonic, ops: []const Operand) !Data {
const inst_enc = InstEnc.fromMnemonic(mnem);
const enc = mnem.encoding();
// special mnemonics
switch (mnem) {
.ecall,
.ebreak,
.unimp,
=> {
assert(ops.len == 0);
return .{
.I = .{
.rd = Register.zero.id(),
.rs1 = Register.zero.id(),
.imm0_11 = switch (mnem) {
.ecall => 0x000,
.ebreak => 0x001,
.unimp => 0,
else => unreachable,
},
.opcode = enc.opcode,
.funct3 = enc.funct3.?,
},
};
},
else => {},
}
switch (inst_enc) {
.R => {
assert(ops.len == 3);
return .{
.R = .{
.rd = ops[0].reg.id(),
.rs1 = ops[1].reg.id(),
.rs2 = ops[2].reg.id(),
.opcode = enc.opcode,
.funct3 = enc.funct3.?,
.funct7 = enc.funct7.?,
},
};
},
.S => {
assert(ops.len == 3);
const umm = ops[2].imm.asBits(u12);
return .{
.S = .{
.imm0_4 = @truncate(umm),
.rs1 = ops[0].reg.id(),
.rs2 = ops[1].reg.id(),
.imm5_11 = @truncate(umm >> 5),
.opcode = enc.opcode,
.funct3 = enc.funct3.?,
},
};
},
.I => {
assert(ops.len == 3);
return .{
.I = .{
.rd = ops[0].reg.id(),
.rs1 = ops[1].reg.id(),
.imm0_11 = ops[2].imm.asBits(u12) + enc.offset,
.opcode = enc.opcode,
.funct3 = enc.funct3.?,
},
};
},
.U => {
assert(ops.len == 2);
return .{
.U = .{
.rd = ops[0].reg.id(),
.imm12_31 = ops[1].imm.asBits(u20),
.opcode = enc.opcode,
},
};
},
.J => {
assert(ops.len == 2);
const umm = ops[1].imm.asBits(u21);
assert(umm % 4 == 0); // misaligned jump target
return .{
.J = .{
.rd = ops[0].reg.id(),
.imm1_10 = @truncate(umm >> 1),
.imm11 = @truncate(umm >> 11),
.imm12_19 = @truncate(umm >> 12),
.imm20 = @truncate(umm >> 20),
.opcode = enc.opcode,
},
};
},
.B => {
assert(ops.len == 3);
const umm = ops[2].imm.asBits(u13);
assert(umm % 4 == 0); // misaligned branch target
return .{
.B = .{
.rs1 = ops[0].reg.id(),
.rs2 = ops[1].reg.id(),
.imm1_4 = @truncate(umm >> 1),
.imm5_10 = @truncate(umm >> 5),
.imm11 = @truncate(umm >> 11),
.imm12 = @truncate(umm >> 12),
.opcode = enc.opcode,
.funct3 = enc.funct3.?,
},
};
},
else => std.debug.panic("TODO: construct {s}", .{@tagName(inst_enc)}),
}
}
};
pub fn findByMnemonic(mnem: Mnemonic, ops: []const Operand) !?Encoding {
if (!verifyOps(mnem, ops)) return null;
return .{
.mnemonic = mnem,
.data = try Data.construct(mnem, ops),
};
}
const Enc = struct {
opcode: u7,
funct3: ?u3,
funct7: ?u7,
offset: u12 = 0,
};
fn verifyOps(mnem: Mnemonic, ops: []const Operand) bool {
const inst_enc = InstEnc.fromMnemonic(mnem);
const list = std.mem.sliceTo(&inst_enc.opsList(), .none);
for (list, ops) |l, o| if (l != std.meta.activeTag(o)) return false;
return true;
}
const std = @import("std");
const assert = std.debug.assert;
const log = std.log.scoped(.encoding);
const Encoding = @This();
const bits = @import("bits.zig");
const Register = bits.Register;
const encoder = @import("encoder.zig");
const Instruction = encoder.Instruction;
const Operand = Instruction.Operand;
const OperandEnum = std.meta.FieldEnum(Operand);