Final project for EELE-0651. Simple single Cccle processor in verlog.
The purpose of this project was to design and implement an 8-bit single cycle processor modelled with Verilog using Intel Quartus Prime Lite software and simulated using ModelSim software and finally implemented on an Alter DE10-Lite board. Unfortunately, due to COVID-19 and the current world climate, we were unable to have access to the lab and physically implement our processor on the FPGA.
A lot of the diagrams will look better in githubs light theme. Sorry in advance.
The single cycle processor uses a RISC-like instruction set where each instruction in the processor is 2-bytes. Four 1-byte general purpose registers are implemented: R0, R1, R2, and R3. In addition, a link register is also implemented to support subroutines. Three different instruction formats are used: A-Format, B-Format, and L-Format which are described further in the next section.
The instruction set provided was used when designing our single cycle processor, based on the format given we were able to then code the instruction set in verilog. Sixteen instructions are supported and implemented in three formats described below.
A-format is used for arithmetic and logical instructions. Instructions are two bytes, however the second byte is unused. The first byte holds the opcode to define the operation to be performed and holds the source and destination operand. There are 9 A-format instructions.
Mnemonic | Op-Code | Function |
---|---|---|
NOP | 0 | Nothing |
ADD | 1 | ![]() ![]() ![]() |
SUB | 2 | ![]() ![]() ![]() |
NAND | 3 | ![]() ![]() ![]() |
SHL | 4 | ![]() ![]() |
SHR | 5 | ![]() ![]() |
OUT | 6 | ![]() |
IN | 7 | ![]() |
MOV | 8 | ![]() |
Table 1: A-Format Instructions
Figure 1: A-Format Instructions
B-format is used for branch instructions. Instructions are two bytes, the first three bits of the first byte are ignored, followed by a 1-bit identifier for differentiating between brz and brn instructions then followed by the opcode. The second byte contains the effective address if there is a branch. There are five B-format instructions shown below:
Mnemonic | Op-Code | Function |
---|---|---|
BR | 9 | ![]() |
BR.Z | 10 | ![]() ![]() |
BR.N | 10 | ![]() ![]() |
BR.SUB | 11 | ![]() ![]() |
RETURN | 12 | ![]() |
Table 2: B-Format Instructions
Figure 2: B-Format Instructions
L-format is used for load and store instructions. Instructions are two bytes, the first byte has two unused bits followed by register operand ra, then the opcode. The second byte holds the effective address or an immediate value. There are three L-format instructions shown below:
Mnemonic | Op-Code | Function |
---|---|---|
LOAD | 13 | ![]() |
STORE | 14 | ![]() |
LOADIMM | 15 | ![]() |
Table 3: L-Format Instructions
Figure 2: L-Format Instructions
To build our processor, we used the provided instruction set to guide our design and select the main components needed for our design. Before coding our modules for each component, we needed to determine the data paths for each instruction format which are shown below.
Figure 4: ADD, SUB, NAND Data Path
Figure 5: SHL and SHR Data Path
Figure 9: BR, BRZ, BRN Data Path
Figure 15: Full Single Cycle Processor Diagram
The purpose of the Instruction Memory module is to, given an address, lookup its value in instruction memory and output its value. Instruction memory stores 1 byte per memory address. Because instructions for the single cycle processor are 2 bytes long, the instruction memory module looks up the memory address given and the memory address given plus 1. These two values are concatenated together for the output. A diagram of the instruction memory can be seen in figure 16.
Figure 16: Full Single Cycle Processor Diagram
The control module is responsible for setting certain flags on or off. These flags are used by various other modules. Depending on the OPcode given to the module, different flags will be raised. A truth table for the different OPcodes and which flags get raised can be seen in table 4.
Figure 17: Control Module Diagram
instruction[7:4] | branchControl | memtoReg | RegWrite | useEa | ALUOP | memRead | linkSrc | memWrite | |
---|---|---|---|---|---|---|---|---|---|
NOP | 0000 | 00 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
ADD | 0001 | 00 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
SUB | 0010 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
NAND | 0011 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
SHL | 0100 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
SHR | 0101 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
OUT | 0110 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
IN | 0111 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
MOV | 1000 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
BR | 1001 | 00 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
BR.C | 1010 | 01 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
BR.SUB | 1011 | 10 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
RETURN | 1100 | 11 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
LOAD | 1101 | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 0 |
STORE | 1110 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 |
LOADIMM | 1111 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
Table 4: Control Module Truth Table
The register module has two jobs; reading from registers and writing to registers. For reading, the module is given two 2-bit values to lookup. The module then outputs these values through Read Data 1 and Read Data 2. Some OPCodes make use of the second byte in the instruction (bits 15 - 8 of the instruction). To handle this, the second bytes and Read Data 2 are passed through a 2-1 MUX controlled by the useEA flag. When writing to registers, the register module uses WriteReg as the index for which register to write to and writeData as the value to write to. The module attempts to write each clock cycle. The module will not write unless the regWrite flag is raised.
Figure 18: Registers Modules Diagram
The link register is used to store the next PC address during subroutine branch operations, and can restore the PC value to the stored state after the subroutine has returned. It also sets the PC to the Ea value during BR, BR.Z, and BR.N operations.
Figure 19: Link Register Diagram
The ALU module will perform different operations on its inputs value1 and value2 depending on the ALUOP flag. Table 1,2 and 3 show the respective operations for each ALUOP. The ALU will raise the zero and negative flag in some cases. These cases can be seen in table 1.
Figure 20: ALU Module Diagram
The Data Memory module performs three operations; reading from data memory, writing to data memory and passing the ALU result to the register module. If the memWrite flag is raised, the module will write readData1 into data memory at the value of ALUResult. If the memRead flag is raised, it will read a value at ALUResult and send it to readData. If the memToReg flag is not raised, the ALU value is passed through the 2-1 mux and then to the register module.
Figure 22: Data Memory Module Diagram
For this project we were able to design and implement an 8-bit single cycle processor successfully. Given the provided instruction set we were able to design datapaths for each instruction type and then code each component in Verilog. We were able to simulate our CPU in ModelSim with a given instruction memory which gave us our desired output shown below.
Figure 23: CPU Waveform for Provided Instruction Memory in ModelSim
The expected output of the CPU module when given a steady clock cycle is 0x81, 0xC3, 0xE7, 0xFF, 0xE7, 0xC3, 0x81. These outputs will repeat indefinitely, as seen in figure 21. The CPU module outputted the expected value.
If the single cycle processor were to be improved, one of the major things would be to allow for shifting bits by more than 1. This can easily be done with the current A-Format structure. Secondly, the single cycle processor was only ever simulated. The next steps would be to attempt to run the code on a FPGA board. The project code would need to be modified in order to run on a FPGA board. This is mainly because of how instruction memory is initialized, as currently its contents are loaded from a file. For the single cycle processor to function on an FPGA board, instruction memory would need to be hard coded.