vchip8/cpu.v

373 lines
14 KiB
Verilog

// Copyright (c) 2014 Sergiusz 'q3k' BazaƄski <sergiusz@bazanski.pl>
// 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