Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

RISC-V Single-Cycle Processor: addi Implementation and DPI-C Termination

Tech Jul 21 4

Understanding addi Execution in Single-Cycle Architecture

The addi instruction belongs to the RISC-V I-type format with the following bit layout:

[31:20] immediate | [19:15] rs1 | [14:12] funct3 | [11:7] rd | [6:0] opcode

Operation: rd = rs1 + immediate

In single-cycle processors, every instruction completes within one clock period. This requires a carefully designed datapath that performs all necessary operations for addi before the next clock edge.

Core Components

  • Instruction Memory (IMEM): Stores program instructions, indexed by the program counter
  • Register File: Provides data from source registers and accepts results for destination registers
  • Immediate Generator: Extracts and sign-extends the 12-bit immediate to 32 bits
  • Arithmetic Unit: Performs addition betwean register value and immediate
  • PC Increment Logic: Calculates PC+4 for sequential execution (jump support deferred for now)

Execution Flow

  1. Fetch instruction from memory using current PC value
  2. Decode instruction fields: opcode, rd, rs1, immediate
  3. Read source register rs1 from register file
  4. Generate sign-extended 32-bit immediate value
  5. Execute addition operation in ALU
  6. Write result back to destination register rd
  7. Update PC to PC+4 for next instruction

Datapath Architecture

The synchronous design uses edge-triggered writes and combinational reads:


Program Counter → Instruction Memory → Decode Logic → Register File (Read rs1)
                                                                    ↓
Immediate Generator → ALU (Add) → Register File (Write rd)
                                                                    ↓
PC Increment Logic → Next PC Register

Verilog Implementation

Top-Level Module

module cpu_core(
    input wire clock,
    input wire reset,
    output reg [31:0] result_out
);

    // Pipeline registers
    reg [31:0] current_pc;
    reg [31:0] next_pc_reg;
    
    // Instruction fields
    wire [6:0] opcode;
    wire [4:0] dest_reg;
    wire [4:0] src1_reg;
    wire [2:0] func3_field;
    wire [31:0] imm_value;
    
    // Register file signals
    wire [31:0] src1_data;
    wire [31:0] writeback_data;
    wire [4:0] writeback_addr;
    wire regfile_wen;
    
    // ALU result
    wire [31:0] alu_result;
    
    // ebreak detection
    wire breakpoint_detected;

    initial begin
        current_pc = 32'h80000000;
        next_pc_reg = current_pc + 4;
        result_out = 32'h0;
    end

    always @(posedge clock or posedge reset) begin
        if (reset) begin
            current_pc <= 32'h80000000;
            next_pc_reg <= current_pc + 4;
            result_out <= 32'h0;
        end else begin
            current_pc <= next_pc_reg;
            next_pc_reg <= current_pc + 4;
            result_out <= alu_result;
            
            if (breakpoint_detected) begin
                $display("Breakpoint at time %0t", $time);
                ebreak_handler();
            end
        end
    end

    // Module instantiations
    instruction_fetch fetch_unit(
        .pc_addr(current_pc),
        .clk(clock),
        .instruction_word(ins)
    );

    decode_unit decoder(
        .instruction_word(ins),
        .opcode(opcode),
        .dest_reg(dest_reg),
        .func3_field(func3_field),
        .src1_reg(src1_reg),
        .imm_value(imm_value),
        .regfile_wen(regfile_wen),
        .breakpoint_detected(breakpoint_detected)
    );

    regfile #(
        .ADDR_BITS(5),
        .DATA_BITS(32)
    ) register_bank(
        .clk(clock),
        .write_data(writeback_data),
        .write_addr(writeback_addr),
        .write_en(regfile_wen),
        .read_addr1(src1_reg),
        .read_data1(src1_data)
    );

    execution_unit alu_block(
        .imm_value(imm_value),
        .src1_value(src1_data),
        .func3_field(func3_field),
        .opcode(opcode),
        .dest_reg(dest_reg),
        .regfile_wen(regfile_wen),
        .writeback_data(writeback_data),
        .writeback_addr(writeback_addr),
        .alu_result(alu_result)
    );

endmodule

Register File Module

module regfile #(
    parameter ADDR_BITS = 5,
    parameter DATA_BITS = 32
)(
    input wire clk,
    input wire [DATA_BITS-1:0] write_data,
    input wire [ADDR_BITS-1:0] write_addr,
    input wire write_en,
    input wire [ADDR_BITS-1:0] read_addr1,
    output reg [DATA_BITS-1:0] read_data1
);

    localparam NUM_REGS = 1 << ADDR_BITS;
    reg [DATA_BITS-1:0] registers[NUM_REGS-1:0];
    integer idx;

    initial begin
        for (idx = 0; idx < NUM_REGS; idx = idx + 1) begin
            registers[idx] = {DATA_BITS{1'b0}};
        end
    end

    always @(*) begin
        if (read_addr1 == {ADDR_BITS{1'b0}}) begin
            read_data1 = {DATA_BITS{1'b0}};
        end else begin
            read_data1 = registers[read_addr1];
        end
    end

    always @(posedge clk) begin
        if (write_en && (write_addr != {ADDR_BITS{1'b0}})) begin
            registers[write_addr] <= write_data;
            $display("Time=%0t: Reg[%0d] <= 0x%08x", $time, write_addr, write_data);
        end
    end

endmodule

Instruction Fetch Unit

module instruction_fetch(
    input wire [31:0] pc_addr,
    input wire clk,
    output reg [31:0] instruction_word
);

    reg [31:0] instruction_memory[0:15];
    integer i;

    initial begin
        // addi x5, x0, 5
        instruction_memory[0] = 32'h00500293;
        // addi x6, x5, 10
        instruction_memory[1] = 32'h00a30313;
        // addi x7, x0, 1
        instruction_memory[2] = 32'h00100393;
        // addi x8, x0, 2
        instruction_memory[3] = 32'h00200413;
        // addi x9, x5, 15
        instruction_memory[4] = 32'h00f28593;
        // ebreak
        instruction_memory[5] = 32'h00100073;
        
        for (i = 6; i < 16; i = i + 1) begin
            instruction_memory[i] = 32'h00000013; // nop
        end
    end

    always @(posedge clk) begin
        if (pc_addr >= 32'h80000000 && pc_addr < 32'h80000040) begin
            instruction_word <= instruction_memory[(pc_addr - 32'h80000000) >> 2];
            $display("Fetch: PC=0x%08x, Inst=0x%08x", pc_addr, instruction_word);
        end else begin
            instruction_word <= 32'h0;
        end
    end

endmodule

Decode Unit

module decode_unit(
    input wire [31:0] instruction_word,
    output reg [6:0] opcode,
    output reg [4:0] dest_reg,
    output reg [4:0] src1_reg,
    output reg [2:0] func3_field,
    output reg [31:0] imm_value,
    output reg regfile_wen,
    output reg breakpoint_detected
);

    always @(*) begin
        opcode = instruction_word[6:0];
        dest_reg = instruction_word[11:7];
        func3_field = instruction_word[14:12];
        src1_reg = instruction_word[19:15];
        
        // ebreak detection
        if (opcode == 7'b1110011 && func3_field == 3'b000 && instruction_word[31:20] == 12'h001) begin
            breakpoint_detected = 1'b1;
        end else begin
            breakpoint_detected = 1'b0;
        end
        
        // Sign-extend immediate
        if (instruction_word[31]) begin
            imm_value = {20'hFFFFF, instruction_word[31:20]};
        end else begin
            imm_value = {20'h0, instruction_word[31:20]};
        end
        
        // Decode addi
        if (opcode == 7'b0010011 && func3_field == 3'b000) begin
            regfile_wen = 1'b1;
        end else begin
            regfile_wen = 1'b0;
        end
    end

endmodule

Execution Unit

module execution_unit(
    input wire [31:0] imm_value,
    input wire [31:0] src1_value,
    input wire [2:0] func3_field,
    input wire [6:0] opcode,
    input wire [4:0] dest_reg,
    input wire regfile_wen,
    output reg [31:0] writeback_data,
    output reg [4:0] writeback_addr,
    output reg [31:0] alu_result
);

    always @(*) begin
        if (opcode == 7'b0010011 && func3_field == 3'b000) begin
            writeback_data = src1_value + imm_value;
            writeback_addr = dest_reg;
            alu_result = writeback_data;
        end else begin
            writeback_data = 32'h0;
            writeback_addr = 5'h0;
            alu_result = 32'h0;
        end
    end

endmodule

Testbench Implementation

#include "Vcpu_core.h"
#include "verilated.h"
#include "verilated_vcd_c.h"
#include <iostream>

extern "C" void ebreak_handler();

int main(int argc, char **argv) {
    Verilated::commandArgs(argc, argv);
    
    Vcpu_core *dut = new Vcpu_core;
    VerilatedVcdC *trace = new VerilatedVcdC;
    
    Verilated::traceEverOn(true);
    dut->trace(trace, 99);
    trace->open("simulation.vcd");
    
    // Reset sequence
    dut->clock = 0;
    dut->reset = 1;
    
    for (int i = 0; i < 4; i++) {
        dut->clock = !dut->clock;
        dut->eval();
        trace->dump(i * 10);
    }
    
    dut->reset = 0;
    
    int cycle = 0;
    while (cycle < 100 && !Verilated::gotFinish()) {
        dut->clock = !dut->clock;
        dut->eval();
        trace->dump(100 + cycle * 10);
        
        if (dut->clock) {
            std::cout << "Cycle " << cycle/2 << ": Result=0x" 
                      << std::hex << dut->result_out << std::dec << std::endl;
        }
        
        cycle++;
    }
    
    trace->close();
    delete dut;
    delete trace;
    
    return 0;
}
</iostream>

DPI-C Integration

DPI-C Interface

// dpi_callback.cpp
#include <cstdlib>
#include <iostream>

extern "C" void ebreak_handler() {
    std::cout << "EBREAK encountered - terminating simulation" << std::endl;
    std::cout << "Program execution completed successfully" << std::endl;
    exit(0);
}
</iostream></cstdlib>

Modified Build Configuration

# Makefile
VERILATOR = verilator
VERILOG_SOURCES = $(wildcard ./rtl/*.v)
CPP_SOURCES = ./tb/main.cpp ./tb/dpi_callback.cpp

SIM_FLAGS = -Wno-fatal --top-module cpu_core --cc --trace --exe
SIM_FLAGS += --build
SIM_FLAGS += -CFLAGS "-I./tb"
SIM_FLAGS += -LDFLAGS "-L./tb"

simulation: $(VERILOG_SOURCES) $(CPP_SOURCES)
	$(VERILATOR) $(SIM_FLAGS) $^ -o sim_binary
	./obj_dir/sim_binary

clean:
	rm -rf obj_dir simulation.vcd

.PHONY: simulation clean

Simulation Results

Typical console output:

Fetch: PC=0x80000000, Inst=0x00500293
Fetch: PC=0x80000004, Inst=0x00a30313
Reg[5] <= 0x00000005
Fetch: PC=0x80000008, Inst=0x00100393
Reg[6] <= 0x0000000f
Fetch: PC=0x8000000c, Inst=0x00200413
Reg[7] <= 0x00000001
Fetch: PC=0x80000010, Inst=0x00f28593
Reg[8] <= 0x00000002
EBREAK encountered - terminating simulation

The processor correctly executes five addi instructions before the ebreak instruction triggers simulation termination through the DPI-C interface.

Key Design Considerations

  • Zero Register Handling: Writes to x0 are silently ignored while reads always return zero
  • Sign Extension: 12-bit immediates are sign-extended to 32 bits for arithmetic operations
  • Timing Model: Register writes occur on clock edges; reads are combinational, enabling single-cycle operation
  • Breakpoint Detection: ebreak instruction (0x00100073) triggers DPI-C callback for clean simulation exit
  • PC Range Checking: Fetch unit validates PC addresses against memory bounds

This implementation demonstrates a minimal yet functional RISC-V single-cycle processor core that can be extended with additional instructions and control flow mechanisms.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.