ECE552 Fall 2026
Project 1: RISC-V Assembly Language Project 2: Digital Design and Debugging Project 3: Single-Cycle Processor Project 3: Extra Credit - FPGA Demo Project 4: Basic Pipelined Processor Project 5: Forwarding and Branch Prediction Project Schedule Verilog Rules Reference Materials
ECE 552 / Project 3: Extra Credit - FPGA Demo
Back to Course Home

Project 3: Extra Credit - FPGA Demo

UW—Madison ECE 552: Fall 2026

Project Introduction

This is an optional extra credit opportunity for Project 3. A successful demo is worth an extra 1% on top of your final course grade. By the end, you will have compiled a C program, loaded it into your own single-cycle processor, and watched it respond to switches and buttons on a DE1-SoC board.

Your Project 3 submission does not change. Submit it to Gradescope exactly as before.

All material is available in the Project 3 Extra Credit repository on GitHub. Everything except rtl/soc_core.v is finished: the memories, the I/O hardware, the board top level, the displays, the C programs and the whole toolchain are done. Your job is to wire your processor in, verify it in simulation, and compile it in Quartus.

The Demo

To earn the credit, you demo your design in discussion on 10/14 or 10/21.

IMPORTANT: You will not have access to a board before your demo. This is deliberate: it is how chip design works. A chip team does not get to try its design on silicon and fix it afterwards. It verifies everything it can in simulation and timing analysis, sends the design off to be fabricated, and finds out whether it works when the chip comes back. Here, your simulation and your Quartus timing report are all the evidence you get before the board.

What to Bring

We provide the board and its USB-Blaster cable, and nothing else. We do not provide a computer, so you bring:

  1. Your own laptop with Quartus installed, including the Programmer and the USB-Blaster driver (see Requirements). You flash the board from it, so install and check everything before discussion.
  2. A compiled .sof for the program you want to show. Changing the program means a full Quartus recompile (see Running a Different Program), so there is no time to build one in discussion. Bring a .sof for echo as well, as a fallback.
  3. Your Quartus project, so you can open the compilation and timing reports from Step 5 during the questions.

Before You Come

Since you cannot test on the board, make sure everything you can check passes:

  1. make sim passes for the program you will show (Step 3).
  2. Quartus compiles with no errors and cpu_clk meets timing (Step 5).
  3. You have read through the flashing steps in Step 6, so you are not seeing them for the first time at the demo.

At the Demo

You power on the board and flash it yourself, in front of us, then use it: flip switches, press buttons, and show that your processor responds. Then we ask a few short questions about your design and the flow.

What We Will Ask About

The questions are about timing and memory: what Quartus reported for your design, and why. You do not need to memorize numbers; have your reports open and find them as we ask. Everything you need is in Step 5.

Timing

  1. Know your cpu_clk Fmax and whether your design meets timing. Be able to say what would happen on the board if it did not.
  2. Quartus reports timing for several corners: Slow and Fast, at 0 °C and 85 °C. Know why we tell you to read Slow 1100mV 85C.
  3. Find your critical path and trace it through your own schematic, stage by stage. Think about which instruction uses that path, and why that one is the slowest.

Memory

  1. Look up what an M10K block is and how its read works, then compare that with how your processor reads instruction and data memory. Be ready to explain why the fitter used zero of them.
  2. Find what Quartus built dmem out of instead, and point to the numbers in the fitter report that show it.
  3. imem and dmem are both 4 KB, but one costs far more than the other. Compare them in the per-module report and think about what makes them different.

Requirements

Docker, the same way you use it for the assembly tests in tests/asm/. Pull the toolchain image if you have not already:

docker pull coderkalyan/ece552-tools:latest

It carries the RISC-V compiler, Icarus Verilog and Python, so it is the only image you need and you do not need any of them installed on your machine.

On Linux, Docker needs permission to run without sudo. If docker run hello-world fails with permission denied ... docker.sock, add yourself to the docker group, then log out and back in:

sudo usermod -aG docker $USER

make. macOS and Linux usually have it already (run make --version to check). On Windows, you will need to install it yourself; search online for how to install make on Windows.

Quartus Prime. If you do not already have Quartus, download Quartus Prime Lite Edition. It is free and needs no license. Search for "Quartus Prime Lite download" to find it on Altera's (formerly Intel's) FPGA software download page. When the installer asks which devices to support, select Cyclone V, the FPGA on the DE1-SoC. You can skip the others to save several GB. Leave the Programmer selected too. You need it to flash the board at your demo (Step 6), and it installs the USB-Blaster driver.

Directory Layout

project3_ec/
  sw/                    the C programs and their build
    echo.c  calc.c  pong.c
    crt0.S               startup code
    link.ld              memory layout
    Makefile
    tools/
      isa_check.py       rejects instructions your processor lacks
  rtl/
    soc_core.v           <-- THE ONLY FILE YOU EDIT
    hart.v               <-- you add this (plus alu.v, rf.v, decode.v, ...)
    Makefile
    demo.qpf  demo.qsf   the Quartus project, pins already assigned
    ip/                  finished wrapper modules -- leave alone

Everything except rtl/soc_core.v is finished. The memories, the I/O hardware, the board top level, the displays, the C programs and the whole toolchain are done.

The Programs

Three programs are provided, in increasing order of complexity.

Echo: Board Bring-Up

echo.c reads the inputs, writes them to the outputs, and repeats. About 22 instructions.

Input Output
SW[9:0] LEDR[9:0] (binary)
SW[9:0] HEX2–HEX0 (same value in hex)

Flip a switch: the matching LED lights and the hex digits track the value.

Calc: A Live 4-Bit Calculator

calc.c is your ALU, on the board. About 72 instructions.

Control Meaning
SW[3:0] operand A
SW[7:4] operand B
SW[9:8] operation within the current bank
KEY0 switch banks (bank 0 = ops 0–3, bank 1 = ops 4–7)
Display Shows
HEX5 operation number, 0–7
HEX4 / HEX3 blank
HEX2–HEX0 result, 12 bits, two's complement
LEDR low 10 bits of the result, in binary
Op Operation Op Operation
0 ADD — a + b 4 XOR — a ^ b
1 SUB — a − b 5 SLL — a << b
2 AND — a & b 6 SRL — a >> b (logical)
3 OR — a or b 7 SLT — a < b (signed)

There is no compute button. The result updates continuously, so you can sweep an operand and watch the output follow.

Pong: Two-Player Pong on the LEDs

In pong.c, the ball is a single lit LED travelling along LEDR. When it reaches your end, press your button to hit it back; miss and the other player scores. Every successful return speeds the ball up. First to 9 wins. About 152 instructions.

Control Meaning
KEY0 left player's paddle
KEY3 right player's paddle
HEX5 / HEX0 left / right score

On reset, and after each match, it sweeps the ball back and forth until someone presses a button to start a game; the cycle counter at that instant seeds the random number generator, so serves are not predictable.

Step 1: Compiling a Program

From sw/, run:

make PROGRAM=echo mem

A successful build should output:

  ok  echo           text   88  rodata   0  bss   0  stack 4008
      ->  build/program.mem

build/program.mem is the file the hardware loads.

Other commands:

Command What it does
make PROGRAM=pong mem Builds a different program. PROGRAM can be echo, calc or pong, and the result always goes to build/program.mem, replacing whatever was there.
make PROGRAM=calc disasm Prints the RISC-V assembly the compiler generated for that program, with the address and machine-code word of each instruction. Use it to see how your C became instructions, or to find the instruction at a PC while debugging. A copy is also saved to build/calc.dis whenever you build.
make clean Deletes the build/ directory and everything the build produced. Run it if a build seems stale or you want to start fresh.
make With no arguments, prints this list of commands.

Step 2: Wiring Your Processor In

  1. Copy your processor into rtl/, next to soc_core.v: hart.v and whatever it instantiates (alu.v, rf.v, decode.v, …). Do not modify them; these should be the same files you submitted. Do not copy tb.v.
  2. Open rtl/soc_core.v and fill in the hart instantiation. Every port is listed with a blank connection, and the table at the top of the file says what each one attaches to.

Everything else in that file is already done: the wire declarations, the address split, and the memory and I/O instantiations. Only the processor is missing.

Step 3: Simulating Before You Synthesize

From rtl/, run:

make lint              # does it elaborate?
make sim PROG=echo     # does it actually run?

A passing run looks like:

=== soc_tb: echo ===
  ok   ledr = 00000155
  ok   hex low 12 = 00000155
  ok   ledr after change = 000002aa
  ok   hex_en = 00000007
=== 0 error(s), 123 cycles ===

Step 4: Making the Quartus Project

The project is already made: rtl/demo.qpf and rtl/demo.qsf. The .qsf holds the device, the top-level entity (de1soc_top), the timing constraints, the ip/ sources, and all 67 pin assignments with their I/O standards.

Picking the device in Quartus's New Project Wizard does not assign pins; Quartus has no built-in notion of this board. So always open the provided demo.qpf rather than making a fresh project.

  1. Open the project. In Quartus, File → Open Project and pick rtl/demo.qpf.
  2. Add the rest of your processor. soc_core.v and hart.v are already in the project. Add every other file your hart instantiates with Project → Add/Remove Files in Project (e.g. alu.v, rf.v, decode.v, ...). Your files must sit in rtl/ next to soc_core.v, as in Step 2.
  3. Put the program where Quartus can find it. The memories load program.mem by relative filename, so it has to sit in the project directory, project3_ec/rtl/, next to demo.qpf. Copy it from where make put it in Step 1:
cp <path-to-repo>/project3_ec/sw/build/program.mem <path-to-repo>/project3_ec/rtl/program.mem

IMPORTANT: Do not add ip/soc_tb.v. It is the simulation testbench; it contains delays and initial blocks that cannot be synthesized, and adding it breaks the compile.

Note: the upper right-hand corner has a drop-down next to Project Navigator. From the drop-down select Files to see what files are included in your project. You should see ip/de1soc.sdc, the seven ip/*.v files, soc_core.v, hart.v, and the files you just added. Do not remove ip/de1soc.sdc. It is the timing constraints file, and it tells the Timing Analyzer that your processor runs on the divided 12.5 MHz clock, not the board's 50 MHz oscillator. Without it the Fmax you read in Step 5 is wrong.

Step 5: Compiling in Quartus

Processing → Start Compilation. Expect anywhere from 5-20 minutes.

A successful compile ends with warnings but no errors.

Read the Timing Report

Compilation Report → Timing Analyzer → Slow 1100mV 85C Model → Fmax Summary. This is the fastest your processor could run.

Find the cpu_clk row and compare it against 12.5 MHz, which is what the divider produces. If Fmax is above 12.5 MHz, the design meets timing and you don't need to change anything. If it is below 12.5 MHz (the Setup Summary will also show negative slack for cpu_clk), the processor will not run reliably. Set CLK_DIV_LOG2 = 3 in ip/de1soc_top.v for 6.25 MHz, change -divide_by to 8 and the counter bit to cnt[2] in ip/de1soc.sdc, then recompile.

Find Your Critical Path

The critical path is the slowest register-to-register path. It is what sets your Fmax.

  1. Tools → Timing Analyzer.
  2. In the Tasks pane, under Custom Reports, double-click Report Timing…. The Timing Analyzer builds its netlist automatically the first time.
  3. Set From clock and To clock to cpu_clk, and click Report Timing.
  4. Click the top path. The Data Path tab lists every node the signal passes through, with the delay of each, from the launching register to the capturing one.

The node names follow your module hierarchy, for example soc_core:u_core|hart:u_hart|pc[3] or soc_core:u_core|dmem:u_dmem|mem[...]. Match them to the blocks on your schematic.

See What Your Design Became

The fitter report shows what Quartus actually built your design from. Open Compilation Report → Fitter → Resource Section.

Resource Usage Summary gives totals for the whole design. Note these:

Line What it counts
Logic utilization (ALMs needed / total ALMs on device) general-purpose logic cells used
Dedicated logic registers flip-flops
M10K blocks dedicated block RAMs
Total MLAB memory bits logic cells used as small RAMs

Resource Utilization by Entity gives the same counts, broken down by module. Expand the tree to find a row for each part of your design: hart and everything inside it (your rf, alu, …), plus imem, dmem and mmio. Compare the rows. Which module is the largest, and does that match what you expected?

To see it as a picture, Tools → Netlist Viewers → RTL Viewer shows your design as a schematic of the modules you wrote. Technology Map Viewer shows what they became on the FPGA.

Step 6: Flashing the Board

You do this live at your demo (see The Demo), from your laptop, with our board. You cannot try it beforehand, so read through the steps now and do the setup that needs no board: install the Programmer and USB-Blaster driver, and on Linux, add the udev rule in step 3.

  1. Connect the USB-Blaster II port (the mini-USB nearest the power jack) and power the board on.
  2. Tools → Programmer.
  3. Hardware Setup… → DE-SoC → Close. If nothing is listed, the driver is not installed or the board is off.
  4. Mode: JTAG.
  5. Click Auto Detect; if it asks for a device, pick 5CSEMA5. You should now see two chips: SOCVHPS → 5CSEMA5.
  6. Click the 5CSEMA5 row, then Change File… and pick your .sof.
  7. Tick Program/Configure on that row.
  8. Start. The bar should reach 100% (Successful).

The design runs as soon as configuration finishes.

Note: on Linux, the board will not be listed in step 3 until you give your user access to the USB-Blaster. Do this once, before your demo:

echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", MODE="0666"' | sudo tee /etc/udev/rules.d/51-usbblaster.rules
sudo udevadm control --reload-rules

If it still does not show up, unplug and replug the board, or run killall jtagd and reopen the Programmer.

Running a Different Program

cd ../sw && make PROGRAM=pong mem
cp build/program.mem ../rtl/

Then recompile in Quartus.

You have to recompile. The program is not data the board loads at startup; it was baked into the bitstream as the initial contents of the instruction memory, so changing a single instruction means re-synthesizing the entire design.

Each compile overwrites demo.sof. To bring more than one program to your demo, copy each .sof to its own name (for example echo.sof, pong.sof) after compiling it.

Writing Your Own Program

You are encouraged to. Drop a .c file in sw/ and build it:

make PROGRAM=yourprogram mem

The constraint is that your processor implements 37 instructions (the RV32I base set minus a few pieces), so not every legal C program will run on it.

Do not use Why Use instead
*, /, % no multiply or divide instructions shift-and-add; repeated subtraction
long long, float, double no 64-bit or floating-point support 32-bit int / unsigned
printf, malloc, the C library needs ecall, which does not exist here memory-mapped I/O
__builtin_popcount, __builtin_clz, … become library calls write the loop yourself
initialized global variables reset does not reload memory, so they come back stale const globals, or initialize at the top of main

If you use * or /, the build fails with a clear message rather than producing a program that misbehaves on the board:

ld: undefined reference to `__mulsi3'
ld: undefined reference to `__udivsi3'

That is the linker telling you the compiler wanted a multiply routine your processor does not have.

Note: multiplying by a constant sometimes works and sometimes does not, because the compiler may turn x * 8 into a shift but x * 12500 into a __mulsi3 call.

Byte and half-word access does work (lb, lbu, lh, lhu, sb, sh), so char arrays and strings are fine.

Why no initialized globals: pressing reset restarts your processor but does not reload memory, so a mutable global would come back holding whatever the previous run left in it. crt0 cannot restore it, because there is no separate copy to restore from. Constants go in .rodata, which is never written; mutable state goes in .bss or a local and is set up at runtime. The build enforces this.

Reference

Memory Map

One flat 4 KB image, loaded into both memories. Each ignores the region it never addresses, which is why one file serves both and crt0 needs no .data copy loop.

Range Contents
0x0000_0000–0x0000_03FF .text, fetched through the imem port
0x0000_0400–0x0000_0EFF .rodata, .bss, reached through the dmem port
0x0000_0F00–0x0000_0FFC stack, grows down

Peripherals

Decoded on address bit 31: anything at 0x8000_0000 and above is I/O.

Address Register Dir Meaning
0x8000_0000 LEDR W 10 bits
0x8000_0004 SW R 10 bits, synchronized
0x8000_0008 KEY R 4 bits, active high
0x8000_000C KEY_EDGE R sticky press latch
0x8000_0010 KEY_ACK W write 1 to clear a latch bit
0x8000_0014 HEX_VAL W 24 bits → six hex digits
0x8000_0020 HEX_EN W 6 bits, per-digit blanking
0x8000_0024 CYCLES R free-running cycle counter
0x8000_0028 INSTRET R retired instruction counter

The counters are memory-mapped because this ISA has no CSRs; there is no rdcycle to read.

KEY_EDGE is cleared by an explicit write to KEY_ACK rather than by the read itself. Read-to-clear would be safe on a single-cycle processor but would drop presses on a pipelined one that can replay a load.

IP Modules

The files in ip/ are finished. Do not modify them.

File Role
de1soc_top.v Board top level: pins, reset, clock, LED override, displays
imem.v 4 KB instruction memory, combinational read
dmem.v 4 KB data memory, combinational read, byte-writable
mmio.v Switches, buttons, LEDs, displays, counters
hex7seg.v Hex digit → seven segments
clkdiv.v 50 MHz → 12.5 MHz CPU clock, freezes on halt
halt_ctrl.v Watches your retire interface for ebreak and traps
soc_tb.v The testbench. Simulation only; never add to Quartus
de1soc.sdc Timing constraints; add to Quartus

Board Behavior

  1. Reset is KEY1. None of the programs use it, so it never gets in the way of a program's own buttons.
  2. All LEDs on, steady: your program executed ebreak and the CPU stopped. Normal for a program that ends.
  3. All LEDs blinking: your processor took a trap (illegal instruction, misaligned load/store, or misaligned branch target). These programs do none of those, so blinking points at the processor, not the software.

Troubleshooting

Software Build

Symptom Cause
undefined reference to '__mulsi3' / '__udivsi3' you used *, / or %
ISA CHECK FAILED an instruction your processor does not implement; the message names it
.data is N bytes, must be 0 initialized global; make it const or set it inside main
region 'RAM' overflowed program too big; check -Os is still there, shrink arrays
warning: ... LOAD segment with RWX permissions expected and harmless

Simulation

Symptom Cause
FAIL lines, or outputs all x a connection left blank in soc_core.v
Unknown module type: hart hart.v not copied into rtl/
Cannot connect to the Docker daemon Docker Desktop is not running (Linux: the Docker service is not running; run sudo systemctl start docker)
permission denied while trying to connect to the Docker daemon socket Linux: you are not in the docker group; see Requirements
make: command not found make is not installed; see Requirements

Quartus

Symptom Cause
Top-level entity "demo" is undefined you made a new project instead of opening the provided demo.qpf; open that one (or set the top-level entity to de1soc_top)
errors about # delays or initial you added ip/soc_tb.v; remove it
Can't elaborate user hierarchy / unknown module under hart one of your processor files is not in the project; add it (Step 4)
Can't open file hart.v hart.v not copied into rtl/
compiles fine, board completely dead pins not assigned; did you open the provided demo.qpf, or make a fresh project?
compiles fine, displays blank program.mem missing from the project directory
huge timing violations, Fmax nonsense ip/de1soc.sdc was removed from the project; add it back

Board

Symptom Cause
Can't find hardware in the Programmer board off, wrong USB port (use the one by the power jack), or driver not installed (Linux: udev rule missing; see Step 6)
Programmer succeeds, nothing happens .sof attached to SOCVHPS instead of the 5CSEMA5 row
Nothing works at all load echo first; if that fails it is the wiring or the board setup, not the program
‹ Project 3: Single-Cycle Processor Project 4: Basic Pipelined Processor ›
Table of Contents
  • Project Introduction
  • The Demo
    • What to Bring
    • Before You Come
    • At the Demo
    • What We Will Ask About
  • Requirements
  • Directory Layout
  • The Programs
    • Echo: Board Bring-Up
    • Calc: A Live 4-Bit Calculator
    • Pong: Two-Player Pong on the LEDs
  • Step 1: Compiling a Program
  • Step 2: Wiring Your Processor In
  • Step 3: Simulating Before You Synthesize
  • Step 4: Making the Quartus Project
  • Step 5: Compiling in Quartus
    • Read the Timing Report
    • Find Your Critical Path
    • See What Your Design Became
  • Step 6: Flashing the Board
  • Running a Different Program
  • Writing Your Own Program
  • Reference
    • Memory Map
    • Peripherals
    • IP Modules
    • Board Behavior
  • Troubleshooting
    • Software Build
    • Simulation
    • Quartus
    • Board

2026 ECE 552 Course Staff, University of Wisconsin-Madison