diff --git a/README.md b/README.md index aa15ebb..d7c3021 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,271 @@ +# Verilog Simple RISC Processor + +> A modular, multi-cycle, accumulator-based RISC processor implemented in Verilog HDL for computer architecture education and digital system design. + +![Language](https://img.shields.io/badge/Language-Verilog-blue) +![Simulation](https://img.shields.io/badge/Simulator-Icarus%20Verilog-green) +![License](https://img.shields.io/badge/License-MIT-yellow) +![Status](https://img.shields.io/badge/Status-Stable-success) + +--- + +## Overview + +This repository contains a complete RTL implementation of a simple accumulator-based Reduced Instruction Set Computer (RISC) processor written in synthesizable Verilog HDL. + +The project is designed for education and experimentation. It demonstrates: + +- processor organization and RTL design; +- separation of datapath and control logic; +- multi-cycle instruction execution; +- finite-state-machine control; +- unified instruction and data memory; +- module-level and CPU integration verification; +- waveform-based debugging and architectural analysis. + +The design prioritizes readability, modularity, and ease of extension rather than performance. Each hardware component is implemented as an independent RTL module. + +--- + +## Key Features + +- 8-bit accumulator-based datapath +- 8-bit instructions with a 3-bit opcode and 5-bit operand +- 32-address unified program and data memory +- Eight native instructions +- Multi-cycle finite-state-machine controller +- Modular and synthesizable Verilog RTL +- Automated regression and CPU integration tests +- VCD waveform generation for GTKWave +- Vivado RTL elaboration and synthesis support + +--- + +## Processor Specification + +| Feature | Description | +|---|---| +| Architecture | Accumulator-based RISC | +| Datapath width | 8 bits | +| Instruction width | 8 bits | +| Opcode width | 3 bits | +| Operand width | 5 bits | +| Address space | 32 memory locations | +| Memory organization | Unified program/data memory | +| Controller | Finite-state machine | +| Execution model | Multi-cycle | +| RTL language | Verilog HDL | +| Primary simulator | Icarus Verilog | + +--- + +## Architecture + +The processor consists of a compact datapath and a finite-state-machine controller. Because the architecture is accumulator-based, arithmetic and logical operations use a single general-purpose register: the accumulator (`AC`). + +```mermaid +flowchart LR + PC[Program Counter] + IR[Instruction Register] + MUX[Address Multiplexer] + MEM[Unified Memory] + BUS[8-bit Data Bus] + AC[Accumulator] + ALU[Arithmetic Logic Unit] + CTRL[Controller FSM] + + PC --> MUX + IR --> MUX + MUX --> MEM + MEM --> BUS + BUS --> IR + BUS --> ALU + AC --> ALU + ALU --> AC + + CTRL --> PC + CTRL --> IR + CTRL --> MEM + CTRL --> AC + CTRL --> MUX +``` + +The controller coordinates memory access, register updates, ALU operations, and program-counter changes over multiple clock cycles. + +### Vivado Elaborated RTL Schematic + +

+ Vivado elaborated RTL schematic +

+ +

+ + Elaborated RTL schematic showing the program counter, instruction register, + address multiplexer, accumulator, ALU, unified memory, and FSM controller. + +

+ +
+Detailed synthesized netlist + +

+ Detailed synthesized processor netlist +

+ +
+ +--- + +## Instruction Set Architecture + +### Instruction Format + +Each instruction occupies one byte: + +```text ++-------------+----------------+ +| Opcode (3) | Operand (5) | ++-------------+----------------+ + 7 5 4 0 +``` + +- **Opcode** identifies the operation. +- **Operand** represents a memory address or jump destination. + +### Instruction Set + +| Opcode | Mnemonic | Operation | +|---|---|---| +| `000` | `HLT` | Stop processor execution | +| `001` | `SKZ` | Skip the next instruction when `AC == 0` | +| `010` | `ADD addr` | `AC ← AC + MEM[addr]` | +| `011` | `AND addr` | `AC ← AC AND MEM[addr]` | +| `100` | `XOR addr` | `AC ← AC XOR MEM[addr]` | +| `101` | `LDA addr` | `AC ← MEM[addr]` | +| `110` | `STO addr` | `MEM[addr] ← AC` | +| `111` | `JMP addr` | `PC ← addr` | + +--- + +## Multi-Cycle Execution + +Instructions are executed over multiple clock cycles so that datapath resources can be reused. A typical instruction passes through some or all of the following stages: + +```text +Instruction Address + │ + ▼ +Instruction Fetch + │ + ▼ +Instruction Load + │ + ▼ +Decode / Idle + │ + ▼ +Operand Address + │ + ▼ +Operand Fetch + │ + ▼ +Execute + │ + ▼ +Write Back +``` + +The controller selects only the states required by the current instruction. + +### Controller States + +| State | Purpose | +|---|---| +| `INST_ADDR` | Select the instruction address | +| `INST_FETCH` | Read instruction memory | +| `INST_LOAD` | Load the instruction register | +| `IDLE` | Decode the current instruction | +| `OP_ADDR` | Select the operand address | +| `OP_FETCH` | Read the operand | +| `ALU_OP` | Execute an arithmetic, logical, or control operation | +| `STORE` | Write a result to memory | + +### Control Signals + +| Signal | Description | +|---|---| +| `rd` | Memory read enable | +| `wr` | Memory write enable | +| `ld_ir` | Load the instruction register | +| `ld_ac` | Load the accumulator | +| `ld_pc` | Load the program counter | +| `inc_pc` | Increment the program counter | +| `sel` | Select the memory address source | +| `data_e` | Enable data-bus output | + +--- + +## RTL Modules + +| Module | Responsibility | +|---|---| +| `CPU.v` | Top-level processor and module integration | +| `Controller.v` | FSM sequencing and control-signal generation | +| `ALU.v` | Arithmetic and logical operations | +| `Memory.v` | Unified instruction and data memory | +| `PC.v` | Program counter | +| `IR.v` | Instruction register | +| `AC.v` | Accumulator register | + +### `CPU.v` + +The top-level module connects the datapath and controller. It selects memory addresses, routes the shared data bus, forwards control signals, and coordinates complete instruction execution. + +### `Controller.v` + +The controller sequences instruction states and generates the signals required for memory access, register loading, ALU operation, and program-counter updates. + +### `ALU.v` + +The ALU supports: + +- addition; +- bitwise AND; +- bitwise XOR; +- accumulator loading. + +It also produces the zero condition used by `SKZ`. + +### `Memory.v` + +The unified memory stores both instructions and data. + +- 32 addressable locations +- 8-bit data width +- asynchronous read +- synchronous write + +### `PC.v` + +The program counter stores the address of the next instruction and supports: + +- increment; +- loading a jump destination; +- synchronous reset. + +### `IR.v` + +The instruction register holds the current instruction so that its opcode and operand remain stable during execution. + +### `AC.v` + +The accumulator is the processor's only general-purpose register. It can load an ALU result, retain its current value, or reset synchronously. + # Simple RISC Processor in Verilog A compact, accumulator-based RISC processor implemented in Verilog HDL. The design demonstrates the core principles of CPU organization, including instruction fetch, instruction decode, arithmetic and logic execution, memory access, program-counter control, and finite-state-machine-based control. @@ -123,6 +391,24 @@ HLT ; Stop execution ## Repository Structure ```text +Verilog-Simple-Risc-Processor/ +├── src/ +│ ├── CPU.v +│ ├── Controller.v +│ ├── ALU.v +│ ├── Memory.v +│ ├── PC.v +│ ├── IR.v +│ └── AC.v +├── testbench/ +│ ├── test_001/ +│ ├── test_002/ +│ ├── ... +│ └── test_010/ +├── docs/ +│ └── images/ +├── run_tests.py +├── LICENSE . ├── src/ │ ├── AC.v @@ -177,6 +463,321 @@ HLT ; Stop execution --- +## Verification + +The project uses two complementary verification levels: + +1. **Module-level verification** checks individual RTL components. +2. **CPU integration verification** checks complete program execution and interactions among processor components. + +Tests are compiled with **Icarus Verilog**, executed with `vvp`, and evaluated against expected output. + +```text +RTL Source + │ + ▼ +Compile with iverilog + │ + ▼ +Simulate with vvp + │ + ▼ +Compare Output + │ + ▼ +PASS / FAIL +``` + +### Test Coverage + +| Test | Description | +|---|---| +| `test_001` | Program-counter verification | +| `test_002` | Memory-module verification | +| `test_003` | Instruction-register verification | +| `test_004` | Accumulator verification | +| `test_005` | ALU arithmetic and logical operations | +| `test_006` | Controller FSM verification | +| `test_007` | Datapath signal verification | +| `test_008` | CPU instruction execution | +| `test_009` | CPU integration test | +| `test_010` | Waveform-oriented CPU integration | + +All eight instructions are exercised by the integration tests: + +| Instruction | Verified | +|---|:---:| +| `HLT` | ✓ | +| `SKZ` | ✓ | +| `ADD` | ✓ | +| `AND` | ✓ | +| `XOR` | ✓ | +| `LDA` | ✓ | +| `STO` | ✓ | +| `JMP` | ✓ | + +### Behavioral Simulation Waveform + +

+ Vivado behavioral simulation waveform +

+ +

+ + Multi-cycle instruction execution showing datapath activity, controller + sequencing, memory access, conditional skip, jump, and halt behavior. + +

+ +--- + +## Running the Project + +### Prerequisites + +Install: + +- Python 3 +- Icarus Verilog +- GTKWave for waveform inspection + +### Run All Tests + +```bash +python3 run_tests.py --src src --testbench testbench --sim icarus +``` + +### Run a Specific Test + +```bash +python3 run_tests.py \ + --src src \ + --testbench testbench \ + --sim icarus \ + --filter test_010 +``` + +Replace `test_010` with the required test name. + +### Expected Output + +```text +Running test_001 ... PASS +Running test_002 ... PASS +... +Running test_010 ... PASS + +Summary + +Passed : 10 +Failed : 0 +Skipped: 0 +``` + +--- + +## Waveform Debugging + +The waveform-oriented integration test produces a Value Change Dump (`.vcd`) file. + +Open it with GTKWave: + +```bash +gtkwave CPU_tb_wave.vcd +``` + +Useful signals include: + +- program counter; +- instruction register; +- accumulator; +- ALU output; +- memory address and data; +- controller state; +- internal data bus; +- zero flag; +- control signals. + +A recommended debugging cycle is: + +1. Run the relevant test. +2. Review the simulation output. +3. Inspect the generated waveform. +4. Locate incorrect datapath or controller behavior. +5. Update the RTL. +6. Run the full regression suite. + +--- + +### Vivado Synthesis + +### Synthesized Design + +

+ Vivado synthesized processor schematic +

+ +

+ + Synthesized processor schematic generated by Vivado, showing the + main processor modules and FPGA-specific clock and I/O resources. + +

+ +### Detailed Synthesized Netlist + +
+View detailed synthesized logic + +

+ Detailed Vivado synthesized processor netlist +

+ +

+ + Detailed gate-level view of the synthesized processor logic. + This diagram illustrates how the RTL design is transformed into + registers, multiplexers, combinational logic, and control circuitry. + +

+ +
+ +--- + +## Design Principles + +The implementation intentionally favors clarity over performance: + +- one hardware component per source file; +- explicit separation of datapath and control logic; +- a single accumulator instead of a register file; +- multi-cycle execution instead of pipelining; +- a compact instruction set; +- unified program and data memory; +- consistent signal naming; +- synthesizable Verilog constructs. + +These choices make the project suitable for introductory computer architecture courses, digital-design laboratories, and architectural experiments. + +--- + +## Current Limitations + +The following features are intentionally omitted: + +| Feature | Status | +|---|:---:| +| General-purpose register file | ✗ | +| Immediate instructions | ✗ | +| Pipeline | ✗ | +| Hazard detection | ✗ | +| Branch prediction | ✗ | +| Interrupt support | ✗ | +| Cache memory | ✗ | +| Separate instruction and data memory | ✗ | +| Memory-mapped I/O | ✗ | +| Exceptions | ✗ | + +--- + +## Future Work + +Possible extensions include: + +### Instruction Set + +- `SUB` and `OR` +- shift operations +- compare instructions +- immediate operands +- additional conditional branches + +### Datapath and Architecture + +- register file +- barrel shifter +- status register and additional flags +- Harvard memory organization +- five-stage pipeline +- forwarding and hazard detection +- pipeline flushing +- interrupt controller + +### Verification + +- functional coverage +- constrained-random testing +- SystemVerilog testbenches +- assertion-based verification +- continuous integration with GitHub Actions + +--- + +## Educational Outcomes + +The project provides practical experience with: + +- register-transfer-level design; +- modular hardware development; +- datapath organization; +- finite-state-machine control; +- instruction decoding and execution; +- ALU, memory, and program-counter design; +- multi-cycle processor control; +- hardware simulation and verification; +- waveform analysis. + +--- + +## Contributing + +Contributions are welcome, including new instructions, additional testbenches, documentation improvements, RTL optimizations, and bug fixes. + +1. Fork the repository. +2. Create a feature branch. +3. Commit your changes. +4. Confirm that all regression tests pass. +5. Open a pull request. + +--- + +## Project Status + +**Current version:** `1.1` + +The project is functionally stable and suitable for educational use. The current release includes the modular RTL implementation, multi-cycle controller, complete original instruction set, automated regression tests, CPU integration tests, waveform debugging, and project documentation. + +--- + +## References + +- M. Morris Mano -*Computer System Architecture* +- David A. Patterson and John L. Hennessy -*Computer Organization and Design* +- Stephen Brown and Zvonko Vranesic -*Fundamentals of Digital Logic with Verilog Design* +- IEEE Standard for the Verilog Hardware Description Language + +--- + +## Authors + +- **VinhTechiee** -https://github.com/VinhTechiee +- **ladonna-2511** -https://github.com/ladonna-2511 +- **lunaz27** -https://github.com/lunaz27 + +--- + +## Acknowledgements + +This project was developed as part of undergraduate coursework in digital logic and computer architecture. + +Special thanks to the instructors and teaching assistants whose lectures and laboratory exercises inspired this educational processor. ## Modules ### `PC.v` - Program Counter @@ -395,4 +996,8 @@ This processor is designed as an educational RTL project and intentionally remai ## License -No license file is currently included in this repository. Add a license before distributing, modifying, or reusing the code in public or commercial projects. +This project is released under the **MIT License**. See the `LICENSE` file for details. + +--- + +> *"The best way to understand a processor is to build one."* diff --git a/docs/images/cpu_behavioral_waveform.png b/docs/images/cpu_behavioral_waveform.png new file mode 100644 index 0000000..39597b0 Binary files /dev/null and b/docs/images/cpu_behavioral_waveform.png differ diff --git a/docs/images/detailed_synthesized_netlist.png b/docs/images/detailed_synthesized_netlist.png new file mode 100644 index 0000000..8c744a5 Binary files /dev/null and b/docs/images/detailed_synthesized_netlist.png differ diff --git a/docs/images/rtl_elaborated_schematic.png b/docs/images/rtl_elaborated_schematic.png new file mode 100644 index 0000000..5d9be8d Binary files /dev/null and b/docs/images/rtl_elaborated_schematic.png differ diff --git a/docs/images/synthesized_schematic.png b/docs/images/synthesized_schematic.png new file mode 100644 index 0000000..2c24780 Binary files /dev/null and b/docs/images/synthesized_schematic.png differ diff --git a/report/riscReport.pdf b/report/riscReport.pdf index a9181f3..3962b40 100644 Binary files a/report/riscReport.pdf and b/report/riscReport.pdf differ diff --git a/testbench/test_010/CPU_tb_wave.v b/testbench/test_010/CPU_tb_wave.v index 5a40e36..3ee67a9 100644 --- a/testbench/test_010/CPU_tb_wave.v +++ b/testbench/test_010/CPU_tb_wave.v @@ -4,14 +4,12 @@ // CPU_tb.v -- Waveform-heavy CPU integration testbench // // Purpose: -// This testbench is designed for viewing a rich waveform, not -// only for checking HLT. It exposes many internal CPU signals -// as top-level waveform aliases so they are easy to add/view. +// This testbench verifies all original CPU instructions and +// generates a rich waveform for GTKWave. // // Waveform outputs: -// - CPU_tb_wave.vcd : standard VCD waveform -// - CPU_tb_wave.shm, optional : Cadence/Xcelium SHM database -// compile with +define+USE_SHM +// - CPU_tb_wave.vcd +// - CPU_tb_wave.shm, optional with +define+USE_SHM // // Instruction coverage: // HLT, SKZ, ADD, AND, XOR, LDA, STO, JMP @@ -39,6 +37,10 @@ module CPU_tb; + // Set to 1 to print one trace row per clock cycle. + // Keep at 0 for golden-output regression testing. + localparam VERBOSE = 1'b0; + // ------------------------------------------------------------ // Testbench signals // ------------------------------------------------------------ @@ -88,73 +90,96 @@ module CPU_tb; localparam [2:0] STORE = 3'd7; // ------------------------------------------------------------ - // Waveform aliases: top-level names are easier to inspect + // Waveform aliases + // ------------------------------------------------------------ + wire [4:0] wave_pc = dut.u_pc.pc_out; + wire [2:0] wave_state = dut.u_controller.state; + wire wave_halted_latch = dut.u_controller.halted; + + wire [7:0] wave_ir_reg = dut.u_ir.ir_reg; + wire [2:0] wave_opcode = dut.opcode; + wire [4:0] wave_operand = dut.operand; + + wire [4:0] wave_mem_addr = dut.mem_addr; + wire [7:0] wave_data_bus = dut.data_bus; + wire [7:0] wave_memory_out = dut.u_memory.data_out; + + wire [7:0] wave_ac = dut.u_ac.ac_out; + wire [7:0] wave_alu_out = dut.alu_out; + wire wave_zero = dut.zero; + + wire wave_sel = dut.sel; + wire wave_rd = dut.rd; + wire wave_ld_ir = dut.ld_ir; + wire wave_inc_pc = dut.inc_pc; + wire wave_ld_ac = dut.ld_ac; + wire wave_ld_pc = dut.ld_pc; + wire wave_wr = dut.wr; + wire wave_data_e = dut.data_e; + + // ------------------------------------------------------------ + // Derived waveform markers + // ------------------------------------------------------------ + wire wave_is_fetch_phase = + (wave_state == INST_ADDR) || + (wave_state == INST_FETCH) || + (wave_state == INST_LOAD) || + (wave_state == IDLE); + + wire wave_is_operand_phase = + (wave_state == OP_ADDR) || + (wave_state == OP_FETCH); + + wire wave_is_execute_phase = + (wave_state == ALU_OP); + + wire wave_is_store_phase = + (wave_state == STORE); + + wire wave_aluop = + (wave_opcode == ADD) || + (wave_opcode == AND) || + (wave_opcode == XOR) || + (wave_opcode == LDA); + + wire wave_skip_taken = + (wave_state == ALU_OP) && + (wave_opcode == SKZ) && + wave_zero; + + wire wave_jump_taken = + ((wave_state == ALU_OP) || + (wave_state == STORE)) && + (wave_opcode == JMP) && + wave_ld_pc; + + wire wave_store_taken = + (wave_state == STORE) && + (wave_opcode == STO) && + wave_wr; + + wire wave_ac_load_event = + (wave_state == STORE) && + wave_ld_ac; + + wire wave_mem_read_event = + wave_rd && !wave_wr; + + wire wave_mem_write_event = + wave_wr && !wave_rd; + + wire wave_exec_hlt = (wave_opcode == HLT); + wire wave_exec_skz = (wave_opcode == SKZ); + wire wave_exec_add = (wave_opcode == ADD); + wire wave_exec_and = (wave_opcode == AND); + wire wave_exec_xor = (wave_opcode == XOR); + wire wave_exec_lda = (wave_opcode == LDA); + wire wave_exec_sto = (wave_opcode == STO); + wire wave_exec_jmp = (wave_opcode == JMP); + + // ------------------------------------------------------------ + // Memory aliases // ------------------------------------------------------------ - wire [4:0] wave_pc = dut.u_pc.pc_out; - wire [2:0] wave_state = dut.u_controller.state; - wire wave_halted_latch= dut.u_controller.halted; - - wire [7:0] wave_ir_reg = dut.u_ir.ir_reg; - wire [2:0] wave_opcode = dut.opcode; - wire [4:0] wave_operand = dut.operand; - - wire [4:0] wave_mem_addr = dut.mem_addr; - wire [7:0] wave_data_bus = dut.data_bus; - wire [7:0] wave_memory_out = dut.u_memory.data_out; - - wire [7:0] wave_ac = dut.u_ac.ac_out; - wire [7:0] wave_alu_out = dut.alu_out; - wire wave_zero = dut.zero; - - wire wave_sel = dut.sel; - wire wave_rd = dut.rd; - wire wave_ld_ir = dut.ld_ir; - wire wave_inc_pc = dut.inc_pc; - wire wave_ld_ac = dut.ld_ac; - wire wave_ld_pc = dut.ld_pc; - wire wave_wr = dut.wr; - wire wave_data_e = dut.data_e; - - // Derived waveform markers. - wire wave_is_fetch_phase = (wave_state == INST_ADDR) || - (wave_state == INST_FETCH) || - (wave_state == INST_LOAD) || - (wave_state == IDLE); - wire wave_is_operand_phase = (wave_state == OP_ADDR) || - (wave_state == OP_FETCH); - wire wave_is_execute_phase = (wave_state == ALU_OP); - wire wave_is_store_phase = (wave_state == STORE); - - wire wave_aluop = (wave_opcode == ADD) || - (wave_opcode == AND) || - (wave_opcode == XOR) || - (wave_opcode == LDA); - wire wave_skip_taken = (wave_state == ALU_OP) && - (wave_opcode == SKZ) && - wave_zero; - wire wave_jump_taken = ((wave_state == ALU_OP) || - (wave_state == STORE)) && - (wave_opcode == JMP) && - wave_ld_pc; - wire wave_store_taken = (wave_state == STORE) && - (wave_opcode == STO) && - wave_wr; - wire wave_ac_load_event = (wave_state == STORE) && - wave_ld_ac; - wire wave_mem_read_event = wave_rd && !wave_wr; - wire wave_mem_write_event = wave_wr && !wave_rd; - - wire wave_exec_hlt = (wave_opcode == HLT); - wire wave_exec_skz = (wave_opcode == SKZ); - wire wave_exec_add = (wave_opcode == ADD); - wire wave_exec_and = (wave_opcode == AND); - wire wave_exec_xor = (wave_opcode == XOR); - wire wave_exec_lda = (wave_opcode == LDA); - wire wave_exec_sto = (wave_opcode == STO); - wire wave_exec_jmp = (wave_opcode == JMP); - - // Memory aliases. Some VCD viewers do not show Verilog memories - // conveniently, so each address is mirrored as a top-level wire. wire [7:0] mem_00 = dut.u_memory.mem_cells[0]; wire [7:0] mem_01 = dut.u_memory.mem_cells[1]; wire [7:0] mem_02 = dut.u_memory.mem_cells[2]; @@ -188,7 +213,9 @@ module CPU_tb; wire [7:0] mem_30 = dut.u_memory.mem_cells[30]; wire [7:0] mem_31 = dut.u_memory.mem_cells[31]; - // ASCII labels. In many waveform viewers these appear as readable text. + // ------------------------------------------------------------ + // Readable waveform labels + // ------------------------------------------------------------ reg [8*12:1] wave_state_name; reg [8*5 :1] wave_opcode_name; reg [8*40:1] wave_program_line; @@ -224,47 +251,125 @@ module CPU_tb; always @(*) begin case (wave_pc) - 5'd0: wave_program_line = "00 LDA 24 AC<-MEM[24]=5 "; - 5'd1: wave_program_line = "01 ADD 25 AC<-AC+MEM[25]=8 "; - 5'd2: wave_program_line = "02 STO 26 MEM[26]<-AC=8 "; - 5'd3: wave_program_line = "03 XOR 24 AC<-8^5=13 "; - 5'd4: wave_program_line = "04 AND 25 AC<-13&3=1 "; - 5'd5: wave_program_line = "05 STO 27 MEM[27]<-1 "; - 5'd6: wave_program_line = "06 LDA 28 AC<-0 "; - 5'd7: wave_program_line = "07 SKZ zero, skip next "; - 5'd8: wave_program_line = "08 STO 29 MUST BE SKIPPED "; - 5'd9: wave_program_line = "09 LDA 25 AC<-3 "; - 5'd10: wave_program_line = "10 SKZ non-zero, no skip "; - 5'd11: wave_program_line = "11 STO 30 MEM[30]<-3 "; - 5'd12: wave_program_line = "12 JMP 14 jump to 14 "; - 5'd13: wave_program_line = "13 STO 31 MUST BE SKIPPED "; - 5'd14: wave_program_line = "14 LDA 26 AC<-MEM[26]=8 "; - 5'd15: wave_program_line = "15 ADD 27 AC<-8+1=9 "; - 5'd16: wave_program_line = "16 STO 23 MEM[23]<-9 "; - 5'd17: wave_program_line = "17 HLT halt CPU "; - default: wave_program_line = "outside programmed instruction area "; + 5'd0: + wave_program_line = + "00 LDA 24 AC<-MEM[24]=5 "; + + 5'd1: + wave_program_line = + "01 ADD 25 AC<-AC+MEM[25]=8 "; + + 5'd2: + wave_program_line = + "02 STO 26 MEM[26]<-AC=8 "; + + 5'd3: + wave_program_line = + "03 XOR 24 AC<-8^5=13 "; + + 5'd4: + wave_program_line = + "04 AND 25 AC<-13&3=1 "; + + 5'd5: + wave_program_line = + "05 STO 27 MEM[27]<-1 "; + + 5'd6: + wave_program_line = + "06 LDA 28 AC<-0 "; + + 5'd7: + wave_program_line = + "07 SKZ zero, skip next "; + + 5'd8: + wave_program_line = + "08 STO 29 MUST BE SKIPPED "; + + 5'd9: + wave_program_line = + "09 LDA 25 AC<-3 "; + + 5'd10: + wave_program_line = + "10 SKZ non-zero, no skip "; + + 5'd11: + wave_program_line = + "11 STO 30 MEM[30]<-3 "; + + 5'd12: + wave_program_line = + "12 JMP 14 jump to 14 "; + + 5'd13: + wave_program_line = + "13 STO 31 MUST BE SKIPPED "; + + 5'd14: + wave_program_line = + "14 LDA 26 AC<-MEM[26]=8 "; + + 5'd15: + wave_program_line = + "15 ADD 27 AC<-8+1=9 "; + + 5'd16: + wave_program_line = + "16 STO 23 MEM[23]<-9 "; + + 5'd17: + wave_program_line = + "17 HLT halt CPU "; + + default: + wave_program_line = + "outside programmed instruction area "; endcase end always @(*) begin - if (wave_skip_taken) - wave_expected_action = "SKZ TAKEN: PC increments again "; - else if ((wave_opcode == SKZ) && (wave_state == ALU_OP) && !wave_zero) - wave_expected_action = "SKZ NOT TAKEN "; - else if (wave_jump_taken) - wave_expected_action = "JMP TAKEN: PC loads operand "; - else if (wave_store_taken) - wave_expected_action = "STO WRITE TO MEMORY "; - else if (wave_ac_load_event) - wave_expected_action = "AC LOAD FROM ALU "; - else if (wave_mem_read_event) - wave_expected_action = "MEMORY READ "; - else if (wave_mem_write_event) - wave_expected_action = "MEMORY WRITE "; - else if (halt) - wave_expected_action = "HALT ASSERTED "; - else - wave_expected_action = "normal CPU cycle "; + if (wave_skip_taken) begin + wave_expected_action = + "SKZ TAKEN: PC increments again "; + end + else if ( + (wave_opcode == SKZ) && + (wave_state == ALU_OP) && + !wave_zero + ) begin + wave_expected_action = + "SKZ NOT TAKEN "; + end + else if (wave_jump_taken) begin + wave_expected_action = + "JMP TAKEN: PC loads operand "; + end + else if (wave_store_taken) begin + wave_expected_action = + "STO WRITE TO MEMORY "; + end + else if (wave_ac_load_event) begin + wave_expected_action = + "AC LOAD FROM ALU "; + end + else if (wave_mem_read_event) begin + wave_expected_action = + "MEMORY READ "; + end + else if (wave_mem_write_event) begin + wave_expected_action = + "MEMORY WRITE "; + end + else if (halt) begin + wave_expected_action = + "HALT ASSERTED "; + end + else begin + wave_expected_action = + "normal CPU cycle "; + end end // ------------------------------------------------------------ @@ -273,46 +378,65 @@ module CPU_tb; initial begin $timeformat(-9, 0, " ns", 10); - // Standard waveform for GTKWave / many simulators. $dumpfile("CPU_tb_wave.vcd"); $dumpvars(0, CPU_tb); `ifdef USE_SHM - // Cadence/Xcelium waveform database. - // Example: - // xrun +access+rwc +define+USE_SHM *.v $shm_open("CPU_tb_wave.shm"); $shm_probe(CPU_tb, "ASCM"); `endif end // ------------------------------------------------------------ - // Helper tasks for checking results + // Helper tasks // ------------------------------------------------------------ task check_mem; input [4:0] addr; input [7:0] expected; + begin tests = tests + 1; + if (dut.u_memory.mem_cells[addr] !== expected) begin errors = errors + 1; - $display("FAIL: MEM[%0d] = 0x%02h, expected 0x%02h", - addr, dut.u_memory.mem_cells[addr], expected); - end else begin - $display("PASS: MEM[%0d] = 0x%02h", addr, expected); + + $display( + "FAIL: MEM[%0d] = 0x%02h, expected 0x%02h", + addr, + dut.u_memory.mem_cells[addr], + expected + ); + end + else begin + $display( + "PASS: MEM[%0d] = 0x%02h", + addr, + expected + ); end end endtask task check_ac; input [7:0] expected; + begin tests = tests + 1; + if (wave_ac !== expected) begin errors = errors + 1; - $display("FAIL: AC = 0x%02h, expected 0x%02h", wave_ac, expected); - end else begin - $display("PASS: AC = 0x%02h", expected); + + $display( + "FAIL: AC = 0x%02h, expected 0x%02h", + wave_ac, + expected + ); + end + else begin + $display( + "PASS: AC = 0x%02h", + expected + ); end end endtask @@ -320,10 +444,16 @@ module CPU_tb; task check_halt; begin tests = tests + 1; + if (halt !== 1'b1) begin errors = errors + 1; - $display("FAIL: halt = %b, expected 1", halt); - end else begin + + $display( + "FAIL: halt = %b, expected 1", + halt + ); + end + else begin $display("PASS: halt asserted"); end end @@ -331,13 +461,24 @@ module CPU_tb; task check_pc; input [4:0] expected; + begin tests = tests + 1; + if (wave_pc !== expected) begin errors = errors + 1; - $display("FAIL: PC = %0d, expected %0d", wave_pc, expected); - end else begin - $display("PASS: PC = %0d", expected); + + $display( + "FAIL: PC = %0d, expected %0d", + wave_pc, + expected + ); + end + else begin + $display( + "PASS: PC = %0d", + expected + ); end end endtask @@ -355,76 +496,99 @@ module CPU_tb; dut.u_memory.mem_cells[i] = 8'h00; end - // Instruction encoding: {opcode[2:0], operand[4:0]}. - dut.u_memory.mem_cells[0] = 8'hB8; // LDA 24 - dut.u_memory.mem_cells[1] = 8'h59; // ADD 25 - dut.u_memory.mem_cells[2] = 8'hDA; // STO 26 - dut.u_memory.mem_cells[3] = 8'h98; // XOR 24 - dut.u_memory.mem_cells[4] = 8'h79; // AND 25 - dut.u_memory.mem_cells[5] = 8'hDB; // STO 27 - dut.u_memory.mem_cells[6] = 8'hBC; // LDA 28 - dut.u_memory.mem_cells[7] = 8'h20; // SKZ - dut.u_memory.mem_cells[8] = 8'hDD; // STO 29, skipped - dut.u_memory.mem_cells[9] = 8'hB9; // LDA 25 - dut.u_memory.mem_cells[10] = 8'h20; // SKZ, not skipped - dut.u_memory.mem_cells[11] = 8'hDE; // STO 30 - dut.u_memory.mem_cells[12] = 8'hEE; // JMP 14 - dut.u_memory.mem_cells[13] = 8'hDF; // STO 31, skipped - dut.u_memory.mem_cells[14] = 8'hBA; // LDA 26 - dut.u_memory.mem_cells[15] = 8'h5B; // ADD 27 - dut.u_memory.mem_cells[16] = 8'hD7; // STO 23 - dut.u_memory.mem_cells[17] = 8'h00; // HLT + // Instruction encoding: + // {opcode[2:0], operand[4:0]} + dut.u_memory.mem_cells[0] = 8'hB8; // LDA 24 + dut.u_memory.mem_cells[1] = 8'h59; // ADD 25 + dut.u_memory.mem_cells[2] = 8'hDA; // STO 26 + dut.u_memory.mem_cells[3] = 8'h98; // XOR 24 + dut.u_memory.mem_cells[4] = 8'h79; // AND 25 + dut.u_memory.mem_cells[5] = 8'hDB; // STO 27 + dut.u_memory.mem_cells[6] = 8'hBC; // LDA 28 + dut.u_memory.mem_cells[7] = 8'h20; // SKZ + dut.u_memory.mem_cells[8] = 8'hDD; // STO 29, skipped + dut.u_memory.mem_cells[9] = 8'hB9; // LDA 25 + dut.u_memory.mem_cells[10] = 8'h20; // SKZ, not skipped + dut.u_memory.mem_cells[11] = 8'hDE; // STO 30 + dut.u_memory.mem_cells[12] = 8'hEE; // JMP 14 + dut.u_memory.mem_cells[13] = 8'hDF; // STO 31, skipped + dut.u_memory.mem_cells[14] = 8'hBA; // LDA 26 + dut.u_memory.mem_cells[15] = 8'h5B; // ADD 27 + dut.u_memory.mem_cells[16] = 8'hD7; // STO 23 + dut.u_memory.mem_cells[17] = 8'h00; // HLT // Data memory and sentinels. - dut.u_memory.mem_cells[23] = 8'h00; // final result should be 9 + dut.u_memory.mem_cells[23] = 8'h00; dut.u_memory.mem_cells[24] = 8'd5; dut.u_memory.mem_cells[25] = 8'd3; - dut.u_memory.mem_cells[26] = 8'h00; // should become 8 - dut.u_memory.mem_cells[27] = 8'h00; // should become 1 + dut.u_memory.mem_cells[26] = 8'h00; + dut.u_memory.mem_cells[27] = 8'h00; dut.u_memory.mem_cells[28] = 8'd0; - dut.u_memory.mem_cells[29] = 8'hAA; // should remain AA; SKZ skip proof - dut.u_memory.mem_cells[30] = 8'h00; // should become 3 - dut.u_memory.mem_cells[31] = 8'hBB; // should remain BB; JMP skip proof - - $display("============================================================"); - $display("CPU WAVEFORM TEST START"); - $display("Open CPU_tb_wave.vcd or CPU_tb_wave.shm to view many signals."); - $display("============================================================"); + dut.u_memory.mem_cells[29] = 8'hAA; + dut.u_memory.mem_cells[30] = 8'h00; + dut.u_memory.mem_cells[31] = 8'hBB; + + if (VERBOSE) begin + $display( + "============================================================" + ); + $display("CPU WAVEFORM TEST START"); + $display( + "Open CPU_tb_wave.vcd or CPU_tb_wave.shm to view many signals." + ); + $display( + "============================================================" + ); + end - // Keep reset high for a few clocks so reset behavior is visible. + // Keep reset high for four clock edges. repeat (4) @(posedge clk); #1 rst = 1'b0; end // ------------------------------------------------------------ - // Console trace: one row per positive clock edge + // Optional console trace // ------------------------------------------------------------ always @(posedge clk) begin if (rst) begin cycle_count <= 0; - $display("t=%0t | RESET | PC=%02d state=%0d AC=0x%02h", - $time, wave_pc, wave_state, wave_ac); - end else begin + + if (VERBOSE) begin + $display( + "t=%0t | RESET | PC=%02d state=%0d AC=0x%02h", + $time, + wave_pc, + wave_state, + wave_ac + ); + end + end + else begin cycle_count <= cycle_count + 1; - $display("t=%0t | cyc=%03d | PC=%02d | state=%0d | op=%b | operand=%02d | addr=%02d | bus=0x%02h | AC=0x%02h | ALU=0x%02h | zero=%b | rd=%b wr=%b ld_ir=%b ld_ac=%b inc_pc=%b ld_pc=%b halt=%b", - $time, - cycle_count, - wave_pc, - wave_state, - wave_opcode, - wave_operand, - wave_mem_addr, - wave_data_bus, - wave_ac, - wave_alu_out, - wave_zero, - wave_rd, - wave_wr, - wave_ld_ir, - wave_ld_ac, - wave_inc_pc, - wave_ld_pc, - halt); + + if (VERBOSE) begin + $display( + "t=%0t | cyc=%03d | PC=%02d | state=%0d | op=%b | operand=%02d | addr=%02d | bus=0x%02h | AC=0x%02h | ALU=0x%02h | zero=%b | rd=%b wr=%b ld_ir=%b ld_ac=%b inc_pc=%b ld_pc=%b halt=%b", + $time, + cycle_count, + wave_pc, + wave_state, + wave_opcode, + wave_operand, + wave_mem_addr, + wave_data_bus, + wave_ac, + wave_alu_out, + wave_zero, + wave_rd, + wave_wr, + wave_ld_ir, + wave_ld_ac, + wave_inc_pc, + wave_ld_pc, + halt + ); + end end end @@ -434,41 +598,62 @@ module CPU_tb; initial begin wait (halt === 1'b1); - // Continue a little after halt so the waveform clearly shows - // halt staying high and the controller holding its state. + // Keep a few cycles after halt visible in waveform. repeat (10) @(posedge clk); - $display("============================================================"); + $display( + "============================================================" + ); $display("CPU FINAL CHECKS"); - $display("============================================================"); + $display( + "============================================================" + ); check_halt(); - check_mem(26, 8'd8); // LDA + ADD + STO - check_mem(27, 8'd1); // XOR + AND + STO - check_mem(29, 8'hAA); // SKZ zero: STO 29 skipped - check_mem(30, 8'd3); // SKZ non-zero: STO 30 executed - check_mem(31, 8'hBB); // JMP: STO 31 skipped - check_mem(23, 8'd9); // 8 + 1 = 9 + check_mem(26, 8'd8); + check_mem(27, 8'd1); + check_mem(29, 8'hAA); + check_mem(30, 8'd3); + check_mem(31, 8'hBB); + check_mem(23, 8'd9); check_ac(8'd9); check_pc(5'd18); - $display("============================================================"); + $display( + "============================================================" + ); + if (errors == 0) begin - $display("CPU WAVEFORM TEST STATUS: PASS (%0d checks)", tests); - end else begin - $display("CPU WAVEFORM TEST STATUS: FAIL (%0d errors / %0d checks)", errors, tests); + $display( + "CPU WAVEFORM TEST STATUS: PASS (%0d checks)", + tests + ); + end + else begin + $display( + "CPU WAVEFORM TEST STATUS: FAIL (%0d errors / %0d checks)", + errors, + tests + ); end - $display("============================================================"); + + $display( + "============================================================" + ); $finish; end + // ------------------------------------------------------------ + // Timeout protection + // ------------------------------------------------------------ initial begin #50000; + $display("FAIL: timeout. CPU did not halt."); $finish; end -endmodule +endmodule \ No newline at end of file diff --git a/testbench/test_010/expected.txt b/testbench/test_010/expected.txt new file mode 100644 index 0000000..5ac726f --- /dev/null +++ b/testbench/test_010/expected.txt @@ -0,0 +1,15 @@ +============================================================ +CPU FINAL CHECKS +============================================================ +PASS: halt asserted +PASS: MEM[26] = 0x08 +PASS: MEM[27] = 0x01 +PASS: MEM[29] = 0xaa +PASS: MEM[30] = 0x03 +PASS: MEM[31] = 0xbb +PASS: MEM[23] = 0x09 +PASS: AC = 0x09 +PASS: PC = 18 +============================================================ +CPU WAVEFORM TEST STATUS: PASS (9 checks) +============================================================ \ No newline at end of file diff --git a/testbench/test_011/CPU_all_opcode_tb.v b/testbench/test_011/CPU_all_opcode_tb.v new file mode 100644 index 0000000..4e92f6e --- /dev/null +++ b/testbench/test_011/CPU_all_opcode_tb.v @@ -0,0 +1,165 @@ +`timescale 1ns / 1ps + +// ============================================================ +// CPU_all_opcode_tb.v +// +// Compact regression test for all original CPU opcodes: +// +// 000 HLT +// 001 SKZ +// 010 ADD +// 011 AND +// 100 XOR +// 101 LDA +// 110 STO +// 111 JMP +// +// This testbench prints only one final PASS or FAIL line. +// ============================================================ + +module CPU_all_opcode_tb; + + reg clk; + reg rst; + + wire halt; + + integer cycles; + integer errors; + integer i; + + // ------------------------------------------------------------ + // Device under test + // ------------------------------------------------------------ + CPU dut ( + .clk (clk), + .rst (rst), + .halt(halt) + ); + + // ------------------------------------------------------------ + // Clock: 10 ns period + // ------------------------------------------------------------ + initial begin + clk = 1'b0; + end + + always #5 clk = ~clk; + + // ------------------------------------------------------------ + // Program and data initialization + // ------------------------------------------------------------ + initial begin + rst = 1'b1; + cycles = 0; + errors = 0; + + // Clear all memory locations. + for (i = 0; i < 32; i = i + 1) begin + dut.u_memory.mem_cells[i] = 8'h00; + end + + // ---------------------------------------------------------- + // Program + // + // 0: LDA 20 AC = 0F + // 1: AND 21 AC = 03 + // 2: XOR 22 AC = F3 + // 3: ADD 23 AC = F8 + // 4: STO 24 MEM[24] = F8 + // 5: LDA 25 AC = 00 + // 6: SKZ skip address 7 + // 7: LDA 26 must not execute + // 8: JMP 10 skip address 9 + // 9: LDA 27 must not execute + // 10: HLT + // ---------------------------------------------------------- + + dut.u_memory.mem_cells[0] = 8'hB4; // LDA 20 + dut.u_memory.mem_cells[1] = 8'h75; // AND 21 + dut.u_memory.mem_cells[2] = 8'h96; // XOR 22 + dut.u_memory.mem_cells[3] = 8'h57; // ADD 23 + dut.u_memory.mem_cells[4] = 8'hD8; // STO 24 + dut.u_memory.mem_cells[5] = 8'hB9; // LDA 25 + dut.u_memory.mem_cells[6] = 8'h20; // SKZ + dut.u_memory.mem_cells[7] = 8'hBA; // LDA 26, skipped + dut.u_memory.mem_cells[8] = 8'hEA; // JMP 10 + dut.u_memory.mem_cells[9] = 8'hBB; // LDA 27, skipped + dut.u_memory.mem_cells[10] = 8'h00; // HLT + + // ---------------------------------------------------------- + // Data + // ---------------------------------------------------------- + dut.u_memory.mem_cells[20] = 8'h0F; + dut.u_memory.mem_cells[21] = 8'h03; + dut.u_memory.mem_cells[22] = 8'hF0; + dut.u_memory.mem_cells[23] = 8'h05; + dut.u_memory.mem_cells[24] = 8'h00; + dut.u_memory.mem_cells[25] = 8'h00; + dut.u_memory.mem_cells[26] = 8'hAA; + dut.u_memory.mem_cells[27] = 8'hBB; + + // Keep reset asserted for four rising edges. + repeat (4) @(posedge clk); + #1 rst = 1'b0; + end + + // ------------------------------------------------------------ + // Run CPU and perform final checks + // ------------------------------------------------------------ + initial begin + wait (rst === 1'b0); + + while ((halt !== 1'b1) && (cycles < 300)) begin + @(posedge clk); + cycles = cycles + 1; + end + + // Allow final sequential updates to settle. + #1; + + if (halt !== 1'b1) begin + errors = errors + 1; + end + + // ADD, AND and XOR result stored by STO. + if (dut.u_memory.mem_cells[24] !== 8'hF8) begin + errors = errors + 1; + end + + // LDA 26 must be skipped by SKZ. + // If it executed, AC would become AA. + if (dut.u_memory.mem_cells[26] !== 8'hAA) begin + errors = errors + 1; + end + + // LDA 27 must be skipped by JMP. + if (dut.u_memory.mem_cells[27] !== 8'hBB) begin + errors = errors + 1; + end + + // LDA 25 executes before SKZ, so final AC must remain zero. + if (dut.u_ac.ac_out !== 8'h00) begin + errors = errors + 1; + end + + if (errors == 0) begin + $display("PASS: all original opcodes"); + end + else begin + $display("FAIL: all original opcodes (%0d errors)", errors); + end + + $finish; + end + + // ------------------------------------------------------------ + // Absolute timeout protection + // ------------------------------------------------------------ + initial begin + #50000; + $display("FAIL: timeout"); + $finish; + end + +endmodule \ No newline at end of file diff --git a/testbench/test_011/expected.txt b/testbench/test_011/expected.txt new file mode 100644 index 0000000..4aaa3fe --- /dev/null +++ b/testbench/test_011/expected.txt @@ -0,0 +1 @@ +PASS: all original opcodes \ No newline at end of file