2014-01-03 23:35:11 +00:00
|
|
|
// Copyright (c) 2014 Sergiusz 'q3k' Bazański <sergiusz@bazanski.pl>
|
|
|
|
// Licensed under 2-clause BSD - see COPYING file from software distribution
|
|
|
|
|
|
|
|
`timescale 1ns / 1ps
|
|
|
|
|
2014-01-04 12:54:08 +00:00
|
|
|
/* verilator lint_off UNDRIVEN */
|
|
|
|
/* verilator lint_off UNUSED */
|
|
|
|
/* verilator lint_off LITENDIAN */
|
|
|
|
module cpu (
|
2014-01-03 23:35:11 +00:00
|
|
|
input reset,
|
|
|
|
input cpu_clock,
|
2014-02-20 12:37:31 +00:00
|
|
|
input hertz_clock,
|
2014-01-03 23:35:11 +00:00
|
|
|
input [15:0] keypad
|
|
|
|
);
|
|
|
|
|
|
|
|
// 4k (12 bits) of 8-bit ram
|
2014-01-04 12:54:08 +00:00
|
|
|
reg [7:0] ram[0:4095]/* verilator public */;
|
2014-01-03 23:35:11 +00:00
|
|
|
// 16 8-bit V registers
|
2014-01-04 12:54:08 +00:00
|
|
|
reg [7:0] vr[0:15]/* verilator public */;
|
2014-01-03 23:35:11 +00:00
|
|
|
// 16-bit I register
|
2014-02-20 12:37:31 +00:00
|
|
|
reg [15:0] i/* verilator public */;
|
|
|
|
// 12-bit memory-indexing version of I
|
|
|
|
wire [11:0] ii = i[11:0];
|
2014-01-03 23:35:11 +00:00
|
|
|
// 12-bit program counter
|
2014-01-04 12:54:08 +00:00
|
|
|
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;
|
2014-01-03 23:35:11 +00:00
|
|
|
|
2014-02-20 12:37:31 +00:00
|
|
|
// 8-bit DT (delay timer)
|
|
|
|
reg [7:0] dt/* verilator public */;
|
|
|
|
// 8-bit ST (sound timer)
|
|
|
|
reg [7:0] st;
|
|
|
|
|
2014-01-03 23:35:11 +00:00
|
|
|
// internal cycle counter (0 - fetch)
|
|
|
|
`define FETCH 0
|
|
|
|
`define EXECUTE 1
|
|
|
|
`define WRITEBACK 2
|
2014-02-20 12:37:31 +00:00
|
|
|
`define WAIT_KEY 3
|
|
|
|
reg [2:0] c/* verilator public */;
|
2014-01-03 23:35:11 +00:00
|
|
|
|
|
|
|
// Instruction register
|
2014-02-20 12:37:31 +00:00
|
|
|
reg [15:0] ins/* verilator public */;
|
2014-01-04 12:54:08 +00:00
|
|
|
wire [3:0] family = ins[15:12];
|
2014-01-03 23:35:11 +00:00
|
|
|
|
|
|
|
// ALU operation
|
2014-01-04 12:54:08 +00:00
|
|
|
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];
|
2014-01-03 23:35:11 +00:00
|
|
|
|
|
|
|
// Jump register
|
2014-01-04 12:54:08 +00:00
|
|
|
reg[11:0] jmp;
|
2014-01-03 23:35:11 +00:00
|
|
|
// Should jump on FETCH?
|
|
|
|
reg should_jmp;
|
|
|
|
|
2014-01-04 12:54:08 +00:00
|
|
|
always @(posedge cpu_clock)
|
2014-01-03 23:35:11 +00:00
|
|
|
begin
|
|
|
|
if (!reset) begin
|
|
|
|
i <= 0;
|
|
|
|
pc <= 0;
|
|
|
|
sp <= 0;
|
|
|
|
should_jmp <= 0;
|
2014-01-04 12:54:08 +00:00
|
|
|
c <= 0;
|
2014-02-20 12:37:31 +00:00
|
|
|
dt <= 0;
|
|
|
|
dt <= 0;
|
2014-01-04 12:54:08 +00:00
|
|
|
|
|
|
|
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;
|
2014-02-20 12:37:31 +00:00
|
|
|
|
2014-01-03 23:35:11 +00:00
|
|
|
end else begin
|
|
|
|
case(c)
|
|
|
|
`FETCH: begin
|
2014-01-04 12:54:08 +00:00
|
|
|
ins <= {ram[pc], ram[pc + 1]};
|
|
|
|
c <= 1;
|
2014-02-20 12:37:31 +00:00
|
|
|
|
|
|
|
if (hertz_clock == 1) begin
|
|
|
|
if (dt > 0)
|
|
|
|
dt <= dt - 1;
|
|
|
|
if (st > 0)
|
|
|
|
st <= st - 1;
|
|
|
|
end
|
2014-01-03 23:35:11 +00:00
|
|
|
end
|
2014-01-04 12:54:08 +00:00
|
|
|
`EXECUTE: begin
|
2014-02-20 12:37:31 +00:00
|
|
|
// $display("TRACE %h @ %h", ins, pc);
|
2014-01-03 23:35:11 +00:00
|
|
|
case (family)
|
2014-02-20 12:37:31 +00:00
|
|
|
// 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
|
2014-01-03 23:35:11 +00:00
|
|
|
// Jump to address
|
|
|
|
1: begin
|
|
|
|
should_jmp <= 1;
|
|
|
|
jmp <= nnn;
|
|
|
|
end
|
|
|
|
// Call address
|
|
|
|
2: begin
|
|
|
|
should_jmp <= 1;
|
2014-01-04 12:54:08 +00:00
|
|
|
stack[sp] <= pc;
|
2014-01-03 23:35:11 +00:00
|
|
|
sp <= sp + 1;
|
|
|
|
jmp <= nnn;
|
|
|
|
end
|
|
|
|
// Skip next if Vx == kk (3xkk)
|
|
|
|
3: begin
|
2014-01-04 12:54:08 +00:00
|
|
|
if (vr[x] == kk) begin
|
2014-01-03 23:35:11 +00:00
|
|
|
should_jmp <= 1;
|
|
|
|
jmp <= pc + 4;
|
|
|
|
end
|
|
|
|
end
|
|
|
|
// Skip next if Vx != kk (4xkk)
|
|
|
|
4: begin
|
2014-01-04 12:54:08 +00:00
|
|
|
if (vr[x] != kk) begin
|
2014-01-03 23:35:11 +00:00
|
|
|
should_jmp <= 1;
|
|
|
|
jmp <= pc + 4;
|
|
|
|
end
|
|
|
|
end
|
|
|
|
// Skip next if Vx == Vy (5xy0)
|
|
|
|
5: begin
|
2014-01-04 12:54:08 +00:00
|
|
|
if (vr[x] == vr[y]) begin
|
2014-01-03 23:35:11 +00:00
|
|
|
should_jmp <= 1;
|
|
|
|
jmp <= pc + 4;
|
|
|
|
end
|
|
|
|
end
|
|
|
|
// Vx <= kk
|
2014-01-04 12:54:08 +00:00
|
|
|
6: vr[x] <= kk;
|
2014-01-03 23:35:11 +00:00
|
|
|
// Vx += kk
|
2014-01-04 12:54:08 +00:00
|
|
|
7: vr[x] <= vr[x] + kk;
|
2014-02-20 12:37:31 +00:00
|
|
|
// These are implemented in the ALU
|
|
|
|
8: begin end
|
2014-01-03 23:35:11 +00:00
|
|
|
// Skip if Vx != Vy (9xy0)
|
|
|
|
9: begin
|
2014-01-04 12:54:08 +00:00
|
|
|
if (vr[x] != vr[y]) begin
|
2014-01-03 23:35:11 +00:00
|
|
|
should_jmp <= 1;
|
|
|
|
jmp <= pc + 4;
|
|
|
|
end
|
|
|
|
end
|
|
|
|
// Set I to nnn
|
2014-01-04 12:54:08 +00:00
|
|
|
10: i <= {4'h0, nnn};
|
2014-01-03 23:35:11 +00:00
|
|
|
// Jump to nnn + V0
|
|
|
|
11: begin
|
|
|
|
should_jmp <= 1;
|
2014-01-04 12:54:08 +00:00
|
|
|
jmp <= nnn + {4'h0, vr[0]};
|
2014-01-03 23:35:11 +00:00
|
|
|
end
|
|
|
|
// Set Vx to random and kk
|
|
|
|
// TODO: undo fair dice roll
|
2014-02-20 12:37:31 +00:00
|
|
|
12: begin vr[x] <= 42 & kk; $display("STUB: random"); end
|
2014-01-03 23:35:11 +00:00
|
|
|
// TODO: draw sprites
|
|
|
|
13: begin end
|
|
|
|
// Key functions
|
|
|
|
14: begin
|
|
|
|
if (kk == 8'h9E) begin
|
2014-01-04 12:54:08 +00:00
|
|
|
if (keypad[x]) begin
|
2014-01-03 23:35:11 +00:00
|
|
|
should_jmp <= 1;
|
|
|
|
jmp <= pc + 4;
|
|
|
|
end
|
|
|
|
end else if (kk == 8'hA1) begin
|
2014-01-04 12:54:08 +00:00
|
|
|
if (~keypad[x]) begin
|
2014-01-03 23:35:11 +00:00
|
|
|
should_jmp <= 1;
|
|
|
|
jmp <= pc + 4;
|
|
|
|
end
|
2014-02-20 12:37:31 +00:00
|
|
|
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
|
2014-01-03 23:35:11 +00:00
|
|
|
end
|
|
|
|
endcase
|
2014-02-20 12:37:31 +00:00
|
|
|
if (family == 4'hF && kk == 8'h0A)
|
|
|
|
c <= `WAIT_KEY;
|
|
|
|
else
|
|
|
|
c <= `WRITEBACK;
|
2014-01-03 23:35:11 +00:00
|
|
|
end
|
|
|
|
`WRITEBACK: begin
|
|
|
|
if (should_jmp) begin
|
|
|
|
pc <= jmp;
|
|
|
|
end else begin
|
|
|
|
pc <= pc + 2;
|
|
|
|
end
|
|
|
|
should_jmp <= 0;
|
2014-01-04 12:54:08 +00:00
|
|
|
c <= 0;
|
|
|
|
end
|
2014-02-20 12:37:31 +00:00
|
|
|
`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
|
2014-01-04 12:54:08 +00:00
|
|
|
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
|
2014-01-04 13:07:57 +00:00
|
|
|
7: begin
|
|
|
|
vr[x] <= vr[y] - vr[x];
|
|
|
|
vr[15] <= {7'b0, vr[y] >= vr[x]};
|
|
|
|
end
|
|
|
|
14: begin
|
2014-01-04 12:54:08 +00:00
|
|
|
vr[15] <= {7'b0, vr[x][7]};
|
|
|
|
vr[x] <= vr[x] << 1;
|
2014-01-03 23:35:11 +00:00
|
|
|
end
|
2014-02-20 12:37:31 +00:00
|
|
|
default: $display("Unknown opcode: %h @ %h", ins, pc);
|
2014-01-03 23:35:11 +00:00
|
|
|
endcase
|
|
|
|
end
|
|
|
|
end
|
|
|
|
endmodule
|