
This project implements a very minimal perceptron-based branch predictor. Using basic SPI, it reads in the lower part of the address of a branch instruction and its ground truth branch direction (taken or not taken). Due to constraints on the memory architecture (namely, 1 byte read per cycle), the prediciton is not single-cycle.
The branch predictor is based on this paper: Dynamic Branch Prediction with Perceptrons.
This project uses latch-based memory from Michael Dell, available at: tt06-memory
It's best to run this project in its Docker container.
The func_sim directory contains a C++ functional simulation of the infrastructure. It parses a log file of a simulated execution of a RISC-V reference program and predicts the branch direction on each branch instruction.
From the func_sim directory:
-Compile the reference: riscv32-unknown-elf-gcc -O0 start.S reference.c -o reference -march=rv32i_zicsr_zifencei -T link.ld -nostartfiles -nostdlib
-Disassemble reference: riscv32-unknown-elf-objdump -d reference > reference_dis.txt (for info only)
-Run Spike on reference: spike --log=spike_log.txt --log-commits --isa=rv32i_zicsr_zifencei --priv=m -m128 reference (to generate execution log)
-Make Makefile: cmake CMakeLists.txt
-Compile functional simulation: make
-Run: ./build/func_sim ./spike_log.txt
The tests directory includes all CocoTB test for this design.
Run them with make.
The documentation waveform is generated by Wavedrom.
The branch predictor reads the lowest 8b of a 32b RISC-V address on pins inst_addr, the branch direction on pin uio[1] and a pulse notifying that new data is available on pin new_data_avail.
Once the prediction is ready, it pulses pin pred_ready, pushes the prediction on pin prediction. Once training is complete, it pulses pin training_done.
You can also request the history buffer by pulsing pin history_buffer_request. On the next cycle, the branch history will be sent to pin DEBUG_history_buffer_output, one cycle at a time, starting from the most recent.
Notes:
mem_reset_done to pulse. This is because the branch predictor resets its own memory. If new data comes in while it is resetting its memory, an inference will not be started.training_done pulses at the same time as pred_ready.Prediction waveform:

Reset waveform:

To generate the instructions to use, you can either:
func_sim/spike_log.txt with is a log of all instructions ran for the program func_sim/reference.c simulated on RISC-V rv32i_zicsr_zifencei. See func_sim/src/func_sim.cpp to see how that's done.docker build -t tt_brand_predictor .docker run -it -v `pwd`:/tmp tt_brand_predictorfunc_sim: cd func_simreference.c: riscv32-unknown-elf-gcc -O0 start.S reference.c -o reference -march=rv32i_zicsr_zifencei -T link.ld -nostartfiles -nostdlibriscv32-unknown-elf-objdump -d reference > reference_dis.txtspike --log=spike_log.txt --log-commits --isa=rv32i_zicsr_zifencei --priv=m -m128 referencefunc_sim outputs a list of all branch instruction and the state of important registers in the branch predictor. You can use that to check if the predicted branch outcome is correct: ./build/func_sim ./spike_log.txt| # | Input | Output | Bidirectional |
|---|---|---|---|
| 0 | inst_addr[0] | pred_ready | new_data_avail |
| 1 | inst_addr[1] | prediction | direction_ground_truth |
| 2 | inst_addr[2] | training_done | DEBUG_perceptron_index[0] |
| 3 | inst_addr[3] | mem_reset_done | DEBUG_perceptron_index[1] |
| 4 | inst_addr[4] | DEBUG_new_data_avail_posedge | DEBUG_perceptron_index[2] |
| 5 | inst_addr[5] | DEBUG_state_pred[0] | DEBUG_wr_en |
| 6 | inst_addr[6] | DEBUG_state_pred[1] | DEBUG_history_buffer_output |
| 7 | inst_addr[7] | DEBUG_state_rst_mem | history_buffer_request |