This is based on a university project of the EISE specialty at Polytech Sorbonne (2018-2019). The goal is to build a processor in VHDL capable of executing a subset of the MIPS instruction set. I tried to re-implement it using only ghdl and gtkwave for testing (and possibly expand it into a multicycle processor). It also serves as a basic demonstration of how to integrate ghdl & gtkwave into a smooth VHDL workflow without using any other IDEs or simulators; I'm programming this processor using only a text editor and a terminal.
If you're looking to work/expand on this project or just run it on your machine, here's how you can get started.
The dependencies of this project are ghdl and gtkwave. See here for instructions on how to install them.
[The architecture is incomplete. I'm still writing the docs and coding the IPs.]
Table of contents
The ALU (Arithmetic and Logic Unit) takes care of performing basic mathematical operations:
Based on the value of OP
, the operations it performs have as follows:
OP | O |
---|---|
00 | A + B |
01 | B |
10 | A - B |
11 | A |
The register bank contains the CPU's 16 32-bit registers.
The read operations are asynchronous; outputs A
and B
contain the values
found at the addresses RA
and RB
respectively. When RA
and/or RB
are
undefined, A
and/or B
output the value of the register at address 0
.
The write operations are synchronous, and are performed on the clock's rising
edge. On rising edge, if WE = 1
and RW
is not undefined, the value of the
W
bus is written onto the register at address RW
.
The RST
is asynchronous.
This is a simple 2v1 multiplexer with variable input/output length (its maximum length is 32 bits).
Here are the outputs based on COM
's value:
COM | Y |
---|---|
0 | A |
1 | B |
This is a simple one input, one output block that converts an N-bit input into a 32-bit output, while keeping the same sign (+/-).
Of course, here N <= 32
.
This is a data memory containing a storage space of 64 32-bit words. Its architecture is not that different from that of the register bank:
The read operation is asynchronous; DataOut
contains the value of the word at
address Addr
, or the word at address 0
if Addr
is undefined.
The write operation is synchronous, and is performed on the clock's rising
edge. On rising edge, if WE = 1
and Addr
is defined, then the 32-bit word
in DataIn
is stored at address Addr
in the memory.
This is the assembled Processing Unit. It contains an ALU, a register bank, two MUXes, a sign extension entity and a data memory.
This unit allows us to perform all the basic operations, given a data memory. Here is a top-level diagram of the Processing Unit entity:
This unit can perform the following operations:
- Move values from the data memory into registers
- Move values from registers into the data memory
- Add/subtract with registers and store the result in another register
- Add/subtract with registers and store the result in the data memory
- Add/subtract with a register and an immediate value (8 bits) and store the result in another register
- Add/subtract with a register and an immediate value (8 bits) and store the result in the data memory
The A
output of the register bank is connected to the A
input
of the ALU.
The B
output of the register bank is connected to the A
input of the first
MUX, and also to the DataIn
input of the data memory.
The W
bus is connected to the output of the second MUX.
The inputs CLK
, RST
, WE
, RW
, RA
and RB
are not connected and are
thus considered inputs to the PU entity.
The input of the sign extension is an 8-bit immediate input. It is converted
to a 32-bit vector in the output, while keeping the same sign; it is then
connected to the B
input of the first MUX.
The A
input of the first MUX is connected to the B
output of the register
bank. The B
input is connected to the output of the sign extension entity.
The Y
output is connected to the B
input of the ALU. The COM
input is not
connected and is thus considered an input to the PU entity.
The A
input of the ALU is connected to the A
output of the register bank.
The B
input is connected to the Y
output of the first MUX.
The O
output is connected to the A
input of the second MUX; its 6 least
significant bits are connected to the Addr
input of the data memory.
The OP
input and N
output are not connected and are thus considered
inputs/outputs to the PU entity.
The DataIn
input is connected to the B
output of the register bank. The
DataOut
output is connected to the B
input of the second MUX. The Addr
input is connected to the 6 least significant bits of the O
output
of the ALU.
The inputs CLK
, RST
and WE
are not connected and are thus considered
inputs to the PU entity.
The A
input is connected to the O
output of the ALU. The B
input is
connected to the DataOut
output of the data memory. The Y
output is
connected to the W
input of the register bank.
The COM
input is not connected and is thus considered an input to the PU
entity.
The instruction memory is similar to the data memory:
It contains a set of MIPS instructions — which, in this case, is hardcoded
into the memory — and, given an address (refered to as PC
in this case, as
in Program Counter), it outputs the instruction that is found at that
address.
The IMU (Instruction Management Unit) handles the processing of the instructions.
It contains the instruction memory, the
PC
register (that contains the address of the next instruction to be read),
a sign extension entity that can convert an address offset
coded on 24 bits to a 32-bit address. Finally, a MUX determines
if the instruction is a normal instruction or if it is a jump instruction:
- If
nPCsel = 0
, the instruction is a normal one, soPC
is simply incremented (PC = PC + 1
) - If
nPCsel = 1
, the instruction is a jump, soPC
is incremented by 1 and also by the 32-bit address that results from the 24-bit offset (PC = PC + 1 + SignExtension(Offset)
).