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.
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.
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
| Path | Source | Destination | Purpose |
|---|---|---|---|
| Instruction fetch | PC → RAM |
CONTROLE |
Read opcode and operands at the current address |
| Stack push | Mux → STACK |
— | Push constants, ALU results, variables, or immediates |
| Stack pop | STACK → ULA / VAR |
— | Pop one or two operands for arithmetic or store |
| Variable access | STACK ↔ VAR |
— | iload / istore read and write local slots |
| Branch | RAM → BRANCH → PC |
— | Conditional and unconditional jumps |
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
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>) |
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
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
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).
.
├── 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.
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).
- Quartus II 13.0 SP1 (or a compatible version)
- ModelSim-Altera (bundled with Quartus) or standalone ModelSim/Questa
- Open
Java.qpfin Quartus II. - Confirm the device under Assignments → Device (Cyclone III, EP3C25F324C6).
- Run Processing → Start Compilation (or press
Ctrl+L). - Build artifacts appear in
output_files/(for exampleJava.soffor FPGA programming).
- Open
Java.qpfin Quartus. - Set the simulation tool under Assignments → Settings → EDA Tool Settings → Simulation (ModelSim-Altera, VHDL, functional netlist).
- Pick a waveform from
waveforms/(the project referencesWaveform.vwfthroughWaveform4.vwf). - Run Processing → Start Simulation (or Tools → Run Simulation Tool → RTL Simulation).
-
Change into the
modelsim/directory. -
Open
Java.mpfin 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
-
The default ROM program is defined in the
init_romfunction insidemodelsim/RAM.vhd. To run other programs, uncomment one of theInitRomFromFilelines and use the matching.datafile:File Test program soma2e4.dataAdd 2 + 4 compara2e3.dataConditional branch comparing 2 and 3 somaAte5.dataIterative sum up to 5
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. |
| 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 |