// Copyright (c) 2014 Sergiusz 'q3k' BazaƄski // Licensed under 2-clause BSD - see COPYING file from software distribution `timescale 1ns / 1ps /* verilator lint_off UNDRIVEN */ /* verilator lint_off UNUSED */ /* verilator lint_off LITENDIAN */ module cpu ( input reset, input cpu_clock, input hertz_clock, input [15:0] keypad ); // 4k (12 bits) of 8-bit ram reg [7:0] ram[0:4095]/* verilator public */; // 16 8-bit V registers reg [7:0] vr[0:15]/* verilator public */; // 16-bit I register reg [15:0] i/* verilator public */; // 12-bit memory-indexing version of I wire [11:0] ii = i[11:0]; // 12-bit program counter reg [11:0] pc/* verilator public */; // 16-element, 12-bit stack reg [11:0] stack [0:15]; // 4-bit stack pointer reg [3:0] sp; // 8-bit DT (delay timer) reg [7:0] dt/* verilator public */; // 8-bit ST (sound timer) reg [7:0] st; // internal cycle counter (0 - fetch) `define FETCH 0 `define EXECUTE 1 `define WRITEBACK 2 `define WAIT_KEY 3 reg [2:0] c/* verilator public */; // Instruction register reg [15:0] ins/* verilator public */; wire [3:0] family = ins[15:12]; // ALU operation wire [3:0] alu_op = ins[3:0]; wire is_alu_op = (c == `EXECUTE) && (family == 8); // Source register indexes wire [3:0] x = ins[11:8]; wire [3:0] y = ins[7:4]; // Immediates wire [7:0] kk = ins[7:0]; wire [11:0] nnn = ins[11:0]; // Jump register reg[11:0] jmp; // Should jump on FETCH? reg should_jmp; always @(posedge cpu_clock) begin if (!reset) begin i <= 0; pc <= 0; sp <= 0; should_jmp <= 0; c <= 0; dt <= 0; dt <= 0; vr[0] <= 0; vr[1] <= 0; vr[2] <= 0; vr[3] <= 0; vr[4] <= 0; vr[5] <= 0; vr[6] <= 0; vr[7] <= 0; vr[8] <= 0; vr[9] <= 0; vr[10] <= 0; vr[11] <= 0; vr[12] <= 0; vr[13] <= 0; vr[14] <= 0; vr[15] <= 0; end else begin case(c) `FETCH: begin ins <= {ram[pc], ram[pc + 1]}; c <= 1; if (hertz_clock == 1) begin if (dt > 0) dt <= dt - 1; if (st > 0) st <= st - 1; end end `EXECUTE: begin // $display("TRACE %h @ %h", ins, pc); case (family) // Old native instructions - we implement them in // hardware 0: begin if (ins == 16'h00E0) begin // clear screen $display("should clear, don't care."); end else if (ins == 16'h00EE) begin // return from subroutine jmp <= stack[sp-1]; sp <= sp - 1; should_jmp <= 1; end else $display("Unknown opcode: %h @ %h", ins, pc); end // Jump to address 1: begin should_jmp <= 1; jmp <= nnn; end // Call address 2: begin should_jmp <= 1; stack[sp] <= pc; sp <= sp + 1; jmp <= nnn; end // Skip next if Vx == kk (3xkk) 3: begin if (vr[x] == kk) begin should_jmp <= 1; jmp <= pc + 4; end end // Skip next if Vx != kk (4xkk) 4: begin if (vr[x] != kk) begin should_jmp <= 1; jmp <= pc + 4; end end // Skip next if Vx == Vy (5xy0) 5: begin if (vr[x] == vr[y]) begin should_jmp <= 1; jmp <= pc + 4; end end // Vx <= kk 6: vr[x] <= kk; // Vx += kk 7: vr[x] <= vr[x] + kk; // These are implemented in the ALU 8: begin end // Skip if Vx != Vy (9xy0) 9: begin if (vr[x] != vr[y]) begin should_jmp <= 1; jmp <= pc + 4; end end // Set I to nnn 10: i <= {4'h0, nnn}; // Jump to nnn + V0 11: begin should_jmp <= 1; jmp <= nnn + {4'h0, vr[0]}; end // Set Vx to random and kk // TODO: undo fair dice roll 12: begin vr[x] <= 42 & kk; $display("STUB: random"); end // TODO: draw sprites 13: begin end // Key functions 14: begin if (kk == 8'h9E) begin if (keypad[x]) begin should_jmp <= 1; jmp <= pc + 4; end end else if (kk == 8'hA1) begin if (~keypad[x]) begin should_jmp <= 1; jmp <= pc + 4; end end else $display("Unknown opcode: %h @ %h", ins, pc); end // Fxxx instructions (mostly timer related) 15: begin case (kk) // load DT into Vx 8'h07: vr[x] <= dt; // load Vx into DT 8'h15: dt <= vr[x]; // load Vx into ST 8'h18: st <= vr[x]; // add Vx to I 8'h1E: begin i <= i + {8'b0, vr[x]}; $display("i, %h, vx %h", i, vr[x]); end // move Vx digit memory location to I 8'h29: i <= 5 * vr[x]; // store BCD rep of Vx into [I], [I+1], [I+2] 8'h33: begin ram[ii+2] <= vr[x] / 100; ram[ii+1] <= (vr[x] / 10) % 10; ram[ii] <= vr[x] % 10; end // store V0 to Vx to [I], [I+1], ..., [I+x] 8'h55: begin ram[ii] <= vr[0]; if (x > 0) ram[ii+1] <= vr[1]; if (x > 1) ram[ii+2] <= vr[2]; if (x > 2) ram[ii+3] <= vr[3]; if (x > 3) ram[ii+4] <= vr[4]; if (x > 4) ram[ii+5] <= vr[5]; if (x > 5) ram[ii+6] <= vr[6]; if (x > 6) ram[ii+7] <= vr[7]; if (x > 7) ram[ii+8] <= vr[8]; if (x > 8) ram[ii+9] <= vr[9]; if (x > 9) ram[ii+10] <= vr[10]; if (x > 10) ram[ii+11] <= vr[11]; if (x > 11) ram[ii+12] <= vr[12]; if (x > 12) ram[ii+13] <= vr[13]; if (x > 13) ram[ii+14] <= vr[14]; if (x > 14) ram[ii+15] <= vr[15]; end // read [I], [I+1], ..., [I+x] into V0 .. Vx 8'h65: begin vr[0] <= ram[ii]; if (x > 0) vr[1] <= ram[ii+1]; if (x > 1) vr[1] <= ram[ii+2]; if (x > 2) vr[1] <= ram[ii+3]; if (x > 3) vr[1] <= ram[ii+4]; if (x > 4) vr[1] <= ram[ii+5]; if (x > 5) vr[1] <= ram[ii+6]; if (x > 6) vr[1] <= ram[ii+7]; if (x > 7) vr[1] <= ram[ii+8]; if (x > 8) vr[1] <= ram[ii+9]; if (x > 9) vr[1] <= ram[ii+10]; if (x > 10) vr[1] <= ram[ii+11]; if (x > 11) vr[1] <= ram[ii+12]; if (x > 12) vr[1] <= ram[ii+13]; if (x > 13) vr[1] <= ram[ii+14]; if (x > 14) vr[1] <= ram[ii+15]; end default: $display("Unknown opcode: %h @ %h", ins, pc); endcase end endcase if (family == 4'hF && kk == 8'h0A) c <= `WAIT_KEY; else c <= `WRITEBACK; end `WRITEBACK: begin if (should_jmp) begin pc <= jmp; end else begin pc <= pc + 2; end should_jmp <= 0; c <= 0; end `WAIT_KEY: begin $display("waiting for key"); if (keypad != 0) begin case (keypad) 16'h1: begin vr[x] <= 0; c <= `WRITEBACK; end 16'h2: begin vr[x] <= 1; c <= `WRITEBACK; end 16'h4: begin vr[x] <= 2; c <= `WRITEBACK; end 16'h8: begin vr[x] <= 3; c <= `WRITEBACK; end 16'h10: begin vr[x] <= 4; c <= `WRITEBACK; end 16'h20: begin vr[x] <= 5; c <= `WRITEBACK; end 16'h40: begin vr[x] <= 6; c <= `WRITEBACK; end 16'h80: begin vr[x] <= 7; c <= `WRITEBACK; end 16'h100: begin vr[x] <= 8; c <= `WRITEBACK; end 16'h200: begin vr[x] <= 9; c <= `WRITEBACK; end 16'h400: begin vr[x] <= 10; c <= `WRITEBACK; end 16'h800: begin vr[x] <= 11; c <= `WRITEBACK; end 16'h1000: begin vr[x] <= 12; c <= `WRITEBACK; end 16'h2000: begin vr[x] <= 13; c <= `WRITEBACK; end 16'h4000: begin vr[x] <= 14; c <= `WRITEBACK; end 16'h8000: begin vr[x] <= 15; c <= `WRITEBACK; end endcase end end endcase end end // ALU always @(posedge cpu_clock) begin if (is_alu_op) begin case(alu_op) 0: vr[x] <= vr[y]; 1: vr[x] <= vr[x] | vr[y]; 2: vr[x] <= vr[x] & vr[y]; 3: vr[x] <= vr[x] ^ vr[y]; 4: { vr[15], vr[x] } <= {8'b0, vr[x]} + {8'b0, vr[y]}; 5: begin vr[x] <= vr[x] - vr[y]; vr[15] <= {7'b0, vr[x] >= vr[y]}; end 6: begin vr[15] <= {7'b0, vr[x][0]}; vr[x] <= vr[x] >> 1; end 7: begin vr[x] <= vr[y] - vr[x]; vr[15] <= {7'b0, vr[y] >= vr[x]}; end 14: begin vr[15] <= {7'b0, vr[x][7]}; vr[x] <= vr[x] << 1; end default: $display("Unknown opcode: %h @ %h", ins, pc); endcase end end endmodule