commit ba0f56bc891eda7e5584172dd84c247f3ff6182f Author: Austin Morlan Date: Tue Oct 12 22:26:53 2021 -0700 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..0d5f075 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# 8bit FPGA + +An FPGA version of the 8-bit computer built by Ben Eater in his [series of videos](https://eater.net/8bit). + +[Here is a blog post about it.](https://austinmorlan.com/posts/8bit_breadboard_fpga/) + +![Demo](https://austinmorlan.com/posts/8bit_breadboard_fpga/media/output_7seg.gif) + diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..9033df5 --- /dev/null +++ b/build.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +CODE_DIR=$(pwd)/code +BUILD_DIR=$(pwd)/build + +COMMAND=$1 + +if [ "$1" == "build" ]; then + mkdir -p $BUILD_DIR + pushd $BUILD_DIR + + yosys -q -p 'synth_ice40 -top top -blif top.blif' $CODE_DIR/top.v + arachne-pnr -q -d 8k -P cm81 -o top.asc -p $CODE_DIR/pins.pcf top.blif + icepack top.asc top.bin + icetime -d lp8k -mtr top.rpt top.asc + + iverilog -g2005-sv -o testbench $CODE_DIR/cpu_tb.v $CODE_DIR/cpu.v + vvp testbench + popd +elif [ "$1" == "clean" ]; then + rm -r $BUILD_DIR +elif [ "$1" == "program" ]; then + tinyprog -p $BUILD_DIR/top.bin +elif [ "$1" == "sim" ]; then + gtkwave $BUILD_DIR/testbench.vcd +else + echo "Invalid arguments" +fi + diff --git a/code/7seg.v b/code/7seg.v new file mode 100644 index 0000000..520c116 --- /dev/null +++ b/code/7seg.v @@ -0,0 +1,21 @@ +module seven_seg( + input wire[3:0] bcd, + output wire[6:0] segments +); + +assign segments = + // ABCDEFG + (bcd == 0) ? 7'b1111110 : + (bcd == 1) ? 7'b0110000 : + (bcd == 2) ? 7'b1101101 : + (bcd == 3) ? 7'b1111001 : + (bcd == 4) ? 7'b0110011 : + (bcd == 5) ? 7'b1011011 : + (bcd == 6) ? 7'b1011111 : + (bcd == 7) ? 7'b1110000 : + (bcd == 8) ? 7'b1111111 : + (bcd == 9) ? 7'b1110011 : + 7'b0000000; + +endmodule + diff --git a/code/bin_to_bcd.v b/code/bin_to_bcd.v new file mode 100644 index 0000000..24186ff --- /dev/null +++ b/code/bin_to_bcd.v @@ -0,0 +1,26 @@ +module bin_to_bcd( + input wire[7:0] bin, + output reg[11:0] bcd); + +integer i; + +always @(bin) begin + bcd = 0; + + for (i = 0; i < 8; i = i+1) begin + if (bcd[3:0] > 4) + bcd[3:0] = bcd[3:0] + 3; + + if (bcd[7:4] > 4) + bcd[7:4] = bcd[7:4] + 3; + + if (bcd[11:8] > 4) + bcd[11:8] = bcd[11:8] + 3; + + // Concatenate acts as a shift + bcd = {bcd[10:0], bin[7-i]}; + end +end + +endmodule + diff --git a/code/cpu.v b/code/cpu.v new file mode 100644 index 0000000..52b3f8b --- /dev/null +++ b/code/cpu.v @@ -0,0 +1,392 @@ +module cpu( + input wire clk, + input wire reset, + output reg[7:0] out + ); + + +/////////////////////////////////////////////////////////////////////////////// +// Opcodes +/////////////////////////////////////////////////////////////////////////////// + +parameter OP_NOP = 4'b0000; +parameter OP_LDA = 4'b0001; +parameter OP_ADD = 4'b0010; +parameter OP_SUB = 4'b0011; +parameter OP_STA = 4'b0100; +parameter OP_LDI = 4'b0101; +parameter OP_JMP = 4'b0110; +parameter OP_JC = 4'b0111; +parameter OP_JZ = 4'b1000; +parameter OP_OUT = 4'b1110; +parameter OP_HLT = 4'b1111; + + +/////////////////////////////////////////////////////////////////////////////// +// Control Signals +/////////////////////////////////////////////////////////////////////////////// + +// Halt +reg ctrl_ht; +always @(negedge clk) begin + if (ir[7:4] == OP_HLT && stage == 2) + ctrl_ht <= 1; + else + ctrl_ht <= 0; +end + +// Memory Address Register In +reg ctrl_mi; +always @(negedge clk) begin + if (stage == 0) + ctrl_mi <= 1; + else if (ir[7:4] == OP_LDA && stage == 2) + ctrl_mi <= 1; + else if (ir[7:4] == OP_ADD && stage == 2) + ctrl_mi <= 1; + else if (ir[7:4] == OP_SUB && stage == 2) + ctrl_mi <= 1; + else if (ir[7:4] == OP_STA && stage == 2) + ctrl_mi <= 1; + else + ctrl_mi <= 0; +end + +// RAM In +reg ctrl_ri; +always @(negedge clk) begin + if (ir[7:4] == OP_STA && stage == 3) + ctrl_ri <= 1; + else + ctrl_ri <= 0; +end + +// RAM Out +reg ctrl_ro; +always @(negedge clk) begin + if (stage == 1) + ctrl_ro <= 1; + else if (ir[7:4] == OP_LDA && stage == 3) + ctrl_ro <= 1; + else if (ir[7:4] == OP_ADD && stage == 3) + ctrl_ro <= 1; + else if (ir[7:4] == OP_SUB && stage == 3) + ctrl_ro <= 1; + else + ctrl_ro <= 0; +end + +// Instruction Register Out +reg ctrl_io; +always @(negedge clk) begin + if (ir[7:4] == OP_LDA && stage == 2) + ctrl_io <= 1; + else if (ir[7:4] == OP_LDI && stage == 2) + ctrl_io <= 1; + else if (ir[7:4] == OP_ADD && stage == 2) + ctrl_io <= 1; + else if (ir[7:4] == OP_SUB && stage == 2) + ctrl_io <= 1; + else if (ir[7:4] == OP_STA && stage == 2) + ctrl_io <= 1; + else if (ir[7:4] == OP_JMP && stage == 2) + ctrl_io <= 1; + else if (ir[7:4] == OP_JC && stage == 2) + ctrl_io <= 1; + else if (ir[7:4] == OP_JZ && stage == 2) + ctrl_io <= 1; + else + ctrl_io <= 0; +end + +// Instruction Register In +reg ctrl_ii; +always @(negedge clk) begin + if (stage == 1) + ctrl_ii <= 1; + else + ctrl_ii <= 0; +end + +// A Register In +reg ctrl_ai; +always @(negedge clk) begin + if (ir[7:4] == OP_LDI && stage == 2) + ctrl_ai <= 1; + else if (ir[7:4] == OP_LDA && stage == 3) + ctrl_ai <= 1; + else if (ir[7:4] == OP_ADD && stage == 4) + ctrl_ai <= 1; + else if (ir[7:4] == OP_SUB && stage == 4) + ctrl_ai <= 1; + else + ctrl_ai <= 0; +end + +// A Register Out +reg ctrl_ao; +always @(negedge clk) begin + if (ir[7:4] == OP_STA && stage == 3) + ctrl_ao <= 1; + else if (ir[7:4] == OP_OUT && stage == 2) + ctrl_ao <= 1; + else + ctrl_ao <= 0; +end + +// Sum Out +reg ctrl_eo; +always @(negedge clk) begin + if (ir[7:4] == OP_ADD && stage == 4) + ctrl_eo <= 1; + else if (ir[7:4] == OP_SUB && stage == 4) + ctrl_eo <= 1; + else + ctrl_eo <= 0; +end + +// Subtract +reg ctrl_su; +always @(negedge clk) begin + if (ir[7:4] == OP_SUB && stage == 4) + ctrl_su <= 1; + else + ctrl_su <= 0; +end + +// B Register In +reg ctrl_bi; +always @(negedge clk) begin + if (ir[7:4] == OP_ADD && stage == 3) + ctrl_bi <= 1; + else if (ir[7:4] == OP_SUB && stage == 3) + ctrl_bi <= 1; + else + ctrl_bi <= 0; +end + +// Output Register In +reg ctrl_oi; +always @(negedge clk) begin + if (ir[7:4] == OP_OUT && stage == 2) + ctrl_oi <= 1; + else + ctrl_oi <= 0; +end + +// Counter Enable +reg ctrl_ce; +always @(negedge clk) begin + if (stage == 1) + ctrl_ce <= 1; + else + ctrl_ce <= 0; +end + +// Counter Out +reg ctrl_co; +always @(negedge clk) begin + // Always in Stage 0 + if (stage == 0) + ctrl_co <= 1; + else + ctrl_co <= 0; +end + +// Jump +reg ctrl_jp; +always @(negedge clk) begin + if (ir[7:4] == OP_JMP && stage == 2) + ctrl_jp <= 1; + else if (ir[7:4] == OP_JC && stage == 2 && flags[FLAG_C] == 1) + ctrl_jp <= 1; + else if (ir[7:4] == OP_JZ && stage == 2 && flags[FLAG_Z] == 1) + ctrl_jp <= 1; + else + ctrl_jp <= 0; +end + +// Flags Register In +reg ctrl_fi; +always @(negedge clk) begin + if (ir[7:4] == OP_ADD && stage == 4) + ctrl_fi <= 1; + else if (ir[7:4] == OP_SUB && stage == 4) + ctrl_fi <= 1; + else + ctrl_fi <= 0; +end + + +/////////////////////////////////////////////////////////////////////////////// +// Bus +/////////////////////////////////////////////////////////////////////////////// + +wire[7:0] bus; +assign bus = + ctrl_co ? pc : + ctrl_ro ? mem[mar] : + ctrl_io ? ir[3:0] : + ctrl_ao ? a_reg : + ctrl_eo ? alu : + 8'b0; + + +/////////////////////////////////////////////////////////////////////////////// +// Program Counter +/////////////////////////////////////////////////////////////////////////////// + +reg[3:0] pc; +always @(posedge clk or posedge reset) begin + if (reset) + pc <= 0; + else if (ctrl_ce) + pc <= pc + 1; + else if (ctrl_jp) + pc <= bus[3:0]; +end + + +/////////////////////////////////////////////////////////////////////////////// +// Instruction Step Counter +/////////////////////////////////////////////////////////////////////////////// + +reg[2:0] stage; +always @(posedge clk or posedge reset) begin + if (reset) + stage <= 0; + else if (stage == 5 || ctrl_jp) + stage <= 0; + else if (ctrl_ht || stage == 6) + // For a halt, put it into a stage it can never get out of + stage <= 6; + else + stage <= stage + 1; +end + + +/////////////////////////////////////////////////////////////////////////////// +// Memory Address Register +/////////////////////////////////////////////////////////////////////////////// + +reg[3:0] mar; +always @(posedge clk or posedge reset) begin + if (reset) + mar <= 0; + else if (ctrl_mi) + mar <= bus[3:0]; +end + + +/////////////////////////////////////////////////////////////////////////////// +// Memory +/////////////////////////////////////////////////////////////////////////////// + +reg[7:0] mem[16]; +always @(posedge clk) begin + if (ctrl_ri) + mem[mar] <= bus; +end + + +/////////////////////////////////////////////////////////////////////////////// +// Instruction Register +/////////////////////////////////////////////////////////////////////////////// + +reg[7:0] ir; +always @(posedge clk or posedge reset) begin + if (reset) + ir <= 0; + else if (ctrl_ii) + ir <= bus; +end + + +/////////////////////////////////////////////////////////////////////////////// +// ALU +/////////////////////////////////////////////////////////////////////////////// + +reg[7:0] a_reg; +reg[7:0] b_reg; +wire[7:0] b_reg_out; +wire[8:0] alu; +wire flag_z, flag_c; +always @(posedge clk or posedge reset) begin + if (reset) + a_reg <= 0; + else if (ctrl_ai) + a_reg <= bus; +end + +always @(posedge clk or posedge reset) begin + if (reset) + b_reg <= 0; + else if (ctrl_bi) + b_reg <= bus; +end + +// Zero flag is set if ALU is zero +assign flag_z = (alu[7:0] == 0) ? 1 : 0; + +// Use twos-complement for subtraction +assign b_reg_out = ctrl_su ? ~b_reg + 1 : b_reg; + +// Carry flag is set if there's an overflow into bit 8 of the ALU +assign flag_c = alu[8]; + +assign alu = a_reg + b_reg_out; + + +/////////////////////////////////////////////////////////////////////////////// +// Flags Register +/////////////////////////////////////////////////////////////////////////////// + +parameter FLAG_C = 1; +parameter FLAG_Z = 0; + +reg[1:0] flags; +always @(posedge clk or posedge reset) begin + if (reset) + flags <= 0; + else if (ctrl_fi) + flags <= {flag_c, flag_z}; +end + + +/////////////////////////////////////////////////////////////////////////////// +// Output Register +/////////////////////////////////////////////////////////////////////////////// + +always @(posedge clk or posedge reset) begin + if (reset) + out <= 0; + else if (ctrl_oi) + out <= bus; +end + + +/////////////////////////////////////////////////////////////////////////////// +// Program to Run +/////////////////////////////////////////////////////////////////////////////// + +initial begin + mem[0] = {OP_OUT, 4'b0}; + mem[1] = {OP_ADD, 4'hF}; + mem[2] = {OP_JC, 4'h4}; + mem[3] = {OP_JMP, 4'h0}; + mem[4] = {OP_SUB, 4'hF}; + mem[5] = {OP_OUT, 4'h0}; + mem[6] = {OP_JZ, 4'h0}; + mem[7] = {OP_JMP, 4'h4}; + mem[8] = {OP_NOP, 4'h0}; + mem[9] = {OP_NOP, 4'h0}; + mem[10] = {OP_NOP, 4'h0}; + mem[11] = {OP_NOP, 4'h0}; + mem[12] = {OP_NOP, 4'h0}; + mem[13] = {OP_NOP, 4'h0}; + mem[14] = {OP_NOP, 4'h0}; + mem[15] = {8'h01}; // DATA = 1 +end + +endmodule + diff --git a/code/pins.pcf b/code/pins.pcf new file mode 100644 index 0000000..fc506cc --- /dev/null +++ b/code/pins.pcf @@ -0,0 +1,94 @@ +############################################################################### +# +# TinyFPGA BX constraint file (.pcf) +# +############################################################################### +# +# Copyright (c) 2018, Luke Valenty +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# The views and conclusions contained in the software and documentation are those +# of the authors and should not be interpreted as representing official policies, +# either expressed or implied, of the project. +# +############################################################################### + +#### +# TinyFPGA BX information: https://github.com/tinyfpga/TinyFPGA-BX/ +#### + +# Left side of board +set_io --warn-no-port PIN_1 A2 +set_io --warn-no-port PIN_2 A1 +set_io --warn-no-port PIN_3 B1 +set_io --warn-no-port PIN_4 C2 +set_io --warn-no-port PIN_5 C1 +set_io --warn-no-port PIN_6 D2 +set_io --warn-no-port PIN_7 D1 +set_io --warn-no-port PIN_8 E2 +set_io --warn-no-port PIN_9 E1 +set_io --warn-no-port PIN_10 G2 +set_io --warn-no-port PIN_11 H1 +set_io --warn-no-port PIN_12 J1 +set_io --warn-no-port PIN_13 H2 + +# Right side of board +set_io --warn-no-port PIN_14 H9 +set_io --warn-no-port PIN_15 D9 +set_io --warn-no-port PIN_16 D8 +set_io --warn-no-port PIN_17 C9 +set_io --warn-no-port PIN_18 A9 +set_io --warn-no-port PIN_19 B8 +set_io --warn-no-port PIN_20 A8 +set_io --warn-no-port PIN_21 B7 +set_io --warn-no-port PIN_22 A7 +set_io --warn-no-port PIN_23 B6 +set_io --warn-no-port PIN_24 A6 + +# SPI flash interface on bottom of board +set_io --warn-no-port SPI_SS F7 +set_io --warn-no-port SPI_SCK G7 +set_io --warn-no-port SPI_IO0 G6 +set_io --warn-no-port SPI_IO1 H7 +set_io --warn-no-port SPI_IO2 H4 +set_io --warn-no-port SPI_IO3 J8 + +# General purpose pins on bottom of board +set_io --warn-no-port PIN_25 G1 +set_io --warn-no-port PIN_26 J3 +set_io --warn-no-port PIN_27 J4 +set_io --warn-no-port PIN_28 G9 +set_io --warn-no-port PIN_29 J9 +set_io --warn-no-port PIN_30 E8 +set_io --warn-no-port PIN_31 J2 + +# LED +set_io --warn-no-port LED B3 + +# USB +set_io --warn-no-port USBP B4 +set_io --warn-no-port USBN A4 +set_io --warn-no-port USBPU A3 + +# 16MHz clock +set_io --warn-no-port CLK B2 # input diff --git a/code/top.v b/code/top.v new file mode 100644 index 0000000..93c1068 --- /dev/null +++ b/code/top.v @@ -0,0 +1,67 @@ +`include "cpu.v" +`include "7seg.v" +`include "bin_to_bcd.v" + +module top( + input CLK, + input PIN_13, + output PIN_9, + output PIN_10, output PIN_11, + output PIN_12, output PIN_14, + output PIN_15, output PIN_16, + output PIN_17, output PIN_18, + output PIN_19, output PIN_20); + +reg[7:0] out; +reg[23:0] clk; +always @(posedge CLK) + clk <= clk + 1; + +cpu cpu0( + .clk(clk[15]), + .reset(PIN_13), + .out(out)); + +reg[3:0] cathode = 4'b1110; +reg[6:0] seg_ones; +reg[6:0] seg_tens; +reg[6:0] seg_hundreds; +wire[11:0] bcd; + +bin_to_bcd bin_to_bcd0(out, bcd); + +seven_seg seven_seg_ones( + .bcd(bcd[3:0]), + .segments(seg_ones)); + +seven_seg seven_seg_tens( + .bcd(bcd[7:4]), + .segments(seg_tens)); + +seven_seg seven_seg_hundreds( + .bcd(bcd[11:8]), + .segments(seg_hundreds)); + +always @(posedge clk[10]) + case (cathode) + 4'b1110: begin + cathode = 4'b1011; + {PIN_11, PIN_9, PIN_15, PIN_18, PIN_19, PIN_10, PIN_14} = seg_hundreds; + end + 4'b1011: begin + cathode = 4'b1101; + {PIN_11, PIN_9, PIN_15, PIN_18, PIN_19, PIN_10, PIN_14} = seg_tens; + end + 4'b1101: begin + cathode = 4'b1110; + {PIN_11, PIN_9, PIN_15, PIN_18, PIN_19, PIN_10, PIN_14} = seg_ones; + end + default: begin + cathode = 4'b1111; + end + endcase + +assign {PIN_20, PIN_17, PIN_16, PIN_12} = cathode; + +endmodule +