Skip to content

Instantly share code, notes, and snippets.

@ScienceElectronicsFun
Created September 12, 2021 16:02
Show Gist options
  • Save ScienceElectronicsFun/63bb93deaf9c41d366794fe4c648e762 to your computer and use it in GitHub Desktop.
Save ScienceElectronicsFun/63bb93deaf9c41d366794fe4c648e762 to your computer and use it in GitHub Desktop.
Verilog CPU for WebFPGA
// @MAP_IO port_out_00[0] 06
// @MAP_IO port_out_00[1] 07
// @MAP_IO port_out_00[2] 08
// @MAP_IO port_out_00[3] 09
// @MAP_IO port_out_00[4] 10
// @MAP_IO port_out_00[5] 11
// @MAP_IO port_out_00[6] 12
// @MAP_IO port_out_00[7] 14
// @MAP_IO reset 18
// Code was provided by Brock J. LaMeres
// See his book "Introduction to Logic Curcuits & Logic Design with Verilog" (2nd edition)
// for more detailed discussions
//
// It was modified for the WebFPGA and pared down for simplicity
// Only 3 instructions LDA, STA, and BRA to demonstrate simple program
// Only a single output port to display result
module fpga_top
(input wire WF_CLK,
input wire reset,
output wire [7:0] port_out_00
);
//-- Signal Declarations
wire [7:0] memory_data_in;
wire [7:0] memory_data_out;
wire [7:0] memory_address;
wire memory_write;
//-- Sub-System Instantiations
cpu CPU_1 (.WF_CLK (WF_CLK),
.reset (reset),
.to_memory (memory_data_in),
.from_memory (memory_data_out),
.address (memory_address),
.write (memory_write)
);
memory MEMORY_1
(.WF_CLK (WF_CLK),
.reset (reset),
.address (memory_address),
.write (memory_write),
.data_in (memory_data_in),
.data_out (memory_data_out),
.port_out_00 (port_out_00)
);
endmodule
module cpu
(input wire WF_CLK,
input wire reset,
output wire [7:0] to_memory,
input wire [7:0] from_memory,
output wire [7:0] address,
output wire write);
//-- Signal Declarations
wire IR_Load;
wire [7:0] IR;
wire MAR_Load;
wire PC_Load;
wire PC_Inc;
wire A_Load;
wire B_Load;
wire [2:0] ALU_Sel;
wire [3:0] CCR_Result;
wire CCR_Load;
wire [1:0] Bus1_Sel;
wire [1:0] Bus2_Sel;
//-- Sub-system Instantiation
control_unit CU_1 (.WF_CLK (WF_CLK),
.reset (reset),
.write (write),
.IR_Load (IR_Load),
.IR (IR),
.MAR_Load (MAR_Load),
.PC_Load (PC_Load),
.PC_Inc (PC_Inc),
.A_Load (A_Load),
.B_Load (B_Load),
.ALU_Sel (ALU_Sel),
.CCR_Result (CCR_Result),
.CCR_Load (CCR_Load),
.Bus1_Sel (Bus1_Sel),
.Bus2_Sel (Bus2_Sel)
);
data_path DP_1 (.WF_CLK (WF_CLK),
.reset (reset),
.from_memory (from_memory),
.to_memory (to_memory),
.address (address),
.IR_Load (IR_Load),
.IR (IR),
.MAR_Load (MAR_Load),
.PC_Load (PC_Load),
.PC_Inc (PC_Inc),
.A_Load (A_Load),
.B_Load (B_Load),
.ALU_Sel (ALU_Sel),
.CCR_Result (CCR_Result),
.CCR_Load (CCR_Load),
.Bus1_Sel (Bus1_Sel),
.Bus2_Sel (Bus2_Sel)
);
endmodule
module memory
(input wire WF_CLK,
input wire reset,
input wire [7:0] address,
input wire write,
input wire [7:0] data_in,
output reg [7:0] data_out,
output reg [7:0] port_out_00
);
//-- Signal Declarations
wire [7:0] rom_data_out;
wire [7:0] rw_data_out;
//-- Memory Sub-System Instantiations
rom_128x8_sync U1
(.WF_CLK(WF_CLK),
.address(address),
.data_out(rom_data_out)
);
rw_96x8_sync U2
(.WF_CLK(WF_CLK),
.data_in(data_in),
.write(write),
.address(address),
.data_out(rw_data_out)
);
//----------------------------------------------------------------------
//-- OUTPUT Ports
//----------------------------------------------------------------------
//-- port_out_00 (address E0)
always @ (posedge WF_CLK or negedge reset)
begin
if (!reset)
port_out_00 <= 8'h00;
else
if ((address == 8'hE0) && (write))
port_out_00 <= data_in;
end
//----------------------------------------------------------------------
//-- MUX for data out
//----------------------------------------------------------------------
always @ (address, rom_data_out, rw_data_out)
begin: MUX1
if ( (address >= 0) && (address <= 8'h7F) )
data_out = rom_data_out;
else if ( (address >= 8'h80) && (address <= 8'hDF) )
data_out = rw_data_out;
end
endmodule
module alu
(input wire [7:0] A,
input wire [7:0] B,
input wire [2:0] ALU_Sel,
output reg [7:0] Result,
output reg [3:0] NZVC);
always @ (A, B, ALU_Sel)
begin
case (ALU_Sel)
3'b000 : begin //-- Addition
//-- Sum and Carry Flag
{NZVC[0], Result} = A + B;
//-- Negative Flag
NZVC[3] = Result[7];
//-- Zero Flag
if (Result == 0)
NZVC[2] = 1;
else
NZVC[2] = 0;
//-- Two's Comp Overflow Flag
if ( ((A[7]==0) && (B[7]==0) && (Result[7] == 1)) || ((A[7]==1) && (B[7]==1) && (Result[7] == 0)) )
NZVC[1] = 1;
else
NZVC[1] = 0;
end
//-- other ALU operations go here...
default : begin
Result = 8'hXX;
NZVC = 4'hX;
end
endcase
end
endmodule
module control_unit
(input wire WF_CLK,
input wire reset,
output reg write,
output reg IR_Load,
input wire [7:0] IR,
output reg MAR_Load,
output reg PC_Load,
output reg PC_Inc,
output reg A_Load,
output reg B_Load,
output reg [2:0] ALU_Sel,
input wire [3:0] CCR_Result,
output reg CCR_Load,
output reg [1:0] Bus1_Sel,
output reg [1:0] Bus2_Sel);
//-- Parameters for Instruction Pmenomics
parameter LDA_IMM = 8'h86; //-- Load Register A with Immediate Addressing
parameter STA_DIR = 8'h96; //-- Store Register A to memory (RAM or IO)
parameter BRA = 8'h20; //-- Branch Always
reg [7:0] current_state, next_state;
parameter S_FETCH_0 = 0, //-- Opcode fetch states
S_FETCH_1 = 1,
S_FETCH_2 = 2,
S_DECODE_3 = 3, //-- Opcode decode state
S_LDA_IMM_4 = 4, //-- Load A (Immediate) states
S_LDA_IMM_5 = 5,
S_LDA_IMM_6 = 6,
S_LDA_DIR_4 = 7, //-- Load A (Direct) states
S_LDA_DIR_5 = 8,
S_LDA_DIR_6 = 9,
S_LDA_DIR_7 = 10,
S_LDA_DIR_8 = 11,
S_STA_DIR_4 = 12, //-- Store A (Direct) States
S_STA_DIR_5 = 13,
S_STA_DIR_6 = 14,
S_STA_DIR_7 = 15,
S_LDB_IMM_4 = 16, //-- Load B (Immediate) states
S_LDB_IMM_5 = 17,
S_LDB_IMM_6 = 18,
S_LDB_DIR_4 = 19, //-- Load B (Direct) states
S_LDB_DIR_5 = 20,
S_LDB_DIR_6 = 21,
S_LDB_DIR_7 = 22,
S_LDB_DIR_8 = 23,
S_STB_DIR_4 = 24, //-- Store B (Direct) States
S_STB_DIR_5 = 25,
S_STB_DIR_6 = 26,
S_STB_DIR_7 = 27,
S_BRA_4 = 28, //-- Branch Always States
S_BRA_5 = 29,
S_BRA_6 = 30,
S_BEQ_4 = 31, //-- Branch if Equal States
S_BEQ_5 = 32,
S_BEQ_6 = 33,
S_BEQ_7 = 34,
S_ADD_AB_4 = 35; //-- Addition States
//--------------------------------------------------------
//-- STATE MEMORY
always @ (posedge WF_CLK or negedge reset)
begin: STATE_MEMORY
if (!reset)
current_state <= S_FETCH_0;
else
current_state <= next_state;
end
//--------------------------------------------------------
//-- NEXT STATE LOGIC
always @ (current_state, IR, CCR_Result)
begin: NEXT_STATE_LOGIC
case (current_state)
S_FETCH_0 : next_state = S_FETCH_1; //-- Path for FETCH instruction
S_FETCH_1 : next_state = S_FETCH_2;
S_FETCH_2 : next_state = S_DECODE_3;
S_DECODE_3 : if (IR == LDA_IMM) next_state = S_LDA_IMM_4; //-- Register A Instructions
else if (IR == STA_DIR) next_state = S_STA_DIR_4;
else if (IR == BRA) next_state = S_BRA_4; //-- Branch Always
else next_state = S_FETCH_0;
S_LDA_IMM_4 : next_state = S_LDA_IMM_5; //-- Path for LDA_IMM instruction
S_LDA_IMM_5 : next_state = S_LDA_IMM_6;
S_LDA_IMM_6 : next_state = S_FETCH_0;
S_STA_DIR_4 : next_state = S_STA_DIR_5; //-- Path for STA_DIR instruction
S_STA_DIR_5 : next_state = S_STA_DIR_6;
S_STA_DIR_6 : next_state = S_STA_DIR_7;
S_STA_DIR_7 : next_state = S_FETCH_0;
S_BRA_4 : next_state = S_BRA_5; //-- Path for BRA instruction
S_BRA_5 : next_state = S_BRA_6;
S_BRA_6 : next_state = S_FETCH_0;
S_ADD_AB_4 : next_state = S_FETCH_0; //-- Path for BRA instruction
default : next_state = S_FETCH_0;
endcase
end
//--------------------------------------------------------
//-- OUTPUT LOGIC
always @ (current_state)
begin: OUTPUT_LOGIC
case (current_state)
S_FETCH_0 : begin //-- Put PC onto MAR to provide address of Opcode
IR_Load = 0;
MAR_Load = 1;
PC_Load = 0;
PC_Inc = 0;
A_Load = 0;
B_Load = 0;
ALU_Sel = 3'b000;
CCR_Load = 0;
Bus1_Sel = 2'b00; //-- "00"=PC, "01"=A, "10"=B
Bus2_Sel = 2'b01; //-- "00"=ALU, "01"=Bus1, "10"=from_memory
write = 0;
end
S_FETCH_1 : begin //-- Increment PC, Opcode will be available next state
IR_Load = 0;
MAR_Load = 0;
PC_Load = 0;
PC_Inc = 1;
A_Load = 0;
B_Load = 0;
ALU_Sel = 3'b000;
CCR_Load = 0;
Bus1_Sel = 2'b00; //-- "00"=PC, "01"=A, "10"=B
Bus2_Sel = 2'b00; //-- "00"=ALU, "01"=Bus1, "10"=from_memory
write = 0;
end
S_FETCH_2 : begin //-- Put Opcode into IR
IR_Load = 1;
MAR_Load = 0;
PC_Load = 0;
PC_Inc = 0;
A_Load = 0;
B_Load = 0;
ALU_Sel = 3'b000;
CCR_Load = 0;
Bus1_Sel = 2'b00; //-- "00"=PC, "01"=A, "10"=B
Bus2_Sel = 2'b10; //-- "00"=ALU, "01"=Bus1, "10"=from_memory
write = 0;
end
S_DECODE_3 : begin //-- No outputs, machine is decoding IR to decide which state to go to next
IR_Load = 0;
MAR_Load = 0;
PC_Load = 0;
PC_Inc = 0;
A_Load = 0;
B_Load = 0;
ALU_Sel = 3'b000;
CCR_Load = 0;
Bus1_Sel = 2'b00; //-- "00"=PC, "01"=A, "10"=B
Bus2_Sel = 2'b00; //-- "00"=ALU, "01"=Bus1, "10"=from_memory
write = 0;
end
//--------------------------------------------------------------------------------------------------
//-- LDA_IMM
//--------------------------------------------------------------------------------------------------
S_LDA_IMM_4 : begin //-- Put PC into MAR to provide address of Operand
IR_Load = 0;
MAR_Load = 1;
PC_Load = 0;
PC_Inc = 0;
A_Load = 0;
B_Load = 0;
ALU_Sel = 3'b000;
CCR_Load = 0;
Bus1_Sel = 2'b00; //-- "00"=PC, "01"=A, "10"=B
Bus2_Sel = 2'b01; //-- "00"=ALU, "01"=Bus1, "10"=from_memory
write = 0;
end
S_LDA_IMM_5 : begin //-- Increment PC, Operand will be available next state
IR_Load = 0;
MAR_Load = 0;
PC_Load = 0;
PC_Inc = 1;
A_Load = 0;
B_Load = 0;
ALU_Sel = 3'b000;
CCR_Load = 0;
Bus1_Sel = 2'b00; //-- "00"=PC, "01"=A, "10"=B
Bus2_Sel = 2'b00; //-- "00"=ALU, "01"=Bus1, "10"=from_memory
write = 0;
end
S_LDA_IMM_6 : begin //-- Operand is available, latch into A
IR_Load = 0;
MAR_Load = 0;
PC_Load = 0;
PC_Inc = 0;
A_Load = 1;
B_Load = 0;
ALU_Sel = 3'b000;
CCR_Load = 0;
Bus1_Sel = 2'b00; //-- "00"=PC, "01"=A, "10"=B
Bus2_Sel = 2'b10; //-- "00"=ALU, "01"=Bus1, "10"=from_memory
write = 0;
end
//--------------------------------------------------------------------------------------------------
//-- STA_DIR
//--------------------------------------------------------------------------------------------------
S_STA_DIR_4 : begin //-- Put PC onto MAR to provide address of Operand
IR_Load = 0;
MAR_Load = 1;
PC_Load = 0;
PC_Inc = 0;
A_Load = 0;
B_Load = 0;
ALU_Sel = 3'b000;
CCR_Load = 0;
Bus1_Sel = 2'b00; //-- "00"=PC, "01"=A, "10"=B
Bus2_Sel = 2'b01; //-- "00"=ALU, "01"=Bus1, "10"=from_memory
write = 0;
end
S_STA_DIR_5 : begin //-- Prepare to receive Operand from memory, increment PC
IR_Load = 0;
MAR_Load = 0;
PC_Load = 0;
PC_Inc = 1;
A_Load = 0;
B_Load = 0;
ALU_Sel = 3'b000;
CCR_Load = 0;
Bus1_Sel = 2'b00; //-- "00"=PC, "01"=A, "10"=B
Bus2_Sel = 2'b10; //-- "00"=ALU, "01"=Bus1, "10"=from_memory
write = 0;
end
S_STA_DIR_6 : begin //-- Put Operand into MAR (Leave Bus2=from_memory)
IR_Load = 0;
MAR_Load = 1;
PC_Load = 0;
PC_Inc = 0;
A_Load = 0;
B_Load = 0;
ALU_Sel = 3'b000;
CCR_Load = 0;
Bus1_Sel = 2'b00; //-- "00"=PC, "01"=A, "10"=B
Bus2_Sel = 2'b10; //-- "00"=ALU, "01"=Bus1, "10"=from_memory
write = 0;
end
S_STA_DIR_7 : begin //-- Put A onto Bus2, which is connected to "to_memory", assert write
IR_Load = 0;
MAR_Load = 0;
PC_Load = 0;
PC_Inc = 0;
A_Load = 0;
B_Load = 0;
ALU_Sel = 3'b000;
CCR_Load = 0;
Bus1_Sel = 2'b01; //-- "00"=PC, "01"=A, "10"=B
Bus2_Sel = 2'b10; //-- "00"=ALU, "01"=Bus1, "10"=from_memory
write = 1;
end
//--------------------------------------------------------------------------------------------------
//-- BRA
//--------------------------------------------------------------------------------------------------
S_BRA_4 : begin // -- Put PC onto MAR to provide address of Operand
IR_Load = 0;
MAR_Load = 1;
PC_Load = 0;
PC_Inc = 0;
A_Load = 0;
B_Load = 0;
ALU_Sel = 3'b000;
CCR_Load = 0;
Bus1_Sel = 2'b00; //-- "00"=PC, "01"=A, "10"=B
Bus2_Sel = 2'b01; //-- "00"=ALU, "01"=Bus1, "10"=from_memory
write = 0;
end
S_BRA_5 : begin //-- Prepare to receive Operand from memory
IR_Load = 0;
MAR_Load = 0;
PC_Load = 0;
PC_Inc = 0;
A_Load = 0;
B_Load = 0;
ALU_Sel = 3'b000;
CCR_Load = 0;
Bus1_Sel = 2'b00; //-- "00"=PC, "01"=A, "10"=B
Bus2_Sel = 2'b10; //-- "00"=ALU, "01"=Bus1, "10"=from_memory
write = 0;
end
S_BRA_6 : begin //-- Put Operand into PC (Leave Bus2=from_memory)
IR_Load = 0;
MAR_Load = 0;
PC_Load = 1;
PC_Inc = 0;
A_Load = 0;
B_Load = 0;
ALU_Sel = 3'b000;
CCR_Load = 0;
Bus1_Sel = 2'b00; //-- "00"=PC, "01"=A, "10"=B
Bus2_Sel = 2'b10; //-- "00"=ALU, "01"=Bus1, "10"=from_memory
write = 0;
end
//--------------------------------------------------------------------------------------------------
//-- OTHERS
//--------------------------------------------------------------------------------------------------
default : begin
IR_Load = 0;
MAR_Load = 0;
PC_Load = 0;
PC_Inc = 0;
A_Load = 0;
B_Load = 0;
ALU_Sel = 3'b000;
CCR_Load = 0;
Bus1_Sel = 2'b00; //-- "00"=PC, "01"=A, "10"=B
Bus2_Sel = 2'b00; //-- "00"=ALU, "01"=Bus1, "10"=from_memory
write = 0;
end
endcase
end
endmodule
module data_path
(input wire WF_CLK,
input wire reset,
input wire [7:0] from_memory,
output reg [7:0] to_memory,
output reg [7:0] address,
input wire IR_Load,
output reg [7:0] IR,
input wire MAR_Load,
input wire PC_Load,
input wire PC_Inc,
input wire A_Load,
input wire B_Load,
input wire [2:0] ALU_Sel,
output reg [3:0] CCR_Result,
input wire CCR_Load,
input wire [1:0] Bus1_Sel,
input wire [1:0] Bus2_Sel);
//-- Signal Declarations
reg [7:0] Bus1, Bus2;
reg [7:0] MAR, PC;
reg [7:0] A, B;
wire [7:0] ALU_Result;
wire [3:0] NZVC;
//---------------------------------------------------
//-- Sub-System Instantiation
//---------------------------------------------------
alu ALU_1
(.A (A),
.B (B),
.ALU_Sel (ALU_Sel),
.Result (ALU_Result),
.NZVC (NZVC)
);
//---------------------------------------------------
//-- Continuous Signal Assignments, using procedural to keep signals type reg
//---------------------------------------------------
always @ (Bus1, MAR)
begin
to_memory = Bus1;
address = MAR;
end
//---------------------------------------------------
//-- Multiplexers
//---------------------------------------------------
always @ (Bus1_Sel, PC, A, B)
begin: MUX_BUS1
case (Bus1_Sel)
2'b00 : Bus1 = PC;
2'b01 : Bus1 = A;
2'b10 : Bus1 = B;
default : Bus1 = 8'hXX;
endcase
end
always @ (Bus2_Sel, ALU_Result, Bus1, from_memory)
begin: MUX_BUS2
case (Bus2_Sel)
2'b00 : Bus2 = ALU_Result;
2'b01 : Bus2 = Bus1;
2'b10 : Bus2 = from_memory;
default : Bus2 = 8'hXX;
endcase
end
//--------------------------------------------------
//-- Registers
//--------------------------------------------------
always @ (posedge WF_CLK or negedge reset)
begin: INSTRUCTION_REGISTER
if (!reset)
IR <= 8'h00;
else
if (IR_Load)
IR <= Bus2;
end
always @ (posedge WF_CLK or negedge reset)
begin: MEMORY_ADDRESS_REGISTER
if (!reset)
MAR <= 8'h00;
else
if (MAR_Load)
MAR <= Bus2;
end
always @ (posedge WF_CLK or negedge reset)
begin: PROGRAM_COUNTER
if (!reset)
PC <= 8'h00;
else
if (PC_Load)
PC <= Bus2;
else if (PC_Inc)
PC <= MAR + 1;
end
always @ (posedge WF_CLK or negedge reset)
begin: A_REGISTER
if (!reset)
A <= 8'h00;
else
if (A_Load)
A <= Bus2;
end
always @ (posedge WF_CLK or negedge reset)
begin: B_REGISTER
if (!reset)
B <= 8'h00;
else
if (B_Load)
B <= Bus2;
end
always @ (posedge WF_CLK or negedge reset)
begin: CONDITION_CODE_REGISTER
if (!reset)
CCR_Result <= 8'h00;
else
if (CCR_Load)
CCR_Result <= NZVC;
end
endmodule
module rw_96x8_sync
(input wire WF_CLK,
input wire [7:0] data_in,
input wire write,
input wire [7:0] address,
output reg [7:0] data_out);
//-- Signal Declarations
reg EN;
//-- Create the 96x8 R/W Array
reg[7:0] RW[8'h80:8'hDF];
//-- We need an enable signal so that data_out is only updated for valid addresses
always @ (address)
begin
if ( (address >= 8'h80) && (address <= 8'hDF) )
EN = 1'b1;
else
EN = 1'b0;
end
//-- Model syncronous behavior
always @ (posedge WF_CLK)
begin
if (write && EN)
RW[address] = data_in;
else if (!write && EN)
data_out = RW[address];
end
endmodule
//----------------------------------------------------------------------
//-- Note(s) : This ROM memory contains the program instructions
//--
//-- Address Description
//-- ----------------------------------
//-- (x00)
//-- : Read Only Memory
//-- (x7F) (128x8-bit)
//-- ----------------------------------
//--
//----------------------------------------------------------------------
module rom_128x8_sync
(input wire WF_CLK,
input wire [7:0] address,
output reg [7:0] data_out);
//-- Parameters for Instruction Pmenomics
parameter LDA_IMM = 8'h86; //-- Load Register A with Immediate Addressing
parameter STA_DIR = 8'h96; //-- Store Register A to memory (RAM or IO)
parameter BRA = 8'h20; //-- Branch Always
//-- Signal Declarations
reg EN;
//-- Create 128x8 Array
reg[7:0] ROM[0:8'h7F];
//-----------------------------------------------------------
//-- Fill ROM Array with Instructions
initial
begin
ROM[0] = LDA_IMM;
ROM[1] = 8'hAA;
ROM[2] = STA_DIR;
ROM[3] = 8'hE0;
ROM[4] = BRA;
ROM[5] = 8'h00;
end
//-------------------------------------------------------------------
//
//-- We need an enable signal so that data_out is only updated for valid addresses
always @ (address)
begin
if ( (address >= 0) && (address <= 8'h7F) )
EN = 1'b1;
else
EN = 1'b0;
end
//-- Model Syncronous Behavior
always @ (posedge WF_CLK)
begin
if (EN)
data_out = ROM[address];
end
endmodule
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment