Build a 4-bit MicroProcessor: A Verilog HDL Project

Ibrahim Bin Mansur
5 min readSep 24, 2024

--

In the realm of digital logic design, creating a functional microprocessor from scratch is a challenging yet rewarding endeavor. This post explores a project that implements a 4-bit number crunching machine (or a simple 4-bit microprocessor) using Verilog HDL. We’ll dissect the various components, understand their roles, and see how they come together to form a working system.

Project Overview

The heart of this project is a custom Instruction Set Architecture (ISA) implemented through various digital logic components. The system is designed to perform basic arithmetic and logical operations, store data, and display results. Let’s break down the main components:

  1. Arithmetic Logic Unit (ALU)
  2. Data Registers and Control Logic
  3. 4-bit D-type Registers
  4. 4-bit Binary Full Adder
  5. 4-bit Binary Counter
  6. Quad 2-Data Selectors (Multiplexers)
  7. 2-Line to 4-Line Decoder
  8. Basic Logic Gates (AND, OR, NOT, XOR)
  9. Parallel Address, Parallel 8-bit I/O EEPROM

Deep Dive into Components

1. Arithmetic Logic Unit (ALU)

The ALU is the computational core of our system. It’s defined in the alu module (which isn't provided in the given files, but we can infer its existence from the main.v file). The ALU takes two 4-bit inputs and performs operations based on control signals.

2. Data Registers

The system uses several D-type registers to store data. These are implemented in the D_register module (also not provided, but referenced in main.v). These registers store the ALU inputs, outputs, and intermediate results.

3. 4-bit Binary Full Adder

The full adder is a crucial component for arithmetic operations. It’s implemented in the fourbit.v and fulladder.v files:

module fourbit(X,Y,Cin,Cout,Sum);
input [3:0] X,Y;
input Cin;
output [3:0] Sum;
output Cout;
wire [3:1] C;

fulladder stage0 (X[0],Y[0],Cin,C[1],Sum[0]);
fulladder stage1 (X[1],Y[1],C[1],C[2],Sum[1]);
fulladder stage2 (X[2],Y[2],C[2],C[3],Sum[2]);
fulladder stage3 (X[3],Y[3],C[3],Cout,Sum[3]);
endmodule
module fulladder(x,y,cin,cout,sum);
input x,y,cin;
output sum,cout;

assign sum = x^y^cin;
assign cout = (x&y)|(x&cin)|(y&cin);
endmodule

This implementation cascades four 1-bit full adders to create a 4-bit adder, demonstrating how larger components can be built from smaller ones.

4. 4-bit Binary Counter

The counter is used to keep track of the current instruction or memory address. It’s implemented in the counter module (referenced in main.v but not provided).

5. Multiplexers (Quad 2-Data Selectors)

Multiplexers are used to select between different data sources. The initialmux module is an example:

module initialmux(alu_out,custom_input,s,m_out);
input [3:0] alu_out;
input [2:0] custom_input;
input s;
output reg [3:0] m_out;
wire [3:0] w;

assign w[2:0] = custom_input;
assign w[3] = 0;

always @(*)
begin
if(s==0)
m_out = alu_out;
else
m_out = w;
end
endmodule

This multiplexer chooses between the ALU output and a custom input based on a select signal.

6. 2-Line to 4-Line Decoder

The decoder is used for instruction decoding or register selection. It’s implemented in the decoder module:

module decoder(a,b,O);
input a,b;
output reg [3:0] O;

always @(*)
begin
case({a,b})
2'b00 : O = 4'b0001;
2'b01 : O = 4'b0010;
2'b10 : O = 4'b0100;
2'b11 : O = 4'b1000;
default : O = 4'b0000;
endcase
end
endmodule

This decoder takes a 2-bit input and activates one of four output lines, useful for selecting registers or operations.

7. EEPROM

The EEPROM serves as the instruction memory for our system. It’s implemented in the eeprom module:

module eeprom
#(parameter DATA_WIDTH=8, parameter ADDR_WIDTH=8)
(
input [(ADDR_WIDTH-1):0] addr,
input clk,
output reg [(DATA_WIDTH-1):0] q
);
reg [DATA_WIDTH-1:0] rom[2**ADDR_WIDTH-1:0];

initial
begin
$readmemb("instructions.txt", rom);
end
always @ (negedge clk)
begin
q <= rom[addr];
end
endmodule

This module reads instructions from a file and outputs them based on the provided address.

8. Display

The system includes a display module to visualize the output:

module display(a,b,c);
input [3:0] a;
output [6:0] b,c;
wire [3:0] w1,w2,w3;

compare c1(a,w1);
cct ct(a,w2);
mux m(a,w2,w1[0],w3);
seg s0(w3,c);
seg s1(w1,b);
endmodule

This module converts the 4-bit output to 7-segment display format, likely for use with LED displays.

Putting It All Together

The main module in main.v ties all these components together:

module main(KEY,SW,HEX1,HEX0);
input [1:0] KEY;
input [1:0] SW;
wire [7:0] q;
wire [3:0] w1,w2,w3,w4,w5,w6,w7,w8;
wire R;
wire w9,w10;
output [6:0] HEX1,HEX0;

assign R = SW[0];
eeprom e1(w7,KEY[0],q);
initialmux m1(w4,q[2:0],q[3],w1);
decoder d1(q[5],q[4],w6);
D_register RA (KEY[0],R,w6[0],w1,w2);
D_register RB (KEY[0],R,w6[1],w1,w3);
D_register RO (KEY[0],R,w6[2],w2,w8);
display(w8,HEX1,HEX0);
alu a1(w2,w3,q[2],w4,w9);
operation_alu f1(KEY[0],w9,w10);
counter c1(R,KEY[0],q[7],q[6],w10,q[2:0],w7);

endmodule

This module orchestrates the operation of the entire system. It connects the EEPROM, ALU, registers, counter, and display components, creating a functional 4-bit number crunching machine.

Final result RTL(left)
Final result RTL(right)

Conclusion

This project demonstrates the power of Verilog HDL in creating complex digital systems from basic components. By understanding each module and how they interact, we gain insight into the workings of simple processors. This knowledge forms a solid foundation for more advanced digital design projects and helps bridge the gap between hardware description languages and actual computer architecture.

The modular nature of this design also showcases good engineering practices, allowing for easy debugging, testing, and potential future expansions. Whether you’re a student learning digital logic design or an experienced engineer looking to refresh your knowledge, projects like these provide valuable hands-on experience in the fascinating world of digital systems design.

References

--

--

No responses yet