repos / lc3vm.zig.git


lc3vm.zig.git / src
Evgenii Akentev  ·  2025-11-01

root.zig

  1//! By convention, root.zig is the root source file when making a library.
  2const std = @import("std");
  3
  4// Implementation of the Little Computer 3 following
  5// https://www.andreinc.net/2021/12/01/writing-a-simple-vm-in-less-than-125-lines-of-c
  6
  7const pcStart: u16 = 0x3000;
  8const u16Max: u32 = 65535;
  9pub var memory: [u16Max + 1]u16 = .{0} ** (u16Max + 1);
 10var running: bool = true;
 11
 12inline fn mr(address: u16) u16 { return memory[address]; }
 13
 14inline fn mw(address: u16, value: u16) void {
 15    memory[address] = value;
 16}
 17
 18const Register = enum(u16) {
 19    R0 = 0,
 20    R1 = 1,
 21    R2 = 2,
 22    R3 = 3,
 23    R4 = 4,
 24    R5 = 5,
 25    R6 = 6,
 26    R7 = 7,
 27    RPC = 8,
 28    RCND = 9,
 29    RCNT = 10
 30};
 31
 32var registers: [11]u16 = .{0} ** 11;
 33
 34// The instruction fits into u16:
 35//
 36// [ OP CODE ][ PARAM1 ][ PARAM2 ][ ][ PARAM3 ]
 37// [ 4 bits  ][          12 bits              ]
 38
 39const Opcode = enum {
 40    br, // conditional
 41    add, // addition
 42    ld, // load RPC + offset
 43    st, // store
 44    jsr, // jump to subroutine
 45    andop, // bitwise and
 46    ldr, // load base + offset
 47    str, // store base + offset
 48    rti, // return from interrupt
 49    not, // bitwise complement
 50    ldi, // load indirect
 51    sti, // store indirect
 52    jmp, // jump/return to subroutine
 53    unused, // unused opcode
 54    lea, // load effective address
 55    trap, // system trap/call
 56
 57    pub fn parse(instruction: u16) Opcode {
 58        // shift 12 bits to the right to get first 4 bits
 59        return @enumFromInt(instruction >> 12);
 60    }
 61};
 62
 63const TrapFunc = enum {
 64    tgetc, tout, tputs, tin, tputsp, thalt, tinu16, toutu16,
 65
 66    pub fn parse(n: u16) TrapFunc {
 67        return @enumFromInt(n);
 68    }
 69};
 70
 71const Flag = enum(u16) {
 72    FP = (1 << 0),
 73    FZ = (1 << 1),
 74    FN = (1 << 2),
 75};
 76
 77inline fn uf(r: u16) void {
 78    if (registers[r] == 0) { registers[@intFromEnum(Register.RCND)] = @intFromEnum(Flag.FZ); }
 79    else if ((registers[r] >> 15) == 1) { registers[@intFromEnum(Register.RCND)] = @intFromEnum(Flag.FN); }
 80    else registers[@intFromEnum(Register.RCND)] = @intFromEnum(Flag.FP);
 81}
 82
 83inline fn fcnd(instruction: u16) u16 { return (instruction >> 9) & 0x7; }
 84inline fn dr(instruction: u16) u16 { return (instruction >> 9) & 0x7; }
 85inline fn sr1(instruction: u16) u16 { return(instruction >> 6) & 0x7; }
 86inline fn sr2(instruction: u16) u16 { return (instruction) & 0x7; }
 87inline fn imm(instruction: u16) u16 { return (instruction) & 0x1F; }
 88inline fn fimm(instruction: u16) u16 { return (instruction >> 5) & 1; }
 89
 90inline fn sext(n: u16, b: u32) u16 {
 91    return if ((n >> (b - 1)) & 1 == 1) @truncate(@as(u32, n) | (0xFFFF << b)) else n;
 92}
 93
 94//  0000 0000 0001 0110 <--- a in binary
 95//  1111 1111 1111 0110 <--- sextimm(a) in binary
 96inline fn sextimm(instruction: u16) u16 { return sext(imm(instruction), 5); }
 97
 98inline fn poff(instruction: u16) u16 { return sext(instruction & 0x3F, 6); }
 99inline fn poff9(instruction: u16) u16 { return sext(instruction & 0x1FF, 9); }
100inline fn poff11(instruction: u16) u16 { return sext(instruction & 0x7FF, 11); }
101inline fn fl(instruction: u16) u16 { return (instruction >> 11) & 1; }
102inline fn br(instruction: u16) u16 { return (instruction >> 6) & 0x7; }
103inline fn trp(instruction: u16) u16 { return instruction & 0xFF; }
104
105pub fn evaluate(allocator: std.mem.Allocator, i: u16) !void {
106    switch (Opcode.parse(i)) {
107        inline .add => {
108            registers[dr(i)] = registers[sr1(i)] +
109                if (fimm(i) == 1)
110                    sextimm(i)
111                else registers[sr2(i)];
112            uf(dr(i));
113        },
114        inline .andop => {
115            registers[dr(i)] = registers[sr1(i)] & (if (fimm(i) == 1) (sextimm(i)) else registers[sr2(i)]);
116            uf(dr(i));
117        },
118        inline .ld => {
119            registers[dr(i)] = mr(registers[@intFromEnum(Register.RPC)] + poff9(i));
120            uf(dr(i));
121        },
122        inline .ldi => {
123            registers[dr(i)] = mr(mr(registers[@intFromEnum(Register.RPC)] + poff9(i)));
124            uf(dr(i));
125        },
126        inline .ldr => {
127            registers[dr(i)] = mr(registers[sr1(i)] + poff(i));
128            uf(dr(i));
129        },
130        inline .lea => {
131            registers[dr(i)] = registers[@intFromEnum(Register.RPC)] + poff9(i);
132            uf(dr(i));
133        },
134        inline .not => {
135            registers[dr(i)] = ~ registers[sr1(i)];
136            uf(dr(i));
137        },
138        inline .st => {
139            mw(registers[@intFromEnum(Register.RPC)] + poff9(i), registers[dr(i)]);
140        },
141        inline .sti => {
142            mw(mr(registers[@intFromEnum(Register.RPC)] + poff9(i)), registers[dr(i)]);
143        },
144        inline .str => {
145            mw(registers[sr1(i)] + poff(i), registers[dr(i)]);
146        },
147        inline .jmp => {
148            registers[@intFromEnum(Register.RPC)] = registers[br(i)];
149        },
150        inline .jsr => {
151            registers[@intFromEnum(Register.R7)] = registers[@intFromEnum(Register.RPC)];
152            registers[@intFromEnum(Register.RPC)] = if (fl(i) == 1) (registers[@intFromEnum(Register.RPC)] + poff11(i)) else registers[br(i)];
153        },
154        inline .br => {
155            if (registers[@intFromEnum(Register.RCND)] & fcnd(i) == 1) {
156                registers[@intFromEnum(Register.RPC)] += poff9(i);
157            }
158        },
159        inline .unused, .rti => {
160        },
161        inline .trap => {
162            switch (TrapFunc.parse(trp(i) - 0x20)) {
163                inline .tgetc => {
164                    const dest: []u8 = try allocator.alloc(u8, 1); 
165                    _ = try std.fs.File.stdin().read(dest);
166                    registers[@intFromEnum(Register.R0)] = dest[0];
167                },
168                inline .tout => {
169                    std.debug.print("{}", .{ registers[@intFromEnum(Register.R0)] });
170                },
171                inline .tputs => {
172                },
173                inline .tin => {
174                    const buffer: []u8 = try allocator.alloc(u8, 1024); 
175                    defer allocator.free(buffer);
176
177                    var stdin = std.fs.File.stdin().reader(buffer).interface;
178                    const m = try stdin.takeInt(u8, .little);
179                    registers[@intFromEnum(Register.R0)] = m;
180                },
181                inline .thalt => {
182                    running = false;
183                },
184                inline .tinu16 => {
185                    const buffer: []u8 = try allocator.alloc(u8, 1024); 
186                    defer allocator.free(buffer);
187
188                    var line_buffer: []u8 = try allocator.alloc(u8, 1024); 
189                    defer allocator.free(line_buffer);
190                    var writer: std.io.Writer = .fixed(line_buffer);
191                    
192                    var stdin = std.fs.File.stdin().reader(buffer).interface;
193
194                    const line_length = try stdin.streamDelimiterLimit(&writer, '\n', .unlimited);
195                    const v: u16 = try std.fmt.parseInt(u16, line_buffer[0..line_length], 10);
196                    registers[@intFromEnum(Register.R0)] = v;
197                },
198                inline .tputsp => {
199                },
200                inline .toutu16 => {
201                    const buffer: []u8 = try allocator.alloc(u8, 1024); 
202                    defer allocator.free(buffer);
203
204                    const v: u16 = registers[@intFromEnum(Register.R0)]; 
205                    std.debug.print("{}\n", .{ v });
206                },
207            }
208        }
209    }
210}
211
212pub fn start(allocator: std.mem.Allocator, offset: u16) !void {
213    registers[@intFromEnum(Register.RPC)] = pcStart + offset;
214    while (running) {
215        const instruction = mr(registers[@intFromEnum(Register.RPC)]);
216        registers[@intFromEnum(Register.RPC)] += 1;
217
218        try evaluate(allocator, instruction);
219    }
220}
221