Introduction

RISC-V FPGA SoC – Introductory Lecture Pack (1–2 hours)

designed for students who already learned the pieces (C, Verilog, FPGA) but do not yet understand the full system flow.

This pack includes:

  • Slide structure (20–25 slides)

  • Slide text (complete content per slide)

  • Lab exercises

  • Code references

  • Explanations in clear, beginner-friendly English

  • Focus on PicoRV32 + FPGA (e.g., Tang Nano 9K) using open-source tools only

You can copy/paste into PowerPoint or Google Slides directly.


============================================================

? RISC-V FPGA SoC – Introductory Lecture Pack

============================================================


? Slide 1 – Title

“From C Code to Hardware: How a RISC-V FPGA SoC Works”

An End-to-End Introduction

PicoRV32 + FPGA + Open-source Toolchain (Yosys/nextpnr)


? Slide 2 – Motivation

Students often know the parts, but not the system.

  • You learned C, assembly, FPGA, Verilog, memory, etc.

  • But these skills remain isolated islands.

  • To build a SoC, we need to connect the islands into one continuous flow.


? Slide 3 – Goal of This Lecture

After this 1–2 hour session, you will understand:

  • How C code becomes instructions

  • How instructions drive the PicoRV32 CPU

  • How the CPU accesses memory-mapped hardware

  • How Verilog peripherals interact with the bus

  • How everything runs on the FPGA device

This is the “big picture” of SoC development.


? Slide 4 – The Full Data Flow (Master Slide)

One diagram that explains everything:

[C Program]
↓ (RISC-V GCC)
[RISC-V Instructions]
↓ (Fetch/Decode/Execute)
[PicoRV32 Internal Logic: PC, ALU, Registers]
↓ (Memory bus: addr, data, wstrb)
[HDL Peripheral Modules]

[FPGA Routing / IO Pins]

[Real hardware: LED, UART, PWM]

We will revisit this diagram many times.


? Slide 5 – Why RISC-V?

  • Free and open ISA

  • Simple, clean, compact

  • Perfect for learning SoC design

  • Many FPGA-ready cores (PicoRV32, VexRiscv, CV32E40P, etc.)

  • Large industry adoption


? Slide 6 – The PicoRV32 CPU

• Small, simple, flexible

• Perfect for FPGA SoC education

Implements:

  • RV32I base ISA

  • Optional MUL/DIV

  • Optional interrupts

  • Simple memory bus (valid, addr, wdata, wstrb, rdata, ready)

This bus is the key to connecting C code and hardware.


? Slide 7 – Memory-Mapped I/O (MMIO)

The most important concept of SoC design.

The CPU sees peripherals as memory:

*(uint32_t*)0x03000000 = 1;

→ becomes a RISC-V sw instruction
→ CPU drives mem_addr=0x03000000
→ LED peripheral reacts
→ Physical LED turns ON

This is how software and hardware “talk.”


? Slide 8 – Minimal RISC-V Instructions Needed

You only need to understand 4 instructions to follow SoC behavior:

Purpose Instruction
arithmetic add
memory read lw
memory write sw
control flow beq / jal

These are the “bridge” between C and hardware.


? Slide 9 – Example: C → Assembly

C:

*LED = 1;

Assembly:

li a5,0x03000000
li a4,1
sw a4,0(a5)

Each instruction will appear in the waveform later.


? Slide 10 – The SoC Memory Map

Typical small FPGA RISC-V SoC:

0x0000_0000 – 0x0000_FFFF RAM
0x0300_0000 LED
0x0300_1000 PWM
0x0300_2000 UART

You will modify this map during the labs.


? Slide 11 – The System Bus (PicoRV32)

Important signals:

  • mem_valid

  • mem_addr

  • mem_wdata

  • mem_wstrb

  • mem_rdata

  • mem_ready

Lecture focus:
Only these signals are needed to understand SoC behavior.


? Slide 12 – HDL Peripheral Example (LED)

if (valid && addr==32'h0300_0000) begin
if (wstrb) led_reg <= wdata[0];
end

Simple, clean, effective.


? Slide 13 – HDL Peripheral Example (PWM)

if (addr==32'h0300_1000) duty <= wdata[7:0];
pwm_out = (counter < duty);

Shows how hardware behavior emerges from a memory write.


? Slide 14 – How C Controls Hardware

Summary:

  1. C writes a value

  2. RISC-V compiler generates sw

  3. CPU sends bus transaction

  4. HDL module receives it

  5. Hardware updates

  6. Physical device changes

This is the heart of SoC development.


? Slide 15 – Required Tools (OSS Only)

We use:

  • riscv32-unknown-elf-gcc (compile C to RISC-V)

  • Yosys (synthesis)

  • nextpnr-gowin (place & route)

  • openFPGALoader (write to board)

  • GTKWave (waveform viewing)

No Gowin IDE required.


? Slide 16 – Lab 0: Build the CPU + RAM + LED SoC

You build:

  • PicoRV32 core

  • RAM

  • LED register

  • Simple bus multiplexer

This SoC boots your compiled C program.


? Slide 17 – Lab 1: “Turn on the LED”

C code:

*(uint32_t*)0x03000000 = 1;
while (1);

Expected results:

  • LED lights

  • objdump shows sw

  • waveform shows mem_valid, mem_addr, wstrb

  • HDL LED register toggles


? Slide 18 – Lab 2: Add PWM Peripheral

Modify memory map:

0x0300_1000 → PWM duty

C code:

*PWM = d;

Expected:

  • FPGA pin outputs PWM

  • LED brightness increases/decreases

  • Waveform shows periodic comparison logic


? Slide 19 – Lab 3: Extend the SoC

Students add one new peripheral:

Ideas:

  • Timer

  • Button input

  • UART transmitter

  • Custom state machine

Goal:
Change the memory map and integrate new Verilog.


? Slide 20 – Understanding the Big Picture

Now students can trace the full path:

CobjdumpCPU busVerilog HWFPGAPhysical signal

This is complete SoC understanding.


? Slide 21 – Debugging Workflow

To diagnose SoC problems:

  1. Check C

  2. Check objdump

  3. Check bus waveform

  4. Check HDL logic

  5. Check pin assignment

  6. Check physical hardware

Students learn to think like SoC developers.


? Slide 22 – The SoC Designer Mindset

You can now:

  • Add new hardware

  • Define new memory addresses

  • Write software to control them

  • Debug using waveforms

  • Think across software and hardware boundaries

This is the purpose of SoC education.


? Slide 23 – Summary

Today you learned:

  • RISC-V SoC architecture

  • Memory-mapped I/O

  • CPU bus transactions

  • HDL peripheral design

  • Full C→Hardware pipeline

  • How to extend a SoC

You now have the “big picture.”


? Slide 24 – Next Steps

Suggested learning paths:

  • UART, SPI, and I²C peripherals

  • Interrupt controllers

  • Custom accelerators

  • RISC-V privilege modes

  • Full OS (Zephyr / FreeRTOS / Linux-capable cores)


? Slide 25 – Questions

(Discussion slide)


================================================

? Appendix A – Lab Code (C / Verilog / Makefile)

(identical to the previous assistant message, reusable)

================================================

If you want, I can embed the full code directly into the slide deck appendix.


================================================

? Optional Add-ons

I can also generate the following:

✔ Instructor notes for each slide
✔ Visual diagrams (bus, pipeline, memory map, waveform)
✔ Student worksheet (fill-in-the-blank)
✔ A printable “SoC flow cheat sheet”
✔ Lab instruction PDF
✔ Waveform screenshots (fake or generated)

? 実習コード一式(C / Verilog / SoC / Makefile)

構成は次の 5 つです:

  1. 00_soc_top.v

  2. 01_picorv32_wrapper.v

  3. 10_led.v(LEDレジスタ)

  4. 20_pwm.v(PWMレジスタ)

  5. software/main_led.c(LED点灯)

  6. software/main_pwm.c(PWM制御)

  7. Makefile(C → ELF → BIN → FPGA書き込みまで)


=======================================

SoC トップ(00_soc_top.v)

=======================================

module soc_top (
input wire clk,
input wire resetn,
output wire led,
output wire pwm_out
);

// -------------------------------------
// CPU と RAM
// -------------------------------------
wire mem_valid;
wire mem_ready;
wire mem_instr;
wire [31:0] mem_addr;
wire [31:0] mem_wdata;
wire [3:0] mem_wstrb;
wire [31:0] mem_rdata;

picorv32 #(
.ENABLE_MUL(0),
.ENABLE_DIV(0)
) cpu (
.clk (clk),
.resetn (resetn),
.mem_valid (mem_valid),
.mem_ready (mem_ready),
.mem_instr (mem_instr),
.mem_addr (mem_addr),
.mem_wdata (mem_wdata),
.mem_wstrb (mem_wstrb),
.mem_rdata (mem_rdata)
);

// -------------------------------------
// 単純 RAM(命令+データ共通)
// -------------------------------------
reg [31:0] ram[0:1023];
initial $readmemh("firmware.hex", ram);

reg ram_ready;
reg [31:0] ram_rdata;

always @(posedge clk) begin
ram_ready <= 0;
if (mem_valid && !mem_addr[31:16]) begin
ram_ready <= 1;
ram_rdata <= ram[mem_addr[11:2]];
if (mem_wstrb) begin
ram[mem_addr[11:2]] <= mem_wdata;
end
end
end

// -------------------------------------
// LED レジスタ
// -------------------------------------
wire led_ready;
wire [31:0] led_rdata;

led_module led0 (
.clk(clk),
.resetn(resetn),
.addr(mem_addr),
.wdata(mem_wdata),
.wstrb(mem_wstrb),
.valid(mem_valid),
.ready(led_ready),
.rdata(led_rdata),
.led(led)
);

// -------------------------------------
// PWM モジュール
// -------------------------------------
wire pwm_ready;
wire [31:0] pwm_rdata;

pwm_module pwm0 (
.clk(clk),
.resetn(resetn),
.addr(mem_addr),
.wdata(mem_wdata),
.wstrb(mem_wstrb),
.valid(mem_valid),
.ready(pwm_ready),
.rdata(pwm_rdata),
.pwm_out(pwm_out)
);

// -------------------------------------
// ready / rdata の多重化
// -------------------------------------
assign mem_ready =
ram_ready |
led_ready |
pwm_ready;

assign mem_rdata =
ram_ready ? ram_rdata :
led_ready ? led_rdata :
pwm_ready ? pwm_rdata : 32'h0;

endmodule


=======================================

LED モジュール(10_led.v)

=======================================

module led_module (
input wire clk,
input wire resetn,
input wire [31:0] addr,
input wire [31:0] wdata,
input wire [3:0] wstrb,
input wire valid,
output reg ready,
output reg [31:0] rdata,
output wire led
);

localparam LED_ADDR = 32'h0300_0000;
reg led_reg;

assign led = led_reg;

always @(posedge clk) begin
ready <= 0;
if (valid && addr == LED_ADDR) begin
ready <= 1;
if (wstrb != 0)
led_reg <= wdata[0];
rdata <= {31'd0, led_reg};
end
end

endmodule


=======================================

PWM モジュール(20_pwm.v)

=======================================

module pwm_module(
input wire clk,
input wire resetn,
input wire [31:0] addr,
input wire [31:0] wdata,
input wire [3:0] wstrb,
input wire valid,
output reg ready,
output reg [31:0] rdata,
output wire pwm_out
);

localparam PWM_ADDR = 32'h0300_1000;

reg [7:0] duty;
reg [7:0] counter;

assign pwm_out = (counter < duty);

always @(posedge clk) begin
counter <= counter + 1;

ready <= 0;
if (valid && addr == PWM_ADDR) begin
ready <= 1;
if (wstrb != 0)
duty <= wdata[7:0];
rdata <= duty;
end
end
endmodule


=======================================

LED実習用 C コード(software/main_led.c)

=======================================

#include <stdint.h>

#define LED ((volatile uint32_t*)0x03000000)

int main() {
*LED = 1; // LED ON
while (1) ;
}


=======================================

PWM実習用 C コード(software/main_pwm.c)

=======================================

#include <stdint.h>

#define LED ((volatile uint32_t*)0x03000000)
#define PWM ((volatile uint32_t*)0x03001000)

int delay() {
for (volatile int i=0;i<20000;i++);
return 0;
}

int main() {
*LED = 1;

while (1) {
for (int d=0; d<255; d++) {
*PWM = d; // duty 書き込み
delay();
}
for (int d=255; d>=0; d--) {
*PWM = d;
delay();
}
}
}


=======================================

Makefile(C → ELF → HEX → FPGA)

=======================================

RISCV_PREFIX = riscv32-unknown-elf
CC = $(RISCV_PREFIX)-gcc
OBJDUMP = $(RISCV_PREFIX)-objdump
OBJCOPY = $(RISCV_PREFIX)-objcopy

CFLAGS = -Os -march=rv32i -mabi=ilp32 -nostdlib -ffreestanding

all: firmware.hex

firmware.elf: main.c
$(CC) $(CFLAGS) -o firmware.elf main.c

firmware.hex: firmware.elf
$(OBJCOPY) -O verilog firmware.elf firmware.hex
$(OBJDUMP) -d firmware.elf > firmware.dump

clean:
rm -f firmware.elf firmware.hex firmware.dump

# FPGA 書き込み
prog:
openFPGALoader -b tangnano9k build/soc_top.fs


=======================================

Yosys / nextpnr 用(synthesize.sh)

=======================================

#!/bin/bash
set -e

yosys -p "read_verilog *.v; synth_gowin -top soc_top -json soc.json"
nextpnr-gowin --json soc.json --write soc_pnr.json --device GW1NR-LV9QN88PC6/I5
gowin_pack soc_pnr.json soc_top.fs


============================

? 提供内容まとめ

============================

上記コードを揃えると、次が可能になります:

C の1行でLEDが光る
✔ objdumpで sw 命令が見える
✔ Verilog の LED レジスタが反応
✔ GTKWaveで mem_valid が見える
✔ PWM を追加して SoC 拡張体験ができる