Skip to content

Repository files navigation

Java Virtual Machine (FPGA)

A VHDL implementation of a simplified Java Virtual Machine, developed as coursework for the Digital Systems course in the Computer Engineering program at Universidade Federal do Pampa (UNIPAMPA).

The design targets an Altera Cyclone III (EP3C25F324C6) FPGA using Quartus II 13.0 SP1.

System architecture

The top-level entity Main connects a control unit, program memory, operand stack, local variable storage, ALU, and branch logic into a fetch–decode–execute pipeline driven by a single clock.

Block diagram

flowchart TB
    subgraph Main["Main (top level)"]
        CTRL["CONTROLE<br/>Control unit"]
        RAM["RAM<br/>Program memory"]
        PC["PC<br/>Program counter"]
        STK["STACK<br/>Operand stack"]
        ULA["ULA<br/>ALU"]
        VAR["VAR<br/>Local variables"]
        BR["BRANCH<br/>Jump address calc"]
        MUX["Stack input mux"]
    end

    CLK((clk)) --> CTRL
    CLK --> RAM
    CLK --> PC
    CLK --> STK
    CLK --> ULA
    CLK --> VAR
    CLK --> BR

    PC -->|address| RAM
    RAM -->|q1: instruction / operand| CTRL
    RAM -->|q1, q2: branch operands| BR

    CTRL -->|control signals| PC
    CTRL -->|control signals| STK
    CTRL -->|control signals| ULA
    CTRL -->|control signals| VAR
    CTRL -->|control signals| BR

    STK -->|out1, out2| ULA
    ULA -->|eq, gt, lt| CTRL

    BR -->|jump address| PC
    MUX -->|stack data in| STK

    ULA --> MUX
    VAR --> MUX
    CTRL -->|branch_out| MUX
    RAM -->|q2: immediate / operand| MUX
Loading

Data paths

Path Source Destination Purpose
Instruction fetch PCRAM CONTROLE Read opcode and operands at the current address
Stack push Mux → STACK Push constants, ALU results, variables, or immediates
Stack pop STACKULA / VAR Pop one or two operands for arithmetic or store
Variable access STACKVAR iload / istore read and write local slots
Branch RAMBRANCHPC Conditional and unconditional jumps

Stack input multiplexer

Main selects the value pushed onto the stack based on the data_stack_from control signal:

flowchart LR
    subgraph Sources
        RAM2["RAM q2<br/>immediates"]
        ICONST["CONTROLE branch_out<br/>iconst value"]
        ALU["ULA result"]
        VREG["VAR read data"]
    end

    MUX{"data_stack_from"}
    STK["STACK push port"]

    RAM2 -->|00| MUX
    ICONST -->|01| MUX
    ALU -->|10| MUX
    VREG -->|11| MUX
    MUX --> STK
Loading
data_stack_from Selected source
00 RAM.q2 — immediate operand (bipush)
01 CONTROLE.branch_out — encoded constant (iconst_<n>)
10 ULA.outOP — arithmetic result (iadd, isub, imul)
11 VAR.q — loaded local variable (iload, iload_<n>)

Instruction execution flow

Each instruction passes through a multi-cycle state machine in CONTROLE:

stateDiagram-v2
    [*] --> resetPC
    resetPC --> leInstrucao: reset
    leInstrucao --> decodifica: latch opcode
    decodifica --> escrevePilha: iconst / bipush
    decodifica --> leMemoria: iload / iload_n
    decodifica --> lePilha: istore / arith / if_icmp
    decodifica --> incremento_adicional: goto / goto_w
    decodifica --> NOP: unknown opcode
    leMemoria --> escrevePilha
    escrevePilha --> atualizaPC: single-byte instr
    escrevePilha --> atualizaPC2: multi-byte instr
    lePilha --> escrevePilha: iadd / isub / imul
    lePilha --> escreveMemoria: istore / istore_n
    lePilha --> incremento_adicional: if_icmp
    escreveMemoria --> atualizaPC
    incremento_adicional --> leBranch1
    leBranch1 --> atualizaPC2: branch taken / goto
    leBranch1 --> incremento_adicional2: read 2nd addr byte
    incremento_adicional2 --> leBranch2
    leBranch2 --> atualizaPC2
    atualizaPC --> leInstrucao: PC += 1
    atualizaPC2 --> leInstrucao: PC += 2
    NOP --> NOP
Loading

Memory model

flowchart TB
    subgraph AddressSpace["8-bit unified address space (RAM)"]
        PROG["Program bytes<br/>opcodes + inline operands"]
    end

    subgraph StackMem["Operand stack (STACK)"]
        TOS["Top of stack<br/>32 entries × 8 bits"]
    end

    subgraph LocalMem["Local variables (VAR)"]
        SLOTS["Indexed slots<br/>32 entries × 8 bits"]
    end

    PC2["PC"] -->|read addr| PROG
    PROG --> CTRL2["CONTROLE"]
    CTRL2 --> TOS
    TOS --> SLOTS
    SLOTS --> TOS
Loading

Program bytes and inline operands live in RAM (initialized at synthesis via init_rom). Runtime data uses the hardware stack (STACK) and indexed local variable slots (VAR).

Repository layout

.
├── Java.qpf / Java.qsf   # Quartus project (synthesis and integrated simulation)
├── *.vhd                 # VHDL sources for FPGA synthesis
├── waveforms/            # Waveform files (.vwf) for Quartus simulation
└── modelsim/             # Standalone RTL simulation with ModelSim
    ├── *.vhd             # Simulation sources (with extra debug ports)
    ├── TESTBENCH.vhd     # Test bench
    ├── wave.do           # ModelSim waveform script
    ├── Java.mpf          # ModelSim project
    └── *.data            # Example ROM programs (optional)

Files under db/, incremental_db/, output_files/, and simulation/ are generated by Quartus at compile time and are not tracked in the repository.

Root vs. modelsim/ sources

The .vhd files at the repository root are used by Quartus for FPGA synthesis. The copies in modelsim/ are adapted for RTL simulation: they expose additional debug signals (for example state_out and stack_out2 on Main) and use slightly different interfaces in some modules (for example RAM accepts addresses as std_logic_vector instead of integer).

Requirements

  • Quartus II 13.0 SP1 (or a compatible version)
  • ModelSim-Altera (bundled with Quartus) or standalone ModelSim/Questa

Building (FPGA synthesis)

  1. Open Java.qpf in Quartus II.
  2. Confirm the device under Assignments → Device (Cyclone III, EP3C25F324C6).
  3. Run Processing → Start Compilation (or press Ctrl+L).
  4. Build artifacts appear in output_files/ (for example Java.sof for FPGA programming).

Simulation

Option 1: Quartus + ModelSim-Altera (NativeLink)

  1. Open Java.qpf in Quartus.
  2. Set the simulation tool under Assignments → Settings → EDA Tool Settings → Simulation (ModelSim-Altera, VHDL, functional netlist).
  3. Pick a waveform from waveforms/ (the project references Waveform.vwf through Waveform4.vwf).
  4. Run Processing → Start Simulation (or Tools → Run Simulation Tool → RTL Simulation).

Option 2: Standalone ModelSim

  1. Change into the modelsim/ directory.

  2. Open Java.mpf in ModelSim, or run from the command line:

    cd modelsim
    vlib work
    vcom -2002 BRANCH.vhd CONTROLE.vhd PC.vhd RAM.vhd Stack.vhd ULA.vhd VAR.vhd Main.vhd TESTBENCH.vhd
    vsim -t ps work.TESTBENCH
    do wave.do
    run -all
  3. The default ROM program is defined in the init_rom function inside modelsim/RAM.vhd. To run other programs, uncomment one of the InitRomFromFile lines and use the matching .data file:

    File Test program
    soma2e4.data Add 2 + 4
    compara2e3.data Conditional branch comparing 2 and 3
    somaAte5.data Iterative sum up to 5

Instruction set

All data values are 8-bit. Most instructions are identified by the 4 most significant bits; some use 5 bits.

Opcode Instruction Description
0000 iconst_<n> Push an integer. If the 4th least-significant bit is 1, the next 3 bits encode a positive N; otherwise N is negative.
00010 bipush Push the byte at the next memory address.
00011 iload Load from local variable memory to the stack; address is at the next byte.
0010x iload_<n> Load from local variable memory; address is in the 3 least-significant bits.
0011 istore Store from stack to local variable memory; address is at the next byte.
0101 istore_<n> Store from stack to local variable memory; address is in the 3 least-significant bits.
0110 iadd / isub / imul Pop two stack values into the ALU. The 2 least-significant bits select the operation: 00 add, 10 subtract, 01 multiply.
1010 if_icmp Compare two stack values. If the condition is true, skip the next two address bytes; otherwise load them and jump. Comparison in the 4 LSBs: 1111 EQ, 0000 NE, 0001 LT, 0010 GE, 0011 GT, 0100 LE.
1011 goto Unconditional jump using the two bytes that follow.
1100 goto_w Unconditional jump using the four bytes that follow.

Modules

Module Role
Main Top-level entity; wires all components together
CONTROLE Control unit; decodes instructions and drives the datapath
PC Program counter with increment-by-1 and increment-by-2 modes
RAM Program memory with initialized bytecode
STACK Operand stack (push/pop, dual read ports)
ULA Arithmetic/logic unit (add, subtract, multiply, compare flags)
VAR Indexed local variable memory
BRANCH Computes jump target addresses from inline operands

About

A simplified Java Virtual Machine implemented in VHDL for Altera Cyclone III FPGAs, with Quartus synthesis and ModelSim RTL simulation

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages